mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 17:13:42 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
parent
90c121d58d
commit
56c0c6e662
3 changed files with 20 additions and 21 deletions
|
|
@ -126,7 +126,7 @@ def download_coding_template(
|
||||||
fallgruppe: Optional[str] = Query(None),
|
fallgruppe: Optional[str] = Query(None),
|
||||||
db: Session = Depends(get_db),
|
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
|
Supports both ``Authorization: Bearer`` header and ``?token=`` query
|
||||||
parameter for direct browser downloads.
|
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
|
user = db.query(User).filter(User.id == user_id, User.is_active == True).first() # noqa: E712
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "User not found")
|
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)
|
xlsx_bytes = generate_coding_template(db, jahr=jahr, fallgruppe=fallgruppe)
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,9 @@ def generate_coding_template(
|
||||||
"""Generate an Excel template for ICD coding.
|
"""Generate an Excel template for ICD coding.
|
||||||
|
|
||||||
Returns .xlsx bytes with columns:
|
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(
|
cases, _ = get_pending_icd_cases(
|
||||||
db, jahr=jahr, fallgruppe=fallgruppe, page=1, per_page=10000
|
db, jahr=jahr, fallgruppe=fallgruppe, page=1, per_page=10000
|
||||||
|
|
@ -118,7 +120,7 @@ def generate_coding_template(
|
||||||
ws.title = "ICD Coding"
|
ws.title = "ICD Coding"
|
||||||
|
|
||||||
# Header
|
# 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):
|
for col, header in enumerate(headers, start=1):
|
||||||
ws.cell(row=1, column=col, value=header)
|
ws.cell(row=1, column=col, value=header)
|
||||||
|
|
||||||
|
|
@ -126,11 +128,9 @@ def generate_coding_template(
|
||||||
for i, case in enumerate(cases, start=2):
|
for i, case in enumerate(cases, start=2):
|
||||||
ws.cell(row=i, column=1, value=case.id)
|
ws.cell(row=i, column=1, value=case.id)
|
||||||
ws.cell(row=i, column=2, value=case.fall_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=3, value=case.fallgruppe)
|
||||||
ws.cell(row=i, column=4, value=case.vorname)
|
ws.cell(row=i, column=4, value=case.datum.isoformat() if case.datum else "")
|
||||||
ws.cell(row=i, column=5, value=case.fallgruppe)
|
# Column 5 (ICD) left empty for admin to fill in
|
||||||
ws.cell(row=i, column=6, value=case.datum.isoformat() if case.datum else "")
|
|
||||||
# Column 7 (ICD) left empty for DAK to fill in
|
|
||||||
|
|
||||||
# Auto-width
|
# Auto-width
|
||||||
for col in ws.columns:
|
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:
|
def import_icd_from_xlsx(db: Session, content: bytes, user_id: int) -> dict:
|
||||||
"""Import ICD codes from a filled-in coding template Excel file.
|
"""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]}
|
Returns: {"updated": int, "errors": list[str]}
|
||||||
"""
|
"""
|
||||||
wb = load_workbook(BytesIO(content), read_only=True)
|
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):
|
except (ValueError, TypeError):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Find ICD column (column 7)
|
# Find ICD column: last column (col 5 in new template, col 7 in legacy)
|
||||||
icd_value = None
|
icd_value = None
|
||||||
if len(row) >= 7 and row[6].value:
|
last_idx = len(row) - 1
|
||||||
icd_value = str(row[6].value).strip()
|
if last_idx >= 0 and row[last_idx].value:
|
||||||
|
icd_value = str(row[last_idx].value).strip()
|
||||||
|
|
||||||
if not icd_value:
|
if not icd_value:
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -73,24 +73,20 @@ class TestGenerateCodingTemplate:
|
||||||
ws = wb.active
|
ws = wb.active
|
||||||
assert ws.title == "ICD Coding"
|
assert ws.title == "ICD Coding"
|
||||||
|
|
||||||
# Verify header row
|
# Verify header row (no patient names for DSGVO)
|
||||||
headers = [cell.value for cell in ws[1]]
|
headers = [cell.value for cell in ws[1]]
|
||||||
assert headers == [
|
assert headers == [
|
||||||
"Case_ID",
|
"Case_ID",
|
||||||
"Fall_ID",
|
"Fall_ID",
|
||||||
"Nachname",
|
|
||||||
"Vorname",
|
|
||||||
"Fallgruppe",
|
"Fallgruppe",
|
||||||
"Datum",
|
"Datum",
|
||||||
"ICD",
|
"ICD",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Verify data row
|
# Verify data row (no nachname/vorname)
|
||||||
row2 = [cell.value for cell in ws[2]]
|
row2 = [cell.value for cell in ws[2]]
|
||||||
assert row2[0] == 1
|
assert row2[0] == 1
|
||||||
assert row2[1] == "FALL-001"
|
assert row2[1] == "FALL-001"
|
||||||
assert row2[2] == "Mustermann"
|
assert row2[2] == "onko"
|
||||||
assert row2[3] == "Max"
|
assert row2[3] == "2026-02-24"
|
||||||
assert row2[4] == "onko"
|
assert row2[4] is None # ICD column should be empty
|
||||||
assert row2[5] == "2026-02-24"
|
|
||||||
assert row2[6] is None # ICD column should be empty
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue