AI-powered fitness program personalization system
A personal trainer is expensive. An AI trainer is available 24/7 and takes into account progress, recovery, goals, and constraints. Apps like Freeletics, Whoop, and Peloton use ML to adapt training plans in real-time based on biometric data.
Adaptive training plan
import numpy as np
import pandas as pd
from dataclasses import dataclass
from typing import Optional
from anthropic import Anthropic
import json
@dataclass
class AthleteProfile:
user_id: str
age: int
sex: str
weight_kg: float
height_cm: float
fitness_level: str # beginner, intermediate, advanced
primary_goal: str # weight_loss, muscle_gain, endurance, general_fitness
available_days_per_week: int
equipment: list # ['dumbbells', 'barbell', 'pull_up_bar']
injuries: list # ['lower_back', 'knee']
vo2max: Optional[float] = None
class FitnessPlanGenerator:
"""Generate and adapt training plans"""
def __init__(self):
self.llm = Anthropic()
def calculate_training_zones(self, profile: AthleteProfile) -> dict:
"""Heart rate zones for cardio workouts"""
# Tanaka formula (more accurate than 220-age)
max_hr = 208 - 0.7 * profile.age
return {
'max_hr': int(max_hr),
'zone1_recovery': (int(max_hr * 0.50), int(max_hr * 0.60)),
'zone2_aerobic': (int(max_hr * 0.60), int(max_hr * 0.70)),
'zone3_tempo': (int(max_hr * 0.70), int(max_hr * 0.80)),
'zone4_threshold': (int(max_hr * 0.80), int(max_hr * 0.90)),
'zone5_vo2max': (int(max_hr * 0.90), int(max_hr * 1.00)),
}
def generate_weekly_plan(self, profile: AthleteProfile,
recent_performance: list[dict]) -> list[dict]:
"""Weekly training plan"""
training_zones = self.calculate_training_zones(profile)
# Periodization: 3 weeks of increasing load + 1 week recovery
# Determine current periodization week from history
week_in_cycle = self._get_week_in_cycle(recent_performance)
load_modifier = [0.85, 1.0, 1.15, 0.70][week_in_cycle % 4]
response = self.llm.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=700,
messages=[{
"role": "user",
"content": f"""Create a personalized weekly training plan.
Profile:
- Fitness level: {profile.fitness_level}
- Goal: {profile.primary_goal}
- Available days: {profile.available_days_per_week}
- Equipment: {profile.equipment}
- Injuries to avoid: {profile.injuries}
- Age: {profile.age}, Weight: {profile.weight_kg}kg
Current training intensity: {load_modifier:.0%} of base load
Training zones: Zone 2 aerobic = {training_zones['zone2_aerobic']} bpm
Recent performance (last 5 sessions):
{json.dumps(recent_performance[-5:], ensure_ascii=False)[:400]}
Create {profile.available_days_per_week} training sessions. Return JSON array:
[{{
"day": "Monday",
"session_type": "strength|cardio|hiit|recovery",
"duration_min": 45,
"exercises": [{{"name": "...", "sets": 3, "reps": "8-10", "rest_sec": 90}}],
"cardio_zone": "zone2",
"notes": "..."
}}]"""
}]
)
try:
return json.loads(response.content[0].text)
except Exception:
return []
def _get_week_in_cycle(self, performance: list[dict]) -> int:
if not performance:
return 0
return len(set(p.get('week_number', 0) for p in performance)) % 4
class RecoveryMonitor:
"""Monitor recovery from biometrics"""
def compute_readiness_score(self, biometrics: dict) -> dict:
"""
Readiness score for training (0-100).
Data: HRV, RHR, sleep_score, previous_day_load.
"""
score = 100.0
factors = []
# HRV (Heart Rate Variability) — primary indicator
hrv = biometrics.get('hrv_ms')
hrv_baseline = biometrics.get('hrv_baseline_ms', 50)
if hrv and hrv_baseline:
hrv_ratio = hrv / hrv_baseline
if hrv_ratio < 0.85:
score -= 25
factors.append(f'HRV lowered ({hrv:.0f}ms vs {hrv_baseline:.0f}ms baseline)')
elif hrv_ratio > 1.15:
score += 5 # Good recovery
# Resting Heart Rate
rhr = biometrics.get('resting_hr_bpm')
rhr_baseline = biometrics.get('rhr_baseline_bpm', 60)
if rhr and rhr_baseline:
if rhr > rhr_baseline + 5:
score -= 15
factors.append(f'RHR elevated ({rhr} vs {rhr_baseline} baseline)')
# Sleep
sleep_score = biometrics.get('sleep_score', 80) # 0-100
if sleep_score < 60:
score -= 20
factors.append(f'Poor sleep (score: {sleep_score})')
elif sleep_score < 75:
score -= 10
# Previous day load
previous_load = biometrics.get('yesterday_training_load', 0) # AU (Arbitrary Units)
high_load_threshold = biometrics.get('weekly_avg_load', 300) * 0.4
if previous_load > high_load_threshold:
score -= 10
factors.append('High load yesterday')
score = float(np.clip(score, 0, 100))
if score > 75:
recommendation = 'Excellent day for intense training'
intensity_modifier = 1.0
elif score > 55:
recommendation = 'Moderate training — reduce intensity by 15%'
intensity_modifier = 0.85
elif score > 35:
recommendation = 'Only light recovery workout or rest'
intensity_modifier = 0.60
else:
recommendation = 'Active rest or off day'
intensity_modifier = 0.0
return {
'readiness_score': round(score),
'recommendation': recommendation,
'intensity_modifier': intensity_modifier,
'limiting_factors': factors
}
class ProgressTracker:
"""Track progress and adjust plan"""
def analyze_progress(self, training_logs: pd.DataFrame,
profile: AthleteProfile,
weeks: int = 8) -> dict:
"""Analyze progress over period"""
recent = training_logs[
training_logs['date'] >= pd.Timestamp.now() - pd.Timedelta(weeks=weeks)
]
if recent.empty:
return {}
return {
'sessions_completed': len(recent),
'sessions_planned': weeks * profile.available_days_per_week,
'adherence_rate': len(recent) / (weeks * profile.available_days_per_week),
# Progress on key exercises
'strength_progress': self._compute_strength_progress(recent),
'endurance_progress': self._compute_endurance_progress(recent),
'avg_session_duration_min': recent.get('duration_minutes', pd.Series([45])).mean(),
'total_volume_kg': recent.get('total_volume_kg', pd.Series([0])).sum(),
}
def _compute_strength_progress(self, logs: pd.DataFrame) -> dict:
"""Change in max weights in key exercises"""
if 'exercise_name' not in logs.columns:
return {}
key_exercises = ['squat', 'bench_press', 'deadlift', 'overhead_press']
progress = {}
for exercise in key_exercises:
exercise_logs = logs[logs['exercise_name'] == exercise]
if len(exercise_logs) < 2:
continue
first_max = exercise_logs.nsmallest(3, 'date')['max_weight_kg'].mean()
last_max = exercise_logs.nlargest(3, 'date')['max_weight_kg'].mean()
progress[exercise] = round((last_max - first_max) / max(first_max, 1) * 100, 1)
return progress
def _compute_endurance_progress(self, logs: pd.DataFrame) -> dict:
if 'pace_min_per_km' not in logs.columns:
return {}
cardio = logs[logs['session_type'] == 'cardio']
if cardio.empty:
return {}
early = cardio.head(3)['pace_min_per_km'].mean()
recent = cardio.tail(3)['pace_min_per_km'].mean()
improvement = (early - recent) / early * 100 # Lower pace = improvement
return {'pace_improvement_pct': round(improvement, 1)}
Personalized fitness plans with biometric adaptation increase adherence rates from 35-45% to 65-75% based on Whoop and Oura data. Key metric: not the number of workouts, but matching load to recovery level — this reduces injury risk by 30-40%.







