195 lines
5.6 KiB
Python
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,
|
|
)
|