VcRlAgent commited on
Commit
3cbe2b7
Β·
1 Parent(s): 7141391

Replace streamlit app file

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +104 -0
src/streamlit_app.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd, joblib, json
3
+
4
+ st.set_page_config(page_title="VOβ‚‚ Max & Training Readiness", page_icon="πŸƒ", layout="centered")
5
+ st.title("πŸƒ VOβ‚‚ Max & Training Readiness (Synthetic, Demo)")
6
+ st.caption("CPU-only β€’ Synthetic data β€’ Not medical advice.")
7
+
8
+ MODEL_PATH = "model/vo2_predictor.joblib"
9
+ DATA_PATH = "assets/vo2max_synthetic.csv"
10
+
11
+ @st.cache_resource
12
+ def load_model():
13
+ return joblib.load(MODEL_PATH)
14
+
15
+ @st.cache_data
16
+ def load_sample():
17
+ try:
18
+ df = pd.read_csv(DATA_PATH)
19
+ return df
20
+ except Exception:
21
+ return pd.DataFrame()
22
+
23
+ pipe = load_model()
24
+ df = load_sample()
25
+
26
+ with st.expander("Sample data (first 50 rows)"):
27
+ if not df.empty:
28
+ st.dataframe(df.head(50), use_container_width=True)
29
+ else:
30
+ st.info("Sample CSV not found.")
31
+
32
+ st.subheader("Enter runner metrics")
33
+ cols = st.columns(2)
34
+
35
+ sex = cols[0].selectbox("Sex (0=female, 1=male)", [0,1], index=1)
36
+ age = cols[1].slider("Age", 18, 70, 35)
37
+ height_cm = cols[0].slider("Height (cm)", 150, 200, 172)
38
+ weight_kg = cols[1].slider("Weight (kg)", 45, 120, 74)
39
+
40
+ resting_hr = cols[0].slider("Resting HR (bpm)", 40, 100, 60)
41
+ max_hr = cols[1].slider("Max HR (bpm)", 150, 205, 185)
42
+ avg_hr_during_run = cols[0].slider("Avg HR During Run (bpm)", 95, 190, 150)
43
+ hr_recovery_1min = cols[1].slider("HR Recovery 1 min (bpm drop)", 10, 55, 30)
44
+
45
+ distance_km = cols[0].slider("Distance of last run (km)", 1, 30, 5)
46
+ duration_min = cols[1].slider("Duration of last run (min)", 10, 180, 30)
47
+ pace_min_per_km = max(3.5, min(12.0, duration_min / max(distance_km, 0.5)))
48
+ avg_speed_kmh = 60.0 / pace_min_per_km
49
+ elevation_gain_m = cols[0].slider("Elevation Gain (m)", 0, 600, 50)
50
+
51
+ training_hours_week = cols[1].slider("Training Hours / Week", 0, 12, 4)
52
+ avg_intensity = cols[0].slider("Avg Intensity (1–10)", 1, 10, 6)
53
+ rest_days = cols[1].slider("Rest Days (last 7d)", 0, 4, 1)
54
+
55
+ sleep_hours_last_night = cols[0].slider("Sleep Hours Last Night", 3, 10, 7)
56
+ avg_sleep_hours_week = cols[1].slider("Avg Sleep Hours / Week", 4, 9, 7)
57
+ sleep_quality_score = cols[0].slider("Sleep Quality (0–100)", 25, 95, 72)
58
+ resting_hr_delta = resting_hr - 60
59
+
60
+ temperature_C = cols[1].slider("Temperature (Β°C)", 0, 35, 18)
61
+ humidity_pct = cols[0].slider("Humidity (%)", 15, 95, 55)
62
+ altitude_m = cols[1].slider("Altitude (m)", 0, 2200, 200)
63
+
64
+ hr_ratio = avg_hr_during_run / max_hr if max_hr>0 else 0
65
+ training_load_index = distance_km * (avg_hr_during_run/100.0) / (duration_min/60.0 + 0.1)
66
+ speed_per_kg = avg_speed_kmh / (weight_kg/70.0)
67
+
68
+ features = {
69
+ "sex":sex,"age":age,"height_cm":height_cm,"weight_kg":weight_kg,
70
+ "bmi": weight_kg/((height_cm/100.0)**2),
71
+ "resting_hr":resting_hr,"max_hr":max_hr,"avg_hr_during_run":avg_hr_during_run,
72
+ "hr_recovery_1min":hr_recovery_1min,
73
+ "distance_km":distance_km,"duration_min":duration_min,
74
+ "pace_min_per_km":pace_min_per_km,"avg_speed_kmh":avg_speed_kmh,
75
+ "elevation_gain_m":elevation_gain_m,
76
+ "training_hours_week":training_hours_week,"avg_intensity":avg_intensity,"rest_days":rest_days,
77
+ "sleep_hours_last_night":sleep_hours_last_night,"avg_sleep_hours_week":avg_sleep_hours_week,
78
+ "sleep_quality_score":sleep_quality_score,"resting_hr_delta":resting_hr_delta,
79
+ "temperature_C":temperature_C,"humidity_pct":humidity_pct,"altitude_m":altitude_m,
80
+ "hr_ratio":hr_ratio,"training_load_index":training_load_index,"speed_per_kg":speed_per_kg
81
+ }
82
+
83
+ def coaching_tip(vo2, sleep_quality, training_hours, hrr):
84
+ tips = []
85
+ if vo2 < 38: tips.append("Prioritize easy aerobic runs (Zone 2) 3–4x/week.")
86
+ elif vo2 < 48: tips.append("Add 1 weekly interval (e.g., 4Γ—4min hard, full recovery).")
87
+ else: tips.append("Maintain with polarized training; 80% easy, 20% hard.")
88
+
89
+ if sleep_quality < 60: tips.append("Boost sleep hygiene to 7–8h; reduce late caffeine/screens.")
90
+ if training_hours > 8: tips.append("High load detected β€” deload 10–20% this week.")
91
+ if hrr < 20: tips.append("Low HRR β€” keep intensities sub-threshold for 2–3 days.")
92
+ return " ".join(tips) or "Keep up the good work!"
93
+
94
+ if st.button("Predict VOβ‚‚ max"):
95
+ x = pd.DataFrame([features])
96
+ pipe = load_model()
97
+ vo2 = float(pipe.predict(x)[0])
98
+ tip = coaching_tip(vo2, sleep_quality_score, training_hours_week, hr_recovery_1min)
99
+
100
+ st.metric("Estimated VOβ‚‚ max (ml/kg/min)", f"{vo2:.1f}")
101
+ st.success(tip)
102
+
103
+ st.divider()
104
+ st.caption("Limitations: Synthetic data; not a medical device; demo use only.")