- Added a new endpoint for the recovery dashboard visualization in `charts.py`, integrating multiple recovery metrics and insights. - Implemented the `get_recovery_dashboard_viz` function to streamline data retrieval for recovery-related charts. - Refactored the `RecoveryCharts` component to utilize the new `RecoveryDashboardOverview`, simplifying the component structure and enhancing maintainability. - Updated the `RecoveryChartsPanelWidget` and `History` page to reflect the new recovery dashboard, improving user navigation and experience. - Deprecated the old recovery charts component, encouraging the use of the new overview for better data presentation.
184 lines
5.7 KiB
Python
184 lines
5.7 KiB
Python
"""
|
||
KPIs und Kurz-Aussagen für Recovery-Dashboard (Layer 2b).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
|
||
def _verdict(status: str) -> str:
|
||
if status == "good":
|
||
return "Gut"
|
||
if status == "warn":
|
||
return "Hinweis"
|
||
return "Achtung"
|
||
|
||
|
||
def _recovery_score_status(score: Optional[int]) -> str:
|
||
if score is None:
|
||
return "warn"
|
||
if score >= 70:
|
||
return "good"
|
||
if score >= 45:
|
||
return "warn"
|
||
return "bad"
|
||
|
||
|
||
def _debt_status(hours: Optional[float]) -> str:
|
||
if hours is None:
|
||
return "warn"
|
||
if hours <= 2:
|
||
return "good"
|
||
if hours <= 8:
|
||
return "warn"
|
||
return "bad"
|
||
|
||
|
||
def build_recovery_dashboard_kpi_tiles(
|
||
recovery_score: Optional[int],
|
||
sleep_debt_hours: Optional[float],
|
||
avg_sleep_hours: Optional[float],
|
||
hrv_vs_baseline_pct: Optional[float],
|
||
rhr_vs_baseline_pct: Optional[float],
|
||
) -> List[Dict[str, Any]]:
|
||
tiles: List[Dict[str, Any]] = []
|
||
|
||
rs = _recovery_score_status(recovery_score)
|
||
tiles.append(
|
||
{
|
||
"key": "recovery_score",
|
||
"category": "Recovery-Score",
|
||
"icon": "💚",
|
||
"value": str(recovery_score) if recovery_score is not None else "—",
|
||
"sublabel": "Modell aus Schlaf + Vitaldaten",
|
||
"status": rs,
|
||
"verdict": _verdict(rs),
|
||
"hoverTop": "Gesamt-Recovery-Score (0–100)",
|
||
"hoverBody": "calculate_recovery_score_v2 — gleiche Quelle wie Platzhalter.",
|
||
"keys": ["recovery_score"],
|
||
}
|
||
)
|
||
|
||
ds = _debt_status(sleep_debt_hours)
|
||
tiles.append(
|
||
{
|
||
"key": "sleep_debt",
|
||
"category": "Schlafschuld",
|
||
"icon": "⏳",
|
||
"value": f"{sleep_debt_hours:.1f} h".replace(".", ",")
|
||
if sleep_debt_hours is not None
|
||
else "—",
|
||
"sublabel": "Kumuliert (Ziel 8 h/Nacht)",
|
||
"status": ds,
|
||
"verdict": _verdict(ds),
|
||
"hoverTop": "Geschätzte Schlafschuld",
|
||
"hoverBody": "calculate_sleep_debt_hours",
|
||
"keys": ["sleep_debt_hours"],
|
||
}
|
||
)
|
||
|
||
tiles.append(
|
||
{
|
||
"key": "avg_sleep",
|
||
"category": "Ø Schlafdauer",
|
||
"icon": "🌙",
|
||
"value": f"{avg_sleep_hours:.1f} h".replace(".", ",") if avg_sleep_hours is not None else "—",
|
||
"sublabel": "Im gewählten Fenster",
|
||
"status": "good" if avg_sleep_hours and avg_sleep_hours >= 7 else "warn",
|
||
"verdict": "Gut" if avg_sleep_hours and avg_sleep_hours >= 7 else "Hinweis",
|
||
"hoverTop": "Durchschnittliche Schlafdauer",
|
||
"hoverBody": "get_sleep_duration_data",
|
||
"keys": ["sleep_duration_avg"],
|
||
}
|
||
)
|
||
|
||
h_s = (
|
||
"good"
|
||
if hrv_vs_baseline_pct is not None and hrv_vs_baseline_pct >= 0
|
||
else "warn"
|
||
if hrv_vs_baseline_pct is not None
|
||
else "warn"
|
||
)
|
||
tiles.append(
|
||
{
|
||
"key": "hrv_baseline",
|
||
"category": "HRV vs. Basis",
|
||
"icon": "〰️",
|
||
"value": f"{hrv_vs_baseline_pct:+.1f} %".replace(".", ",")
|
||
if hrv_vs_baseline_pct is not None
|
||
else "—",
|
||
"sublabel": "Letzte 3 Tage vs. ältere Basis",
|
||
"status": h_s,
|
||
"verdict": _verdict(h_s),
|
||
"hoverTop": "Abweichung HRV vom Referenzmittel",
|
||
"hoverBody": "calculate_hrv_vs_baseline_pct",
|
||
"keys": ["hrv_vs_baseline"],
|
||
}
|
||
)
|
||
|
||
tiles.append(
|
||
{
|
||
"key": "rhr_baseline",
|
||
"category": "Ruhepuls vs. Basis",
|
||
"icon": "❤️",
|
||
"value": f"{rhr_vs_baseline_pct:+.1f} %".replace(".", ",")
|
||
if rhr_vs_baseline_pct is not None
|
||
else "—",
|
||
"sublabel": "Niedriger oft günstiger",
|
||
"status": "good",
|
||
"verdict": "Gut",
|
||
"hoverTop": "Abweichung Ruhepuls",
|
||
"hoverBody": "calculate_rhr_vs_baseline_pct",
|
||
"keys": ["rhr_vs_baseline"],
|
||
}
|
||
)
|
||
|
||
return tiles
|
||
|
||
|
||
def build_recovery_progress_insights(
|
||
recovery_score: Optional[int],
|
||
sleep_debt_hours: Optional[float],
|
||
hrv_vs_baseline_pct: Optional[float],
|
||
) -> List[Dict[str, Any]]:
|
||
out: List[Dict[str, Any]] = []
|
||
|
||
if recovery_score is not None:
|
||
tone = "good" if recovery_score >= 65 else "warn" if recovery_score >= 45 else "bad"
|
||
out.append(
|
||
{
|
||
"key": "ins_rec",
|
||
"tone": tone,
|
||
"title": "Gesamterholung",
|
||
"body": f"Der Recovery-Score liegt bei {recovery_score}/100. "
|
||
"Er kombiniert Schlaf- und Vital-Signale — ideal für die Einordnung von Trainingstagen.",
|
||
}
|
||
)
|
||
|
||
if sleep_debt_hours is not None:
|
||
tone = "good" if sleep_debt_hours <= 3 else "warn" if sleep_debt_hours <= 10 else "bad"
|
||
out.append(
|
||
{
|
||
"key": "ins_debt",
|
||
"tone": tone,
|
||
"title": "Schlaf nachholen",
|
||
"body": f"Geschätzte Schlafschuld: {sleep_debt_hours:.1f} h. "
|
||
"Hohe Schulden erhöhen Verletzungs- und Ermüdungsrisiko — Priorität Schlafhygiene.",
|
||
}
|
||
)
|
||
|
||
if hrv_vs_baseline_pct is not None:
|
||
tone = "good" if hrv_vs_baseline_pct >= 0 else "warn"
|
||
out.append(
|
||
{
|
||
"key": "ins_hrv",
|
||
"tone": tone,
|
||
"title": "Autonomes System",
|
||
"body": f"HRV liegt {hrv_vs_baseline_pct:+.1f} % relativ zur Basis. "
|
||
"Positive Werte werden oft mit guter Regeneration assoziiert (individuell interpretieren).",
|
||
}
|
||
)
|
||
|
||
return out
|