"""Wochenübersicht Excel export for DAK weekly summary sheets. Generates .xlsx files matching the format sent to DAK for ICD code entry. Two variants exist: - "c2s" → Fallgruppen onko + intensiv (columns ZMO / ZMI) - "c2s_g_s" → Fallgruppen galle + sd (columns Galle / Schild) Each KW block has 4 header rows + data rows + 2 blank separator rows. KWs are sorted descending (newest first). """ from __future__ import annotations from datetime import date from io import BytesIO from typing import Any from openpyxl import Workbook from openpyxl.styles import Alignment, Border, Font, PatternFill, Side from app.services.excel_export import HEADER_FILL, HEADER_FONT # --------------------------------------------------------------------------- # Type configuration # --------------------------------------------------------------------------- WOCHENUEBERSICHT_TYPES: dict[str, dict[str, Any]] = { "c2s": { "fallgruppen": ("onko", "intensiv"), "fg_labels": ("ZMO", "ZMI"), "filename_infix": "c2s", }, "c2s_g_s": { "fallgruppen": ("galle", "sd"), "fg_labels": ("Galle", "Schild"), "filename_infix": "c2s_g_s", }, } # Column headers (row 4 of each KW block) COL_HEADERS = [ "KVNR", # A "Datum", # B "Erstgespräch", # C "Zweitmeinung/Vorbereitung (bei Abbruch)", # D "Zweitmeinung/Vorbereitung + Erteilung", # E "Schriftliche Dokumentation (Gutachten)", # F # G and H are dynamic (FG labels) # I = ICD-10 ] THIN_BORDER = Border( left=Side(style="thin"), right=Side(style="thin"), top=Side(style="thin"), bottom=Side(style="thin"), ) def _kw_date_range(jahr: int, kw: int) -> tuple[date, date]: """Return (Monday, Sunday) for the given ISO year/week.""" monday = date.fromisocalendar(jahr, kw, 1) sunday = date.fromisocalendar(jahr, kw, 7) return monday, sunday def _format_date_range(monday: date, sunday: date) -> str: """Format as 'DD.MM. - DD.MM.YYYY'.""" return f"{monday.strftime('%d.%m.')} - {sunday.strftime('%d.%m.%Y')}" def generate_wochenuebersicht_xlsx( cases_by_kw: dict[int, list[Any]], export_type: str, jahr: int, ) -> bytes: """Generate a Wochenübersicht Excel workbook. Args: cases_by_kw: Cases grouped by calendar week number. export_type: One of ``"c2s"`` or ``"c2s_g_s"``. jahr: The report year. Returns: The ``.xlsx`` file contents as bytes. """ type_cfg = WOCHENUEBERSICHT_TYPES[export_type] fg1, fg2 = type_cfg["fallgruppen"] fg1_label, fg2_label = type_cfg["fg_labels"] wb = Workbook() ws = wb.active ws.title = "Wochenübersicht" # Column widths ws.column_dimensions["A"].width = 14 ws.column_dimensions["B"].width = 12 ws.column_dimensions["C"].width = 14 ws.column_dimensions["D"].width = 18 ws.column_dimensions["E"].width = 18 ws.column_dimensions["F"].width = 16 ws.column_dimensions["G"].width = 10 ws.column_dimensions["H"].width = 10 ws.column_dimensions["I"].width = 14 current_row = 1 # Sort KWs descending (newest first) sorted_kws = sorted(cases_by_kw.keys(), reverse=True) for kw in sorted_kws: cases = cases_by_kw[kw] monday, sunday = _kw_date_range(jahr, kw) # --- Row 1: "Kalenderwoche:" label + number --- ws.cell(row=current_row, column=5, value="Kalenderwoche:") ws.cell(row=current_row, column=5).font = HEADER_FONT ws.cell(row=current_row, column=6, value=kw) ws.cell(row=current_row, column=6).font = HEADER_FONT current_row += 1 # --- Row 2: Date range --- ws.cell(row=current_row, column=5, value=_format_date_range(monday, sunday)) ws.cell(row=current_row, column=5).font = Font(italic=True) current_row += 1 # --- Row 3: Instruction --- ws.cell( row=current_row, column=3, value="Bitte entsprechendes Feld mit x kennzeichnen", ) ws.cell(row=current_row, column=3).font = Font(italic=True, color="FF808080") current_row += 1 # --- Row 4: Column headers --- headers = COL_HEADERS + [fg1_label, fg2_label, "ICD-10"] for ci, header in enumerate(headers, start=1): cell = ws.cell(row=current_row, column=ci, value=header) cell.fill = HEADER_FILL cell.font = HEADER_FONT cell.border = THIN_BORDER cell.alignment = Alignment(wrap_text=True, vertical="center") current_row += 1 # --- Data rows --- # Sort cases by datum ascending within each KW sorted_cases = sorted(cases, key=lambda c: (c.datum or date.min)) for case in sorted_cases: # A: KVNR ws.cell(row=current_row, column=1, value=case.kvnr or "") # B: Datum ws.cell( row=current_row, column=2, value=case.datum.strftime("%d.%m.%Y") if case.datum else "", ) # C: Erstgespräch — always x ws.cell(row=current_row, column=3, value="x") # D: Zweitmeinung/Vorbereitung (bei Abbruch) if case.ablehnung or case.abbruch: ws.cell(row=current_row, column=4, value="x") # E: Zweitmeinung/Vorbereitung + Erteilung if case.unterlagen and not case.ablehnung and not case.abbruch: ws.cell(row=current_row, column=5, value="x") # F: Schriftliche Dokumentation (Gutachten) if case.gutachten: ws.cell(row=current_row, column=6, value="x") # G: FG1 (ZMO / Galle) if case.fallgruppe == fg1: ws.cell(row=current_row, column=7, value="x") # H: FG2 (ZMI / Schild) if case.fallgruppe == fg2: ws.cell(row=current_row, column=8, value="x") # I: ICD-10 ws.cell(row=current_row, column=9, value=case.icd or "") # Apply thin borders to data row for ci in range(1, 10): ws.cell(row=current_row, column=ci).border = THIN_BORDER ws.cell(row=current_row, column=ci).alignment = Alignment( horizontal="center" if ci >= 3 else "left" ) current_row += 1 # --- 2 blank separator rows --- current_row += 2 buf = BytesIO() wb.save(buf) return buf.getvalue()