mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 14:53:41 +00:00
feat: project scaffolding with FastAPI, config, database connection
- Initialize project structure with backend/app/ package layout - Add FastAPI app with CORS middleware and health check endpoint - Add Pydantic Settings config with DB, JWT, SMTP, and app settings - Add SQLAlchemy database engine and session management - Add requirements.txt with all dependencies (FastAPI, SQLAlchemy, Alembic, etc.) - Add .env.example template and .gitignore - Add empty frontend/ and backend test scaffolding - Include project specification and design/implementation plans Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
5d57b1f349
21 changed files with 2900 additions and 0 deletions
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
*.egg-info/
|
||||||
|
*.egg
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.whl
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
backend/alembic/versions/*.pyc
|
||||||
|
|
||||||
|
# Node / Frontend
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.next/
|
||||||
|
.nuxt/
|
||||||
|
|
||||||
|
# Data files (reference Excel/CSV)
|
||||||
|
data/
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
899
Dak_projekt_spezifikation_final.md
Normal file
899
Dak_projekt_spezifikation_final.md
Normal file
|
|
@ -0,0 +1,899 @@
|
||||||
|
# DAK Zweitmeinungs-Portal — Implementierungsspezifikation
|
||||||
|
|
||||||
|
## FINAL v4.0 — Alle Entscheidungen getroffen
|
||||||
|
**Datum:** 24.02.2026
|
||||||
|
**Status:** IMPLEMENTIERUNGSBEREIT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entscheidungsprotokoll (komplett)
|
||||||
|
|
||||||
|
| # | Thema | Entscheidung |
|
||||||
|
|---|-------|-------------|
|
||||||
|
| 1 | Historischer Import | Alle Excel-Sheets (2023-2026) → DB |
|
||||||
|
| 2 | Therapieänderungen | Eigenes Sheet UND integriert in Gutachten-Sheet |
|
||||||
|
| 3 | Abrechnung_DAK.xlsx | Bleibt parallel; DB = Master für Berichtswesen |
|
||||||
|
| 4 | DAK-Registrierung | Domain-Whitelist (@dak.de) + Einladungslinks |
|
||||||
|
| 5 | Benachrichtigungen | Bidirektional: Admin↔DAK per E-Mail + In-App |
|
||||||
|
| 6 | Masse-Coding | Ja, Queue-Interface für historische Nachbearbeitung |
|
||||||
|
| 7 | Datenbank | MariaDB 10.11.14 (dak_c2s auf localhost) |
|
||||||
|
| 8 | Deployment | Nativ, systemd-Service, Plesk-Nginx |
|
||||||
|
| 9 | Nginx | Über Plesk "Additional nginx directives" |
|
||||||
|
| 10 | Dev-DB | MariaDB direkt auf sv-frontend |
|
||||||
|
| 11 | Process Manager | Manueller systemd-Service |
|
||||||
|
| 12 | Max Upload | 20 MB |
|
||||||
|
| 13 | Docker | Nein — native Deployment |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Infrastruktur
|
||||||
|
|
||||||
|
### Produktion (Hetzner 1)
|
||||||
|
```
|
||||||
|
Server: Hetzner 1 (Plesk-managed)
|
||||||
|
Domain: dak.complexcaresolutions.de (SSL vorhanden)
|
||||||
|
Datenbank: MariaDB 10.11.14 → DB: dak_c2s / User: dak_c2s_admin
|
||||||
|
Python: 3.11.2
|
||||||
|
Node.js: vorhanden (Plesk-Modul)
|
||||||
|
Nginx: Plesk-verwaltet → "Additional nginx directives"
|
||||||
|
Process Mgr: systemd (manuell)
|
||||||
|
Git: Plesk-Modul
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entwicklung (sv-frontend)
|
||||||
|
```
|
||||||
|
Server: Debian-basiert, vollständiger Dev-Server
|
||||||
|
Python: installiert
|
||||||
|
Node.js: installiert
|
||||||
|
Claude Code: läuft
|
||||||
|
MariaDB: lokal für Entwicklung
|
||||||
|
Git: → GitHub (complexcaresolutions/dak.c2s)
|
||||||
|
```
|
||||||
|
|
||||||
|
### E-Mail
|
||||||
|
```
|
||||||
|
SMTP Host: smtp.complexcaresolutions.de
|
||||||
|
Port: 465 (SSL)
|
||||||
|
Benutzer: noreply@complexcaresolutions.de
|
||||||
|
Passwort: 9Vw0!3y6o
|
||||||
|
Absender: noreply@complexcaresolutions.de
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git
|
||||||
|
```
|
||||||
|
Organisation: complexcaresolutions (GitHub Business)
|
||||||
|
Repository: dak.c2s (noch anzulegen)
|
||||||
|
Workflow: sv-frontend → git push → GitHub → Hetzner 1 git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Architektur
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────┐
|
||||||
|
│ Hetzner 1 (Plesk) │
|
||||||
|
│ │
|
||||||
|
│ Nginx (Plesk + Additional directives) │
|
||||||
|
│ ┌────────────────────────────────────────────┐ │
|
||||||
|
│ │ dak.complexcaresolutions.de:443 (SSL) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ /api/* → proxy_pass 127.0.0.1:8000 │ │
|
||||||
|
│ │ /docs → proxy_pass 127.0.0.1:8000 │ │
|
||||||
|
│ │ /* → /frontend/dist/ (React SPA) │ │
|
||||||
|
│ └──────────────┬────────────────┬────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ ┌──────────────▼─────┐ ┌──────▼────────────┐ │
|
||||||
|
│ │ FastAPI Backend │ │ React Frontend │ │
|
||||||
|
│ │ Gunicorn+Uvicorn │ │ Static Build │ │
|
||||||
|
│ │ systemd managed │ │ /frontend/dist │ │
|
||||||
|
│ │ Port 8000 │ │ │ │
|
||||||
|
│ └──────────┬─────────┘ └───────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌──────────▼─────────┐ │
|
||||||
|
│ │ MariaDB 10.11.14 │ │
|
||||||
|
│ │ DB: dak_c2s │ │
|
||||||
|
│ │ localhost:3306 │ │
|
||||||
|
│ └────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tech-Stack
|
||||||
|
|
||||||
|
| Layer | Technologie | Details |
|
||||||
|
|-------|------------|---------|
|
||||||
|
| Frontend | React 18 + TypeScript | Vite, Tailwind CSS, shadcn/ui, Recharts |
|
||||||
|
| Backend | Python 3.11 + FastAPI | SQLAlchemy 2.0, Pandas, openpyxl |
|
||||||
|
| DB-Treiber | PyMySQL + cryptography | mysql+pymysql Dialekt |
|
||||||
|
| Auth | python-jose + passlib + pyotp | JWT, bcrypt, TOTP |
|
||||||
|
| Migrations | Alembic | MariaDB-kompatibel |
|
||||||
|
| Process | Gunicorn + Uvicorn Workers | systemd-Service |
|
||||||
|
| E-Mail | smtplib | SSL, Port 465 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Quelldaten-Analyse
|
||||||
|
|
||||||
|
### 3.1 CRM-CSV (wöchentlicher Import)
|
||||||
|
```
|
||||||
|
Dateiname-Pattern: YYYY-MM-DD-HHMM.csv
|
||||||
|
Encoding: UTF-8-BOM
|
||||||
|
Delimiter: Komma
|
||||||
|
Spalten: Hauptkontakt, Name, Thema, Erstellungsdatum, Modul
|
||||||
|
|
||||||
|
Hauptkontakt (Pipe-delimited):
|
||||||
|
"Nachname | Vorname | Geburtsdatum | KVNR"
|
||||||
|
Beispiel: "Tonn | Regina | 28.04.1960 | D410126355"
|
||||||
|
|
||||||
|
Edge Cases:
|
||||||
|
- KVNR kann fehlen: "Daum | Luana | 05.02.2016 |"
|
||||||
|
- Geburtsdatum fehlerhaft: "29.08.0196"
|
||||||
|
- Leerzeichen um Pipes
|
||||||
|
|
||||||
|
Erstellungsdatum:
|
||||||
|
Format: "DD.MM.YY, HH:MM"
|
||||||
|
Beispiel: "02.02.26, 08:50"
|
||||||
|
|
||||||
|
Modul → Fallgruppe Mapping:
|
||||||
|
"Zweitmeinung Onkologie" → "onko"
|
||||||
|
"Zweitmeinung Kardiologie" → "kardio"
|
||||||
|
"Zweitmeinung Intensiv" → "intensiv"
|
||||||
|
"Zweitmeinung Gallenblase" → "galle"
|
||||||
|
"Zweitmeinung Schilddrüse" → "sd"
|
||||||
|
"Begutachtung *" → aus Kontext ableiten
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Abrechnung_DAK.xlsx (Master-Datei)
|
||||||
|
```
|
||||||
|
Sheets: 2026, 2025, 2024, 2023, 2020-2022, Gutachten, Übersicht, ...
|
||||||
|
Spalten pro Jahres-Sheet (39):
|
||||||
|
ID, KW, Datum, Anrede, Vorname, Nachname, Geburtsdatum, KVNR,
|
||||||
|
Versicherung, ICD, Fallgruppe, Straße, PLZ, Ort, E-Mail,
|
||||||
|
Ansprechpartner, Telefonnummer, Mobiltelefon, Unterlagen,
|
||||||
|
Unterlagen verschickt, Erhalten, Unterlagen erhalten,
|
||||||
|
Unterlagen an Gutachter, Gutachten, Gutachter, Gutachten erstellt,
|
||||||
|
Gutachten versendet, Schweigepflicht, Ablehnung, Abbruch,
|
||||||
|
Abbruch_Datum, Kurzbeschreibung, Fragestellung, Kommentar,
|
||||||
|
E-Mail2, Telefon2, Sonstiges, Abgerechnet, Abrechnung_Datum
|
||||||
|
|
||||||
|
Sheet "2020-2022" hat zusätzliche Spalte "Jahr" (Position 2)
|
||||||
|
|
||||||
|
Datenvolumen:
|
||||||
|
2026: 68 Zeilen
|
||||||
|
2025: 631 Zeilen
|
||||||
|
2024: 769 Zeilen
|
||||||
|
2023: 635 Zeilen
|
||||||
|
2020-2022: 1182 Zeilen
|
||||||
|
GESAMT: ~3.285 Fälle
|
||||||
|
|
||||||
|
Hinweis: "Tabelle1" ignorieren (temporäre Arbeitstabelle)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Berichtswesen (Referenzformat)
|
||||||
|
```
|
||||||
|
4 Sheets (5 ab jetzt):
|
||||||
|
|
||||||
|
Sheet 1 "Auswertung KW gesamt":
|
||||||
|
Zeile 1: "Gesamtübersicht"
|
||||||
|
Zeile 2: [leer] [leer] [AKTUELLES_JAHR] [leer] [VORJAHR] [leer]
|
||||||
|
Zeile 3: "Gesamtzahl an Erstberatungen" [leer] [Wert] [leer] [VJ-Wert] [leer]
|
||||||
|
Zeile 4: "Anzahl Ablehnungen" [leer] [Wert] [Prozent] [VJ-Wert] [VJ-Prozent]
|
||||||
|
Zeile 5: "Anzahl versendeter Unterlagen" [leer] [Wert] [Prozent] [VJ-Wert] [VJ-Prozent]
|
||||||
|
Zeile 6: "Anzahl keine Rückmeldung" [leer] [Wert] [Prozent] [VJ-Wert] [VJ-Prozent]
|
||||||
|
Zeile 7: "Anzahl erstellter Gutachten" [leer] [Wert] [Prozent] [VJ-Wert] [VJ-Prozent]
|
||||||
|
Zeile 8-9: leer
|
||||||
|
Zeile 10: KW | Anzahl Erstberatungen | Unterlagen | Ablehnung | Keine Rückmeldung | Gutachten
|
||||||
|
Zeile 11+: Daten pro KW (1-52)
|
||||||
|
|
||||||
|
Sheet 2 "Auswertung nach Fachgebieten":
|
||||||
|
Zeile 1: "Übersicht nach Fallgruppen"
|
||||||
|
Zeile 3: Gruppenköpfe: onko | kardio | intensiv | galle | sd
|
||||||
|
Zeile 4: KW | Anzahl | Gutachten | Keine RM/Ablehnung (×5 Fallgruppen)
|
||||||
|
Zeile 5+: Daten pro KW
|
||||||
|
|
||||||
|
ACHTUNG: 2023 hatte nur 3 Fallgruppen (onko, kardio, intensiv)
|
||||||
|
Ab 2024 kamen galle + sd dazu
|
||||||
|
|
||||||
|
Sheet 3 "Auswertung Gutachten":
|
||||||
|
Zeile 3: Gruppenköpfe: Gesamt | onko | kardio | intensiv | galle | sd
|
||||||
|
Zeile 4: KW | Gutachten | Alternative | Bestätigung (×6)
|
||||||
|
Zeile 5+: Daten pro KW
|
||||||
|
|
||||||
|
Sheet 4 "Auswertung Therapieänderungen" (NEU):
|
||||||
|
→ Gleiche Struktur wie Sheet 3, aber mit:
|
||||||
|
KW | Gutachten | TA Ja | TA Nein | Diagnosekorrektur | Unterversorgung | Übertherapie
|
||||||
|
|
||||||
|
Sheet 5 "Auswertung ICD onko":
|
||||||
|
Zeile 1: ICD | Anzahl von ICD
|
||||||
|
Zeile 2+: Daten (ICD normalisiert, uppercase, sortiert)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. MariaDB Schema
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- ============================================
|
||||||
|
-- Vorab: Charset sicherstellen
|
||||||
|
-- ============================================
|
||||||
|
ALTER DATABASE dak_c2s CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- BENUTZER & AUTH
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(100) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL,
|
||||||
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
|
role VARCHAR(20) NOT NULL DEFAULT 'dak_mitarbeiter',
|
||||||
|
mfa_secret VARCHAR(255) DEFAULT NULL,
|
||||||
|
mfa_enabled TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
must_change_password TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
last_login DATETIME DEFAULT NULL,
|
||||||
|
failed_login_attempts INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
locked_until DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_username (username),
|
||||||
|
UNIQUE KEY uk_email (email),
|
||||||
|
CONSTRAINT chk_role CHECK (role IN ('admin', 'dak_mitarbeiter'))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT UNSIGNED NOT NULL,
|
||||||
|
token_hash VARCHAR(255) NOT NULL,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
revoked TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_user (user_id),
|
||||||
|
INDEX idx_token (token_hash),
|
||||||
|
CONSTRAINT fk_rt_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS invitation_links (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
token VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) DEFAULT NULL,
|
||||||
|
role VARCHAR(20) NOT NULL DEFAULT 'dak_mitarbeiter',
|
||||||
|
created_by INT UNSIGNED DEFAULT NULL,
|
||||||
|
expires_at DATETIME NOT NULL,
|
||||||
|
used_at DATETIME DEFAULT NULL,
|
||||||
|
used_by INT UNSIGNED DEFAULT NULL,
|
||||||
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_token (token),
|
||||||
|
CONSTRAINT fk_inv_created FOREIGN KEY (created_by) REFERENCES users(id),
|
||||||
|
CONSTRAINT fk_inv_used FOREIGN KEY (used_by) REFERENCES users(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS allowed_domains (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
domain VARCHAR(255) NOT NULL,
|
||||||
|
role VARCHAR(20) NOT NULL DEFAULT 'dak_mitarbeiter',
|
||||||
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_domain (domain)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO allowed_domains (domain, role) VALUES ('dak.de', 'dak_mitarbeiter');
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- KERN: FÄLLE
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS cases (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
fall_id VARCHAR(100) DEFAULT NULL,
|
||||||
|
crm_ticket_id VARCHAR(20) DEFAULT NULL,
|
||||||
|
|
||||||
|
jahr SMALLINT UNSIGNED NOT NULL,
|
||||||
|
kw TINYINT UNSIGNED NOT NULL,
|
||||||
|
datum DATE NOT NULL,
|
||||||
|
|
||||||
|
anrede VARCHAR(20) DEFAULT NULL,
|
||||||
|
vorname VARCHAR(100) DEFAULT NULL,
|
||||||
|
nachname VARCHAR(100) NOT NULL,
|
||||||
|
geburtsdatum DATE DEFAULT NULL,
|
||||||
|
kvnr VARCHAR(20) DEFAULT NULL,
|
||||||
|
versicherung VARCHAR(50) NOT NULL DEFAULT 'DAK',
|
||||||
|
|
||||||
|
icd TEXT DEFAULT NULL,
|
||||||
|
fallgruppe VARCHAR(20) NOT NULL,
|
||||||
|
|
||||||
|
strasse VARCHAR(255) DEFAULT NULL,
|
||||||
|
plz VARCHAR(10) DEFAULT NULL,
|
||||||
|
ort VARCHAR(100) DEFAULT NULL,
|
||||||
|
email VARCHAR(255) DEFAULT NULL,
|
||||||
|
ansprechpartner VARCHAR(200) DEFAULT NULL,
|
||||||
|
telefonnummer VARCHAR(50) DEFAULT NULL,
|
||||||
|
mobiltelefon VARCHAR(50) DEFAULT NULL,
|
||||||
|
email2 VARCHAR(255) DEFAULT NULL,
|
||||||
|
telefon2 VARCHAR(50) DEFAULT NULL,
|
||||||
|
|
||||||
|
unterlagen TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
unterlagen_verschickt DATE DEFAULT NULL,
|
||||||
|
erhalten TINYINT(1) DEFAULT NULL,
|
||||||
|
unterlagen_erhalten DATE DEFAULT NULL,
|
||||||
|
unterlagen_an_gutachter DATE DEFAULT NULL,
|
||||||
|
gutachten TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
gutachter VARCHAR(100) DEFAULT NULL,
|
||||||
|
gutachten_erstellt DATE DEFAULT NULL,
|
||||||
|
gutachten_versendet DATE DEFAULT NULL,
|
||||||
|
schweigepflicht TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
ablehnung TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
abbruch TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
abbruch_datum DATE DEFAULT NULL,
|
||||||
|
|
||||||
|
gutachten_typ VARCHAR(20) DEFAULT NULL,
|
||||||
|
therapieaenderung VARCHAR(5) DEFAULT NULL,
|
||||||
|
ta_diagnosekorrektur TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
ta_unterversorgung TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
ta_uebertherapie TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
kurzbeschreibung TEXT DEFAULT NULL,
|
||||||
|
fragestellung TEXT DEFAULT NULL,
|
||||||
|
kommentar TEXT DEFAULT NULL,
|
||||||
|
sonstiges TEXT DEFAULT NULL,
|
||||||
|
|
||||||
|
abgerechnet TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
abrechnung_datum DATE DEFAULT NULL,
|
||||||
|
|
||||||
|
import_source VARCHAR(255) DEFAULT NULL,
|
||||||
|
imported_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
updated_by INT UNSIGNED DEFAULT NULL,
|
||||||
|
icd_entered_by INT UNSIGNED DEFAULT NULL,
|
||||||
|
icd_entered_at DATETIME DEFAULT NULL,
|
||||||
|
coding_completed_by INT UNSIGNED DEFAULT NULL,
|
||||||
|
coding_completed_at DATETIME DEFAULT NULL,
|
||||||
|
|
||||||
|
UNIQUE KEY uk_fall_id (fall_id),
|
||||||
|
INDEX idx_jahr_kw (jahr, kw),
|
||||||
|
INDEX idx_kvnr (kvnr),
|
||||||
|
INDEX idx_fallgruppe (fallgruppe),
|
||||||
|
INDEX idx_datum (datum),
|
||||||
|
INDEX idx_nachname_vorname (nachname, vorname),
|
||||||
|
INDEX idx_pending_icd (jahr, kw, fallgruppe, icd(20)),
|
||||||
|
INDEX idx_pending_coding (gutachten, gutachten_typ),
|
||||||
|
|
||||||
|
CONSTRAINT fk_c_updated FOREIGN KEY (updated_by) REFERENCES users(id),
|
||||||
|
CONSTRAINT fk_c_icd_by FOREIGN KEY (icd_entered_by) REFERENCES users(id),
|
||||||
|
CONSTRAINT fk_c_coding_by FOREIGN KEY (coding_completed_by) REFERENCES users(id),
|
||||||
|
CONSTRAINT chk_fallgruppe CHECK (fallgruppe IN ('onko', 'kardio', 'intensiv', 'galle', 'sd')),
|
||||||
|
CONSTRAINT chk_gutachten_typ CHECK (gutachten_typ IS NULL OR gutachten_typ IN ('Bestätigung', 'Alternative')),
|
||||||
|
CONSTRAINT chk_ta CHECK (therapieaenderung IS NULL OR therapieaenderung IN ('Ja', 'Nein'))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS case_icd_codes (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
case_id INT UNSIGNED NOT NULL,
|
||||||
|
icd_code VARCHAR(20) NOT NULL,
|
||||||
|
icd_hauptgruppe VARCHAR(10) DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_case (case_id),
|
||||||
|
INDEX idx_code (icd_code),
|
||||||
|
INDEX idx_haupt (icd_hauptgruppe),
|
||||||
|
CONSTRAINT fk_icd_case FOREIGN KEY (case_id) REFERENCES cases(id) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- BERICHTSWESEN
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS weekly_reports (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
jahr SMALLINT UNSIGNED NOT NULL,
|
||||||
|
kw TINYINT UNSIGNED NOT NULL,
|
||||||
|
report_date DATE NOT NULL,
|
||||||
|
report_file_path VARCHAR(500) DEFAULT NULL,
|
||||||
|
report_data JSON DEFAULT NULL,
|
||||||
|
generated_by INT UNSIGNED DEFAULT NULL,
|
||||||
|
generated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY uk_jahr_kw (jahr, kw),
|
||||||
|
CONSTRAINT fk_rpt_by FOREIGN KEY (generated_by) REFERENCES users(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS yearly_summary (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
jahr SMALLINT UNSIGNED NOT NULL,
|
||||||
|
kw TINYINT UNSIGNED NOT NULL,
|
||||||
|
erstberatungen INT UNSIGNED DEFAULT 0,
|
||||||
|
ablehnungen INT UNSIGNED DEFAULT 0,
|
||||||
|
unterlagen INT UNSIGNED DEFAULT 0,
|
||||||
|
keine_rueckmeldung INT UNSIGNED DEFAULT 0,
|
||||||
|
gutachten_gesamt INT UNSIGNED DEFAULT 0,
|
||||||
|
gutachten_alternative INT UNSIGNED DEFAULT 0,
|
||||||
|
gutachten_bestaetigung INT UNSIGNED DEFAULT 0,
|
||||||
|
onko_anzahl INT UNSIGNED DEFAULT 0, onko_gutachten INT UNSIGNED DEFAULT 0, onko_keine_rm INT UNSIGNED DEFAULT 0,
|
||||||
|
kardio_anzahl INT UNSIGNED DEFAULT 0, kardio_gutachten INT UNSIGNED DEFAULT 0, kardio_keine_rm INT UNSIGNED DEFAULT 0,
|
||||||
|
intensiv_anzahl INT UNSIGNED DEFAULT 0, intensiv_gutachten INT UNSIGNED DEFAULT 0, intensiv_keine_rm INT UNSIGNED DEFAULT 0,
|
||||||
|
galle_anzahl INT UNSIGNED DEFAULT 0, galle_gutachten INT UNSIGNED DEFAULT 0, galle_keine_rm INT UNSIGNED DEFAULT 0,
|
||||||
|
sd_anzahl INT UNSIGNED DEFAULT 0, sd_gutachten INT UNSIGNED DEFAULT 0, sd_keine_rm INT UNSIGNED DEFAULT 0,
|
||||||
|
onko_alternative INT UNSIGNED DEFAULT 0, onko_bestaetigung INT UNSIGNED DEFAULT 0,
|
||||||
|
kardio_alternative INT UNSIGNED DEFAULT 0, kardio_bestaetigung INT UNSIGNED DEFAULT 0,
|
||||||
|
intensiv_alternative INT UNSIGNED DEFAULT 0, intensiv_bestaetigung INT UNSIGNED DEFAULT 0,
|
||||||
|
galle_alternative INT UNSIGNED DEFAULT 0, galle_bestaetigung INT UNSIGNED DEFAULT 0,
|
||||||
|
sd_alternative INT UNSIGNED DEFAULT 0, sd_bestaetigung INT UNSIGNED DEFAULT 0,
|
||||||
|
ta_ja INT UNSIGNED DEFAULT 0,
|
||||||
|
ta_nein INT UNSIGNED DEFAULT 0,
|
||||||
|
ta_diagnosekorrektur INT UNSIGNED DEFAULT 0,
|
||||||
|
ta_unterversorgung INT UNSIGNED DEFAULT 0,
|
||||||
|
ta_uebertherapie INT UNSIGNED DEFAULT 0,
|
||||||
|
UNIQUE KEY uk_jahr_kw (jahr, kw)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- LOGGING & BENACHRICHTIGUNGEN
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS import_log (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
filename VARCHAR(255) NOT NULL,
|
||||||
|
import_type VARCHAR(50) NOT NULL,
|
||||||
|
cases_imported INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
cases_skipped INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
cases_updated INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
errors TEXT DEFAULT NULL,
|
||||||
|
details JSON DEFAULT NULL,
|
||||||
|
imported_by INT UNSIGNED DEFAULT NULL,
|
||||||
|
imported_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT fk_imp_by FOREIGN KEY (imported_by) REFERENCES users(id),
|
||||||
|
CONSTRAINT chk_imp_type CHECK (import_type IN ('csv_crm', 'icd_xlsx', 'historical_excel', 'excel_sync'))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS audit_log (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT UNSIGNED DEFAULT NULL,
|
||||||
|
action VARCHAR(100) NOT NULL,
|
||||||
|
entity_type VARCHAR(50) DEFAULT NULL,
|
||||||
|
entity_id INT UNSIGNED DEFAULT NULL,
|
||||||
|
old_values JSON DEFAULT NULL,
|
||||||
|
new_values JSON DEFAULT NULL,
|
||||||
|
ip_address VARCHAR(45) DEFAULT NULL,
|
||||||
|
user_agent TEXT DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_user (user_id),
|
||||||
|
INDEX idx_entity (entity_type, entity_id),
|
||||||
|
INDEX idx_created (created_at),
|
||||||
|
CONSTRAINT fk_audit_user FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS notifications (
|
||||||
|
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
recipient_id INT UNSIGNED NOT NULL,
|
||||||
|
notification_type VARCHAR(50) NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
message TEXT DEFAULT NULL,
|
||||||
|
related_entity_type VARCHAR(50) DEFAULT NULL,
|
||||||
|
related_entity_id INT UNSIGNED DEFAULT NULL,
|
||||||
|
is_read TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
email_sent TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
email_sent_at DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_recipient (recipient_id, is_read),
|
||||||
|
CONSTRAINT fk_notif_user FOREIGN KEY (recipient_id) REFERENCES users(id),
|
||||||
|
CONSTRAINT chk_notif CHECK (notification_type IN (
|
||||||
|
'new_cases_uploaded', 'icd_entered', 'icd_uploaded',
|
||||||
|
'report_ready', 'coding_completed'))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Backend-Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
dak.c2s/
|
||||||
|
├── backend/
|
||||||
|
│ ├── app/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── main.py # FastAPI App, Middleware, CORS, Lifespan
|
||||||
|
│ │ ├── config.py # Pydantic Settings (.env)
|
||||||
|
│ │ ├── database.py # SQLAlchemy engine (mysql+pymysql)
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── models/
|
||||||
|
│ │ │ ├── __init__.py # Alle Models exportieren
|
||||||
|
│ │ │ ├── user.py # User, RefreshToken, InvitationLink, AllowedDomain
|
||||||
|
│ │ │ ├── case.py # Case, CaseICDCode
|
||||||
|
│ │ │ ├── report.py # WeeklyReport, YearlySummary
|
||||||
|
│ │ │ └── audit.py # AuditLog, ImportLog, Notification
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── schemas/
|
||||||
|
│ │ │ ├── __init__.py
|
||||||
|
│ │ │ ├── auth.py # LoginRequest, TokenResponse, RegisterRequest, MFASetup
|
||||||
|
│ │ │ ├── user.py # UserResponse, UserCreate, UserUpdate
|
||||||
|
│ │ │ ├── case.py # CaseResponse, CaseList, CaseUpdate, ICDUpdate, CodingUpdate
|
||||||
|
│ │ │ ├── import_schemas.py # ImportPreview, ImportResult, ImportRow
|
||||||
|
│ │ │ ├── report.py # DashboardKPIs, WeeklyData, ReportMeta
|
||||||
|
│ │ │ └── notification.py # NotificationResponse, NotificationList
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── api/
|
||||||
|
│ │ │ ├── __init__.py
|
||||||
|
│ │ │ ├── auth.py # POST login, register, refresh, mfa/setup, mfa/verify
|
||||||
|
│ │ │ ├── cases.py # GET/PUT cases, pending-icd, pending-coding
|
||||||
|
│ │ │ ├── import_router.py # POST csv, icd-xlsx, historical
|
||||||
|
│ │ │ ├── coding.py # GET queue, PUT coding/{id}
|
||||||
|
│ │ │ ├── reports.py # GET dashboard, weekly, download; POST generate
|
||||||
|
│ │ │ ├── notifications.py # GET list, PUT mark-read
|
||||||
|
│ │ │ └── admin.py # Users CRUD, invitations, audit-log
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── services/
|
||||||
|
│ │ │ ├── __init__.py
|
||||||
|
│ │ │ ├── auth_service.py # JWT create/verify, password hash/verify, MFA
|
||||||
|
│ │ │ ├── csv_parser.py # CRM CSV parsing (pipe-delimited contacts)
|
||||||
|
│ │ │ ├── import_service.py # Import orchestration + duplicate detection
|
||||||
|
│ │ │ ├── icd_service.py # ICD normalize, split, validate, hauptgruppe
|
||||||
|
│ │ │ ├── coding_service.py # Coding queue, batch update
|
||||||
|
│ │ │ ├── report_service.py # All 5 sheet calculations (pandas)
|
||||||
|
│ │ │ ├── excel_export.py # openpyxl → Berichtswesen format
|
||||||
|
│ │ │ ├── excel_import.py # Abrechnung_DAK.xlsx → DB (historical)
|
||||||
|
│ │ │ ├── vorjahr_service.py # Year-over-year comparison
|
||||||
|
│ │ │ ├── notification_service.py # SMTP + in-app notifications
|
||||||
|
│ │ │ ├── audit_service.py # Audit log helper
|
||||||
|
│ │ │ └── excel_sync.py # Bidirectional Excel ↔ DB sync
|
||||||
|
│ │ │
|
||||||
|
│ │ ├── core/
|
||||||
|
│ │ │ ├── __init__.py
|
||||||
|
│ │ │ ├── security.py # JWT encode/decode, password helpers
|
||||||
|
│ │ │ ├── dependencies.py # get_current_user, require_admin, get_db
|
||||||
|
│ │ │ └── exceptions.py # Custom HTTP exceptions
|
||||||
|
│ │ │
|
||||||
|
│ │ └── utils/
|
||||||
|
│ │ ├── __init__.py
|
||||||
|
│ │ ├── fallgruppe_map.py # Modul string → fallgruppe code
|
||||||
|
│ │ ├── kw_utils.py # ISO KW calculation, date helpers
|
||||||
|
│ │ └── validators.py # ICD regex, KVNR format, date validation
|
||||||
|
│ │
|
||||||
|
│ ├── alembic/
|
||||||
|
│ │ ├── alembic.ini
|
||||||
|
│ │ ├── env.py
|
||||||
|
│ │ └── versions/
|
||||||
|
│ │
|
||||||
|
│ ├── scripts/
|
||||||
|
│ │ ├── init_db.py # Create schema + seed data
|
||||||
|
│ │ ├── import_historical.py # One-time: Abrechnung → DB
|
||||||
|
│ │ ├── import_berichtswesen.py # One-time: Vorjahres-Berichte → yearly_summary
|
||||||
|
│ │ └── create_admin.py # Create initial admin user
|
||||||
|
│ │
|
||||||
|
│ ├── tests/
|
||||||
|
│ │ ├── conftest.py
|
||||||
|
│ │ ├── test_csv_parser.py
|
||||||
|
│ │ ├── test_import.py
|
||||||
|
│ │ ├── test_icd_service.py
|
||||||
|
│ │ ├── test_report_service.py
|
||||||
|
│ │ └── test_auth.py
|
||||||
|
│ │
|
||||||
|
│ ├── requirements.txt
|
||||||
|
│ ├── .env.example
|
||||||
|
│ └── .env
|
||||||
|
│
|
||||||
|
├── frontend/
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── components/
|
||||||
|
│ │ │ ├── ui/ # shadcn/ui
|
||||||
|
│ │ │ ├── layout/ # AppLayout, Sidebar, Header, ProtectedRoute
|
||||||
|
│ │ │ ├── dashboard/ # KPICards, WeeklyChart, FallgruppenDonut, etc.
|
||||||
|
│ │ │ ├── cases/ # CaseTable, CaseDetail, ICDInlineEdit
|
||||||
|
│ │ │ ├── import/ # CSVUpload, ImportPreview, ICDUpload
|
||||||
|
│ │ │ ├── coding/ # CodingQueue, CodingCard, CodingProgress
|
||||||
|
│ │ │ ├── reports/ # ReportList, ReportDownload
|
||||||
|
│ │ │ ├── notifications/ # NotificationBell, NotificationDropdown
|
||||||
|
│ │ │ └── admin/ # UserManagement, InvitationLinks, AuditLog
|
||||||
|
│ │ ├── pages/ # LoginPage, DashboardPage, CasesPage, etc.
|
||||||
|
│ │ ├── services/ # api.ts, authService.ts, etc.
|
||||||
|
│ │ ├── hooks/ # useAuth, useCases, useNotifications
|
||||||
|
│ │ ├── types/ # TypeScript interfaces
|
||||||
|
│ │ ├── context/ # AuthContext
|
||||||
|
│ │ ├── App.tsx
|
||||||
|
│ │ └── main.tsx
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── vite.config.ts
|
||||||
|
│ ├── tailwind.config.js
|
||||||
|
│ └── tsconfig.json
|
||||||
|
│
|
||||||
|
├── data/ # Referenzdaten (nicht im Git)
|
||||||
|
│ ├── Abrechnung_DAK.xlsx
|
||||||
|
│ ├── Berichtswesen_2024_29122024.xlsx
|
||||||
|
│ ├── Berichtswesen_2023_31122023.xlsx
|
||||||
|
│ └── sample_csv/
|
||||||
|
│ └── 2026-02-06-0406.csv
|
||||||
|
│
|
||||||
|
├── .gitignore
|
||||||
|
├── README.md
|
||||||
|
└── SPEC.md # Diese Datei
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. API-Endpunkte
|
||||||
|
|
||||||
|
### Auth
|
||||||
|
```
|
||||||
|
POST /api/auth/login → {access_token, refresh_token, user}
|
||||||
|
POST /api/auth/register → {user} (Domain-Check oder Invitation-Token)
|
||||||
|
POST /api/auth/refresh → {access_token}
|
||||||
|
POST /api/auth/mfa/setup → {secret, qr_uri} (Admin)
|
||||||
|
POST /api/auth/mfa/verify → {verified: true}
|
||||||
|
POST /api/auth/logout → Revoke refresh token
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cases
|
||||||
|
```
|
||||||
|
GET /api/cases → Paginated list (filter: jahr, kw, fallgruppe, has_icd, has_coding)
|
||||||
|
GET /api/cases/{id} → Single case detail
|
||||||
|
PUT /api/cases/{id} → Update case fields
|
||||||
|
GET /api/cases/pending-icd → Cases without ICD (for DAK)
|
||||||
|
PUT /api/cases/{id}/icd → Set ICD (DAK or Admin)
|
||||||
|
GET /api/cases/pending-coding → Cases with gutachten but no typ (for Admin)
|
||||||
|
PUT /api/cases/{id}/coding → Set gutachten_typ + therapieaenderung (Admin)
|
||||||
|
GET /api/cases/coding-template → Download .xlsx template for ICD entry
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import
|
||||||
|
```
|
||||||
|
POST /api/import/csv → Upload CRM CSV → preview
|
||||||
|
POST /api/import/csv/confirm → Confirm import from preview
|
||||||
|
POST /api/import/icd-xlsx → Upload ICD-coded Excel (DAK)
|
||||||
|
POST /api/import/historical → Import from Abrechnung_DAK.xlsx (Admin, one-time)
|
||||||
|
GET /api/import/log → Import history
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reports
|
||||||
|
```
|
||||||
|
GET /api/reports/dashboard → Live KPIs + chart data
|
||||||
|
GET /api/reports/weekly/{jahr}/{kw} → Specific week data
|
||||||
|
POST /api/reports/generate → Generate report .xlsx (Admin)
|
||||||
|
GET /api/reports/download/{id} → Download generated .xlsx
|
||||||
|
GET /api/reports/list → All generated reports
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
```
|
||||||
|
GET /api/notifications → Unread + recent
|
||||||
|
PUT /api/notifications/{id}/read → Mark as read
|
||||||
|
PUT /api/notifications/read-all → Mark all as read
|
||||||
|
```
|
||||||
|
|
||||||
|
### Admin
|
||||||
|
```
|
||||||
|
GET /api/admin/users → List users
|
||||||
|
POST /api/admin/users → Create user
|
||||||
|
PUT /api/admin/users/{id} → Update user (role, active)
|
||||||
|
POST /api/admin/invitations → Create invitation link
|
||||||
|
GET /api/admin/invitations → List invitation links
|
||||||
|
GET /api/admin/audit-log → Paginated audit log
|
||||||
|
POST /api/admin/excel-sync → Trigger Excel ↔ DB sync
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Implementierungsphasen
|
||||||
|
|
||||||
|
### Phase 1: Setup + DB + Auth (Woche 1)
|
||||||
|
```
|
||||||
|
Ziel: Lauffähiges Backend mit Auth auf sv-frontend
|
||||||
|
|
||||||
|
□ GitHub Repo complexcaresolutions/dak.c2s anlegen
|
||||||
|
□ Projekt klonen auf sv-frontend
|
||||||
|
□ MariaDB auf sv-frontend einrichten (dev)
|
||||||
|
□ Backend-Projektstruktur erstellen
|
||||||
|
□ Python venv + requirements.txt
|
||||||
|
□ .env + config.py (Pydantic Settings)
|
||||||
|
□ database.py (SQLAlchemy mysql+pymysql)
|
||||||
|
□ Alle SQLAlchemy Models
|
||||||
|
□ Alembic init + erste Migration
|
||||||
|
□ Schema deployen (dev)
|
||||||
|
□ Auth: JWT + bcrypt + MFA
|
||||||
|
□ API: auth (login, register, refresh, mfa)
|
||||||
|
□ API: admin (users CRUD)
|
||||||
|
□ Domain-Whitelist + Einladungslinks
|
||||||
|
□ Audit-Log Middleware
|
||||||
|
□ CORS + Error Handling
|
||||||
|
□ create_admin.py Script
|
||||||
|
□ Erster Commit + Push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Import + ICD (Woche 2)
|
||||||
|
```
|
||||||
|
Ziel: CSV-Import und ICD-Workflow funktionsfähig
|
||||||
|
|
||||||
|
□ csv_parser.py (Pipe-Format, Modul-Mapping)
|
||||||
|
□ import_service.py (Duplikatprüfung, Fall_ID)
|
||||||
|
□ icd_service.py (Normalisierung, Validierung)
|
||||||
|
□ validators.py (ICD-Regex, KVNR, Datum)
|
||||||
|
□ API: import/csv (Upload + Preview + Confirm)
|
||||||
|
□ API: import/icd-xlsx (DAK-Rückläufer)
|
||||||
|
□ API: cases/pending-icd
|
||||||
|
□ API: cases/{id}/icd
|
||||||
|
□ Coding-Template Generierung (.xlsx)
|
||||||
|
□ excel_import.py (Abrechnung → DB)
|
||||||
|
□ import_historical.py Script
|
||||||
|
□ Historischen Import durchführen (2023-2026)
|
||||||
|
□ Import-Logging
|
||||||
|
□ notification_service.py (SMTP)
|
||||||
|
□ Tests: csv_parser, import, icd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Berichtswesen + Coding (Woche 3)
|
||||||
|
```
|
||||||
|
Ziel: Berichte generierbar, Coding-Queue nutzbar
|
||||||
|
|
||||||
|
□ report_service.py (alle 5 Sheet-Berechnungen)
|
||||||
|
□ vorjahr_service.py
|
||||||
|
□ excel_export.py (exaktes Berichtswesen-Format)
|
||||||
|
□ import_berichtswesen.py (Vorjahres-Cache)
|
||||||
|
□ API: reports/dashboard
|
||||||
|
□ API: reports/generate + download
|
||||||
|
□ coding_service.py (Queue-Logik)
|
||||||
|
□ API: coding (queue, update)
|
||||||
|
□ API: cases/pending-coding
|
||||||
|
□ Benachrichtigungen (alle Trigger)
|
||||||
|
□ excel_sync.py (DB ↔ Abrechnung)
|
||||||
|
□ Tests: reports, coding
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Frontend Core (Woche 4-5)
|
||||||
|
```
|
||||||
|
Ziel: Funktionsfähiges Web-Interface
|
||||||
|
|
||||||
|
Woche 4:
|
||||||
|
□ React + Vite + TS + Tailwind + shadcn/ui Setup
|
||||||
|
□ API-Client (axios/fetch + JWT interceptor)
|
||||||
|
□ AuthContext + ProtectedRoute
|
||||||
|
□ Login + Register Seiten
|
||||||
|
□ AppLayout (Sidebar + Header)
|
||||||
|
□ Dashboard: KPI-Cards
|
||||||
|
□ Dashboard: WeeklyTrendChart (Recharts)
|
||||||
|
□ Dashboard: FallgruppenDonut
|
||||||
|
□ Dashboard: VorjahresComparison
|
||||||
|
|
||||||
|
Woche 5:
|
||||||
|
□ CaseTable + Filter + Pagination
|
||||||
|
□ ICD-Inline-Edit (DAK)
|
||||||
|
□ CSVUpload + ImportPreview
|
||||||
|
□ ICDUpload (Excel)
|
||||||
|
□ CodingQueue + CodingCard
|
||||||
|
□ ReportList + Download
|
||||||
|
□ NotificationBell + Dropdown
|
||||||
|
□ Admin: UserManagement
|
||||||
|
□ Admin: InvitationLinks
|
||||||
|
□ Admin: AuditLogViewer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: Deploy + Go-Live (Woche 6)
|
||||||
|
```
|
||||||
|
Ziel: Live auf dak.complexcaresolutions.de
|
||||||
|
|
||||||
|
□ Frontend Production Build
|
||||||
|
□ Git push → GitHub
|
||||||
|
□ Hetzner 1: git pull
|
||||||
|
□ Python venv auf Server
|
||||||
|
□ MariaDB Schema auf Produktion deployen
|
||||||
|
□ .env auf Server konfigurieren
|
||||||
|
□ systemd-Service einrichten + starten
|
||||||
|
□ Plesk: Additional nginx directives
|
||||||
|
□ SMTP testen (Produktion)
|
||||||
|
□ Admin-Account anlegen
|
||||||
|
□ Historische Daten importieren (Produktion)
|
||||||
|
□ DAK-Mitarbeiter einladen
|
||||||
|
□ Smoke Tests
|
||||||
|
□ Go-Live ✓
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Sicherheit
|
||||||
|
|
||||||
|
### Authentifizierung
|
||||||
|
- bcrypt (12 Rounds) für Passwort-Hashing
|
||||||
|
- JWT Access Token: 15 Min Laufzeit
|
||||||
|
- JWT Refresh Token: 7 Tage, in DB gespeichert, revokierbar
|
||||||
|
- MFA: TOTP (pyotp), optional aktivierbar
|
||||||
|
- Account-Sperre nach 5 Fehlversuchen (30 Min)
|
||||||
|
- Domain-Whitelist: @dak.de für Selbstregistrierung
|
||||||
|
- Einladungslinks: Token + Ablaufdatum + optional E-Mail-Bindung
|
||||||
|
|
||||||
|
### Autorisierung (RBAC)
|
||||||
|
- admin: Voller Zugriff (Import, Coding, Reports, Users)
|
||||||
|
- dak_mitarbeiter: Dashboard, ICD-Eingabe, Report-Download
|
||||||
|
|
||||||
|
### Audit
|
||||||
|
- Jede Datenänderung wird geloggt (entity, old/new values, user, IP)
|
||||||
|
- Unveränderlich (INSERT only)
|
||||||
|
- Einsehbar über Admin-UI
|
||||||
|
|
||||||
|
### Transport
|
||||||
|
- TLS/SSL (Plesk-verwaltet)
|
||||||
|
- CORS: nur dak.complexcaresolutions.de
|
||||||
|
|
||||||
|
### Daten
|
||||||
|
- MariaDB auf localhost (kein externer Zugang)
|
||||||
|
- Verschlüsselung at rest: MariaDB encryption + Dateisystem
|
||||||
|
- Uploads in geschütztem Verzeichnis (nicht öffentlich)
|
||||||
|
- Sensible Daten (.env, Passwörter) nicht im Git
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Nginx Konfiguration (Plesk Additional Directives)
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# In Plesk → dak.complexcaresolutions.de → Apache & nginx Settings
|
||||||
|
# → Additional nginx directives
|
||||||
|
|
||||||
|
# API Reverse Proxy
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:8000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_read_timeout 120s;
|
||||||
|
client_max_body_size 20M;
|
||||||
|
}
|
||||||
|
|
||||||
|
# FastAPI Docs
|
||||||
|
location /docs {
|
||||||
|
proxy_pass http://127.0.0.1:8000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /openapi.json {
|
||||||
|
proxy_pass http://127.0.0.1:8000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
|
# React SPA fallback
|
||||||
|
location / {
|
||||||
|
root /var/www/vhosts/dak.complexcaresolutions.de/frontend/dist;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. systemd Service
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/systemd/system/dak-backend.service
|
||||||
|
|
||||||
|
[Unit]
|
||||||
|
Description=DAK Portal Backend (FastAPI/Gunicorn)
|
||||||
|
After=network.target mariadb.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=www-data
|
||||||
|
Group=www-data
|
||||||
|
WorkingDirectory=/var/www/vhosts/dak.complexcaresolutions.de/backend
|
||||||
|
Environment="PATH=/var/www/vhosts/dak.complexcaresolutions.de/backend/venv/bin"
|
||||||
|
EnvironmentFile=/var/www/vhosts/dak.complexcaresolutions.de/backend/.env
|
||||||
|
ExecStart=/var/www/vhosts/dak.complexcaresolutions.de/backend/venv/bin/gunicorn \
|
||||||
|
app.main:app \
|
||||||
|
--workers 2 \
|
||||||
|
--worker-class uvicorn.workers.UvicornWorker \
|
||||||
|
--bind 127.0.0.1:8000 \
|
||||||
|
--access-logfile /var/www/vhosts/dak.complexcaresolutions.de/logs/access.log \
|
||||||
|
--error-logfile /var/www/vhosts/dak.complexcaresolutions.de/logs/error.log
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Management:
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable dak-backend
|
||||||
|
sudo systemctl start dak-backend
|
||||||
|
sudo systemctl status dak-backend
|
||||||
|
sudo journalctl -u dak-backend -f # Live-Logs
|
||||||
|
```
|
||||||
24
backend/.env.example
Normal file
24
backend/.env.example
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Database
|
||||||
|
DB_HOST=
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_NAME=dak_c2s
|
||||||
|
DB_USER=dak_c2s_admin
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
JWT_SECRET_KEY=
|
||||||
|
JWT_ALGORITHM=HS256
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=15
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||||
|
|
||||||
|
# SMTP
|
||||||
|
SMTP_HOST=smtp.complexcaresolutions.de
|
||||||
|
SMTP_PORT=465
|
||||||
|
SMTP_USER=noreply@complexcaresolutions.de
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
SMTP_FROM=noreply@complexcaresolutions.de
|
||||||
|
|
||||||
|
# App
|
||||||
|
APP_NAME=DAK Zweitmeinungs-Portal
|
||||||
|
CORS_ORIGINS=http://localhost:5173,https://dak.complexcaresolutions.de
|
||||||
|
MAX_UPLOAD_SIZE=20971520
|
||||||
0
backend/alembic/.gitkeep
Normal file
0
backend/alembic/.gitkeep
Normal file
0
backend/app/__init__.py
Normal file
0
backend/app/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
0
backend/app/api/__init__.py
Normal file
46
backend/app/config.py
Normal file
46
backend/app/config.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
# backend/app/config.py
|
||||||
|
from pydantic_settings import BaseSettings
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
|
class Settings(BaseSettings):
|
||||||
|
# Database
|
||||||
|
DB_HOST: str = "localhost"
|
||||||
|
DB_PORT: int = 3306
|
||||||
|
DB_NAME: str = "dak_c2s"
|
||||||
|
DB_USER: str = "dak_c2s_admin"
|
||||||
|
DB_PASSWORD: str = ""
|
||||||
|
|
||||||
|
# JWT
|
||||||
|
JWT_SECRET_KEY: str = "change-me-in-production"
|
||||||
|
JWT_ALGORITHM: str = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS: int = 7
|
||||||
|
|
||||||
|
# SMTP
|
||||||
|
SMTP_HOST: str = "smtp.complexcaresolutions.de"
|
||||||
|
SMTP_PORT: int = 465
|
||||||
|
SMTP_USER: str = "noreply@complexcaresolutions.de"
|
||||||
|
SMTP_PASSWORD: str = ""
|
||||||
|
SMTP_FROM: str = "noreply@complexcaresolutions.de"
|
||||||
|
|
||||||
|
# App
|
||||||
|
APP_NAME: str = "DAK Zweitmeinungs-Portal"
|
||||||
|
CORS_ORIGINS: str = "http://localhost:5173,https://dak.complexcaresolutions.de"
|
||||||
|
MAX_UPLOAD_SIZE: int = 20971520 # 20MB
|
||||||
|
|
||||||
|
@property
|
||||||
|
def database_url(self) -> str:
|
||||||
|
return (
|
||||||
|
f"mysql+pymysql://{self.DB_USER}:{self.DB_PASSWORD}"
|
||||||
|
f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}?charset=utf8mb4"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = ".env"
|
||||||
|
env_file_encoding = "utf-8"
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def get_settings() -> Settings:
|
||||||
|
return Settings()
|
||||||
0
backend/app/core/__init__.py
Normal file
0
backend/app/core/__init__.py
Normal file
24
backend/app/database.py
Normal file
24
backend/app/database.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# backend/app/database.py
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker, DeclarativeBase
|
||||||
|
from app.config import get_settings
|
||||||
|
|
||||||
|
settings = get_settings()
|
||||||
|
engine = create_engine(
|
||||||
|
settings.database_url,
|
||||||
|
pool_pre_ping=True,
|
||||||
|
pool_recycle=3600,
|
||||||
|
)
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
class Base(DeclarativeBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
21
backend/app/main.py
Normal file
21
backend/app/main.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# backend/app/main.py
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from app.config import get_settings
|
||||||
|
|
||||||
|
settings = get_settings()
|
||||||
|
|
||||||
|
app = FastAPI(title=settings.APP_NAME, docs_url="/docs")
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=settings.CORS_ORIGINS.split(","),
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/health")
|
||||||
|
def health_check():
|
||||||
|
return {"status": "ok"}
|
||||||
0
backend/app/models/__init__.py
Normal file
0
backend/app/models/__init__.py
Normal file
0
backend/app/schemas/__init__.py
Normal file
0
backend/app/schemas/__init__.py
Normal file
0
backend/app/services/__init__.py
Normal file
0
backend/app/services/__init__.py
Normal file
0
backend/app/utils/__init__.py
Normal file
0
backend/app/utils/__init__.py
Normal file
24
backend/requirements.txt
Normal file
24
backend/requirements.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
fastapi==0.115.6
|
||||||
|
uvicorn[standard]==0.34.0
|
||||||
|
gunicorn==23.0.0
|
||||||
|
sqlalchemy==2.0.36
|
||||||
|
alembic==1.14.1
|
||||||
|
pymysql==1.1.1
|
||||||
|
cryptography==44.0.0
|
||||||
|
python-jose[cryptography]==3.3.0
|
||||||
|
passlib[bcrypt]==1.7.4
|
||||||
|
pyotp==2.9.0
|
||||||
|
qrcode==8.0
|
||||||
|
python-multipart==0.0.20
|
||||||
|
pandas==2.2.3
|
||||||
|
openpyxl==3.1.5
|
||||||
|
pydantic==2.10.4
|
||||||
|
pydantic-settings==2.7.1
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
email-validator==2.2.0
|
||||||
|
httpx==0.28.1
|
||||||
|
|
||||||
|
# Dev/Test
|
||||||
|
pytest==8.3.4
|
||||||
|
pytest-asyncio==0.25.0
|
||||||
|
pytest-cov==6.0.0
|
||||||
0
backend/scripts/__init__.py
Normal file
0
backend/scripts/__init__.py
Normal file
0
backend/tests/__init__.py
Normal file
0
backend/tests/__init__.py
Normal file
16
backend/tests/conftest.py
Normal file
16
backend/tests/conftest.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# backend/tests/conftest.py
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def app_settings():
|
||||||
|
"""Provide test settings."""
|
||||||
|
from app.config import Settings
|
||||||
|
return Settings(
|
||||||
|
DB_HOST="localhost",
|
||||||
|
DB_PORT=3306,
|
||||||
|
DB_NAME="dak_c2s_test",
|
||||||
|
DB_USER="test",
|
||||||
|
DB_PASSWORD="test",
|
||||||
|
JWT_SECRET_KEY="test-secret-key",
|
||||||
|
)
|
||||||
61
docs/plans/2026-02-23-dak-portal-design.md
Normal file
61
docs/plans/2026-02-23-dak-portal-design.md
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# DAK Zweitmeinungs-Portal — Design-Dokument
|
||||||
|
|
||||||
|
**Datum:** 2026-02-23
|
||||||
|
**Status:** Genehmigt
|
||||||
|
**Basis:** Dak_projekt_spezifikation_final.md (v4.0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architektur
|
||||||
|
|
||||||
|
FastAPI-Backend (Python) + React-SPA-Frontend (Vite/TypeScript) + MariaDB auf Hetzner 1.
|
||||||
|
|
||||||
|
```
|
||||||
|
Nginx (Plesk) → /api/* → FastAPI (Gunicorn+Uvicorn, Port 8000)
|
||||||
|
→ /* → React SPA (Static Build)
|
||||||
|
→ MariaDB dak_c2s (localhost:3306 in Prod)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Entscheidungen (abweichend von Spec)
|
||||||
|
|
||||||
|
| Thema | Spec | Anpassung | Begründung |
|
||||||
|
|-------|------|-----------|------------|
|
||||||
|
| Dev-DB | Lokale MariaDB auf sv-frontend | Remote → Hetzner 1 | DB existiert bereits, kein Doppel-Setup |
|
||||||
|
| Python Dev | 3.11.2 | 3.13.5 (sv-frontend) | Kompatibel, kein Pinning auf 3.11-Features |
|
||||||
|
| gh CLI | Nicht erwähnt | Installiert auf sv-frontend | Für Repo-Erstellung und PR-Workflow |
|
||||||
|
|
||||||
|
Alle anderen Entscheidungen aus der Spec bleiben unverändert (13 Punkte).
|
||||||
|
|
||||||
|
## Tech-Stack
|
||||||
|
|
||||||
|
- **Backend:** Python 3.13 (Dev) / 3.11 (Prod), FastAPI, SQLAlchemy 2.0, Alembic, Pandas, openpyxl
|
||||||
|
- **Frontend:** React 18, Vite, TypeScript, Tailwind CSS, shadcn/ui, Recharts
|
||||||
|
- **DB:** MariaDB 10.11.14 (dak_c2s auf Hetzner 1)
|
||||||
|
- **Auth:** JWT (python-jose) + bcrypt (passlib) + TOTP (pyotp)
|
||||||
|
- **E-Mail:** SMTP smtp.complexcaresolutions.de:465 (SSL)
|
||||||
|
- **Deploy:** systemd + Plesk-Nginx, GitHub-Webhook
|
||||||
|
|
||||||
|
## Rollen
|
||||||
|
|
||||||
|
- `admin` — Voller Zugriff (Import, Coding, Reports, Users)
|
||||||
|
- `dak_mitarbeiter` — Dashboard, ICD-Eingabe, Report-Download
|
||||||
|
|
||||||
|
## Dev-Workflow
|
||||||
|
|
||||||
|
- Entwicklung auf sv-frontend `/home/frontend/dak_c2s/`
|
||||||
|
- DB-Verbindung: Remote zu Hetzner 1 MariaDB
|
||||||
|
- Git: `develop` → `main`, GitHub `complexcaresolutions/dak.c2s`
|
||||||
|
- Backend-Dev: `uvicorn app.main:app --reload`
|
||||||
|
- Frontend-Dev: `pnpm dev` mit API-Proxy auf Backend
|
||||||
|
|
||||||
|
## Referenzdaten
|
||||||
|
|
||||||
|
Bereitgestellt durch User in `data/` (gitignored):
|
||||||
|
- Abrechnung_DAK.xlsx
|
||||||
|
- Berichtswesen_2024_29122024.xlsx
|
||||||
|
- Berichtswesen_2023_31122023.xlsx
|
||||||
|
- Sample-CSVs
|
||||||
|
|
||||||
|
## Implementierungsphasen
|
||||||
|
|
||||||
|
5 Phasen, 6 Wochen — Details im Implementierungsplan.
|
||||||
1738
docs/plans/2026-02-23-dak-portal-implementation.md
Normal file
1738
docs/plans/2026-02-23-dak-portal-implementation.md
Normal file
File diff suppressed because it is too large
Load diff
0
frontend/.gitkeep
Normal file
0
frontend/.gitkeep
Normal file
Loading…
Reference in a new issue