- Updated the `build_vital_signs_matrix_chart_payload` function to accept optional keys for omitting specific snapshot data, improving flexibility in data presentation. - Enhanced the `build_recovery_dashboard_kpi_tiles` function to conditionally merge heart and autonomic tiles based on new parameters, refining the dashboard's insights. - Integrated new analytics features in the `RecoveryDashboardOverview` component, including consolidated paragraphs for better narrative context and visual representation of trends. - Improved the handling of vital signs data in the frontend, ensuring clearer messaging and enhanced user experience when displaying vital metrics.
157 lines
5.8 KiB
Python
157 lines
5.8 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, Set
|
||
|
||
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]],
|
||
omit_keys: Optional[Set[str]] = None,
|
||
) -> List[Dict[str, Any]]:
|
||
"""omit_keys: z. B. {'resting_hr','hrv'} wenn Einordnung zentral im Herz-/Autonomie-Block steht."""
|
||
skip = omit_keys or set()
|
||
items: List[Dict[str, Any]] = []
|
||
order = 0
|
||
|
||
if vitals_row:
|
||
rhr = vitals_row.get("resting_hr")
|
||
if rhr is not None and "resting_hr" not in skip:
|
||
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 and "hrv" not in skip:
|
||
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
|