From 02c425594dcb640122ce84725673e5702057cbd9 Mon Sep 17 00:00:00 2001 From: Dualmind-Assistant Date: Tue, 21 Apr 2026 13:34:46 +0000 Subject: [PATCH] Add modular flow step for path-specific triage questions --- README.md | 13 +++ frontend/src/components/BooleanChoice.vue | 84 ++++++++++++++++++++ frontend/src/composables/useTriageSession.ts | 58 +++++++++++--- frontend/src/router/index.ts | 2 + frontend/src/views/ComplaintView.vue | 2 +- frontend/src/views/FlowView.vue | 65 +++++++++++++++ frontend/src/views/PainView.vue | 2 +- 7 files changed, 212 insertions(+), 14 deletions(-) create mode 100644 frontend/src/components/BooleanChoice.vue create mode 100644 frontend/src/views/FlowView.vue diff --git a/README.md b/README.md index 6e615dd..63538cc 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,16 @@ Die Datei `mts-config/flowcharts.json` enthält aktuell folgende abgebildete Pr - BEHAVING_STRANGELY → chief_complaint `behaving_strangely` / `psychiatric` Diese Konfiguration bildet die Zuordnung von Chief-Complaint-Codes zu den entsprechenden MTS-Präsentations-Flowcharts ab. Im Backend (FastAPI) ist bereits eine erste datengetriebene Logik für alle 10 Flowcharts umgesetzt: Das API ordnet `chief_complaint` automatisch dem passenden MTS-Flowchart zu, erkennt erste Red Flags wie `breathlessness`, `severe_pain` und `moderate_pain` und schlägt darauf basierend eine grobe Prioritätsstufe vor. Zusätzlich enthalten `questions.de.json` und `questions.en.json` bereits eine Ja/Nein-Frage zu starker Luftnot als ersten expliziten Red-Flag-Discriminator im Fragenstrom. + + +## Aktueller App-Flow + +Die Frontend-App bleibt modular als Vue-3-SPA aufgebaut und wurde um einen zusätzlichen Schritt `FlowView` erweitert. Der Ablauf ist aktuell: + +- `LanguageView` – Sprache wählen +- `ComplaintView` – Hauptbeschwerde auswählen +- `FlowView` – pfadspezifische Zusatzfragen / Red-Flag-Fragen (derzeit als modulare Boolean-Komponenten) +- `PainView` – Schmerzintensität erfassen +- `SummaryView` – vorgeschlagenes MTS-Flowchart, Red Flags und Prioritätsstufe anzeigen + +Die Zusatzfragen werden aus `mts-config/questions..json` geladen. Damit kann die App schrittweise in Richtung mehrerer MTS-spezifischer Entscheidungsbäume erweitert werden, ohne eine monolithische Einzeldatei zu erzeugen. diff --git a/frontend/src/components/BooleanChoice.vue b/frontend/src/components/BooleanChoice.vue new file mode 100644 index 0000000..095aa8d --- /dev/null +++ b/frontend/src/components/BooleanChoice.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/frontend/src/composables/useTriageSession.ts b/frontend/src/composables/useTriageSession.ts index f70bd84..1481a47 100644 --- a/frontend/src/composables/useTriageSession.ts +++ b/frontend/src/composables/useTriageSession.ts @@ -29,23 +29,38 @@ const questions = reactive([]) const isLoadingConfig = ref(false) const loadError = ref(null) -const chiefComplaint = ref(null) -const pain = ref(0) +const answers = reactive>({ + chief_complaint: null, + breathlessness: null, + pain_intensity: 0, +}) const isSubmitting = ref(false) const submitError = ref(null) const result = ref(null) -const chiefComplaintQuestion = computed(() => - questions.find((q) => q.code === 'chief_complaint') ?? null, -) - +const orderedQuestions = computed(() => questions) +const chiefComplaintQuestion = computed(() => questions.find((q) => q.code === 'chief_complaint') ?? null) +const flowQuestions = computed(() => questions.filter((q) => q.code !== 'chief_complaint' && q.code !== 'pain_intensity')) const painQuestion = computed(() => questions.find((q) => q.code === 'pain_intensity') ?? null) +const chiefComplaint = computed({ + get: () => (typeof answers.chief_complaint === 'string' ? answers.chief_complaint : null), + set: (value: string | null) => { + answers.chief_complaint = value + }, +}) +const pain = computed({ + get: () => (typeof answers.pain_intensity === 'number' ? answers.pain_intensity : 0), + set: (value: number) => { + answers.pain_intensity = value + }, +}) const priorityClass = computed(() => { const level = result.value?.suggested_priority_level if (!level) return '' if (level === 'RED_OR_ORANGE') return 'priority-high' + if (level === 'YELLOW_OR_ORANGE') return 'priority-medium' return 'priority-default' }) @@ -53,15 +68,29 @@ const priorityLabel = computed(() => { const level = result.value?.suggested_priority_level if (!level) return '' if (level === 'RED_OR_ORANGE') return 'Hohe Dringlichkeit' + if (level === 'YELLOW_OR_ORANGE') return 'Erhöhte Dringlichkeit' return 'Priorität' }) +function resetAnswers() { + answers.chief_complaint = null + answers.breathlessness = null + answers.pain_intensity = 0 +} + +function setAnswer(questionCode: string, value: string | number | boolean | null) { + answers[questionCode] = value +} + +function getAnswer(questionCode: string) { + return answers[questionCode] ?? null +} + async function loadQuestions() { isLoadingConfig.value = true loadError.value = null questions.splice(0, questions.length) - chiefComplaint.value = null - pain.value = 0 + resetAnswers() result.value = null try { @@ -88,10 +117,9 @@ async function startSession() { const payload = { language: language.value, - answers: [ - { question_code: 'chief_complaint', value: chiefComplaint.value }, - { question_code: 'pain_intensity', value: pain.value }, - ], + answers: orderedQuestions.value + .filter((q) => answers[q.code] !== null && answers[q.code] !== undefined) + .map((q) => ({ question_code: q.code, value: answers[q.code] })), } try { @@ -118,10 +146,13 @@ export function useTriageSession() { return { language, questions, + orderedQuestions, isLoadingConfig, loadError, + answers, chiefComplaint, pain, + flowQuestions, isSubmitting, submitError, result, @@ -129,7 +160,10 @@ export function useTriageSession() { painQuestion, priorityClass, priorityLabel, + setAnswer, + getAnswer, loadQuestions, startSession, + resetAnswers, } } diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 8e2711b..284c62c 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,6 +1,7 @@ import { createRouter, createWebHistory } from 'vue-router' import LanguageView from '../views/LanguageView.vue' import ComplaintView from '../views/ComplaintView.vue' +import FlowView from '../views/FlowView.vue' import PainView from '../views/PainView.vue' import SummaryView from '../views/SummaryView.vue' @@ -10,6 +11,7 @@ const router = createRouter({ { path: '/', redirect: '/language' }, { path: '/language', component: LanguageView }, { path: '/complaint', component: ComplaintView }, + { path: '/flow', component: FlowView }, { path: '/pain', component: PainView }, { path: '/summary', component: SummaryView }, ], diff --git a/frontend/src/views/ComplaintView.vue b/frontend/src/views/ComplaintView.vue index e8df709..353b25f 100644 --- a/frontend/src/views/ComplaintView.vue +++ b/frontend/src/views/ComplaintView.vue @@ -17,7 +17,7 @@ onMounted(() => { function goNext() { if (chiefComplaint.value) { - router.push('/pain') + router.push('/flow') } } diff --git a/frontend/src/views/FlowView.vue b/frontend/src/views/FlowView.vue new file mode 100644 index 0000000..38d0504 --- /dev/null +++ b/frontend/src/views/FlowView.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/frontend/src/views/PainView.vue b/frontend/src/views/PainView.vue index d301858..b74eca6 100644 --- a/frontend/src/views/PainView.vue +++ b/frontend/src/views/PainView.vue @@ -11,7 +11,7 @@ const isReady = computed(() => !!chiefComplaint.value && !!painQuestion.value) onMounted(() => { if (!chiefComplaint.value) { - router.replace('/complaint') + router.replace('/flow') } })