diff --git a/DashboardApp_WebVersion.png b/DashboardApp_WebVersion.png index 5ff5aee..43c52af 100644 Binary files a/DashboardApp_WebVersion.png and b/DashboardApp_WebVersion.png differ diff --git a/jogging_dashboard_browser_app.py b/jogging_dashboard_browser_app.py index d969e18..e7a0180 100644 --- a/jogging_dashboard_browser_app.py +++ b/jogging_dashboard_browser_app.py @@ -460,7 +460,7 @@ def safe_add_column_to_dataframe(df, column_name, values): # ── Biometrics (kalibriert gegen Strava) ───────────────────────────────────── _WEIGHT_KG = 75.0 _HEIGHT_CM = 178.0 -_AGE_YEARS = 35 +_AGE_YEARS = 36 _IS_MALE = True _HR_REST = 64.5 # Ruhepuls in bpm – kalibriert gegen Strava 550 kcal # ───────────────────────────────────────────────────────────────────────────── @@ -488,30 +488,45 @@ def calculate_calories_burned(df): # Kein fixer HR_REST — nutze den tatsächlichen Minimum-HR aus dem Lauf # als Annäherung an den Ruhepuls hr_max = 220 - _AGE_YEARS + hr_rest = _HR_REST # 64.5 bpm + # Check, ob genug Herzfrequenz-Daten vorhanden sind use_hr = ('heart_rate' in df.columns and df['heart_rate'].notna().sum() > len(df) * 0.5) if not use_hr: return 0 + # Datenvorbereitung hr = df['heart_rate'].ffill().fillna(df['heart_rate'].bfill()).values ts = df['time_diff_sec'].values - # Ruhepuls aus dem Lauf selbst schätzen: 5. Perzentile der HR-Werte - hr_rest = float(df['heart_rate'].quantile(0.05)) - hr_rest = max(45.0, min(hr_rest, 80.0)) # Plausibilitätsgrenze - + # 2. VO2max Schätzung basierend auf deinem echten Ruhepuls + # Die Formel 15 * (HR_max / HR_rest) ist ein Standard-Approximationswert vo2max = 15.0 * (hr_max / hr_rest) - dt = np.diff(ts, prepend=ts[0]) - mask = (dt > 0) & (dt <= 10) + # Zeitdifferenzen berechnen + dt = np.diff(ts, prepend=ts[0]) + mask = (dt > 0) & (dt <= 10) # Filtert Pausen oder Sprünge in der Aufzeichnung + + # 3. Berechnung der Intensität (Karvonen-Prinzip) + # Anteil der Herzfrequenzreserve (HRR) frac = np.clip((hr - hr_rest) / (hr_max - hr_rest), 0, None) - met = np.clip((frac * vo2max) / 3.5, 1.0, 18.0) + + # Umrechnung in MET (Metabolic Equivalent of Task) + # Ein MET = 3.5 ml VO2/kg/min + met = np.clip((frac * vo2max) / 3.5, 1.0, 18.0) + + # 4. Umrechnung in kcal pro Sekunde + # Formel: (MET * 3.5 * Gewicht_kg) / (200 * 60) -> 12000 kcal_per_s = met * _WEIGHT_KG * 3.5 / 12000.0 + + # Summation über den gesamten Lauf cumulative = float(np.sum(kcal_per_s[mask] * dt[mask])) - print(f"DEBUG calories: rows={len(df)}, hr_valid={df['heart_rate'].notna().sum()}, duration={df['time_diff_sec'].iloc[-1]:.0f}s, hr_mean={df['heart_rate'].mean():.1f}") + print(f"DEBUG calories: HR_REST_USED={hr_rest}, VO2MAX_EST={vo2max:.1f}, " + f"DURATION={df['time_diff_sec'].iloc[-1]/60:.1f}min, TOTAL_KCAL={cumulative:.1f}") + return int(round(cumulative)) @@ -2408,7 +2423,7 @@ app.layout = html.Div([ # START !!!!!!!!!!!!!!! # Pixel-Map Überschrift - html.Hr(style={'borderColor': '#333', 'margin': '10px 20px'}), + html.Hr(style={'borderColor': '#111111', 'margin': '10px 20px'}), #'#333' html.Div([ html.H3("Pixel-Maps", style={ 'color': '#aaaaaa', 'margin': '10px 0 0 0', 'fontSize': '16px' @@ -2416,7 +2431,7 @@ app.layout = html.Div([ html.P( "Plot 1) Heatmap: Einzellauf oder alle Läufe der Region umschalten.\n" "Plot 2), 3) & 4) Elevation-, Pace- & HR-Map: des aktuell gewählten Laufs.", - style={'color': '#666', 'margin': '2px 0 8px 0', 'fontSize': '12px'} + style={'color': '#aaaaaa', 'margin': '2px 0 8px 0', 'fontSize': '12px'} ), ], style={'padding': '0 20px'}), @@ -2488,7 +2503,7 @@ app.layout = html.Div([ 'backgroundColor': '#111111' }), - html.Hr(style={'borderColor': '#333', 'margin': '10px 20px'}), + html.Hr(style={'borderColor': '#111111', 'margin': '10px 20px'}), # ENDE !!!!!!!!!!!!!!!! dcc.Graph(id='fig-elevation'),