2026-03-28 18:26:22 +01:00
|
|
|
|
"""
|
|
|
|
|
|
Charts Router - Chart.js-compatible Data Endpoints
|
|
|
|
|
|
|
|
|
|
|
|
Provides structured data for frontend charts/diagrams.
|
|
|
|
|
|
|
|
|
|
|
|
All endpoints return Chart.js-compatible JSON format:
|
|
|
|
|
|
{
|
|
|
|
|
|
"chart_type": "line" | "bar" | "scatter" | "pie",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [...],
|
|
|
|
|
|
"datasets": [...]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "high" | "medium" | "low" | "insufficient",
|
|
|
|
|
|
"data_points": int,
|
|
|
|
|
|
...
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Phase 0c: Multi-Layer Architecture
|
|
|
|
|
|
Version: 1.0
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
2026-04-20 10:29:43 +02:00
|
|
|
|
from typing import Dict, List, Optional, Set
|
2026-03-28 18:26:22 +01:00
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
|
|
|
|
|
|
from auth import require_auth
|
|
|
|
|
|
from data_layer.body_metrics import (
|
|
|
|
|
|
get_weight_trend_data,
|
|
|
|
|
|
get_body_composition_data,
|
|
|
|
|
|
get_circumference_summary_data
|
|
|
|
|
|
)
|
2026-04-19 16:06:07 +02:00
|
|
|
|
from data_layer.body_viz import get_body_history_viz_bundle
|
2026-04-19 17:20:24 +02:00
|
|
|
|
from data_layer.nutrition_viz import get_nutrition_history_viz_bundle
|
2026-04-20 14:43:31 +02:00
|
|
|
|
from data_layer.fitness_viz import get_fitness_dashboard_viz_bundle, get_activity_last_updated_iso
|
2026-04-20 08:11:23 +02:00
|
|
|
|
from data_layer.recovery_viz import get_recovery_dashboard_viz_bundle
|
2026-04-20 13:45:28 +02:00
|
|
|
|
from data_layer.history_overview_viz import get_history_overview_viz_bundle
|
2026-04-20 08:11:23 +02:00
|
|
|
|
from data_layer.recovery_chart_payloads import (
|
|
|
|
|
|
build_recovery_score_chart_payload,
|
|
|
|
|
|
build_hrv_rhr_baseline_chart_payload,
|
|
|
|
|
|
build_sleep_duration_quality_chart_payload,
|
|
|
|
|
|
build_sleep_debt_chart_payload,
|
|
|
|
|
|
build_vital_signs_matrix_chart_payload,
|
|
|
|
|
|
)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
from data_layer.nutrition_metrics import (
|
|
|
|
|
|
get_nutrition_average_data,
|
|
|
|
|
|
get_protein_targets_data,
|
2026-04-11 19:04:27 +02:00
|
|
|
|
get_macro_consistency_data,
|
2026-04-19 17:20:24 +02:00
|
|
|
|
get_weekly_macro_distribution_chart_data,
|
2026-04-19 17:43:29 +02:00
|
|
|
|
get_energy_availability_warning_payload,
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
)
|
|
|
|
|
|
from data_layer.activity_metrics import (
|
|
|
|
|
|
get_activity_summary_data,
|
|
|
|
|
|
calculate_training_minutes_week,
|
|
|
|
|
|
calculate_monotony_score,
|
|
|
|
|
|
calculate_strain_score,
|
2026-04-19 21:27:12 +02:00
|
|
|
|
calculate_ability_balance,
|
|
|
|
|
|
build_training_volume_chart_payload,
|
|
|
|
|
|
build_training_type_distribution_chart_payload,
|
2026-04-20 08:04:50 +02:00
|
|
|
|
build_quality_sessions_chart_payload,
|
|
|
|
|
|
build_load_monitoring_chart_payload,
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
)
|
|
|
|
|
|
from data_layer.recovery_metrics import (
|
|
|
|
|
|
get_sleep_duration_data,
|
|
|
|
|
|
get_sleep_quality_data,
|
|
|
|
|
|
calculate_recovery_score_v2,
|
|
|
|
|
|
calculate_hrv_vs_baseline_pct,
|
|
|
|
|
|
calculate_rhr_vs_baseline_pct,
|
|
|
|
|
|
calculate_sleep_debt_hours
|
|
|
|
|
|
)
|
2026-04-22 11:55:11 +02:00
|
|
|
|
from data_layer.correlation_chart_payloads import (
|
|
|
|
|
|
build_lbm_protein_correlation_chart_payload,
|
|
|
|
|
|
build_load_vitals_correlation_chart_payload,
|
|
|
|
|
|
build_recovery_performance_chart_payload,
|
|
|
|
|
|
build_weight_energy_correlation_chart_payload,
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
)
|
|
|
|
|
|
from data_layer.utils import serialize_dates, safe_float, calculate_confidence
|
2026-04-20 14:51:27 +02:00
|
|
|
|
from data_layer.nutrition_chart_payloads import (
|
|
|
|
|
|
build_energy_balance_chart_payload,
|
|
|
|
|
|
build_protein_adequacy_chart_payload,
|
|
|
|
|
|
build_nutrition_adherence_score_payload,
|
|
|
|
|
|
)
|
2026-03-28 18:26:22 +01:00
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
router = APIRouter(prefix="/api/charts", tags=["charts"])
|
2026-03-28 18:26:22 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Body Charts ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/weight-trend")
|
2026-03-28 18:26:22 +01:00
|
|
|
|
def get_weight_trend_chart(
|
|
|
|
|
|
days: int = Query(default=90, ge=7, le=365, description="Analysis window in days"),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Weight trend chart data.
|
|
|
|
|
|
|
|
|
|
|
|
Returns Chart.js-compatible line chart with:
|
|
|
|
|
|
- Raw weight values
|
|
|
|
|
|
- Trend line (if enough data)
|
|
|
|
|
|
- Confidence indicator
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-365 days, default 90)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
{
|
|
|
|
|
|
"chart_type": "line",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": ["2026-01-01", ...],
|
|
|
|
|
|
"datasets": [{
|
|
|
|
|
|
"label": "Gewicht",
|
|
|
|
|
|
"data": [85.0, 84.5, ...],
|
|
|
|
|
|
"borderColor": "#1D9E75",
|
|
|
|
|
|
"backgroundColor": "rgba(29, 158, 117, 0.1)",
|
|
|
|
|
|
"tension": 0.4
|
|
|
|
|
|
}]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "high",
|
|
|
|
|
|
"data_points": 60,
|
|
|
|
|
|
"first_value": 92.0,
|
|
|
|
|
|
"last_value": 85.0,
|
|
|
|
|
|
"delta": -7.0,
|
|
|
|
|
|
"direction": "decreasing"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Metadata fields:
|
|
|
|
|
|
- confidence: Data quality ("high", "medium", "low", "insufficient")
|
|
|
|
|
|
- data_points: Number of weight entries in period
|
|
|
|
|
|
- first_value: First weight in period (kg)
|
|
|
|
|
|
- last_value: Latest weight (kg)
|
|
|
|
|
|
- delta: Weight change (kg, negative = loss)
|
|
|
|
|
|
- direction: "increasing" | "decreasing" | "stable"
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
|
|
|
|
|
|
2026-04-11 20:46:17 +02:00
|
|
|
|
# Get structured data from data layer (includes series — no second weight_log query)
|
2026-03-28 18:26:22 +01:00
|
|
|
|
trend_data = get_weight_trend_data(profile_id, days)
|
|
|
|
|
|
|
|
|
|
|
|
# Early return if insufficient data
|
|
|
|
|
|
if trend_data['confidence'] == 'insufficient':
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "line",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Nicht genug Daten für Trend-Analyse"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-11 20:46:17 +02:00
|
|
|
|
series = trend_data.get("series") or []
|
|
|
|
|
|
labels = [
|
|
|
|
|
|
pt["date"].isoformat() if hasattr(pt["date"], "isoformat") else str(pt["date"])
|
|
|
|
|
|
for pt in series
|
|
|
|
|
|
]
|
|
|
|
|
|
values = [pt["weight"] for pt in series]
|
2026-03-28 18:26:22 +01:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "line",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": labels,
|
|
|
|
|
|
"datasets": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "Gewicht",
|
|
|
|
|
|
"data": values,
|
|
|
|
|
|
"borderColor": "#1D9E75",
|
|
|
|
|
|
"backgroundColor": "rgba(29, 158, 117, 0.1)",
|
|
|
|
|
|
"borderWidth": 2,
|
|
|
|
|
|
"tension": 0.4,
|
|
|
|
|
|
"fill": True,
|
|
|
|
|
|
"pointRadius": 3,
|
|
|
|
|
|
"pointHoverRadius": 5
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": serialize_dates({
|
|
|
|
|
|
"confidence": trend_data['confidence'],
|
|
|
|
|
|
"data_points": trend_data['data_points'],
|
|
|
|
|
|
"first_value": trend_data['first_value'],
|
|
|
|
|
|
"last_value": trend_data['last_value'],
|
|
|
|
|
|
"delta": trend_data['delta'],
|
|
|
|
|
|
"direction": trend_data['direction'],
|
|
|
|
|
|
"first_date": trend_data['first_date'],
|
|
|
|
|
|
"last_date": trend_data['last_date'],
|
|
|
|
|
|
"days_analyzed": trend_data['days_analyzed']
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/body-composition")
|
2026-03-28 18:26:22 +01:00
|
|
|
|
def get_body_composition_chart(
|
|
|
|
|
|
days: int = Query(default=90, ge=7, le=365),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Body composition chart (body fat percentage, lean mass trend).
|
|
|
|
|
|
|
|
|
|
|
|
Returns Chart.js-compatible multi-line chart.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-365 days, default 90)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js format with datasets for:
|
|
|
|
|
|
- Body fat percentage (%)
|
|
|
|
|
|
- Lean mass (kg, if weight available)
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
|
|
|
|
|
|
|
|
|
|
|
# Get latest body composition
|
|
|
|
|
|
comp_data = get_body_composition_data(profile_id, days)
|
|
|
|
|
|
|
|
|
|
|
|
if comp_data['confidence'] == 'insufficient':
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "line",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Keine Körperfett-Messungen vorhanden"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# For PoC: Return single data point
|
|
|
|
|
|
# TODO in bulk migration: Fetch historical caliper entries
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "line",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [comp_data['date'].isoformat() if comp_data['date'] else ""],
|
|
|
|
|
|
"datasets": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "Körperfett %",
|
|
|
|
|
|
"data": [comp_data['body_fat_pct']],
|
|
|
|
|
|
"borderColor": "#D85A30",
|
|
|
|
|
|
"backgroundColor": "rgba(216, 90, 48, 0.1)",
|
|
|
|
|
|
"borderWidth": 2
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": serialize_dates({
|
|
|
|
|
|
"confidence": comp_data['confidence'],
|
|
|
|
|
|
"data_points": comp_data['data_points'],
|
|
|
|
|
|
"body_fat_pct": comp_data['body_fat_pct'],
|
|
|
|
|
|
"method": comp_data['method'],
|
|
|
|
|
|
"date": comp_data['date']
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-19 16:06:07 +02:00
|
|
|
|
@router.get("/body-history-viz")
|
|
|
|
|
|
def get_body_history_viz(
|
|
|
|
|
|
days: int = Query(
|
|
|
|
|
|
default=90,
|
|
|
|
|
|
ge=7,
|
|
|
|
|
|
le=9999,
|
|
|
|
|
|
description="Analysefenster in Tagen (9999 = gesamte Historie im Rohdatensatz)",
|
|
|
|
|
|
),
|
|
|
|
|
|
session: dict = Depends(require_auth),
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Layer 2b: Ein Bundle für Verlauf «Körper» — Charts, Kennzahlen, Bewertungskacheln.
|
|
|
|
|
|
|
|
|
|
|
|
Alle Reihen und Kennzahlen stammen aus Layer 1 (dieselben Tabellen wie die
|
|
|
|
|
|
Körper-Platzhalter / body_metrics). Interpretationskacheln sind mit
|
|
|
|
|
|
``related_placeholder_keys`` an Layer 2a ausgewiesen.
|
|
|
|
|
|
|
|
|
|
|
|
Frontend: ausschließlich Darstellung — keine parallele Berechnung.
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
bundle = get_body_history_viz_bundle(profile_id, days)
|
|
|
|
|
|
return serialize_dates(bundle)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-19 17:20:24 +02:00
|
|
|
|
@router.get("/nutrition-history-viz")
|
|
|
|
|
|
def get_nutrition_history_viz(
|
|
|
|
|
|
days: int = Query(
|
|
|
|
|
|
default=90,
|
|
|
|
|
|
ge=7,
|
|
|
|
|
|
le=9999,
|
|
|
|
|
|
description="Analysefenster in Tagen (9999 = gesamte Historie)",
|
|
|
|
|
|
),
|
|
|
|
|
|
session: dict = Depends(require_auth),
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Layer 2b: Ein Bundle für Verlauf «Ernährung» — Kennzahlen, Reihen, TDEE-Referenz, Wochen-Chart.
|
|
|
|
|
|
|
|
|
|
|
|
Alle Kennzahlen aus nutrition_metrics (gleiche Logik wie Platzhalter / Chart-Endpunkte).
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
bundle = get_nutrition_history_viz_bundle(profile_id, days)
|
|
|
|
|
|
return serialize_dates(bundle)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-19 21:27:12 +02:00
|
|
|
|
@router.get("/fitness-dashboard-viz")
|
|
|
|
|
|
def get_fitness_dashboard_viz(
|
|
|
|
|
|
days: int = Query(
|
|
|
|
|
|
default=28,
|
|
|
|
|
|
ge=7,
|
|
|
|
|
|
le=9999,
|
|
|
|
|
|
description="Analysefenster in Tagen (9999 = lange Historie)",
|
|
|
|
|
|
),
|
|
|
|
|
|
session: dict = Depends(require_auth),
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Layer 2b: Fitness-Übersicht — KPI-Kacheln + Volumen- und Typ-Verteilungs-Charts.
|
|
|
|
|
|
|
|
|
|
|
|
Daten aus activity_metrics (gleiche Payloads wie training-volume / training-type-distribution).
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
bundle = get_fitness_dashboard_viz_bundle(profile_id, days)
|
|
|
|
|
|
return serialize_dates(bundle)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-20 14:43:31 +02:00
|
|
|
|
@router.get("/activity-last-updated")
|
|
|
|
|
|
def get_activity_last_updated(session: dict = Depends(require_auth)) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Minimal-Metadatum: letztes Trainingsdatum — gleiche Quelle wie ``last_updated`` im Fitness-Viz-Bundle.
|
|
|
|
|
|
|
|
|
|
|
|
Vermeidet Massen-Ladevorgänge (z. B. listActivity) nur für Datumsanzeige im Verlauf.
|
|
|
|
|
|
"""
|
|
|
|
|
|
pid = session["profile_id"]
|
|
|
|
|
|
return {"last_activity_date": get_activity_last_updated_iso(pid)}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-20 08:11:23 +02:00
|
|
|
|
@router.get("/recovery-dashboard-viz")
|
|
|
|
|
|
def get_recovery_dashboard_viz(
|
|
|
|
|
|
days: int = Query(
|
|
|
|
|
|
default=28,
|
|
|
|
|
|
ge=7,
|
|
|
|
|
|
le=9999,
|
|
|
|
|
|
description="Analysefenster in Tagen (9999 = lange Historie)",
|
|
|
|
|
|
),
|
|
|
|
|
|
session: dict = Depends(require_auth),
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Layer 2b: Recovery/Erholung — KPIs, Insights, Charts R1–R5 (recovery_metrics).
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
bundle = get_recovery_dashboard_viz_bundle(profile_id, days)
|
|
|
|
|
|
return serialize_dates(bundle)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-20 13:45:28 +02:00
|
|
|
|
@router.get("/history-overview-viz")
|
|
|
|
|
|
def get_history_overview_viz(
|
|
|
|
|
|
days: int = Query(
|
|
|
|
|
|
default=30,
|
|
|
|
|
|
ge=7,
|
|
|
|
|
|
le=9999,
|
|
|
|
|
|
description="Analysefenster in Tagen (komponiert Körper/Ernährung/Fitness/Erholung)",
|
|
|
|
|
|
),
|
|
|
|
|
|
session: dict = Depends(require_auth),
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
2026-04-22 11:55:11 +02:00
|
|
|
|
Layer 2b: Gesamtansicht «Verlauf» — KPI-Kurzformen aus den vier History-Bundles,
|
|
|
|
|
|
Lag-Korrelationen C1–C4 (Metadaten) und Chart.js-Payloads C1–C4 (chart_payloads, wie /charts/*).
|
2026-04-20 13:45:28 +02:00
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
bundle = get_history_overview_viz_bundle(profile_id, days)
|
|
|
|
|
|
return serialize_dates(bundle)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/circumferences")
|
2026-03-28 18:26:22 +01:00
|
|
|
|
def get_circumferences_chart(
|
|
|
|
|
|
max_age_days: int = Query(default=90, ge=7, le=365),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Latest circumference measurements as bar chart.
|
|
|
|
|
|
|
|
|
|
|
|
Shows most recent measurement for each body point.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
max_age_days: Maximum age of measurements (default 90)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js bar chart with all circumference points
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
|
|
|
|
|
|
|
|
|
|
|
circ_data = get_circumference_summary_data(profile_id, max_age_days)
|
|
|
|
|
|
|
|
|
|
|
|
if circ_data['confidence'] == 'insufficient':
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "bar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Keine Umfangsmessungen vorhanden"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Sort by value (descending) for better visualization
|
|
|
|
|
|
measurements = sorted(
|
|
|
|
|
|
circ_data['measurements'],
|
|
|
|
|
|
key=lambda m: m['value'],
|
|
|
|
|
|
reverse=True
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
labels = [m['point'] for m in measurements]
|
|
|
|
|
|
values = [m['value'] for m in measurements]
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "bar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": labels,
|
|
|
|
|
|
"datasets": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "Umfang (cm)",
|
|
|
|
|
|
"data": values,
|
|
|
|
|
|
"backgroundColor": "#1D9E75",
|
|
|
|
|
|
"borderColor": "#085041",
|
|
|
|
|
|
"borderWidth": 1
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": serialize_dates({
|
|
|
|
|
|
"confidence": circ_data['confidence'],
|
|
|
|
|
|
"data_points": circ_data['data_points'],
|
|
|
|
|
|
"newest_date": circ_data['newest_date'],
|
|
|
|
|
|
"oldest_date": circ_data['oldest_date'],
|
|
|
|
|
|
"measurements": circ_data['measurements'] # Full details
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
# ── Nutrition Charts ────────────────────────────────────────────────────────
|
2026-03-28 18:26:22 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/energy-balance")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_energy_balance_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
2026-03-28 18:26:22 +01:00
|
|
|
|
"""
|
2026-03-29 07:28:56 +02:00
|
|
|
|
Energy balance timeline (E1) - Konzept-konform.
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
2026-03-29 07:28:56 +02:00
|
|
|
|
Shows:
|
|
|
|
|
|
- Daily calorie intake
|
|
|
|
|
|
- 7d rolling average
|
|
|
|
|
|
- 14d rolling average
|
|
|
|
|
|
- TDEE reference line
|
|
|
|
|
|
- Energy deficit/surplus
|
|
|
|
|
|
- Lagged comparison to weight trend
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
2026-03-28 18:26:22 +01:00
|
|
|
|
|
|
|
|
|
|
Returns:
|
2026-03-29 07:28:56 +02:00
|
|
|
|
Chart.js line chart with multiple datasets
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-20 14:51:27 +02:00
|
|
|
|
return build_energy_balance_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/macro-distribution")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_macro_distribution_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Macronutrient distribution pie chart (E2).
|
|
|
|
|
|
|
|
|
|
|
|
Shows average protein/carbs/fat distribution over period.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js pie chart with macro percentages
|
2026-03-28 18:26:22 +01:00
|
|
|
|
"""
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
profile_id = session['profile_id']
|
|
|
|
|
|
|
|
|
|
|
|
# Get average macros
|
|
|
|
|
|
macro_data = get_nutrition_average_data(profile_id, days)
|
|
|
|
|
|
|
|
|
|
|
|
if macro_data['confidence'] == 'insufficient':
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "pie",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Keine Ernährungsdaten vorhanden"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Calculate calories from macros (protein/carbs = 4 kcal/g, fat = 9 kcal/g)
|
|
|
|
|
|
protein_kcal = macro_data['protein_avg'] * 4
|
|
|
|
|
|
carbs_kcal = macro_data['carbs_avg'] * 4
|
|
|
|
|
|
fat_kcal = macro_data['fat_avg'] * 9
|
|
|
|
|
|
total_kcal = protein_kcal + carbs_kcal + fat_kcal
|
|
|
|
|
|
|
|
|
|
|
|
if total_kcal == 0:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "pie",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Keine Makronährstoff-Daten"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protein_pct = (protein_kcal / total_kcal * 100)
|
|
|
|
|
|
carbs_pct = (carbs_kcal / total_kcal * 100)
|
|
|
|
|
|
fat_pct = (fat_kcal / total_kcal * 100)
|
|
|
|
|
|
|
2026-03-28 18:26:22 +01:00
|
|
|
|
return {
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
"chart_type": "pie",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": ["Protein", "Kohlenhydrate", "Fett"],
|
|
|
|
|
|
"datasets": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"data": [round(protein_pct, 1), round(carbs_pct, 1), round(fat_pct, 1)],
|
|
|
|
|
|
"backgroundColor": [
|
|
|
|
|
|
"#1D9E75", # Protein (green)
|
|
|
|
|
|
"#F59E0B", # Carbs (amber)
|
|
|
|
|
|
"#EF4444" # Fat (red)
|
|
|
|
|
|
],
|
|
|
|
|
|
"borderWidth": 2,
|
|
|
|
|
|
"borderColor": "#fff"
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": macro_data['confidence'],
|
|
|
|
|
|
"data_points": macro_data['data_points'],
|
|
|
|
|
|
"protein_g": round(macro_data['protein_avg'], 1),
|
|
|
|
|
|
"carbs_g": round(macro_data['carbs_avg'], 1),
|
|
|
|
|
|
"fat_g": round(macro_data['fat_avg'], 1),
|
|
|
|
|
|
"protein_pct": round(protein_pct, 1),
|
|
|
|
|
|
"carbs_pct": round(carbs_pct, 1),
|
|
|
|
|
|
"fat_pct": round(fat_pct, 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/protein-adequacy")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_protein_adequacy_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
2026-03-29 07:28:56 +02:00
|
|
|
|
Protein adequacy timeline (E2) - Konzept-konform.
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
2026-03-29 07:28:56 +02:00
|
|
|
|
Shows:
|
|
|
|
|
|
- Daily protein intake
|
|
|
|
|
|
- 7d rolling average
|
|
|
|
|
|
- 28d rolling average
|
|
|
|
|
|
- Target range bands
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
2026-03-29 07:28:56 +02:00
|
|
|
|
Chart.js line chart with protein intake + averages + target bands
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-20 14:51:27 +02:00
|
|
|
|
return build_protein_adequacy_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/nutrition-consistency")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_nutrition_consistency_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Nutrition consistency score (E5).
|
|
|
|
|
|
|
|
|
|
|
|
Shows macro consistency score as bar chart.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js bar chart with consistency metrics
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
|
|
|
|
|
|
|
|
|
|
|
consistency_data = get_macro_consistency_data(profile_id, days)
|
|
|
|
|
|
|
|
|
|
|
|
if consistency_data['confidence'] == 'insufficient':
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "bar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
2026-03-28 18:26:22 +01:00
|
|
|
|
},
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Nicht genug Daten für Konsistenz-Analyse"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Show consistency score + macro averages
|
|
|
|
|
|
labels = [
|
|
|
|
|
|
"Gesamt-Score",
|
|
|
|
|
|
f"Protein ({consistency_data['avg_protein_pct']:.0f}%)",
|
|
|
|
|
|
f"Kohlenhydrate ({consistency_data['avg_carbs_pct']:.0f}%)",
|
|
|
|
|
|
f"Fett ({consistency_data['avg_fat_pct']:.0f}%)"
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
# Score = 100 - std_dev (inverted for display)
|
|
|
|
|
|
# Higher bar = more consistent
|
|
|
|
|
|
protein_consistency = max(0, 100 - consistency_data['std_dev_protein'] * 10)
|
|
|
|
|
|
carbs_consistency = max(0, 100 - consistency_data['std_dev_carbs'] * 10)
|
|
|
|
|
|
fat_consistency = max(0, 100 - consistency_data['std_dev_fat'] * 10)
|
|
|
|
|
|
|
|
|
|
|
|
values = [
|
|
|
|
|
|
consistency_data['consistency_score'],
|
|
|
|
|
|
protein_consistency,
|
|
|
|
|
|
carbs_consistency,
|
|
|
|
|
|
fat_consistency
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "bar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": labels,
|
|
|
|
|
|
"datasets": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "Konsistenz-Score",
|
|
|
|
|
|
"data": values,
|
|
|
|
|
|
"backgroundColor": ["#1D9E75", "#1D9E75", "#F59E0B", "#EF4444"],
|
|
|
|
|
|
"borderColor": "#085041",
|
|
|
|
|
|
"borderWidth": 1
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": consistency_data['confidence'],
|
|
|
|
|
|
"data_points": consistency_data['data_points'],
|
|
|
|
|
|
"consistency_score": consistency_data['consistency_score'],
|
|
|
|
|
|
"std_dev_protein": round(consistency_data['std_dev_protein'], 2),
|
|
|
|
|
|
"std_dev_carbs": round(consistency_data['std_dev_carbs'], 2),
|
|
|
|
|
|
"std_dev_fat": round(consistency_data['std_dev_fat'], 2)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:28:56 +02:00
|
|
|
|
# ── NEW: Konzept-konforme Nutrition Endpoints (E3, E4, E5) ──────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/weekly-macro-distribution")
|
|
|
|
|
|
def get_weekly_macro_distribution_chart(
|
|
|
|
|
|
weeks: int = Query(default=12, ge=4, le=52),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Weekly macro distribution (E3) - Konzept-konform.
|
|
|
|
|
|
|
|
|
|
|
|
100%-gestapelter Wochenbalken statt Pie Chart.
|
2026-04-19 17:20:24 +02:00
|
|
|
|
Datenberechnung: data_layer.nutrition_metrics.get_weekly_macro_distribution_chart_data
|
2026-03-29 07:28:56 +02:00
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-19 17:20:24 +02:00
|
|
|
|
return get_weekly_macro_distribution_chart_data(profile_id, weeks)
|
2026-03-29 07:28:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/nutrition-adherence-score")
|
|
|
|
|
|
def get_nutrition_adherence_score(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Nutrition Adherence Score (E4) - Konzept-konform.
|
|
|
|
|
|
|
|
|
|
|
|
Score 0-100 based on goal-specific criteria:
|
|
|
|
|
|
- Calorie target adherence
|
|
|
|
|
|
- Protein target adherence
|
|
|
|
|
|
- Intake consistency
|
|
|
|
|
|
- Food quality indicators (fiber, sugar)
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
{
|
|
|
|
|
|
"score": 0-100,
|
|
|
|
|
|
"components": {...},
|
|
|
|
|
|
"recommendation": "..."
|
|
|
|
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-20 14:51:27 +02:00
|
|
|
|
return build_nutrition_adherence_score_payload(profile_id, days)
|
2026-03-29 07:28:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/energy-availability-warning")
|
|
|
|
|
|
def get_energy_availability_warning(
|
|
|
|
|
|
days: int = Query(default=14, ge=7, le=28),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Energy Availability Warning (E5) - Konzept-konform.
|
|
|
|
|
|
|
2026-04-19 17:43:29 +02:00
|
|
|
|
Datenberechnung: data_layer.nutrition_metrics.get_energy_availability_warning_payload
|
2026-03-29 07:28:56 +02:00
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-19 17:43:29 +02:00
|
|
|
|
return get_energy_availability_warning_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/training-volume")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_training_volume_chart(
|
|
|
|
|
|
weeks: int = Query(default=12, ge=4, le=52),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Training volume week-over-week (A1).
|
|
|
|
|
|
|
|
|
|
|
|
Shows weekly training minutes over time.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
weeks: Number of weeks to analyze (4-52, default 12)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js bar chart with weekly training minutes
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-19 21:27:12 +02:00
|
|
|
|
return build_training_volume_chart_payload(profile_id, weeks)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/training-type-distribution")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_training_type_distribution_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Training type distribution (A2).
|
|
|
|
|
|
|
|
|
|
|
|
Shows distribution of training categories as pie chart.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js pie chart with training categories
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-19 21:27:12 +02:00
|
|
|
|
return build_training_type_distribution_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/quality-sessions")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_quality_sessions_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Quality session rate (A3).
|
|
|
|
|
|
|
|
|
|
|
|
Shows percentage of quality sessions (RPE >= 7 or duration >= 60min).
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js bar chart with quality metrics
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-20 08:04:50 +02:00
|
|
|
|
return build_quality_sessions_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/load-monitoring")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_load_monitoring_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=14, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Load monitoring (A4).
|
|
|
|
|
|
|
|
|
|
|
|
Shows acute load (7d) vs chronic load (28d) and ACWR.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (14-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js line chart with load metrics
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-20 08:04:50 +02:00
|
|
|
|
return build_load_monitoring_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/monotony-strain")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_monotony_strain_chart(
|
|
|
|
|
|
days: int = Query(default=7, ge=7, le=28),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Monotony & Strain (A5).
|
|
|
|
|
|
|
|
|
|
|
|
Shows training monotony and strain scores.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-28 days, default 7)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js bar chart with monotony and strain
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
|
|
|
|
|
|
|
|
|
|
|
monotony = calculate_monotony_score(profile_id, days)
|
|
|
|
|
|
strain = calculate_strain_score(profile_id, days)
|
|
|
|
|
|
|
|
|
|
|
|
if monotony == 0 and strain == 0:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "bar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Nicht genug Daten für Monotonie-Analyse"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "bar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": ["Monotonie", "Strain"],
|
|
|
|
|
|
"datasets": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "Score",
|
|
|
|
|
|
"data": [round(monotony, 2), round(strain, 1)],
|
|
|
|
|
|
"backgroundColor": ["#F59E0B", "#EF4444"],
|
|
|
|
|
|
"borderColor": "#085041",
|
|
|
|
|
|
"borderWidth": 1
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "medium", # Fixed for monotony calculations
|
|
|
|
|
|
"monotony_score": round(monotony, 2),
|
|
|
|
|
|
"strain_score": round(strain, 1),
|
|
|
|
|
|
"monotony_status": "high" if monotony > 2.0 else "normal",
|
|
|
|
|
|
"strain_status": "high" if strain > 10000 else "normal"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/ability-balance")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_ability_balance_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Ability balance radar chart (A6).
|
|
|
|
|
|
|
|
|
|
|
|
Shows training distribution across 5 abilities.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js radar chart with ability balance
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
|
|
|
|
|
|
|
|
|
|
|
balance_data = calculate_ability_balance(profile_id, days)
|
|
|
|
|
|
|
|
|
|
|
|
if balance_data['total_minutes'] == 0:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "radar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Keine Aktivitätsdaten"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
labels = ["Kraft", "Ausdauer", "Beweglichkeit", "Gleichgewicht", "Geist"]
|
|
|
|
|
|
values = [
|
|
|
|
|
|
balance_data['strength_pct'],
|
|
|
|
|
|
balance_data['endurance_pct'],
|
|
|
|
|
|
balance_data['flexibility_pct'],
|
|
|
|
|
|
balance_data['balance_pct'],
|
|
|
|
|
|
balance_data['mind_pct']
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "radar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": labels,
|
|
|
|
|
|
"datasets": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "Fähigkeiten-Balance (%)",
|
|
|
|
|
|
"data": values,
|
|
|
|
|
|
"borderColor": "#1D9E75",
|
|
|
|
|
|
"backgroundColor": "rgba(29, 158, 117, 0.2)",
|
|
|
|
|
|
"borderWidth": 2,
|
|
|
|
|
|
"pointBackgroundColor": "#1D9E75",
|
|
|
|
|
|
"pointBorderColor": "#fff",
|
|
|
|
|
|
"pointHoverBackgroundColor": "#fff",
|
|
|
|
|
|
"pointHoverBorderColor": "#1D9E75"
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": balance_data['confidence'],
|
|
|
|
|
|
"total_minutes": balance_data['total_minutes'],
|
|
|
|
|
|
"strength_pct": round(balance_data['strength_pct'], 1),
|
|
|
|
|
|
"endurance_pct": round(balance_data['endurance_pct'], 1),
|
|
|
|
|
|
"flexibility_pct": round(balance_data['flexibility_pct'], 1),
|
|
|
|
|
|
"balance_pct": round(balance_data['balance_pct'], 1),
|
|
|
|
|
|
"mind_pct": round(balance_data['mind_pct'], 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/volume-by-ability")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_volume_by_ability_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Training volume by ability (A8).
|
|
|
|
|
|
|
|
|
|
|
|
Shows absolute minutes per ability category.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
days: Analysis window (7-90 days, default 28)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js bar chart with volume per ability
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
|
|
|
|
|
|
|
|
|
|
|
from db import get_db, get_cursor
|
|
|
|
|
|
with get_db() as conn:
|
|
|
|
|
|
cur = get_cursor(conn)
|
|
|
|
|
|
cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')
|
|
|
|
|
|
|
|
|
|
|
|
cur.execute(
|
|
|
|
|
|
"""SELECT
|
|
|
|
|
|
COALESCE(ability, 'unknown') as ability,
|
|
|
|
|
|
SUM(duration_min) as total_minutes
|
|
|
|
|
|
FROM activity_log
|
|
|
|
|
|
WHERE profile_id=%s AND date >= %s
|
|
|
|
|
|
GROUP BY ability
|
|
|
|
|
|
ORDER BY total_minutes DESC""",
|
|
|
|
|
|
(profile_id, cutoff)
|
|
|
|
|
|
)
|
|
|
|
|
|
rows = cur.fetchall()
|
|
|
|
|
|
|
|
|
|
|
|
if not rows:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "bar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": [],
|
|
|
|
|
|
"datasets": []
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": "insufficient",
|
|
|
|
|
|
"data_points": 0,
|
|
|
|
|
|
"message": "Keine Ability-Daten"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Map ability names to German
|
|
|
|
|
|
ability_map = {
|
|
|
|
|
|
"strength": "Kraft",
|
|
|
|
|
|
"endurance": "Ausdauer",
|
|
|
|
|
|
"flexibility": "Beweglichkeit",
|
|
|
|
|
|
"balance": "Gleichgewicht",
|
|
|
|
|
|
"mind": "Geist",
|
|
|
|
|
|
"unknown": "Nicht zugeordnet"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
labels = [ability_map.get(row['ability'], row['ability']) for row in rows]
|
|
|
|
|
|
values = [safe_float(row['total_minutes']) for row in rows]
|
|
|
|
|
|
|
|
|
|
|
|
total_minutes = sum(values)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"chart_type": "bar",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"labels": labels,
|
|
|
|
|
|
"datasets": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"label": "Trainingsminuten",
|
|
|
|
|
|
"data": values,
|
|
|
|
|
|
"backgroundColor": "#1D9E75",
|
|
|
|
|
|
"borderColor": "#085041",
|
|
|
|
|
|
"borderWidth": 1
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"confidence": calculate_confidence(len(rows), days, "general"),
|
|
|
|
|
|
"data_points": len(rows),
|
|
|
|
|
|
"total_minutes": total_minutes
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Recovery Charts ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/recovery-score")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_recovery_score_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
2026-04-20 08:11:23 +02:00
|
|
|
|
"""Recovery score timeline (R1). Delegiert an recovery_chart_payloads."""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
return build_recovery_score_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/hrv-rhr-baseline")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_hrv_rhr_baseline_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
2026-04-20 08:11:23 +02:00
|
|
|
|
"""HRV/RHR vs baseline (R2)."""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
return build_hrv_rhr_baseline_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/sleep-duration-quality")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_sleep_duration_quality_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
2026-04-20 08:11:23 +02:00
|
|
|
|
"""Sleep duration + quality (R3)."""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
return build_sleep_duration_quality_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/sleep-debt")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_sleep_debt_chart(
|
|
|
|
|
|
days: int = Query(default=28, ge=7, le=90),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
2026-04-20 08:11:23 +02:00
|
|
|
|
"""Sleep debt (R4)."""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
|
|
|
|
|
return build_sleep_debt_chart_payload(profile_id, days)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/vital-signs-matrix")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_vital_signs_matrix_chart(
|
2026-04-20 08:36:45 +02:00
|
|
|
|
days: int = Query(default=7, ge=7, le=365),
|
2026-04-20 10:29:43 +02:00
|
|
|
|
omit_snapshot_keys: Optional[str] = Query(
|
|
|
|
|
|
default=None,
|
|
|
|
|
|
description="Optional: Komma-getrennte Keys ausblenden (z. B. resting_hr,hrv) wenn Einordnung woanders steht.",
|
|
|
|
|
|
),
|
|
|
|
|
|
session: dict = Depends(require_auth),
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
) -> Dict:
|
2026-04-20 08:11:23 +02:00
|
|
|
|
"""Vital signs matrix (R5)."""
|
|
|
|
|
|
profile_id = session["profile_id"]
|
2026-04-20 10:29:43 +02:00
|
|
|
|
omit_set: Optional[Set[str]] = None
|
|
|
|
|
|
if omit_snapshot_keys and omit_snapshot_keys.strip():
|
|
|
|
|
|
omit_set = {x.strip() for x in omit_snapshot_keys.split(",") if x.strip()}
|
|
|
|
|
|
return build_vital_signs_matrix_chart_payload(profile_id, days, omit_snapshot_keys=omit_set)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Correlation Charts ──────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/weight-energy-correlation")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_weight_energy_correlation_chart(
|
|
|
|
|
|
max_lag: int = Query(default=14, ge=7, le=28),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Weight vs energy balance correlation (C1).
|
|
|
|
|
|
|
|
|
|
|
|
Shows lag correlation between energy intake and weight change.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
max_lag: Maximum lag days to analyze (7-28, default 14)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js scatter chart with correlation data
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-22 11:55:11 +02:00
|
|
|
|
return build_weight_energy_correlation_chart_payload(profile_id, max_lag)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/lbm-protein-correlation")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_lbm_protein_correlation_chart(
|
|
|
|
|
|
max_lag: int = Query(default=14, ge=7, le=28),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Lean mass vs protein intake correlation (C2).
|
|
|
|
|
|
|
|
|
|
|
|
Shows lag correlation between protein intake and lean mass change.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
max_lag: Maximum lag days to analyze (7-28, default 14)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js scatter chart with correlation data
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-22 11:55:11 +02:00
|
|
|
|
return build_lbm_protein_correlation_chart_payload(profile_id, max_lag)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/load-vitals-correlation")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_load_vitals_correlation_chart(
|
|
|
|
|
|
max_lag: int = Query(default=14, ge=7, le=28),
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Training load vs vitals correlation (C3).
|
|
|
|
|
|
|
|
|
|
|
|
Shows lag correlation between training load and HRV/RHR.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
max_lag: Maximum lag days to analyze (7-28, default 14)
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js scatter chart with correlation data
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-22 11:55:11 +02:00
|
|
|
|
return build_load_vitals_correlation_chart_payload(profile_id, max_lag)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/recovery-performance")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def get_recovery_performance_chart(
|
|
|
|
|
|
session: dict = Depends(require_auth)
|
|
|
|
|
|
) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Recovery vs performance correlation (C4).
|
|
|
|
|
|
|
|
|
|
|
|
Shows relationship between recovery metrics and training quality.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Auth session (injected)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Chart.js bar chart with top drivers
|
|
|
|
|
|
"""
|
|
|
|
|
|
profile_id = session['profile_id']
|
2026-04-22 11:55:11 +02:00
|
|
|
|
return build_recovery_performance_chart_payload(profile_id)
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Health Endpoint ──────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-29 07:08:05 +02:00
|
|
|
|
@router.get("/health")
|
feat: Phase 0c - Complete chart endpoints (E1-E5, A1-A8, R1-R5, C1-C4)
- Nutrition: Energy balance, macro distribution, protein adequacy, consistency (4 endpoints)
- Activity: Volume, type distribution, quality, load, monotony, ability balance (7 endpoints)
- Recovery: Recovery score, HRV/RHR, sleep, sleep debt, vitals matrix (5 endpoints)
- Correlations: Weight-energy, LBM-protein, load-vitals, recovery-performance (4 endpoints)
Total: 20 new chart endpoints (3 → 23 total)
All endpoints return Chart.js-compatible JSON
All use data_layer functions (Single Source of Truth)
charts.py: 329 → 2246 lines (+1917)
2026-03-28 22:08:31 +01:00
|
|
|
|
def health_check() -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Health check endpoint for charts API.
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
{
|
|
|
|
|
|
"status": "ok",
|
|
|
|
|
|
"version": "1.0",
|
|
|
|
|
|
"available_charts": [...]
|
|
|
|
|
|
}
|
|
|
|
|
|
"""
|
|
|
|
|
|
return {
|
|
|
|
|
|
"status": "ok",
|
|
|
|
|
|
"version": "1.0",
|
|
|
|
|
|
"phase": "0c",
|
|
|
|
|
|
"available_charts": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "body",
|
|
|
|
|
|
"endpoint": "/charts/weight-trend",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "Weight trend over time"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "body",
|
|
|
|
|
|
"endpoint": "/charts/body-composition",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "Body fat % and lean mass"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "body",
|
|
|
|
|
|
"endpoint": "/charts/circumferences",
|
|
|
|
|
|
"type": "bar",
|
|
|
|
|
|
"description": "Latest circumference measurements"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "nutrition",
|
|
|
|
|
|
"endpoint": "/charts/energy-balance",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "Daily calorie intake vs. TDEE"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "nutrition",
|
|
|
|
|
|
"endpoint": "/charts/macro-distribution",
|
|
|
|
|
|
"type": "pie",
|
|
|
|
|
|
"description": "Protein/Carbs/Fat distribution"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "nutrition",
|
|
|
|
|
|
"endpoint": "/charts/protein-adequacy",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "Protein intake vs. target range"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "nutrition",
|
|
|
|
|
|
"endpoint": "/charts/nutrition-consistency",
|
|
|
|
|
|
"type": "bar",
|
|
|
|
|
|
"description": "Macro consistency score"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "activity",
|
|
|
|
|
|
"endpoint": "/charts/training-volume",
|
|
|
|
|
|
"type": "bar",
|
|
|
|
|
|
"description": "Weekly training minutes"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "activity",
|
|
|
|
|
|
"endpoint": "/charts/training-type-distribution",
|
|
|
|
|
|
"type": "pie",
|
|
|
|
|
|
"description": "Training category distribution"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "activity",
|
|
|
|
|
|
"endpoint": "/charts/quality-sessions",
|
|
|
|
|
|
"type": "bar",
|
|
|
|
|
|
"description": "Quality session rate"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "activity",
|
|
|
|
|
|
"endpoint": "/charts/load-monitoring",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "Acute vs chronic load + ACWR"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "activity",
|
|
|
|
|
|
"endpoint": "/charts/monotony-strain",
|
|
|
|
|
|
"type": "bar",
|
|
|
|
|
|
"description": "Training monotony and strain"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "activity",
|
|
|
|
|
|
"endpoint": "/charts/ability-balance",
|
|
|
|
|
|
"type": "radar",
|
|
|
|
|
|
"description": "Training balance across 5 abilities"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "activity",
|
|
|
|
|
|
"endpoint": "/charts/volume-by-ability",
|
|
|
|
|
|
"type": "bar",
|
|
|
|
|
|
"description": "Training volume per ability"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "recovery",
|
|
|
|
|
|
"endpoint": "/charts/recovery-score",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "Recovery score timeline"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "recovery",
|
|
|
|
|
|
"endpoint": "/charts/hrv-rhr-baseline",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "HRV and RHR vs baseline"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "recovery",
|
|
|
|
|
|
"endpoint": "/charts/sleep-duration-quality",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "Sleep duration and quality"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "recovery",
|
|
|
|
|
|
"endpoint": "/charts/sleep-debt",
|
|
|
|
|
|
"type": "line",
|
|
|
|
|
|
"description": "Cumulative sleep debt"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "recovery",
|
|
|
|
|
|
"endpoint": "/charts/vital-signs-matrix",
|
|
|
|
|
|
"type": "bar",
|
|
|
|
|
|
"description": "Latest vital signs overview"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "correlations",
|
|
|
|
|
|
"endpoint": "/charts/weight-energy-correlation",
|
|
|
|
|
|
"type": "scatter",
|
|
|
|
|
|
"description": "Weight vs energy balance (lag correlation)"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "correlations",
|
|
|
|
|
|
"endpoint": "/charts/lbm-protein-correlation",
|
|
|
|
|
|
"type": "scatter",
|
|
|
|
|
|
"description": "Lean mass vs protein intake (lag correlation)"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "correlations",
|
|
|
|
|
|
"endpoint": "/charts/load-vitals-correlation",
|
|
|
|
|
|
"type": "scatter",
|
|
|
|
|
|
"description": "Training load vs HRV/RHR (lag correlation)"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"category": "correlations",
|
|
|
|
|
|
"endpoint": "/charts/recovery-performance",
|
|
|
|
|
|
"type": "bar",
|
|
|
|
|
|
"description": "Top drivers (hindering/helpful factors)"
|
2026-03-28 18:26:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|