- Introduced new functions to handle vital signs data retrieval and processing, including fallback mechanisms for missing values. - Updated SQL queries in `build_vital_signs_matrix_chart_payload` to improve date filtering and data accuracy. - Enhanced the frontend `RecoveryDashboardOverview` component to display vital signs with contextual coloring based on health tones. - Adjusted the data structure for chart rendering, ensuring a more informative and visually appealing representation of vital metrics.
154 lines
5.6 KiB
Python
154 lines
5.6 KiB
Python
"""
|
||
Orientierende Zonen-Einschätzungen für Vitalwerte (Layer 1, Issue 53).
|
||
Keine Diagnose — typische Referenzbereiche für UI/Coaching.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from data_layer.utils import safe_float
|
||
|
||
Tone = str # good | warn | bad | neutral
|
||
|
||
|
||
def _item(
|
||
key: str,
|
||
label_de: str,
|
||
value_display: str,
|
||
tone: Tone,
|
||
zone_label_de: str,
|
||
hint_de: str,
|
||
sort_order: int,
|
||
) -> Dict[str, Any]:
|
||
return {
|
||
"key": key,
|
||
"label_de": label_de,
|
||
"value_display": value_display,
|
||
"tone": tone,
|
||
"zone_label_de": zone_label_de,
|
||
"hint_de": hint_de,
|
||
"sort_order": sort_order,
|
||
}
|
||
|
||
|
||
def assess_resting_hr(bpm: float) -> tuple:
|
||
if bpm < 50:
|
||
return (
|
||
"warn",
|
||
"Niedrig",
|
||
"Unter 50 bpm kann bei Sportlern normal sein — sonst ärztlich klären, wenn neu oder mit Beschwerden.",
|
||
)
|
||
if bpm < 60:
|
||
return ("good", "Günstig / athletisch", "Häufig bei gut trainierten Personen im unteren Normbereich.")
|
||
if bpm <= 100:
|
||
return ("good", "Im üblichen Normbereich", "Typischer Ruhepuls bei Erwachsenen oft ca. 60–100 bpm.")
|
||
if bpm <= 110:
|
||
return ("warn", "Leicht erhöht", "Kann durch Stress, Krankheit, Koffein oder Untrainiertheit erhöht sein — Verlauf beobachten.")
|
||
return ("bad", "Deutlich erhöht", "Bei anhaltend hohem Ruhepuls medizinische Abklärung sinnvoll.")
|
||
|
||
|
||
def assess_hrv_ms(ms: float) -> tuple:
|
||
_ = ms
|
||
return (
|
||
"neutral",
|
||
"Individuell",
|
||
"HRV (ms) ist sehr personenabhängig; Aussagekraft vor allem im Vergleich zu deiner eigenen Basis/Trend.",
|
||
)
|
||
|
||
|
||
def assess_blood_pressure(systolic: float, diastolic: float) -> tuple:
|
||
sys_, dia = systolic, diastolic
|
||
if sys_ >= 180 or dia >= 110:
|
||
return ("bad", "Sehr hoch", "Sehr hohe Werte — bei Beschwerden oder neu aufgetreten ärztlich zeitnah abklären.")
|
||
if sys_ >= 140 or dia >= 90:
|
||
return (
|
||
"bad",
|
||
"Erhöht",
|
||
"Liegt in einem Bereich, der oft als Hypertonie eingestuft wird — Bestätigung und Beratung durch ärztliche Messung.",
|
||
)
|
||
if sys_ >= 130 or dia >= 85:
|
||
return ("warn", "Hochnormal", "Oberer Normal-/hochnormaler Bereich — Lebensstil und Verlauf beachten.")
|
||
if sys_ < 120 and dia < 80:
|
||
return ("good", "Optimal", "Liegt in einem oft als günstig beschriebenen Bereich (<120/80 mmHg).")
|
||
return ("good", "Normal", "Im gängigen Zielbereich für viele Erwachsene.")
|
||
|
||
|
||
def assess_spo2(pct: float) -> tuple:
|
||
if pct >= 97:
|
||
return ("good", "Günstig", "Sauerstoffsättigung im üblichen Zielbereich.")
|
||
if pct >= 95:
|
||
return ("good", "Unauffällig", "Häufig noch als normal eingestuft; Verlauf bei Atembeschwerden beobachten.")
|
||
if pct >= 90:
|
||
return ("warn", "Leicht vermindert", "Unter 95 % kann je nach Kontext relevant sein — bei Symptomen abklären.")
|
||
return ("bad", "Niedrig", "Niedrige SpO2 — bei anhaltend unter 90 % oder Beschwerden ärztlich vorstellen.")
|
||
|
||
|
||
def assess_respiratory_rate(rpm: float) -> tuple:
|
||
if 12 <= rpm <= 20:
|
||
return ("good", "Im üblichen Bereich", "Ruheatmung oft ca. 12–20/min.")
|
||
if 10 <= rpm < 12 or 20 < rpm <= 24:
|
||
return ("warn", "Grenzbereich", "Leicht außerhalb des häufig zitierten Ruhebereichs — Kontext (Belastung, Stress) beachten.")
|
||
return ("bad", "Auffällig", "Deutlich außerhalb typischer Ruhewerte — bei Beschwerden medizinisch abklären.")
|
||
|
||
|
||
def assess_vo2_max(value: float) -> tuple:
|
||
_ = value
|
||
return (
|
||
"neutral",
|
||
"Orientativ",
|
||
"VO2max hängt stark von Alter, Geschlecht und Messmethode ab; Trends in der App sind aussagekräftiger als Einzelwerte.",
|
||
)
|
||
|
||
|
||
def build_vital_items_from_rows(
|
||
vitals_row: Optional[Dict[str, Any]],
|
||
bp_row: Optional[Dict[str, Any]],
|
||
) -> List[Dict[str, Any]]:
|
||
items: List[Dict[str, Any]] = []
|
||
order = 0
|
||
|
||
if vitals_row:
|
||
rhr = vitals_row.get("resting_hr")
|
||
if rhr is not None:
|
||
v = safe_float(rhr)
|
||
t, z, h = assess_resting_hr(v)
|
||
items.append(_item("resting_hr", "Ruhepuls", f"{v:.0f} bpm", t, z, h, order))
|
||
order += 1
|
||
|
||
hrv = vitals_row.get("hrv")
|
||
if hrv is not None:
|
||
v = safe_float(hrv)
|
||
t, z, h = assess_hrv_ms(v)
|
||
items.append(_item("hrv", "HRV", f"{v:.0f} ms", t, z, h, order))
|
||
order += 1
|
||
|
||
vo2 = vitals_row.get("vo2_max")
|
||
if vo2 is not None:
|
||
v = safe_float(vo2)
|
||
t, z, h = assess_vo2_max(v)
|
||
items.append(_item("vo2_max", "VO2max", f"{v:.1f} ml/kg/min", t, z, h, order))
|
||
order += 1
|
||
|
||
spo2 = vitals_row.get("spo2")
|
||
if spo2 is not None:
|
||
v = safe_float(spo2)
|
||
t, z, h = assess_spo2(v)
|
||
items.append(_item("spo2", "SpO2", f"{v:.0f} %", t, z, h, order))
|
||
order += 1
|
||
|
||
rr = vitals_row.get("respiratory_rate")
|
||
if rr is not None:
|
||
v = safe_float(rr)
|
||
t, z, h = assess_respiratory_rate(v)
|
||
items.append(_item("respiratory_rate", "Atemfrequenz", f"{v:.0f} /min", t, z, h, order))
|
||
order += 1
|
||
|
||
if bp_row and bp_row.get("systolic") is not None and bp_row.get("diastolic") is not None:
|
||
sys_v = safe_float(bp_row["systolic"])
|
||
dia_v = safe_float(bp_row["diastolic"])
|
||
t, z, h = assess_blood_pressure(sys_v, dia_v)
|
||
items.append(_item("blood_pressure", "Blutdruck", f"{sys_v:.0f}/{dia_v:.0f} mmHg", t, z, h, order))
|
||
|
||
return items
|