mirror of
https://github.com/complexcaresolutions/dak.c2s.git
synced 2026-03-17 17:13:42 +00:00
- 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>
899 lines
No EOL
35 KiB
Markdown
899 lines
No EOL
35 KiB
Markdown
# 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
|
||
``` |