Spaces:
Sleeping
Sleeping
| import os, joblib | |
| import streamlit as st | |
| import pandas as pd, joblib, json | |
| from pathlib import Path | |
| st.set_page_config(page_title="VOβ Max & Training Readiness", page_icon="π", layout="centered") | |
| st.title("π VOβ Max & Training Readiness (Synthetic, Demo)") | |
| st.caption("CPU-only β’ Synthetic data β’ Not medical advice.") | |
| #DATA_PATH = "assets/vo2_real_augmented.csv" | |
| #MODEL_PATH = "model/vo2_predictor.joblib" | |
| HERE = Path(__file__).resolve().parent | |
| MODEL_PATH = HERE / "model" / "vo2_predictor.joblib" # src/model/... | |
| # If your model is at repo_root/model, use: HERE.parent / "model" / "vo2_predictor.joblib" | |
| DATA_PATH = HERE / ".." / "assets" / "vo2_real_augmented.csv" | |
| assert MODEL_PATH.exists(), f"Model not found at: {MODEL_PATH}" | |
| pipe = joblib.load(MODEL_PATH) | |
| def load_model(): | |
| return joblib.load(MODEL_PATH) | |
| pipe = load_model() | |
| def load_sample(): | |
| try: | |
| df = pd.read_csv(DATA_PATH) | |
| return df | |
| except Exception: | |
| return pd.DataFrame() | |
| df = load_sample() | |
| #with st.expander("Sample data (first 10 rows)"): | |
| # if not df.empty: | |
| # st.dataframe(df.head(10), use_container_width=True) | |
| # else: | |
| # st.info("Sample CSV not found.") | |
| st.subheader("Enter runner metrics") | |
| cols = st.columns(2) | |
| sex = cols[0].selectbox("Sex (0=female, 1=male)", [0,1], index=1) | |
| age = cols[1].slider("Age", 18, 70, 35) | |
| height_cm = cols[0].slider("Height (cm)", 150, 200, 172) | |
| weight_kg = cols[1].slider("Weight (kg)", 45, 120, 74) | |
| total_lean_mass = cols[0].slider("Total Lean Mass", 30, 90, 55) | |
| wbtpf = cols[1].slider("Whole Body Tissue Percent Fat", 12, 49, 28) | |
| resting_hr = cols[0].slider("Resting HR (bpm)", 40, 100, 60) | |
| max_hr = cols[1].slider("Max HR (bpm)", 150, 205, 185) | |
| avg_hr_during_run = cols[0].slider("Avg HR During Run (bpm)", 95, 190, 150) | |
| hr_recovery_1min = cols[1].slider("HR Recovery 1 min (bpm drop)", 10, 55, 30) | |
| distance_km = cols[0].slider("Distance of last run (km)", 1, 30, 5) | |
| duration_min = cols[1].slider("Duration of last run (min)", 10, 180, 30) | |
| pace_min_per_km = max(3.5, min(12.0, duration_min / max(distance_km, 0.5))) | |
| avg_speed_kmh = 60.0 / pace_min_per_km | |
| elevation_gain_m = cols[0].slider("Elevation Gain (m)", 0, 600, 50) | |
| training_hours_week = cols[1].slider("Training Hours / Week", 0, 12, 4) | |
| avg_intensity = cols[0].slider("Avg Intensity (1β10)", 1, 10, 6) | |
| rest_days = cols[1].slider("Rest Days (last 7d)", 0, 4, 1) | |
| sleep_hours_last_night = cols[0].slider("Sleep Hours Last Night", 3, 10, 7) | |
| avg_sleep_hours_week = cols[1].slider("Avg Sleep Hours / Week", 4, 9, 7) | |
| sleep_quality_score = cols[0].slider("Sleep Quality (0β100)", 25, 95, 72) | |
| resting_hr_delta = resting_hr - 60 | |
| temperature_C = cols[1].slider("Temperature (Β°C)", 0, 35, 18) | |
| humidity_pct = cols[0].slider("Humidity (%)", 15, 95, 55) | |
| altitude_m = cols[1].slider("Altitude (m)", 0, 2200, 200) | |
| hr_ratio = avg_hr_during_run / max_hr if max_hr>0 else 0 | |
| training_load_index = distance_km * (avg_hr_during_run/100.0) / (duration_min/60.0 + 0.1) | |
| speed_per_kg = avg_speed_kmh / (weight_kg/70.0) | |
| features = { | |
| "sex":sex,"age":age,"height_cm":height_cm,"weight_kg":weight_kg, | |
| "bmi": weight_kg/((height_cm/100.0)**2), | |
| "total_lean_mass":total_lean_mass,"wbtpf":wbtpf, | |
| "resting_hr":resting_hr,"max_hr":max_hr,"avg_hr_during_run":avg_hr_during_run, | |
| "hr_recovery_1min":hr_recovery_1min, | |
| "distance_km":distance_km,"duration_min":duration_min, | |
| "pace_min_per_km":pace_min_per_km,"avg_speed_kmh":avg_speed_kmh, | |
| "elevation_gain_m":elevation_gain_m, | |
| "training_hours_week":training_hours_week,"avg_intensity":avg_intensity,"rest_days":rest_days, | |
| "sleep_hours_last_night":sleep_hours_last_night,"avg_sleep_hours_week":avg_sleep_hours_week, | |
| "sleep_quality_score":sleep_quality_score,"resting_hr_delta":resting_hr_delta, | |
| "temperature_C":temperature_C,"humidity_pct":humidity_pct,"altitude_m":altitude_m, | |
| "hr_ratio":hr_ratio,"training_load_index":training_load_index,"speed_per_kg":speed_per_kg | |
| } | |
| def coaching_tip(vo2, sleep_quality, training_hours, hrr): | |
| tips = [] | |
| if vo2 < 38: tips.append("Prioritize easy aerobic runs (Zone 2) 3β4x/week.") | |
| elif vo2 < 48: tips.append("Add 1 weekly interval (e.g., 4Γ4min hard, full recovery).") | |
| else: tips.append("Maintain with polarized training; 80% easy, 20% hard.") | |
| if sleep_quality < 60: tips.append("Boost sleep hygiene to 7β8h; reduce late caffeine/screens.") | |
| if training_hours > 8: tips.append("High load detected β deload 10β20% this week.") | |
| if hrr < 20: tips.append("Low HRR β keep intensities sub-threshold for 2β3 days.") | |
| return " ".join(tips) or "Keep up the good work!" | |
| if st.button("Predict VOβ max"): | |
| x = pd.DataFrame([features]) | |
| pipe = load_model() | |
| vo2 = float(pipe.predict(x)[0]) | |
| tip = coaching_tip(vo2, sleep_quality_score, training_hours_week, hr_recovery_1min) | |
| st.metric("Estimated VOβ max (ml/kg/min)", f"{vo2:.1f}") | |
| st.success(tip) | |
| st.divider() | |
| st.caption("Limitations: Synthetic data; not a medical device; demo use only.") | |