Pflegepilot v3 Phase 2: 6-Schritte-Interview-Wizard fuer neue Patienten

Regelbasierte (nicht KI-gestuetzte) Pflegegrad-Schaetzung 1:1 aus der alten
App portiert (NEU_FRAGEN/NEU_ZG_OPTIONEN/neuSchaetzung -> pflegegrad_regeln.py
+ POST /api/pflegegrad/schaetzen), damit die Regel nur einmal zentral im
Backend existiert statt dupliziert im Frontend. Krankenkassen-Stammdaten
(~86 Versicherer DE/AT/CH) 1:1 portiert fuer die Autocomplete in Schritt 3.

Frontend: kompletter Wizard als useReducer-State-Maschine (Schritte 1-6,
verschachtelter Sub-Wizard in Schritt 4 mit Auto-Advance je Antwort ausser
bei der letzten Frage, Zurueck-Navigation landet korrekt auf der letzten
Frage), ersetzt das Kurzformular aus Phase 1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Sonnet 5 committed 2026-07-19 14:09:01 +02:00
1 parent 899fa41b77
commit 9289dc1957
8 files changed
+1194 -71

No files matched your search

+3 -1
View File
@@ -6,7 +6,7 @@ from starlette.middleware.sessions import SessionMiddleware
from app.config import settings
from app.pdf import render_pdf
from app.routers import auth, dashboard, patienten
from app.routers import auth, dashboard, patienten, pflegegrad, stammdaten
app = FastAPI(title="Pflegepilot API")
@@ -39,6 +39,8 @@ async def security_headers(request, call_next):
app.include_router(auth.router)
app.include_router(patienten.router)
app.include_router(dashboard.router)
app.include_router(pflegegrad.router)
app.include_router(stammdaten.router)
@app.get("/api/health")
@@ -0,0 +1,17 @@
from fastapi import APIRouter, Depends
from app.deps import require_login
from app.schemas import PflegegradFragenOut, SchaetzungOut, SchaetzungRequest
from app.static_data.pflegegrad_regeln import FRAGEN, ZIELGRUPPEN_OPTIONEN, schaetzen
router = APIRouter(prefix="/api/pflegegrad", tags=["pflegegrad"])
@router.get("/fragen", response_model=PflegegradFragenOut)
def fragen(_=Depends(require_login)):
return PflegegradFragenOut(fragen=FRAGEN, zielgruppen=ZIELGRUPPEN_OPTIONEN)
@router.post("/schaetzen", response_model=SchaetzungOut)
def schaetzen_route(payload: SchaetzungRequest, _=Depends(require_login)):
return schaetzen(payload.antworten)
@@ -0,0 +1,20 @@
from fastapi import APIRouter, Depends
from app.deps import require_login
from app.schemas import KrankenkasseOut
from app.static_data.krankenkassen import search as kk_search
router = APIRouter(prefix="/api/krankenkassen", tags=["stammdaten"])
@router.get("", response_model=list[KrankenkasseOut])
def liste(q: str = "", land: str | None = None, _=Depends(require_login)):
if not q:
from app.static_data.krankenkassen import KRANKENKASSEN
ergebnis = KRANKENKASSEN
if land:
ergebnis = [k for k in ergebnis if k.get("land", "DE") == land]
else:
ergebnis = kk_search(q, land)
return ergebnis
+43
View File
@@ -107,3 +107,46 @@ class DashboardOut(BaseModel):
offene_antraege: int
offene_aufgaben: int
letzte_patienten: list[PatientOut]
class PflegegradOption(BaseModel):
label: str
wert: int
class PflegegradFrage(BaseModel):
key: str
frage: str
optionen: list[PflegegradOption]
class ZielgruppenOption(BaseModel):
label: str
zg: str
hinweis: str
class PflegegradFragenOut(BaseModel):
fragen: list[PflegegradFrage]
zielgruppen: list[ZielgruppenOption]
class SchaetzungRequest(BaseModel):
antworten: dict[str, int]
class SchaetzungOut(BaseModel):
summe: int
von: int
bis: int
vorschlag: int
class KrankenkasseOut(BaseModel):
name: str
kurzname: str
typ: str
land: str = "DE"
strasse: str
plz: str
ort: str
@@ -0,0 +1,407 @@
"""
Pflegepilot – Krankenkassen-Stammdaten
1:1 aus der alten App (pflegepilot_alpha_v2/pflegepilot/krankenkassen.py) portiert.
Adressen Stand 2025. Vor Verwendung bitte auf Aktualität pruefen.
Pflegegrad-Bescheide werden meist ueber die zustaendige Pflegekasse abgewickelt,
die in der Regel identisch mit der Krankenkasse ist.
"""
KRANKENKASSEN = [
# ── GKV: AOK ──────────────────────────────────────────────────────────────
{"name": "AOK Bayern – Die Gesundheitskasse",
"kurzname": "AOK Bayern", "typ": "GKV",
"strasse": "Carl-Wery-Straße 28", "plz": "81739", "ort": "München"},
{"name": "AOK Baden-Württemberg",
"kurzname": "AOK BW", "typ": "GKV",
"strasse": "Presselstraße 19", "plz": "70191", "ort": "Stuttgart"},
{"name": "AOK Berlin-Brandenburg",
"kurzname": "AOK BE/BB", "typ": "GKV",
"strasse": "Wilhelmstraße 1", "plz": "10963", "ort": "Berlin"},
{"name": "AOK Bremen/Bremerhaven",
"kurzname": "AOK Bremen", "typ": "GKV",
"strasse": "Bürgermeister-Smidt-Straße 95", "plz": "28195", "ort": "Bremen"},
{"name": "AOK Hessen",
"kurzname": "AOK Hessen", "typ": "GKV",
"strasse": "Berner Straße 2", "plz": "60437", "ort": "Frankfurt am Main"},
{"name": "AOK Niedersachsen",
"kurzname": "AOK Niedersachsen", "typ": "GKV",
"strasse": "Hildesheimer Straße 273", "plz": "30519", "ort": "Hannover"},
{"name": "AOK Nordost – Die Gesundheitskasse",
"kurzname": "AOK Nordost", "typ": "GKV",
"strasse": "Warnowufer 54", "plz": "18057", "ort": "Rostock"},
{"name": "AOK NordWest – Die Gesundheitskasse",
"kurzname": "AOK NordWest", "typ": "GKV",
"strasse": "Kopenhagener Straße 1", "plz": "44269", "ort": "Dortmund"},
{"name": "AOK PLUS – Die Gesundheitskasse für Sachsen und Thüringen",
"kurzname": "AOK PLUS", "typ": "GKV",
"strasse": "Sternplatz 7", "plz": "01067", "ort": "Dresden"},
{"name": "AOK Rheinland/Hamburg – Die Gesundheitskasse",
"kurzname": "AOK Rheinland/HH", "typ": "GKV",
"strasse": "Kaiserstraße 80", "plz": "40479", "ort": "Düsseldorf"},
{"name": "AOK Rheinland-Pfalz/Saarland – Die Gesundheitskasse",
"kurzname": "AOK RLP/SL", "typ": "GKV",
"strasse": "Virchowstraße 30", "plz": "67304", "ort": "Eisenberg"},
{"name": "AOK Sachsen-Anhalt – Die Gesundheitskasse",
"kurzname": "AOK ST", "typ": "GKV",
"strasse": "Doctor-Eisenbart-Ring 2", "plz": "39120", "ort": "Magdeburg"},
# ── GKV: Große Kassen ─────────────────────────────────────────────────────
{"name": "Barmer",
"kurzname": "Barmer", "typ": "GKV",
"strasse": "Lichtscheider Straße 89", "plz": "42285", "ort": "Wuppertal"},
{"name": "DAK-Gesundheit",
"kurzname": "DAK", "typ": "GKV",
"strasse": "Nagelsweg 27–31", "plz": "20097", "ort": "Hamburg"},
{"name": "Techniker Krankenkasse",
"kurzname": "TK", "typ": "GKV",
"strasse": "Bramfelder Straße 140", "plz": "22305", "ort": "Hamburg"},
{"name": "KKH – Kaufmännische Krankenkasse",
"kurzname": "KKH", "typ": "GKV",
"strasse": "Karl-Wiechert-Allee 61", "plz": "30625", "ort": "Hannover"},
{"name": "HEK – Hanseatische Krankenkasse",
"kurzname": "HEK", "typ": "GKV",
"strasse": "Wandsbeker Zollstraße 86–90", "plz": "22041", "ort": "Hamburg"},
{"name": "hkk – Handelskrankenkasse",
"kurzname": "hkk", "typ": "GKV",
"strasse": "Martinistraße 26", "plz": "28195", "ort": "Bremen"},
{"name": "Knappschaft",
"kurzname": "Knappschaft", "typ": "GKV",
"strasse": "Pieperstraße 14–28", "plz": "44789", "ort": "Bochum"},
# ── GKV: IKK ──────────────────────────────────────────────────────────────
{"name": "IKK classic",
"kurzname": "IKK classic", "typ": "GKV",
"strasse": "Tannenstraße 4", "plz": "01099", "ort": "Dresden"},
{"name": "IKK gesund plus",
"kurzname": "IKK gesund plus", "typ": "GKV",
"strasse": "Guerickestraße 2", "plz": "39104", "ort": "Magdeburg"},
{"name": "IKK Nord",
"kurzname": "IKK Nord", "typ": "GKV",
"strasse": "Heßstraße 35", "plz": "20097", "ort": "Hamburg"},
{"name": "IKK Südwest",
"kurzname": "IKK Südwest", "typ": "GKV",
"strasse": "Europaallee 9", "plz": "67657", "ort": "Kaiserslautern"},
# ── GKV: BKK ──────────────────────────────────────────────────────────────
{"name": "BKK VBU",
"kurzname": "BKK VBU", "typ": "GKV",
"strasse": "Koppenstraße 4–46", "plz": "10243", "ort": "Berlin"},
{"name": "BKK firmus",
"kurzname": "BKK firmus", "typ": "GKV",
"strasse": "Gewerbering 4", "plz": "27356", "ort": "Rotenburg (Wümme)"},
{"name": "BKK ProVita",
"kurzname": "BKK ProVita", "typ": "GKV",
"strasse": "Am Westpark 8", "plz": "81373", "ort": "München"},
{"name": "energie BKK",
"kurzname": "energie BKK", "typ": "GKV",
"strasse": "Lübeckertordamm 1–3", "plz": "20099", "ort": "Hamburg"},
{"name": "mhplus BKK",
"kurzname": "mhplus", "typ": "GKV",
"strasse": "Eisenbahnstraße 66", "plz": "73207", "ort": "Plochingen"},
{"name": "Novitas BKK",
"kurzname": "Novitas", "typ": "GKV",
"strasse": "Wintgensstraße 27", "plz": "47058", "ort": "Duisburg"},
{"name": "Pronova BKK",
"kurzname": "Pronova BKK", "typ": "GKV",
"strasse": "Harrlachweg 1", "plz": "68163", "ort": "Mannheim"},
{"name": "R+V Betriebskrankenkasse",
"kurzname": "R+V BKK", "typ": "GKV",
"strasse": "Raiffeisenplatz 1", "plz": "65189", "ort": "Wiesbaden"},
{"name": "SBK – Siemens-Betriebskrankenkasse",
"kurzname": "SBK", "typ": "GKV",
"strasse": "Heimeranstraße 31", "plz": "80339", "ort": "München"},
{"name": "Vivida BKK",
"kurzname": "Vivida BKK", "typ": "GKV",
"strasse": "Wilhelm-Beck-Straße 6–8", "plz": "88662", "ort": "Überlingen"},
{"name": "mkk – meine krankenkasse",
"kurzname": "mkk", "typ": "GKV",
"strasse": "Maximilianstraße 2", "plz": "83435", "ort": "Bad Reichenhall"},
{"name": "WMF BKK",
"kurzname": "WMF BKK", "typ": "GKV",
"strasse": "Eberhardstraße 14", "plz": "73312", "ort": "Geislingen an der Steige"},
# ── GKV: Weitere ──────────────────────────────────────────────────────────
{"name": "BIG direkt gesund",
"kurzname": "BIG", "typ": "GKV",
"strasse": "Westhellweg 41", "plz": "44137", "ort": "Dortmund"},
{"name": "Heimat Krankenkasse",
"kurzname": "Heimat KK", "typ": "GKV",
"strasse": "Bielefeldstraße 1", "plz": "33606", "ort": "Bielefeld"},
{"name": "Viactiv Krankenkasse",
"kurzname": "Viactiv", "typ": "GKV",
"strasse": "Huyssenallee 52–56", "plz": "45128", "ort": "Essen"},
{"name": "actimonda BKK",
"kurzname": "actimonda", "typ": "GKV",
"strasse": "Brunnenstraße 1", "plz": "45128", "ort": "Essen"},
{"name": "Audi BKK",
"kurzname": "Audi BKK", "typ": "GKV",
"strasse": "Ettingerstraße 1", "plz": "85045", "ort": "Ingolstadt"},
{"name": "BMW BKK",
"kurzname": "BMW BKK", "typ": "GKV",
"strasse": "Moosacher Straße 90", "plz": "80809", "ort": "München"},
# ── PKV: Private Krankenversicherung ──────────────────────────────────────
{"name": "Allianz Private Krankenversicherungs-AG",
"kurzname": "Allianz PKV", "typ": "PKV",
"strasse": "Königinstraße 28", "plz": "80802", "ort": "München"},
{"name": "AXA Krankenversicherung AG",
"kurzname": "AXA", "typ": "PKV",
"strasse": "Colonia-Allee 10–20", "plz": "51067", "ort": "Köln"},
{"name": "Barmenia Krankenversicherung a.G.",
"kurzname": "Barmenia", "typ": "PKV",
"strasse": "Kronprinzenallee 12–18", "plz": "42094", "ort": "Wuppertal"},
{"name": "Continentale Krankenversicherung a.G.",
"kurzname": "Continentale", "typ": "PKV",
"strasse": "Ruhrallee 92", "plz": "44139", "ort": "Dortmund"},
{"name": "Debeka Krankenversicherungsverein a.G.",
"kurzname": "Debeka", "typ": "PKV",
"strasse": "Ferdinand-Sauerbruch-Straße 18", "plz": "56058", "ort": "Koblenz"},
{"name": "DKV Deutsche Krankenversicherung AG",
"kurzname": "DKV", "typ": "PKV",
"strasse": "Aachener Straße 300", "plz": "50933", "ort": "Köln"},
{"name": "Gothaer Krankenversicherung AG",
"kurzname": "Gothaer", "typ": "PKV",
"strasse": "Gothaer Allee 1", "plz": "50969", "ort": "Köln"},
{"name": "Hallesche Krankenversicherung a.G.",
"kurzname": "Hallesche", "typ": "PKV",
"strasse": "Reinsburgstraße 10", "plz": "70178", "ort": "Stuttgart"},
{"name": "HanseMerkur Krankenversicherung AG",
"kurzname": "HanseMerkur", "typ": "PKV",
"strasse": "Siegfried-Wedells-Platz 1", "plz": "20354", "ort": "Hamburg"},
{"name": "Inter Krankenversicherung AG",
"kurzname": "Inter", "typ": "PKV",
"strasse": "Erzbergerstraße 9–15", "plz": "68165", "ort": "Mannheim"},
{"name": "LVM Krankenversicherung AG",
"kurzname": "LVM", "typ": "PKV",
"strasse": "Kolde-Ring 21", "plz": "48126", "ort": "Münster"},
{"name": "Münchener Verein Krankenversicherung a.G.",
"kurzname": "Münchener Verein", "typ": "PKV",
"strasse": "Pettenkoferstraße 19", "plz": "80336", "ort": "München"},
{"name": "NÜRNBERGER Krankenversicherung AG",
"kurzname": "NÜRNBERGER", "typ": "PKV",
"strasse": "Ostendstraße 100", "plz": "90334", "ort": "Nürnberg"},
{"name": "ottonova Krankenversicherung AG",
"kurzname": "ottonova", "typ": "PKV",
"strasse": "Maximiliansplatz 5", "plz": "80333", "ort": "München"},
{"name": "R+V Krankenversicherung AG",
"kurzname": "R+V PKV", "typ": "PKV",
"strasse": "Raiffeisenplatz 1", "plz": "65189", "ort": "Wiesbaden"},
{"name": "SDK – Süddeutsche Krankenversicherung a.G.",
"kurzname": "SDK", "typ": "PKV",
"strasse": "Raiffeisenplatz 1", "plz": "70736", "ort": "Fellbach"},
{"name": "Signal Iduna Krankenversicherung AG",
"kurzname": "Signal Iduna", "typ": "PKV",
"strasse": "Joseph-Scherer-Straße 3", "plz": "44139", "ort": "Dortmund"},
{"name": "UNIVERSA Krankenversicherung a.G.",
"kurzname": "UNIVERSA", "typ": "PKV",
"strasse": "Sulzbacher Straße 1–7", "plz": "90489", "ort": "Nürnberg"},
{"name": "Württembergische Krankenversicherung AG",
"kurzname": "WKV", "typ": "PKV",
"strasse": "Gutenbergstraße 30", "plz": "70176", "ort": "Stuttgart"},
{"name": "Bayerische Beamtenkrankenkasse AG",
"kurzname": "BBKK", "typ": "PKV",
"strasse": "Maximilianstraße 53", "plz": "81537", "ort": "München"},
{"name": "Mecklenburgische Krankenversicherung AG",
"kurzname": "Mecklenburgische", "typ": "PKV",
"strasse": "Platz der Göttinger Sieben 1", "plz": "30169", "ort": "Hannover"},
# ── Österreich: Sozialversicherung (SV) ───────────────────────────────────
{"name": "Österreichische Gesundheitskasse",
"kurzname": "ÖGK", "typ": "SV", "land": "AT",
"strasse": "Kundmanngasse 21", "plz": "1031", "ort": "Wien"},
{"name": "ÖGK Landesstelle Wien",
"kurzname": "ÖGK Wien", "typ": "SV", "land": "AT",
"strasse": "Wienerbergstraße 15–19", "plz": "1100", "ort": "Wien"},
{"name": "ÖGK Landesstelle Niederösterreich",
"kurzname": "ÖGK NÖ", "typ": "SV", "land": "AT",
"strasse": "Kremser Landstraße 3", "plz": "3100", "ort": "St. Pölten"},
{"name": "ÖGK Landesstelle Oberösterreich",
"kurzname": "ÖGK OÖ", "typ": "SV", "land": "AT",
"strasse": "Gruberstraße 77", "plz": "4021", "ort": "Linz"},
{"name": "ÖGK Landesstelle Steiermark",
"kurzname": "ÖGK Stmk", "typ": "SV", "land": "AT",
"strasse": "Josef-Pongratz-Platz 1", "plz": "8011", "ort": "Graz"},
{"name": "ÖGK Landesstelle Kärnten",
"kurzname": "ÖGK Ktn", "typ": "SV", "land": "AT",
"strasse": "Kempfstraße 8", "plz": "9021", "ort": "Klagenfurt"},
{"name": "ÖGK Landesstelle Salzburg",
"kurzname": "ÖGK Sbg", "typ": "SV", "land": "AT",
"strasse": "Engelbert-Weiß-Weg 10", "plz": "5020", "ort": "Salzburg"},
{"name": "ÖGK Landesstelle Tirol",
"kurzname": "ÖGK Tirol", "typ": "SV", "land": "AT",
"strasse": "Klara-Pölt-Weg 2", "plz": "6020", "ort": "Innsbruck"},
{"name": "ÖGK Landesstelle Vorarlberg",
"kurzname": "ÖGK Vbg", "typ": "SV", "land": "AT",
"strasse": "Jahngasse 4", "plz": "6850", "ort": "Dornbirn"},
{"name": "ÖGK Landesstelle Burgenland",
"kurzname": "ÖGK Bgld", "typ": "SV", "land": "AT",
"strasse": "Esterházyplatz 3", "plz": "7001", "ort": "Eisenstadt"},
{"name": "Sozialversicherungsanstalt der Selbständigen",
"kurzname": "SVS", "typ": "SV", "land": "AT",
"strasse": "Wiedner Hauptstraße 84–86", "plz": "1051", "ort": "Wien"},
{"name": "Versicherungsanstalt öffentlich Bediensteter, Eisenbahnen und Bergbau",
"kurzname": "BVAEB", "typ": "SV", "land": "AT",
"strasse": "Josefstädter Straße 80", "plz": "1080", "ort": "Wien"},
# ── Schweiz: Krankenversicherung nach KVG ─────────────────────────────────
{"name": "Helsana Versicherungen AG",
"kurzname": "Helsana", "typ": "KVG", "land": "CH",
"strasse": "Zürichstrasse 130", "plz": "8600", "ort": "Dübendorf"},
{"name": "CSS Versicherung AG",
"kurzname": "CSS", "typ": "KVG", "land": "CH",
"strasse": "Tribschenstraße 21", "plz": "6005", "ort": "Luzern"},
{"name": "SWICA Krankenversicherung AG",
"kurzname": "SWICA", "typ": "KVG", "land": "CH",
"strasse": "Römerstraße 38", "plz": "8401", "ort": "Winterthur"},
{"name": "Sanitas Krankenversicherung",
"kurzname": "Sanitas", "typ": "KVG", "land": "CH",
"strasse": "Jägergasse 3", "plz": "8021", "ort": "Zürich"},
{"name": "Concordia Schweizerische Kranken- und Unfallversicherung AG",
"kurzname": "Concordia", "typ": "KVG", "land": "CH",
"strasse": "Bundesplatz 15", "plz": "6003", "ort": "Luzern"},
{"name": "Visana Versicherungen AG",
"kurzname": "Visana", "typ": "KVG", "land": "CH",
"strasse": "Weltpoststrasse 19", "plz": "3015", "ort": "Bern"},
{"name": "KPT Krankenkasse AG",
"kurzname": "KPT", "typ": "KVG", "land": "CH",
"strasse": "Wankdorfallee 3", "plz": "3014", "ort": "Bern"},
{"name": "Assura-Basis SA",
"kurzname": "Assura", "typ": "KVG", "land": "CH",
"strasse": "Avenue Charles-Ferdinand-Ramuz 70", "plz": "1009", "ort": "Pully"},
{"name": "Groupe Mutuel Versicherungen GmbH",
"kurzname": "Groupe Mutuel", "typ": "KVG", "land": "CH",
"strasse": "Rue des Cèdres 5", "plz": "1919", "ort": "Martigny"},
{"name": "Atupri Gesundheitsversicherung",
"kurzname": "Atupri", "typ": "KVG", "land": "CH",
"strasse": "Zieglerstrasse 29", "plz": "3007", "ort": "Bern"},
{"name": "Sympany AG",
"kurzname": "Sympany", "typ": "KVG", "land": "CH",
"strasse": "Peter Merian-Weg 4", "plz": "4052", "ort": "Basel"},
{"name": "EGK Gesundheitskasse",
"kurzname": "EGK", "typ": "KVG", "land": "CH",
"strasse": "Brislachstrasse 2", "plz": "4242", "ort": "Laufen"},
{"name": "Aquilana Versicherungen",
"kurzname": "Aquilana", "typ": "KVG", "land": "CH",
"strasse": "Bruggerstrasse 46", "plz": "5401", "ort": "Baden"},
{"name": "Galenos Krankenversicherung AG",
"kurzname": "Galenos", "typ": "KVG", "land": "CH",
"strasse": "Militärstrasse 36", "plz": "8004", "ort": "Zürich"},
{"name": "Sana24 AG",
"kurzname": "Sana24", "typ": "KVG", "land": "CH",
"strasse": "Weltpoststrasse 19", "plz": "3015", "ort": "Bern"},
{"name": "Kolping Krankenkasse AG",
"kurzname": "Kolping", "typ": "KVG", "land": "CH",
"strasse": "Raemistrasse 5", "plz": "8024", "ort": "Zürich"},
]
def get_all(land=None):
if land:
return [k for k in KRANKENKASSEN if k.get("land", "DE") == land]
return KRANKENKASSEN
def get_by_land(land):
return [k for k in KRANKENKASSEN if k.get("land", "DE") == land]
def get_by_name(name):
name_l = name.lower()
for k in KRANKENKASSEN:
if k["name"].lower() == name_l or k["kurzname"].lower() == name_l:
return k
return None
def search(q, land=None):
q = q.lower()
results = [k for k in KRANKENKASSEN
if q in k["name"].lower()
or q in (k["kurzname"] or "").lower()
or q in k["ort"].lower()]
if land:
results = [k for k in results if k.get("land", "DE") == land]
return results
@@ -0,0 +1,99 @@
"""
Regelbasierte (NICHT KI-gestuetzte) Pflegegrad-Einschaetzung fuer den
Interview-Assistenten. 1:1 aus der alten App (NEU_FRAGEN/NEU_ZG_OPTIONEN/
neuSchaetzung() in templates/index.html) portiert - dort lag die Logik im
Frontend-JS, hier lebt sie zentral im Backend, damit Frontend und
Schaetzungs-Endpunkt nie auseinanderlaufen koennen.
Die Schaetzung ist bewusst grob (Summe aus 5 Fragen zu je 0-3 Punkten) und
ersetzt keine Begutachtung durch den Medizinischen Dienst - das muss die
UI immer deutlich als unverbindlichen Vorschlag kennzeichnen.
"""
FRAGEN = [
{
"key": "mobilitaet",
"frage": "Wie bewegt sich die Person im Alltag?",
"optionen": [
{"label": "Ohne Einschränkungen unterwegs", "wert": 0},
{"label": "Nutzt Hilfsmittel (Stock, Rollator) und kommt allein zurecht", "wert": 1},
{"label": "Braucht regelmäßig Unterstützung beim Gehen oder Aufstehen", "wert": 2},
{"label": "Ist bettlägerig oder braucht beim Bewegen fremde Hilfe", "wert": 3},
],
},
{
"key": "selbstversorgung",
"frage": "Wie viel Hilfe braucht die Person beim Waschen, Anziehen und Essen?",
"optionen": [
{"label": "Keine Hilfe nötig", "wert": 0},
{"label": "Braucht bei einzelnen Schritten Hilfe", "wert": 1},
{"label": "Braucht bei den meisten Schritten Hilfe", "wert": 2},
{"label": "Braucht bei fast allem vollständige Hilfe", "wert": 3},
],
},
{
"key": "kognition",
"frage": "Gibt es Gedächtnisprobleme, Verwirrtheit oder auffälliges Verhalten (z. B. bei Demenz)?",
"optionen": [
{"label": "Nein", "wert": 0},
{"label": "Leicht, kommt selten vor", "wert": 1},
{"label": "Deutlich spürbar, kommt öfter vor", "wert": 2},
{"label": "Stark ausgeprägt, fast durchgehend", "wert": 3},
],
},
{
"key": "medizin",
"frage": "Sind regelmäßig medizinische Maßnahmen nötig (Medikamente, Verbände, Spritzen, Wundversorgung)?",
"optionen": [
{"label": "Nein", "wert": 0},
{"label": "Gelegentlich", "wert": 1},
{"label": "Täglich", "wert": 2},
{"label": "Mehrmals täglich oder aufwendig", "wert": 3},
],
},
{
"key": "alltag",
"frage": "Kann die Person ihren Tag noch selbst gestalten (Termine, Kontakte, Beschäftigung)?",
"optionen": [
{"label": "Ja, ganz selbstständig", "wert": 0},
{"label": "Braucht ab und zu Anleitung", "wert": 1},
{"label": "Braucht viel Anleitung oder Begleitung", "wert": 2},
{"label": "Ist dabei komplett auf Hilfe angewiesen", "wert": 3},
],
},
]
ZIELGRUPPEN_OPTIONEN = [
{
"label": "Altersbedingte Unterstützung",
"zg": "alterspflege",
"hinweis": "z. B. altersbedingter Kraft- oder Gedächtnisverlust",
},
{
"label": "Behinderung (angeboren oder erworben)",
"zg": "behinderte",
"hinweis": "z. B. körperliche, geistige oder seelische Behinderung",
},
{
"label": "Andere Bedürftigkeit oder soziale Notlage",
"zg": "beduerftige",
"hinweis": "z. B. vorübergehende Notlage ohne dauerhafte Behinderung",
},
]
def schaetzen(antworten: dict[str, int]) -> dict:
summe = sum(antworten.get(f["key"], 0) for f in FRAGEN)
if summe <= 1:
von, bis = 0, 1
elif summe <= 4:
von, bis = 1, 2
elif summe <= 7:
von, bis = 2, 3
elif summe <= 10:
von, bis = 3, 4
elif summe <= 13:
von, bis = 4, 4
else:
von, bis = 5, 5
return {"summe": summe, "von": von, "bis": bis, "vorschlag": round((von + bis) / 2)}
@@ -1,11 +1,118 @@
"use client";
import { useState } from "react";
import { useEffect, useReducer, useState } from "react";
import { useRouter } from "next/navigation";
import { usePatientMutations } from "@/lib/patienten";
import {
usePflegegradFragen,
useSchaetzungMutation,
useKrankenkassenSuche,
} from "@/lib/pflegegrad";
import { DateWheelPicker } from "@/components/DateWheelPicker";
const vorEinemJahrhundert = () => {
const STEP_TITEL = [
"Persönliche Daten",
"Kontakt & Adresse",
"Krankenversicherung",
"Pflegesituation",
"Einschätzung & Pflegeperson",
"Übersicht",
];
type WizardData = {
vorname: string;
nachname: string;
geburtsdatum: string | null;
strasse: string;
plz: string;
ort: string;
telefon: string;
email: string;
krankenkasse: string;
versichertennummer: string;
versicherungsart: string;
pflegeperson: string;
notizen: string;
pflegegrad: number;
};
type WizardState = {
step: number;
q4sub: number;
data: WizardData;
antworten: Record<string, number>;
zielgruppe: string;
};
const initialState: WizardState = {
step: 1,
q4sub: 0,
data: {
vorname: "",
nachname: "",
geburtsdatum: null,
strasse: "",
plz: "",
ort: "",
telefon: "",
email: "",
krankenkasse: "",
versichertennummer: "",
versicherungsart: "GKV",
pflegeperson: "",
notizen: "",
pflegegrad: 0,
},
antworten: {},
zielgruppe: "alterspflege",
};
type Action =
| { type: "SET_FIELD"; field: keyof WizardData; value: string | number | null }
| { type: "SET_ZIELGRUPPE"; zg: string; total: number }
| { type: "SET_ANTWORT"; key: string; wert: number; total: number }
| { type: "NEXT"; total: number }
| { type: "BACK"; total: number };
function reducer(state: WizardState, action: Action): WizardState {
switch (action.type) {
case "SET_FIELD":
return { ...state, data: { ...state.data, [action.field]: action.value } };
case "SET_ZIELGRUPPE": {
const q4sub = state.q4sub < action.total - 1 ? state.q4sub + 1 : state.q4sub;
return { ...state, zielgruppe: action.zg, q4sub };
}
case "SET_ANTWORT": {
const antworten = { ...state.antworten, [action.key]: action.wert };
const q4sub = state.q4sub < action.total - 1 ? state.q4sub + 1 : state.q4sub;
return { ...state, antworten, q4sub };
}
case "NEXT": {
if (state.step === 4 && state.q4sub < action.total - 1) {
return { ...state, q4sub: state.q4sub + 1 };
}
if (state.step < STEP_TITEL.length) {
const step = state.step + 1;
return { ...state, step, q4sub: step === 4 ? 0 : state.q4sub };
}
return state;
}
case "BACK": {
if (state.step === 4 && state.q4sub > 0) {
return { ...state, q4sub: state.q4sub - 1 };
}
if (state.step > 1) {
const step = state.step - 1;
return { ...state, step, q4sub: step === 4 ? action.total - 1 : state.q4sub };
}
return state;
}
default:
return state;
}
}
const vor75Jahren = () => {
const d = new Date();
d.setFullYear(d.getFullYear() - 75);
return d;
@@ -14,87 +121,469 @@ const vorEinemJahrhundert = () => {
export default function NeuerPatientPage() {
const router = useRouter();
const { anlegen } = usePatientMutations();
const [form, setForm] = useState({
vorname: "",
nachname: "",
geburtsdatum: null as string | null,
zielgruppe: "alterspflege",
pflegegrad: 0,
});
const { data: fragenData, isLoading: fragenLoading } = usePflegegradFragen();
const schaetzung = useSchaetzungMutation();
const submit = async (e: React.FormEvent) => {
e.preventDefault();
const patient = await anlegen.mutateAsync(form);
const [state, dispatch] = useReducer(reducer, initialState);
const total = 1 + (fragenData?.fragen.length ?? 5);
const [error, setError] = useState<string | null>(null);
const next = () => {
if (state.step === 1 && (!state.data.vorname.trim() || !state.data.nachname.trim())) {
setError("Bitte Vor- und Nachname eintragen");
return;
}
setError(null);
dispatch({ type: "NEXT", total });
};
const back = () => dispatch({ type: "BACK", total });
useEffect(() => {
if (state.step === 5 && Object.keys(state.antworten).length > 0) {
schaetzung.mutate(state.antworten);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.step]);
useEffect(() => {
if (schaetzung.data && state.data.pflegegrad === 0) {
dispatch({ type: "SET_FIELD", field: "pflegegrad", value: schaetzung.data.vorschlag });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [schaetzung.data]);
const speichern = async () => {
const patient = await anlegen.mutateAsync({
vorname: state.data.vorname,
nachname: state.data.nachname,
geburtsdatum: state.data.geburtsdatum ?? undefined,
strasse: state.data.strasse || undefined,
plz: state.data.plz || undefined,
ort: state.data.ort || undefined,
telefon: state.data.telefon || undefined,
email: state.data.email || undefined,
krankenkasse: state.data.krankenkasse || undefined,
versichertennummer: state.data.versichertennummer || undefined,
versicherungsart: state.data.versicherungsart,
pflegeperson: state.data.pflegeperson || undefined,
notizen: state.data.notizen || undefined,
pflegegrad: state.data.pflegegrad,
zielgruppe: state.zielgruppe,
});
router.push(`/patienten/${patient.id}`);
};
return (
<div className="mx-auto max-w-md p-6">
<h1 className="mb-4 text-2xl font-semibold text-slate-800">Neuer Patient</h1>
<div className="mx-auto max-w-lg p-6">
<h1 className="mb-1 text-2xl font-semibold text-slate-800">Neuer Patient</h1>
<p className="mb-4 text-sm text-slate-500">
Kurzformular für Phase 1 – der ausführliche Interview-Assistent folgt in Phase 2.
Ein kurzes Interview – ohne KI, nur einfache Fragen zur Pflegesituation.
</p>
<form onSubmit={submit} className="flex flex-col gap-3">
<input
required
placeholder="Vorname"
value={form.vorname}
onChange={(e) => setForm((f) => ({ ...f, vorname: e.target.value }))}
className="rounded-lg border border-slate-300 px-3 py-2 text-sm"
/>
<input
required
placeholder="Nachname"
value={form.nachname}
onChange={(e) => setForm((f) => ({ ...f, nachname: e.target.value }))}
className="rounded-lg border border-slate-300 px-3 py-2 text-sm"
/>
<StepDots current={state.step} />
<label className="text-xs font-medium text-slate-500">Geburtsdatum</label>
<DateWheelPicker
value={form.geburtsdatum}
onChange={(iso) => setForm((f) => ({ ...f, geburtsdatum: iso }))}
fallbackDate={vorEinemJahrhundert()}
label="Geburtsdatum"
/>
<label className="text-xs font-medium text-slate-500">Zielgruppe</label>
<select
value={form.zielgruppe}
onChange={(e) => setForm((f) => ({ ...f, zielgruppe: e.target.value }))}
className="rounded-lg border border-slate-300 px-3 py-2 text-sm"
>
<option value="alterspflege">Altersbedingt</option>
<option value="beduerftige">Bedürftige</option>
<option value="behinderte">Behinderung</option>
</select>
<label className="text-xs font-medium text-slate-500">Pflegegrad</label>
<select
value={form.pflegegrad}
onChange={(e) => setForm((f) => ({ ...f, pflegegrad: Number(e.target.value) }))}
className="rounded-lg border border-slate-300 px-3 py-2 text-sm"
>
{[0, 1, 2, 3, 4, 5].map((g) => (
<option key={g} value={g}>
{g === 0 ? "Noch kein Pflegegrad" : `Pflegegrad ${g}`}
</option>
<div className="mt-6 rounded-2xl border border-slate-200 bg-white p-5">
{state.step === 1 && <Step1 state={state} dispatch={dispatch} />}
{state.step === 2 && <Step2 state={state} dispatch={dispatch} />}
{state.step === 3 && <Step3 state={state} dispatch={dispatch} />}
{state.step === 4 &&
(fragenLoading || !fragenData ? (
<p className="text-slate-400">Lädt…</p>
) : (
<Step4 state={state} dispatch={dispatch} fragenData={fragenData} total={total} />
))}
</select>
{anlegen.isError && (
<p className="text-sm text-red-600">{(anlegen.error as Error).message}</p>
{state.step === 5 && (
<Step5 state={state} dispatch={dispatch} schaetzung={schaetzung.data} />
)}
{state.step === 6 && <Step6 state={state} />}
<button
type="submit"
disabled={anlegen.isPending}
className="mt-2 rounded-lg bg-slate-800 px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
<div className="mt-6 flex justify-between">
<button
type="button"
onClick={back}
disabled={state.step === 1 && state.q4sub === 0}
className="rounded-lg border border-slate-300 px-4 py-2 text-sm text-slate-600 disabled:opacity-0"
>
Zurück
</button>
{state.step < 6 ? (
<button
type="button"
onClick={next}
className="rounded-lg bg-slate-800 px-4 py-2 text-sm font-medium text-white"
>
Weiter
</button>
) : (
<button
type="button"
onClick={speichern}
disabled={anlegen.isPending}
className="rounded-lg bg-slate-800 px-4 py-2 text-sm font-medium text-white disabled:opacity-50"
>
Patient speichern
</button>
)}
</div>
</div>
</div>
);
}
function StepDots({ current }: { current: number }) {
return (
<div className="flex items-center">
{STEP_TITEL.map((t, i) => {
const n = i + 1;
const done = n < current;
const on = n === current;
return (
<div key={t} className="flex flex-1 items-center last:flex-none">
<div
title={t}
className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-medium ${
on
? "bg-slate-800 text-white"
: done
? "bg-emerald-500 text-white"
: "border border-slate-300 text-slate-400"
}`}
>
{done ? "✓" : n}
</div>
{n < STEP_TITEL.length && <div className="mx-1 h-px flex-1 bg-slate-200" />}
</div>
);
})}
</div>
);
}
function Field({
label,
value,
onChange,
required,
}: {
label: string;
value: string;
onChange: (v: string) => void;
required?: boolean;
}) {
return (
<div>
<label className="mb-1 block text-xs font-medium text-slate-500">
{label}
{required && " *"}
</label>
<input
value={value}
onChange={(e) => onChange(e.target.value)}
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm"
/>
</div>
);
}
function Step1({
state,
dispatch,
}: {
state: WizardState;
dispatch: React.Dispatch<Action>;
}) {
return (
<div className="flex flex-col gap-3">
<p className="text-sm font-medium text-slate-700">
Wie heißt die Person, die Sie anmelden möchten?
</p>
<Field
label="Vorname"
required
value={state.data.vorname}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "vorname", value: v })}
/>
<Field
label="Nachname"
required
value={state.data.nachname}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "nachname", value: v })}
/>
<div>
<label className="mb-1 block text-xs font-medium text-slate-500">Geburtsdatum</label>
<DateWheelPicker
value={state.data.geburtsdatum}
onChange={(iso) => dispatch({ type: "SET_FIELD", field: "geburtsdatum", value: iso })}
fallbackDate={vor75Jahren()}
label="Geburtsdatum"
/>
</div>
</div>
);
}
function Step2({ state, dispatch }: { state: WizardState; dispatch: React.Dispatch<Action> }) {
return (
<div className="flex flex-col gap-3">
<Field
label="Straße"
value={state.data.strasse}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "strasse", value: v })}
/>
<div className="flex gap-2">
<div className="w-1/3">
<Field
label="PLZ"
value={state.data.plz}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "plz", value: v })}
/>
</div>
<div className="flex-1">
<Field
label="Ort"
value={state.data.ort}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "ort", value: v })}
/>
</div>
</div>
<Field
label="Telefon"
value={state.data.telefon}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "telefon", value: v })}
/>
<Field
label="E-Mail"
value={state.data.email}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "email", value: v })}
/>
</div>
);
}
function Step3({ state, dispatch }: { state: WizardState; dispatch: React.Dispatch<Action> }) {
const [suchtext, setSuchtext] = useState(state.data.krankenkasse);
const { data: treffer } = useKrankenkassenSuche(suchtext);
return (
<div className="flex flex-col gap-3">
<div className="relative">
<label className="mb-1 block text-xs font-medium text-slate-500">Krankenkasse</label>
<input
value={suchtext}
onChange={(e) => {
setSuchtext(e.target.value);
dispatch({ type: "SET_FIELD", field: "krankenkasse", value: e.target.value });
}}
placeholder="Name eingeben…"
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm"
/>
{treffer && treffer.length > 0 && suchtext.length >= 2 && (
<ul className="absolute z-10 mt-1 max-h-48 w-full overflow-y-auto rounded-lg border border-slate-200 bg-white shadow-lg">
{treffer.slice(0, 8).map((k) => (
<li key={k.name}>
<button
type="button"
onClick={() => {
setSuchtext(k.name);
dispatch({ type: "SET_FIELD", field: "krankenkasse", value: k.name });
}}
className="block w-full px-3 py-2 text-left text-sm hover:bg-slate-50"
>
{k.name}
</button>
</li>
))}
</ul>
)}
</div>
<Field
label="Versichertennummer"
value={state.data.versichertennummer}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "versichertennummer", value: v })}
/>
<div>
<label className="mb-1 block text-xs font-medium text-slate-500">Versicherungsart</label>
<select
value={state.data.versicherungsart}
onChange={(e) =>
dispatch({ type: "SET_FIELD", field: "versicherungsart", value: e.target.value })
}
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm"
>
Patient anlegen
</button>
</form>
<option value="GKV">Gesetzlich (GKV)</option>
<option value="PKV">Privat (PKV)</option>
</select>
</div>
</div>
);
}
function Step4({
state,
dispatch,
fragenData,
total,
}: {
state: WizardState;
dispatch: React.Dispatch<Action>;
fragenData: { fragen: { key: string; frage: string; optionen: { label: string; wert: number }[] }[]; zielgruppen: { label: string; zg: string; hinweis: string }[] };
total: number;
}) {
return (
<div>
<div className="mb-4 flex gap-1">
{Array.from({ length: total }, (_, i) => (
<div
key={i}
className={`h-1 flex-1 rounded-full ${i <= state.q4sub ? "bg-slate-800" : "bg-slate-200"}`}
/>
))}
</div>
{state.q4sub === 0 ? (
<div>
<p className="mb-3 text-sm font-medium text-slate-700">
Worum geht es bei der Pflegesituation in erster Linie?
</p>
<div className="flex flex-col gap-2">
{fragenData.zielgruppen.map((o) => (
<button
key={o.zg}
type="button"
onClick={() => dispatch({ type: "SET_ZIELGRUPPE", zg: o.zg, total })}
className={`rounded-lg border px-3 py-2 text-left text-sm ${
state.zielgruppe === o.zg
? "border-slate-800 bg-slate-50"
: "border-slate-300"
}`}
>
<div className="font-medium text-slate-800">{o.label}</div>
<div className="text-xs text-slate-500">{o.hinweis}</div>
</button>
))}
</div>
</div>
) : (
(() => {
const frage = fragenData.fragen[state.q4sub - 1];
return (
<div>
<p className="mb-3 text-sm font-medium text-slate-700">{frage.frage}</p>
<div className="flex flex-col gap-2">
{frage.optionen.map((o) => (
<button
key={o.wert}
type="button"
onClick={() =>
dispatch({ type: "SET_ANTWORT", key: frage.key, wert: o.wert, total })
}
className={`rounded-lg border px-3 py-2 text-left text-sm ${
state.antworten[frage.key] === o.wert
? "border-slate-800 bg-slate-50"
: "border-slate-300"
}`}
>
{o.label}
</button>
))}
</div>
</div>
);
})()
)}
</div>
);
}
function Step5({
state,
dispatch,
schaetzung,
}: {
state: WizardState;
dispatch: React.Dispatch<Action>;
schaetzung?: { summe: number; von: number; bis: number; vorschlag: number };
}) {
return (
<div className="flex flex-col gap-4">
<div className="rounded-lg bg-amber-50 p-3 text-sm text-amber-800">
{schaetzung ? (
<>
Grober Anhaltspunkt: <strong>Pflegegrad {schaetzung.von}–{schaetzung.bis}</strong>.
Das ist <strong>keine offizielle Feststellung</strong> – nur der Medizinische Dienst
kann per Begutachtung einen verbindlichen Pflegegrad bescheinigen.
</>
) : (
"Schätzung wird berechnet…"
)}
</div>
<div>
<label className="mb-1 block text-xs font-medium text-slate-500">
Tatsächlicher / vermuteter Pflegegrad
</label>
<div className="flex flex-wrap gap-2">
{[0, 1, 2, 3, 4, 5].map((g) => (
<button
key={g}
type="button"
onClick={() => dispatch({ type: "SET_FIELD", field: "pflegegrad", value: g })}
className={`rounded-full px-3 py-1 text-sm ${
state.data.pflegegrad === g
? "bg-slate-800 text-white"
: "border border-slate-300 text-slate-600"
}`}
>
{g === 0 ? "keiner" : g}
</button>
))}
</div>
</div>
<Field
label="Pflegeperson"
value={state.data.pflegeperson}
onChange={(v) => dispatch({ type: "SET_FIELD", field: "pflegeperson", value: v })}
/>
<div>
<label className="mb-1 block text-xs font-medium text-slate-500">
Notizen / Diagnosen
</label>
<textarea
value={state.data.notizen}
onChange={(e) => dispatch({ type: "SET_FIELD", field: "notizen", value: e.target.value })}
rows={3}
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm"
/>
</div>
</div>
);
}
function Step6({ state }: { state: WizardState }) {
const d = state.data;
return (
<div className="flex flex-col gap-2 text-sm">
<p className="mb-2 font-medium text-slate-700">Bitte prüfen, bevor gespeichert wird:</p>
<SummaryRow label="Name" value={`${d.vorname} ${d.nachname}`} />
<SummaryRow label="Geburtsdatum" value={d.geburtsdatum ?? "–"} />
<SummaryRow label="Adresse" value={[d.strasse, [d.plz, d.ort].filter(Boolean).join(" ")].filter(Boolean).join(", ") || "–"} />
<SummaryRow label="Krankenkasse" value={d.krankenkasse || "–"} />
<SummaryRow label="Pflegegrad" value={d.pflegegrad === 0 ? "keiner" : String(d.pflegegrad)} />
<SummaryRow label="Pflegeperson" value={d.pflegeperson || "–"} />
</div>
);
}
function SummaryRow({ label, value }: { label: string; value: string }) {
return (
<div className="flex justify-between border-b border-slate-100 py-1.5">
<span className="text-slate-500">{label}</span>
<span className="font-medium text-slate-800">{value}</span>
</div>
);
}
@@ -0,0 +1,46 @@
"use client";
import { useMutation, useQuery } from "@tanstack/react-query";
import { api } from "./api";
export type PflegegradOption = { label: string; wert: number };
export type PflegegradFrage = { key: string; frage: string; optionen: PflegegradOption[] };
export type ZielgruppenOption = { label: string; zg: string; hinweis: string };
export type PflegegradFragenOut = { fragen: PflegegradFrage[]; zielgruppen: ZielgruppenOption[] };
export type Schaetzung = { summe: number; von: number; bis: number; vorschlag: number };
export function usePflegegradFragen() {
return useQuery<PflegegradFragenOut>({
queryKey: ["pflegegrad-fragen"],
queryFn: () => api<PflegegradFragenOut>("/api/pflegegrad/fragen"),
staleTime: Infinity,
});
}
export function useSchaetzungMutation() {
return useMutation({
mutationFn: (antworten: Record<string, number>) =>
api<Schaetzung>("/api/pflegegrad/schaetzen", {
method: "POST",
body: JSON.stringify({ antworten }),
}),
});
}
export type Krankenkasse = {
name: string;
kurzname: string;
typ: string;
land: string;
strasse: string;
plz: string;
ort: string;
};
export function useKrankenkassenSuche(q: string) {
return useQuery<Krankenkasse[]>({
queryKey: ["krankenkassen", q],
queryFn: () => api<Krankenkasse[]>(`/api/krankenkassen?q=${encodeURIComponent(q)}`),
enabled: q.length >= 2,
});
}