From 56c0c6e662ed313e1cbd5414eac9fff66a2b6eb3 Mon Sep 17 00:00:00 2001 From: CCS Admin Date: Thu, 26 Feb 2026 17:20:28 +0000 Subject: [PATCH] fix: remove patient names from coding template and restrict to admin - Remove Nachname/Vorname columns from ICD coding template (DSGVO) - Restrict /cases/coding-template endpoint to admin-only - ICD import reads last column for backwards compatibility Co-Authored-By: Claude Opus 4.6 --- backend/app/api/cases.py | 4 +++- backend/app/services/icd_service.py | 23 ++++++++++++----------- backend/tests/test_icd_service.py | 14 +++++--------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/backend/app/api/cases.py b/backend/app/api/cases.py index fc34994..73ce76c 100644 --- a/backend/app/api/cases.py +++ b/backend/app/api/cases.py @@ -126,7 +126,7 @@ def download_coding_template( fallgruppe: Optional[str] = Query(None), db: Session = Depends(get_db), ): - """Download an Excel template for ICD coding. + """Download an Excel template for ICD coding. Admin only. Supports both ``Authorization: Bearer`` header and ``?token=`` query parameter for direct browser downloads. @@ -149,6 +149,8 @@ def download_coding_template( user = db.query(User).filter(User.id == user_id, User.is_active == True).first() # noqa: E712 if not user: raise HTTPException(status.HTTP_401_UNAUTHORIZED, "User not found") + if user.role != "admin": + raise HTTPException(status.HTTP_403_FORBIDDEN, "Admin access required") xlsx_bytes = generate_coding_template(db, jahr=jahr, fallgruppe=fallgruppe) from io import BytesIO diff --git a/backend/app/services/icd_service.py b/backend/app/services/icd_service.py index 09ec0ff..36b4708 100644 --- a/backend/app/services/icd_service.py +++ b/backend/app/services/icd_service.py @@ -107,7 +107,9 @@ def generate_coding_template( """Generate an Excel template for ICD coding. Returns .xlsx bytes with columns: - Case_ID, Fall_ID, Nachname, Vorname, Fallgruppe, Datum, ICD (empty) + Case_ID, Fall_ID, Fallgruppe, Datum, ICD (empty) + + Patient names are excluded for data privacy (DSGVO). """ cases, _ = get_pending_icd_cases( db, jahr=jahr, fallgruppe=fallgruppe, page=1, per_page=10000 @@ -118,7 +120,7 @@ def generate_coding_template( ws.title = "ICD Coding" # Header - headers = ["Case_ID", "Fall_ID", "Nachname", "Vorname", "Fallgruppe", "Datum", "ICD"] + headers = ["Case_ID", "Fall_ID", "Fallgruppe", "Datum", "ICD"] for col, header in enumerate(headers, start=1): ws.cell(row=1, column=col, value=header) @@ -126,11 +128,9 @@ def generate_coding_template( for i, case in enumerate(cases, start=2): ws.cell(row=i, column=1, value=case.id) ws.cell(row=i, column=2, value=case.fall_id) - ws.cell(row=i, column=3, value=case.nachname) - ws.cell(row=i, column=4, value=case.vorname) - ws.cell(row=i, column=5, value=case.fallgruppe) - ws.cell(row=i, column=6, value=case.datum.isoformat() if case.datum else "") - # Column 7 (ICD) left empty for DAK to fill in + ws.cell(row=i, column=3, value=case.fallgruppe) + ws.cell(row=i, column=4, value=case.datum.isoformat() if case.datum else "") + # Column 5 (ICD) left empty for admin to fill in # Auto-width for col in ws.columns: @@ -145,7 +145,7 @@ def generate_coding_template( def import_icd_from_xlsx(db: Session, content: bytes, user_id: int) -> dict: """Import ICD codes from a filled-in coding template Excel file. - Expects columns: Case_ID (col 1), ICD (col 7 or last col) + Expects columns: Case_ID (col 1), ICD (last col — col 5 or col 7) Returns: {"updated": int, "errors": list[str]} """ wb = load_workbook(BytesIO(content), read_only=True) @@ -164,10 +164,11 @@ def import_icd_from_xlsx(db: Session, content: bytes, user_id: int) -> dict: except (ValueError, TypeError): continue - # Find ICD column (column 7) + # Find ICD column: last column (col 5 in new template, col 7 in legacy) icd_value = None - if len(row) >= 7 and row[6].value: - icd_value = str(row[6].value).strip() + last_idx = len(row) - 1 + if last_idx >= 0 and row[last_idx].value: + icd_value = str(row[last_idx].value).strip() if not icd_value: continue diff --git a/backend/tests/test_icd_service.py b/backend/tests/test_icd_service.py index 9d22ee1..3a22522 100644 --- a/backend/tests/test_icd_service.py +++ b/backend/tests/test_icd_service.py @@ -73,24 +73,20 @@ class TestGenerateCodingTemplate: ws = wb.active assert ws.title == "ICD Coding" - # Verify header row + # Verify header row (no patient names for DSGVO) headers = [cell.value for cell in ws[1]] assert headers == [ "Case_ID", "Fall_ID", - "Nachname", - "Vorname", "Fallgruppe", "Datum", "ICD", ] - # Verify data row + # Verify data row (no nachname/vorname) row2 = [cell.value for cell in ws[2]] assert row2[0] == 1 assert row2[1] == "FALL-001" - assert row2[2] == "Mustermann" - assert row2[3] == "Max" - assert row2[4] == "onko" - assert row2[5] == "2026-02-24" - assert row2[6] is None # ICD column should be empty + assert row2[2] == "onko" + assert row2[3] == "2026-02-24" + assert row2[4] is None # ICD column should be empty