schema.go
⎇
Raw
1// Package befund beschreibt das Befundformular als Daten. Formular, Druckansicht,
2// Vergleich und Import werden alle aus diesem einen Schema erzeugt.
3//
4// Die Feldschlüssel entsprechen der früheren Befundseite (data-bind), damit alte
5// JSON-Exporte 1:1 importiert werden können.
6package befund
7
8type Field struct {
9 Key, Label, Placeholder string
10 Type string // "text" (Standard), "area", "select", "check", "nrs" (0–10)
11 Rows int
12 Options []string
13 Wide bool // volle Breite im Raster
14}
15
16type Col struct {
17 Key, Label, Placeholder string
18 Type string // "text", "number", "date", "select"
19 Options []string
20 List string // id einer <datalist> mit Vorschlägen
21}
22
23type Table struct {
24 Key string
25 Cols []Col
26 Match []string // Spalten, über die Zeilen zweier Befunde zugeordnet werden
27 Diff []string // Spalten, deren Werte verglichen werden
28}
29
30// Part ist ein Abschnitt innerhalb einer Sektion: Feldraster, Tabelle oder Körperschema.
31type Part struct {
32 Title string
33 Collapsible bool
34 Fields []Field
35 Table *Table
36 Body bool
37}
38
39type Section struct {
40 ID, Title string
41 Specialty bool // Fachbereich: eingeklappt, außer er enthält Daten
42 Parts []Part
43}
44
45func area(key, label string) Field { return Field{Key: key, Label: label, Type: "area"} }
46func text(key, label string) Field { return Field{Key: key, Label: label} }
47func wide(f Field) Field { f.Wide = true; return f }
48func ph(f Field, p string) Field { f.Placeholder = p; return f }
49func rows(f Field, n int) Field { f.Rows = n; return f }
50func check(key, label string) Field { return Field{Key: key, Label: label, Type: "check"} }
51func nrs(key, label string) Field { return Field{Key: key, Label: label, Type: "nrs"} }
52
53var (
54 TableTimeline = &Table{Key: "timeline", Cols: []Col{
55 {Key: "date", Label: "Datum", Type: "date"},
56 {Key: "title", Label: "Ereignis"},
57 {Key: "note", Label: "Kurzbeschreibung"},
58 }}
59 TableROM = &Table{Key: "rom", Match: []string{"joint", "move"}, Diff: []string{"act", "pass", "end"}, Cols: []Col{
60 {Key: "joint", Label: "Gelenk", Placeholder: "z. B. Knie re"},
61 {Key: "move", Label: "Bewegung", Placeholder: "Flex/Ext"},
62 {Key: "act", Label: "Aktiv", Placeholder: "0-0-130"},
63 {Key: "pass", Label: "Passiv"},
64 {Key: "end", Label: "Endgefühl", Placeholder: "z. B. elastisch"},
65 }}
66 TableMFP = &Table{Key: "mfp", Match: []string{"muscle"}, Diff: []string{"right", "left"}, Cols: []Col{
67 {Key: "muscle", Label: "Muskel / Gruppe", Placeholder: "z. B. Quadrizeps"},
68 {Key: "right", Label: "rechts (0–5)", Placeholder: "z. B. 4+"},
69 {Key: "left", Label: "links (0–5)", Placeholder: "z. B. 4"},
70 }}
71 TableCirc = &Table{Key: "circ", Match: []string{"point"}, Diff: []string{"right", "left"}, Cols: []Col{
72 {Key: "point", Label: "Messpunkt", Placeholder: "z. B. 10 cm unter Patella"},
73 {Key: "right", Label: "rechts (cm)"},
74 {Key: "left", Label: "links (cm)"},
75 }}
76 TableTests = &Table{Key: "tests", Match: []string{"test", "side"}, Diff: []string{"result"}, Cols: []Col{
77 {Key: "test", Label: "Test", Placeholder: "z. B. Lachman", List: "dl-tests"},
78 {Key: "side", Label: "Seite", Type: "select", Options: []string{"", "re", "li", "bds", "–"}},
79 {Key: "result", Label: "Ergebnis", Type: "select", Options: []string{"", "positiv", "negativ", "fraglich"}},
80 {Key: "note", Label: "Bemerkung", Placeholder: "z. B. Schmerz reproduziert, Endgefühl"},
81 }}
82 TableMeasures = &Table{Key: "measures", Match: []string{"tool"}, Diff: []string{"value"}, Cols: []Col{
83 {Key: "tool", Label: "Instrument", Placeholder: "z. B. ODI, TUG", List: "dl-measures"},
84 {Key: "value", Label: "Wert", Placeholder: "z. B. 34"},
85 {Key: "unit", Label: "Einheit / Max.", Placeholder: "z. B. % · s · /80"},
86 {Key: "note", Label: "Bemerkung"},
87 }}
88 TablePSFS = &Table{Key: "psfs", Match: []string{"activity"}, Diff: []string{"score"}, Cols: []Col{
89 {Key: "activity", Label: "Aktivität (vom Patienten genannt)", Placeholder: "z. B. Treppe steigen 2 Stockwerke"},
90 {Key: "score", Label: "0–10", Type: "number"},
91 }}
92 Tables = []*Table{TableTimeline, TableROM, TableMFP, TableCirc, TableTests, TableMeasures, TablePSFS}
93
94 // Vorschläge für Instrumente (nur Namen; Werte trägt man selbst ein)
95 Measures = []string{
96 "NRS Schmerz", "PSFS (Mittelwert)", "ODI", "RMDQ", "NDI", "KOOS", "HOOS", "LEFS", "QuickDASH", "SPADI",
97 "VISA-A", "VISA-P", "FAAM", "TSK", "PCS", "STarT Back", "Örebro (Kurzform)", "EQ-5D",
98 "TUG", "30s Chair Stand", "5x Sit-to-Stand", "6MWT", "10m Gehtest", "BBS", "Tinetti", "Einbeinstand",
99 "Handkraft (Dynamometer)", "Y-Balance", "Single Leg Hop", "Borg (RPE)",
100 }
101)
102
103var Sections = []Section{
104 {ID: "patient", Title: "Patientendaten", Parts: []Part{{Fields: []Field{
105 text("diag", "Ärztl. Diagnose"),
106 text("onset", "Datum der Schädigung/Erkrankung/OP"),
107 area("coDiag", "Nebendiagnosen"),
108 area("meds", "Medikamente und Dosierung"),
109 area("contra", "Kontraindikationen"),
110 area("loadLimit", "Belastbarkeit (ärztl. Vorgaben/Grenze)"),
111 area("nursing", "Hinweise aus der Pflege"),
112 area("tools", "Hilfsmittel"),
113 wide(area("firstHyp", "Erste Hypothese/erwartbare Probleme")),
114 }}}},
115 {ID: "screening", Title: "Screening: Red & Yellow Flags", Parts: []Part{
116 {Title: "Red Flags (Hinweise auf ernste Pathologie)", Fields: []Field{
117 check("rf_cancer", "Krebserkrankung in der Vorgeschichte"),
118 check("rf_weight", "Ungewollter Gewichtsverlust"),
119 check("rf_fever", "Fieber, Infektzeichen, Immunsuppression"),
120 check("rf_night", "Ruhe-/Nachtschmerz ohne Bewegungsbezug"),
121 check("rf_trauma", "Relevantes Trauma / Osteoporose, Kortison (Fraktur?)"),
122 check("rf_cauda", "Reithosenanästhesie, Blasen-/Mastdarmstörung"),
123 check("rf_neuro", "Progrediente neurologische Ausfälle"),
124 check("rf_vasc", "Hinweise auf TVT/LE, kardiovaskuläre Ursache"),
125 check("rf_unwell", "Allgemeines Krankheitsgefühl, systemische Symptome"),
126 wide(ph(area("rf_action", "Bewertung & Vorgehen"), "z. B. keine Red Flags · Arzt am … informiert · Behandlung nur nach Abklärung")),
127 }},
128 {Title: "Yellow / Blue Flags (Chronifizierungsrisiko)", Fields: []Field{
129 check("yf_fear", "Angst-Vermeidungsverhalten"),
130 check("yf_cata", "Katastrophisieren"),
131 check("yf_mood", "Niedergeschlagenheit, Angst, Stress"),
132 check("yf_expect", "Geringe Erwartung an Besserung, passive Bewältigung"),
133 check("yf_work", "Arbeitsplatzprobleme, Konflikte, fehlende Unterstützung (Blue Flags)"),
134 wide(ph(text("screen_tool", "Screening-Instrument & Ergebnis"), "z. B. STarT Back: mittleres Risiko · Örebro-Kurzform: 52")),
135 }},
136 }},
137 {ID: "anamnese", Title: "Anamnese", Parts: []Part{
138 {Title: "Körperschema", Body: true},
139 {Title: "Schmerz", Fields: []Field{
140 nrs("pain_now", "NRS aktuell (0–10)"),
141 nrs("pain_best", "NRS beste 24 h"),
142 nrs("pain_worst", "NRS schlimmste 24 h"),
143 {
144 Key: "pain_type", Label: "Dominanter Schmerzmechanismus", Type: "select",
145 Options: []string{"", "nozizeptiv", "neuropathisch", "noziplastisch", "gemischt", "unklar"},
146 },
147 }},
148 {Fields: []Field{
149 wide(rows(ph(area("anaIntro", "Einleitende Frage(n)"), "Was führt Sie zu mir? …"), 5)),
150 }},
151 {Title: "Zeitlicher Verlauf", Table: TableTimeline},
152 {Fields: []Field{
153 area("24h", "24h-Verlauf"),
154 area("provo", "Provokation"),
155 text("lind", "Linderung"),
156 text("S/I", "Schwere/Irritierbarkeit"),
157 area("nas", "NAS nach Situation, Qualität etc."),
158 area("anaLoad", "Alltägliche Belastung (Beruf, Sport, Hobby, Tätigkeit)"),
159 area("envPos", "Umweltfaktoren – positiv"),
160 area("envNeg", "Umweltfaktoren – negativ"),
161 }},
162 {Title: "Allgemeiner Eindruck, Orientierung, Psyche, Kognition, Adhärenz", Collapsible: true, Fields: []Field{
163 text("psyche", "Psyche"),
164 text("cognition", "Kognition"),
165 wide(area("adh", "Compliance/Adhärenz/Sonstiges")),
166 }},
167 }},
168 {ID: "inspektion", Title: "Inspektion", Parts: []Part{{Fields: []Field{
169 wide(area("inspection", "Inspektionsbefund (Haltung, Haut, Ödeme, Narben, Atmung etc.)")),
170 }}}},
171 {ID: "bps", Title: "Psychosoziale Anamnese", Parts: []Part{{Fields: []Field{
172 area("bps_family", "Familie/soziales Netz"),
173 area("bps_home", "Wohn-/Lebenssituation"),
174 area("bps_work", "Beruf/Arbeitsbelastung"),
175 area("bps_leisure", "Freizeit/Aktivität"),
176 ph(area("bps_activity", "Körperliche Aktivität"), "Art, Min./Woche moderat/intensiv, Krafttraining?"),
177 area("bps_sleep", "Schlaf"),
178 wide(ph(area("bps_expect", "Vorstellungen & Erwartungen des Patienten"), "Eigene Erklärung der Beschwerden, erwarteter Verlauf, Wunsch an die Behandlung")),
179 }}}},
180 {ID: "hyp-anam", Title: "Hypothese nach Anamnese", Parts: []Part{{Fields: []Field{
181 wide(rows(area("hypAfterAnam", ""), 4)),
182 }}}},
183 {ID: "exam", Title: "Körperliche Untersuchung – Basis", Parts: []Part{
184 {Title: "ROM (Neutral-Null)", Table: TableROM},
185 {Title: "MFP (Muskelkraftprüfung)", Fields: []Field{wide(ph(text("mfp-allgemein", "Allgemein"), "z. B. Seitenvergleich, Ermüdung"))}, Table: TableMFP},
186 {Title: "Umfangmessung", Collapsible: true, Table: TableCirc},
187 {Title: "Spezifische Tests", Table: TableTests},
188 {Title: "Assessments & PROMs (Messwerte für den Verlauf)", Table: TableMeasures},
189 {Title: "Ganganalyse", Collapsible: true, Fields: []Field{wide(ph(area("gait", ""), "Gangphase, Spurbreite, Armpendel, Ausweichmechanismen …"))}},
190 {Title: "Assessments – Notizen", Collapsible: true, Fields: []Field{wide(ph(area("assess", ""), "Durchführung, Auffälligkeiten (Werte bitte in die Tabelle oben)"))}},
191 {Title: "Sensibilität/Wahrnehmung", Collapsible: true, Fields: []Field{wide(ph(area("sens", ""), "Reflextests, Dermatome …"))}},
192 {Title: "Neurologische Untersuchung", Collapsible: true, Fields: []Field{
193 area("neuro_derm", "Sensibilität nach Dermatomen"),
194 area("neuro_myo", "Kennmuskeln/Myotome"),
195 area("neuro_reflex", "Reflexe (MER, Babinski)"),
196 area("neuro_dyn", "Neurodynamik (SLR, Slump, ULNT, PKB)"),
197 }},
198 {Title: "Sicherheitstests", Collapsible: true, Fields: []Field{wide(ph(area("safetyNeuro", ""), "z. B. Instabilitätstests HWS, vaskuläre Tests"))}},
199 }},
200
201 // ---- Fachbereiche ----
202 {ID: "ortho", Title: "Chirurgie/Orthopädie", Specialty: true, Parts: []Part{{Fields: []Field{
203 area("or_jointProv", "Gelenktest/Provokation"),
204 area("or_len", "Muskellänge – Details"),
205 }}}},
206 {ID: "gyn-geb", Title: "Gynäkologie – Geburtshilfe", Specialty: true, Parts: []Part{
207 {Fields: []Field{
208 text("gg_gravida", "Schwangerschaften (Gravida)"),
209 text("gg_para", "Geburten (Para)"),
210 area("gg_pregCourse", "Schwangerschaftsverlauf"),
211 area("gg_pregFindings", "Auffälligkeiten in der Schwangerschaft"),
212 text("gg_mode", "Geburtsmodus"),
213 area("gg_birthCourse", "Geburtsverlauf"),
214 text("gg_position", "Gebärposition"),
215 area("gg_comp", "Komplikationen"),
216 }},
217 {Title: "Angaben zum Kind", Collapsible: true, Fields: []Field{
218 text("gg_apgar", "APGAR"),
219 text("gg_child", "Name/Geschlecht"),
220 text("gg_stats", "Größe/Gewicht/KU"),
221 }},
222 {Fields: []Field{
223 area("gg_pelvic", "Beckenbodenbefund"),
224 area("gg_abdomen", "Bauchbefund"),
225 }},
226 }},
227 {ID: "gyn-uro", Title: "Gynäkologie/Urologie", Specialty: true, Parts: []Part{{Fields: []Field{
228 area("gu_pelvic", "Beckenbodenbefund"),
229 area("gu_abdomen", "Bauchbefund"),
230 }}}},
231 {ID: "innere", Title: "Innere Medizin", Specialty: true, Parts: []Part{
232 {Fields: []Field{
233 text("im_pulse_point", "Puls (Messpunkt)"),
234 ph(text("im_pulse_freqs", "Frequenz Ruhe/Belastung/nachher"), "z. B. 68 / 102 / 80"),
235 {Key: "im_pulse_rhythm", Label: "Pulsrhythmus", Type: "select", Options: []string{"", "regelmäßig", "unregelmäßig"}},
236 text("im_pulse_fill", "Pulsfüllung"),
237 ph(text("im_rr", "RR (Ruhe/Belastung/nachher)"), "z. B. 120/70 / …"),
238 text("im_meds", "Beeinflussende Medikamente"),
239 wide(area("im_breath", "Atemdiagramm (CSV/CSL/CAL/CAV | ARL/MI/ME)")),
240 }},
241 {Title: "Thrombosedruckpunkte (positiv)", Fields: []Field{
242 check("im_payr", "Payr"), check("im_homann", "Homans"), check("im_adduct", "Adduktoren"),
243 check("im_meyer", "Meyer"), check("im_bisgard", "Bisgaard"),
244 }},
245 }},
246 {ID: "neuro", Title: "Neurologie", Specialty: true, Parts: []Part{{Fields: []Field{
247 wide(area("ne_tone", "Muskeltonus (Spastik, Klonus, Rigor, Tremor, Akinese, FoG, Ataxie)")),
248 area("ne_sens", "Sensibilität (Seitenvergleich)"),
249 area("ne_prop", "Propriozeption (UEX/OEX)"),
250 area("ne_balance", "Romberg/Einbeinstand"),
251 area("ne_reflex", "Holding/Placing/Mirroring"),
252 }}}},
253 {ID: "paedo", Title: "Pädiatrie", Specialty: true, Parts: []Part{{Fields: []Field{
254 area("pa_breath", "Atmung (Sekret, Husten, Atemgeräusche, Atelektasen …)"),
255 area("pa_vitals", "Vitalfunktionen (Herzfrequenz, Puls, RR)"),
256 area("pa_tone", "Muskeltonus"),
257 area("pa_fine", "Feinmotorik"),
258 text("pa_gmfcs", "GMFCS"),
259 text("pa_mac", "MACS"),
260 area("pa_adl", "ADL/Selbstständigkeit"),
261 area("pa_move", "Bewegungsanalyse"),
262 area("pa_play", "Situations-/Spielverhalten"),
263 area("pa_assess", "Assessments"),
264 }}}},
265
266 // ---- Auswertung ----
267 {ID: "hyp-exam", Title: "Hypothese nach körperlicher Untersuchung", Parts: []Part{{Fields: []Field{
268 wide(rows(ph(area("hypAfterExam", "Arbeitshypothese"), "Was spricht dafür (Anamnese, Tests, Messwerte)?"), 4)),
269 wide(ph(area("ddx", "Differenzialdiagnosen"), "Alternative Erklärungen – und was dagegen spricht")),
270 }}}},
271 {ID: "icf", Title: "ICF – Hauptproblem und Problemanalyse", Parts: []Part{{Fields: []Field{
272 area("icf_part", "Partizipationsebene"),
273 area("icf_activity", "Aktivitätsebene"),
274 wide(area("icf_body", "Funktions- und Strukturebene")),
275 area("goal_patient", "Patienten-/Angehörigenziel"),
276 area("goal_pt", "PT-Ziel (SMART)"),
277 wide(area("goal_agreement", "Zielvereinbarung")),
278 }}, {Title: "PSFS – Patientenspezifische Funktionsskala (0 = nicht möglich, 10 = wie vor dem Problem)", Table: TablePSFS}}},
279 {ID: "prognosis", Title: "Prognose mit Begründung", Parts: []Part{{Fields: []Field{
280 wide(rows(area("prognosis", ""), 4)),
281 }}}},
282 {ID: "plan", Title: "Therapieplan", Parts: []Part{{Fields: []Field{
283 wide(rows(area("plan", "Plan (Methoden, Frequenz, Edukation, Heimprogramm, Hilfsmittel)"), 5)),
284 wide(ph(area("plan_evidence", "Begründung / Evidenz"), "Leitlinie, Studienlage, Protokoll aus der Wissensdatenbank")),
285 ph(text("plan_reassess", "Re-Assessment geplant"), "z. B. nach 6 Behandlungen: NRS, PSFS, ODI"),
286 check("consent", "Aufklärung über Befund, Prognose und Plan erfolgt; Einwilligung liegt vor"),
287 }}}},
288 {ID: "zusatz", Title: "Zusätzliche Notizen", Parts: []Part{{Fields: []Field{
289 wide(rows(area("zusatz", ""), 4)),
290 }}}},
291}
292
293// Symbole für das Körperschema (wie auf der alten Befundseite).
294type Symbol struct{ Key, Char, Name string }
295
296var Symbols = []Symbol{
297 {"pain", "⚡\ufe0e", "Schmerz"},
298 {"swelling", "(( ))", "Schwellung"},
299 {"fracture", "◆", "Fraktur"},
300 {"tender", "×", "Druckschmerz"},
301 {"lesion", "●", "Läsion"},
302 {"radiating", "→", "Ausstrahlung"},
303 {"sensory", "≈", "Sensibilitätsstörung"},
304}
305
306// Ansichten des Körperschemas; W/H = Bildgröße, dient als SVG-viewBox.
307type View struct {
308 Name, Image string
309 W, H int
310}
311
312var Views = []View{
313 {"Vorne", "/static/body/body-front.png", 78, 180},
314 {"Hinten", "/static/body/body-back.png", 84, 176},
315 {"Seitlich", "/static/body/body-side.png", 106, 181},
316}
317
318var fieldIndex = func() map[string]Field {
319 m := map[string]Field{}
320 for _, s := range Sections {
321 for _, p := range s.Parts {
322 for _, f := range p.Fields {
323 if _, dup := m[f.Key]; dup {
324 panic("doppelter Feldschlüssel: " + f.Key)
325 }
326 m[f.Key] = f
327 }
328 }
329 }
330 return m
331}()
332
333func FieldByKey(k string) (Field, bool) { f, ok := fieldIndex[k]; return f, ok }
334