Commit graph

54 commits

Author SHA1 Message Date
28e4dc4333 fix: allow reactivating rejected disclosures in admin view
"Wieder aufleben" now also works for rejected disclosures, setting
status to approved with a new 24h window. Both buttons (reactivate
and delete) now appear for rejected and expired/revoked disclosures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:45:26 +00:00
78ae11fb99 feat: add reactivate and delete buttons for admin disclosure view
After revoking or expiry, admins now see "Wieder aufleben" (reactivate
with new 24h window) and "Verwerfen" (hard delete) buttons. Rejected
disclosures also show "Verwerfen".

Backend: PUT .../reactivate and DELETE endpoints for admin disclosures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:25:46 +00:00
547bfa3ea5 feat: add re-request and delete buttons for inactive disclosures
DAK employees now see "Erneute Anfrage" (opens dialog with pre-filled
reason) and "Verwerfen" (hard delete) buttons for expired, revoked,
or rejected disclosure requests on their My Disclosures page.

Backend: new DELETE /cases/disclosure-requests/{id} endpoint.
Frontend: new hooks useDeleteDisclosure, useRequestDisclosure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:11:08 +00:00
1bedbcf243 fix: resolve TypeError in disclosure revoke due to naive/aware datetime comparison
PyMySQL returns naive datetimes from MySQL DATETIME columns, but
revoke_disclosure() compared them with timezone-aware datetime.now(timezone.utc),
causing an unhandled TypeError (not caught by except ValueError) and a 500 error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 07:57:59 +00:00
95e84a6978 fix: filter report data by max KW when generating reports
Previously, generate_full_report() ignored the kw parameter for data
filtering — it was only stored as metadata. This caused all reports to
contain data up to the latest available KW, making historical reports
(e.g., for KW 8) identical to the current one.

Now all 5 sheet calculation functions accept an optional max_kw parameter.
When generating a report for a specific KW, only cases with kw <= max_kw
are included. Dashboard and vorjahr callers are unaffected (max_kw=None).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 23:16:20 +00:00
dbb5afaeb9 feat: add inline report viewer with 5-tab sheet display
- Backend: GET /reports/{id}/data endpoint returns stored report JSON
- Frontend: ReportViewer component renders all 5 Excel sheets as tabs
  (KW gesamt, Fachgebiete, Gutachten, Therapieänderungen, ICD onko)
- ReportsPage: clickable rows with inline expansion to view reports
- Empty KW rows filtered, summary row at bottom, German labels
- Download button still available alongside inline view

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:57:35 +00:00
32cee4d30d feat: add KPI links, My Disclosures page, and extend Admin Disclosures
- Dashboard KPI cards are now clickable with role-based links
- New GutachtenStatistikPage placeholder at /gutachten-statistik
- New "Meine Freigaben" page for DAK-Mitarbeiter to view/revoke own disclosures
- Backend: GET /cases/my-disclosure-requests, PUT /cases/disclosure-requests/{id}/revoke
- Admin Disclosures page: full history with status tabs and revoke capability
- Backend: PUT /admin/disclosure-requests/{id}/revoke, default shows all statuses
- Sidebar: "Meine Freigaben" entry for dak_mitarbeiter role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 22:19:46 +00:00
56c0c6e662 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>
2026-02-26 17:20:28 +00:00
90c121d58d fix: correct down_revision in 006 migration to match 005_disclosure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:13:22 +00:00
c302a91411 feat: auto-update fall_id when KVNR is entered on a case
When a KVNR is entered on a case that has a random-suffix or legacy
Nachname-based fall_id, the fall_id is automatically rebuilt using
the new KVNR.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:09:50 +00:00
7bbe501bfa feat: add migration to anonymize existing fall_ids
Replaces Nachname-based fall_ids with KVNR or random 6-char suffix
for all existing cases in the database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:07:58 +00:00
04a2e8fe93 test: verify duplicate detection works with new fall_id format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:06:58 +00:00
a436580b03 feat: use KVNR instead of Nachname in fall_id generation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 17:04:48 +00:00
1612d449c0 fix: make nachname nullable in CaseResponse to support data masking
CaseResponse.nachname was required (str) but the masking function sets
it to None for dak_mitarbeiter. This caused Pydantic validation errors
(500) making the case list empty for non-admin users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:24:56 +00:00
9825489781 feat: add case masking for dak_mitarbeiter and disclosure endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:07:30 +00:00
00076e2c00 feat: add disclosure service with create, review, and check logic
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:04:54 +00:00
3496e4acfe feat: add disclosure schemas and case response masking helper
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:03:58 +00:00
bb13ec80a2 feat: add DisclosureRequest model and migration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 16:02:52 +00:00
32127c30c3 fix: filter all case queries to DAK insurance only
Add VERSICHERUNG_FILTER="DAK" to config and apply it to all case
queries: list, detail, pending-icd, pending-coding, coding queue,
dashboard KPIs, all 5 report sheets, and excel sync export.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 10:20:33 +00:00
fc609401c3 feat: add MFA disable (self-service + admin reset) endpoints
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:40:57 +00:00
661f1ce552 feat: add avatar upload/delete endpoints with static file serving
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:39:21 +00:00
1fa5d20d40 feat: add PUT /api/auth/profile endpoint for self-service profile update
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:37:17 +00:00
6bc0b3ac5a feat: add ProfileUpdate schema and extend UserResponse with profile fields
- Add first_name, last_name, display_name, avatar_url to UserResponse
- Add ProfileUpdate schema for self-service profile editing
- Add MFADisableRequest schema for password-confirmed MFA disable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:35:54 +00:00
d09fdccc75 feat: add first_name, last_name, display_name, avatar_url to User model
Add 4 new nullable profile fields to support the upcoming account
management (Kontoverwaltung) feature. Includes Alembic migration
that has been applied to production database.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 09:34:33 +00:00
400520aebd fix: auto-detect CSV delimiter (comma, semicolon, tab)
German Excel/CRM exports often use semicolons instead of commas.
The parser now uses csv.Sniffer to auto-detect the delimiter,
fixing the issue where semicolon-delimited CSVs produced 0 rows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 23:09:16 +00:00
f8a8befb19 fix: add versicherung field to CaseUpdate schema
The field was editable in the frontend but missing from the backend
CaseUpdate Pydantic schema, causing changes to be silently dropped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 13:05:55 +00:00
97731552c5 feat: add editable KVNR field in case detail view with dedicated API endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 11:07:49 +00:00
7ef1fc9335 feat: add checkbox selection and bulk delete for reports
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:40:19 +00:00
9650889d24 feat: add audit logging for login, logout, imports, and report generation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:36:25 +00:00
4a3648a018 feat: serve frontend SPA from FastAPI backend
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:10:11 +00:00
9d79ba35bc fix: wrap vorjahr data in "summary" key to match excel_export structure
get_vorjahr_summary returned a flat dict, but excel_export looked for
data under vj.get("summary", {}), resulting in empty Vorjahr columns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:34:19 +00:00
8502c7a7fb fix: use one-decimal percentage format in Sheet 1 (0.0% instead of 0%)
Shows "0,8%" instead of "1%" for small ratios like Ablehnungen/Erstberatungen.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:32:32 +00:00
4467e1b1cb fix: upsert report metadata to avoid duplicate key error on re-generation
When generating a report for the same jahr/kw, update the existing
record instead of inserting a duplicate (which caused IntegrityError).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:28:40 +00:00
db963a8e12 fix: align excel_export keys with report_service output
report_service uses 'summary'/'weekly'/'keine_rm'/'gutachten' but
excel_export expected 'totals'/'weeks'/'keine_rueckmeldung'/
'gutachten_gesamt'. Also fix nested gesamt dict access in sheet3
and icd_codes path from sheet5.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:26:11 +00:00
590125073c fix: support token query param for file download endpoints
Browser-initiated downloads (window.open) cannot set Authorization
headers. Accept ?token= query parameter as fallback on the report
download and coding-template endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:20:14 +00:00
5ee1cff0d6 fix: use SQLAlchemy Integer type in .cast() calls in report_service
.cast(int) uses Python's builtin int, which lacks SQLAlchemy's
_isnull attribute. Replace all occurrences with .cast(Integer).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:06:48 +00:00
0e4d19e8bc fix: use savepoints for row-by-row error isolation in Excel import
Prevents a single duplicate fall_id from rolling back the entire
import session. Each row insert now uses db.begin_nested() so
constraint violations are isolated to the offending row.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:01:42 +00:00
93884f3c8d fix: add missing cases API, import router, and case schemas
These files were created by a subagent but not included in the commit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:49:55 +00:00
3f8f96097d fix: URL-encode DB password in connection string
Passwords with special characters (@, &, etc.) broke the SQLAlchemy
connection URL parsing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:48:28 +00:00
1748379253 feat: Excel export in Berichtswesen format + historical import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:07:28 +00:00
d6fb04d5a7 feat: coding queue, reports API, Excel sync
- Add coding_service.py with queue retrieval, single + batch coding updates
- Add report schemas (DashboardKPIs, WeeklyDataPoint, ReportMeta)
- Add coding API router with /queue, PUT /{case_id}, POST /batch endpoints
- Add reports API router with /dashboard, /weekly, /generate, /download, /list
- Add excel_sync.py for bidirectional Abrechnung DB<->XLSX sync
- Register coding and reports routers in main.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:03:17 +00:00
72b6f784fc feat: report service — all 5 sheet calculations + year-over-year
Implements report_service.py with:
- Sheet 1: Auswertung KW gesamt (weekly totals + year summary)
- Sheet 2: Auswertung nach Fachgebieten (per-KW per-Fallgruppe)
- Sheet 3: Auswertung Gutachten (alternative/bestaetigung per group)
- Sheet 4: Auswertung Therapieaenderungen (TA metrics per KW)
- Sheet 5: Auswertung ICD onko (ICD code frequency for onko)
- Dashboard KPIs (total_cases, pending_icd, pending_coding, etc.)
- generate_full_report() for all 5 sheets combined

Implements vorjahr_service.py with:
- Cached year-over-year comparison via yearly_summary table
- get_vorjahr_summary() for Sheet 1 comparison columns
- get_vorjahr_detail() for full previous-year breakdown
- refresh_vorjahr_cache() for cache invalidation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 08:02:53 +00:00
f4afea7f85 feat: historical Excel import (Abrechnung_DAK.xlsx)
Add service and standalone script to import all cases from the master
Excel workbook into the database. Handles 5 year-sheets (2020-2022,
2023, 2024, 2025, 2026) with dynamic column mapping, fallgruppe
normalization, boolean/date parsing, phone number formatting, and
duplicate detection. Supports dry-run mode and per-sheet import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:58:04 +00:00
e793bad01f feat: notification service — in-app + SMTP email
Adds notification_service.py with:
- send_email() for SMTP SSL delivery via complexcaresolutions.de
- create_notification() for in-app + optional email notifications
- notify_all_users_with_role() for role-based bulk notifications
- Convenience functions for all 5 notification types:
  new_cases_uploaded, icd_entered, icd_uploaded,
  report_ready, coding_completed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:52:24 +00:00
78c2c682a4 feat: import service with duplicate detection and fall_id generation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:49:15 +00:00
498cb7048d feat: ICD service — normalize, split, validate, coding template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:49:05 +00:00
df26b51e14 feat: admin API, audit logging, notifications, create_admin script
Add audit_service for compliance logging, admin endpoints (user CRUD,
invitation management, audit log), notification endpoints (list, mark
read), and interactive create_admin script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:48:41 +00:00
518de3da27 feat: auth system — login, register, refresh, MFA, domain whitelist
Add complete authentication layer:
- Pydantic v2 schemas for auth requests/responses and user representation
- Auth service with login (account locking, MFA), registration (invitation
  tokens + domain whitelist), token management, MFA setup/activation, and
  password change
- FastAPI router with 8 endpoints: login, register, refresh, logout,
  mfa/setup, mfa/verify, change-password, me
- Router registered in main.py under /api/auth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:46:04 +00:00
84d11822e0 feat: CRM CSV parser with pipe-delimited contact parsing
Parse CRM CSV exports (UTF-8-BOM, comma-delimited) with:
- Pipe-delimited Hauptkontakt field (Nachname|Vorname|Geburtsdatum|KVNR)
- German date formats (DD.MM.YYYY, DD.MM.YY, HH:MM)
- Modul-to-Fallgruppe mapping
- Graceful handling of missing KVNR, bad dates, empty fields, spam rows
- 19 tests (synthetic + all 4 real CSV files)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:45:34 +00:00
178d40d036 feat: JWT auth, bcrypt, MFA, dependency injection, security tests
Add core security layer:
- security.py: password hashing (bcrypt), JWT access/refresh tokens,
  SHA-256 token hashing, TOTP MFA (generate, verify, provisioning URI),
  plus passlib/bcrypt 5.x compatibility patch
- dependencies.py: FastAPI deps for get_current_user (Bearer JWT) and
  require_admin (role check)
- exceptions.py: domain-specific HTTP exceptions (CaseNotFound,
  DuplicateCase, InvalidImportFile, ICDValidation, AccountLocked,
  InvalidCredentials)
- test_security.py: 9 tests covering all security functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:41:35 +00:00