Files
2026-04-21 13:24:50 +00:00

195 lines
5.6 KiB
Python

from pathlib import Path
import json
from typing import Any, Dict, List, Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
# --- Domain models ---
class Answer(BaseModel):
question_code: str
value: Any
class SessionCreate(BaseModel):
language: str
answers: List[Answer]
class MtsPreparation(BaseModel):
session_id: str
proposed_presenting_flowchart: Optional[str] = None
red_flag_indicators: List[str] = []
suggested_priority_level: Optional[str] = None
class QuestionOption(BaseModel):
value: str
label: str
icon: str | None = None
class Question(BaseModel):
code: str
type: str
title: str
options: Optional[List[QuestionOption]] = None
min: Optional[int] = None
max: Optional[int] = None
class QuestionsResponse(BaseModel):
language: str
questions: List[Question]
BASE_DIR = Path(__file__).resolve().parents[2]
MTS_CONFIG_DIR = BASE_DIR / "mts-config"
def load_questions(language: str) -> QuestionsResponse:
"""Load questions configuration for the given language from mts-config.
This keeps the triage logic data-driven and allows adding further
languages by simply providing a new questions.<lang>.json file.
"""
file_path = MTS_CONFIG_DIR / f"questions.{language}.json"
if not file_path.exists():
raise HTTPException(status_code=404, detail=f"No question config for language '{language}'")
with file_path.open("r", encoding="utf-8") as f:
raw: Dict[str, Any] = json.load(f)
return QuestionsResponse(**raw)
def load_flowcharts_config() -> List[Dict[str, Any]]:
"""Load flowchart mapping configuration.
This maps chief-complaint codes (e.g. "chest_pain") to MTS
presenting flowchart codes (e.g. "CHEST_PAIN").
"""
file_path = MTS_CONFIG_DIR / "flowcharts.json"
if not file_path.exists():
return []
with file_path.open("r", encoding="utf-8") as f:
raw: Dict[str, Any] = json.load(f)
flowcharts = raw.get("flowcharts", [])
if not isinstance(flowcharts, list):
return []
return flowcharts
app = FastAPI(title="Triage-Fragen API", version="0.3.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/health")
async def health() -> Dict[str, str]:
return {"status": "ok"}
@app.get("/questions/{language}", response_model=QuestionsResponse)
async def get_questions(language: str) -> QuestionsResponse:
"""Return the triage question configuration for the selected language."""
return load_questions(language)
@app.post("/sessions", response_model=MtsPreparation)
async def create_session(payload: SessionCreate) -> MtsPreparation:
"""Create a triage preparation proposal from the given answers.
Aktuell wird bewusst nur ein kleiner, aber sinnvoller Ausschnitt
der Manchester-Triage-Logik abgebildet:
- Zuordnung von "chief_complaint" zu einem der konfigurierten
MTS-Präsentations-Flowcharts (10 Pfade in mts-config/flowcharts.json).
- Ableitung einfacher Red-Flag-Indikatoren wie "severe_pain" aus
der Schmerzskala.
- Grobe Prioritätsstufe, insbesondere für Hochrisiko-Flowcharts
wie Brustschmerz, Atemnot, Kollaps und Intoxikation.
"""
session_id = "dummy-session-id"
# Antworten bequem zugreifbar machen
answers_by_code: Dict[str, Any] = {a.question_code: a.value for a in payload.answers}
# 1) Flowchart aus chief_complaint bestimmen (Daten-getrieben aus mts-config)
flowchart: Optional[str] = None
flowcharts = load_flowcharts_config()
complaint_to_flowchart: Dict[str, str] = {}
for fc in flowcharts:
linked = fc.get("linked_chief_complaints") or []
if not isinstance(linked, list):
continue
for cc in linked:
# Erstes Mapping gewinnt; spätere Einträge überschreiben nicht
complaint_to_flowchart.setdefault(cc, fc.get("code"))
chief = answers_by_code.get("chief_complaint")
if isinstance(chief, str):
mapped = complaint_to_flowchart.get(chief)
if isinstance(mapped, str):
flowchart = mapped
# 2) Red-Flag-Indikatoren aus vorhandenen Antworten ableiten
red_flags: List[str] = []
severe_pain = False
# Atemnot: vorgesehen für spätere UI-Erweiterung
if answers_by_code.get("breathlessness") is True:
red_flags.append("breathlessness")
pain_value = answers_by_code.get("pain_intensity")
if isinstance(pain_value, int):
if pain_value >= 8:
red_flags.append("severe_pain")
severe_pain = True
elif pain_value >= 5:
red_flags.append("moderate_pain")
# 3) Grobe Priorität abhängig vom Flowchart und den Red Flags
priority: Optional[str] = None
high_risk_flowcharts = {
"CHEST_PAIN",
"SHORTNESS_OF_BREATH",
"COLLAPSED_ADULT",
"OVERDOSE_POISONING",
}
if "breathlessness" in red_flags:
# Starke Atemnot ist immer hoch dringlich
priority = "RED_OR_ORANGE"
elif severe_pain and flowchart in high_risk_flowcharts:
# Starke Schmerzen bei Hochrisiko-Präsentation → hohe Dringlichkeit
priority = "RED_OR_ORANGE"
elif severe_pain:
# Starke Schmerzen bei anderen Flowcharts → mindestens erhöhte Dringlichkeit
priority = "YELLOW_OR_ORANGE"
return MtsPreparation(
session_id=session_id,
proposed_presenting_flowchart=flowchart,
red_flag_indicators=red_flags,
suggested_priority_level=priority,
)