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