File size: 5,042 Bytes
34e78aa
3cbe2b7
 
de5fb2b
3cbe2b7
 
 
 
 
185bfc5
34e78aa
 
 
 
 
 
4d3e684
185bfc5
34e78aa
 
 
 
3cbe2b7
 
 
 
72ee068
 
3cbe2b7
 
 
 
 
 
 
 
 
 
2a9ddd1
 
 
 
 
185bfc5
3cbe2b7
 
 
 
 
 
 
 
 
3dfbd3d
 
 
3cbe2b7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f5e2953
3cbe2b7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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)

@st.cache_resource
def load_model():
    return joblib.load(MODEL_PATH)

pipe = load_model()

@st.cache_data
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.")