Commit graph

66 commits

Author SHA1 Message Date
42fd1808c4 feat: add admin-only billing status donut and top gutachter charts to Dashboard
Extends DashboardKPIs with total_abgerechnet/pending_abrechnung. Adds
new GET /reports/dashboard/top-gutachter endpoint (admin-only). Frontend
shows Abrechnungsstatus donut + Gutachter-Verteilung progress bars in a
new third row, visible only to admins.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:55:30 +00:00
f3846813a4 feat: add saved filter presets for case list
Users can save frequently used filter combinations (year, fallgruppe,
ICD status, search) as named presets. Stored server-side in new
filter_presets table (max 10 per user). Star-icon Popover on CasesPage
to load, save, or delete presets. TanStack Query hooks for CRUD.

New files: FilterPreset model, migration 009, API router, schemas,
useFilterPresets hook.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 15:13:20 +00:00
d5db84d93f feat: add self-service password reset via email
Adds "Passwort vergessen?" to login page with email-based password
reset flow. Backend generates secure token (SHA-256 hashed, 1h expiry),
sends reset link via SMTP, and validates on submission. Includes rate
limiting (3 requests/hour/email), audit logging, and account unlock
on successful reset. New ResetPasswordPage with password confirmation.

New DB table: password_reset_tokens (migration 008).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:56:07 +00:00
002021d7c7 feat: add Excel export for filtered case list
New GET /cases/export endpoint generates .xlsx with openpyxl using the
same filters as the case list (year, fallgruppe, ICD status, search).
Role-aware columns: admins see patient names, DAK staff does not.
Frontend adds a Download button next to the filter bar with loading
state. Refactors shared query logic into _build_case_query helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:43:00 +00:00
2f0a556371 fix: use same KW range for year-over-year KPI comparison
The previous implementation compared a partial current year against a
full previous year, producing misleading percentages. Now determines the
max KW with data in the selected year and filters the previous year to
the same KW range for a fair comparison.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:23:03 +00:00
73b0d6761c feat: add year-over-year comparison to Dashboard KPI cards
Dashboard endpoint now returns prev_kpis (previous year's KPIs) alongside
current KPIs. KpiCard component shows percentage change with colored trend
indicators (green up, red down, grey neutral). Also marks completed items
in todo.md (notification center, dark mode toggle were already implemented).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 13:15:05 +00:00
3216dd6d53 feat: add email notifications for disclosure request decisions
Replace direct Notification inserts with notification_service functions
that send both in-app and email notifications. Admins receive an email
when a DAK-Mitarbeiter submits a new disclosure request. The requesting
Mitarbeiter receives an email when their request is approved (with
expiration date) or rejected. SMTP was already configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:59:21 +00:00
5da1e523d3 feat: implement Gutachten-Statistik page with KPIs and charts
Add full statistics page replacing placeholder: 4 KPI cards (total, Bestätigung,
Alternative, Uncodiert), stacked bar chart for gutachten types per KW, donut chart
for type distribution, grouped bar chart for therapy changes per KW, and horizontal
bar chart for therapy change reasons. Includes new backend endpoint and service
function combining sheet3/sheet4 data with KPI aggregation. Also adds feature
roadmap todo.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:37:08 +00:00
cb73cf5461 feat: add yearly KW comparison chart and top 10 ICD box to Dashboard
Backend: two new endpoints /reports/dashboard/yearly-comparison and
/reports/dashboard/top-icd for multi-year KW case counts and ICD
frequency aggregation.

Frontend: grouped bar chart comparing KW across years (2022+) and
a ranked top 10 ICD list with progress bars on the Dashboard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 12:11:51 +00:00
2e41242c9e feat: add year filter to Coding queue
Add jahr parameter to the coding queue API endpoint and frontend
filter, allowing admins to filter the coding queue by year.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 17:37:04 +00:00
899c125a62 fix: show individual case data in Wochenübersicht preview instead of aggregated summaries
Preview now mirrors the Excel export content: KVNR, Datum, x-markers
for Erstgespräch/Abbruch/Unterlagen/Gutachten/FG, and ICD-10 per case,
grouped by KW blocks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:32:30 +00:00
27b3810250 feat: add inline preview for Wochenübersicht reports
- Backend stores per-KW summary in report_data (count, ICD status, FG split)
- WochenuebersichtViewer component shows expandable KW table with totals
- Expand/collapse pattern matching ReportsPage (ChevronRight/Down, useReportData)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:26:20 +00:00
efeb619b06 feat: persist Wochenübersicht exports, DAK-Mitarbeiter access + ICD upload
- Wochenübersicht exports now persisted in DB (WeeklyReport) + disk
- POST /reports/wochenuebersicht/generate replaces GET (admin-only)
- POST /reports/wochenuebersicht/upload-icd for ICD upload (all roles)
- GET /reports/list supports report_type_prefix filter
- WochenuebersichtPage: report table + ICD drag-drop upload for all roles
- Route + sidebar open to all authenticated users
- ReportsPage filters out wochenuebersicht report types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:51:14 +00:00
6f6a721973 feat: add Wochenübersicht export + ICD import auto-detect
- New Excel export service for weekly DAK summary sheets (c2s / c2s_g_s variants)
- New API endpoint GET /reports/wochenuebersicht (admin-only)
- ICD import auto-detects format (coding template vs. Wochenübersicht KVNR-based)
- New admin frontend page with download form
- Route + sidebar navigation entry

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:28:17 +00:00
48939f01dd feat: add Onko-Intensiv and Galle-Schild report types
Adds report_type support across the full stack:
- Backend: REPORT_TYPES mapping, fallgruppen filter in all 5 sheet
  calculations, dynamic Excel columns, report_type DB column with
  Alembic migration 007
- Frontend: report type dropdown in generation form, type column in
  reports table, dynamic fallgruppen in ReportViewer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 12:47:59 +00:00
5f957ee8ed fix: mask contact fields for non-admin users, require disclosure for visibility
- Add strasse, plz, ort, email, telefonnummer, mobiltelefon, ansprechpartner
  to SENSITIVE_FIELDS in backend (nullified without disclosure)
- Add visibleTo: 'admin' to all Kontakt fields in frontend fieldConfig
- Consolidate _utcnow_naive() usage across all disclosure service functions
  for consistent naive datetime handling with MySQL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 11:13:32 +00:00
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
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
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