AI-Personalized Fitness Programs: Adaptive Workouts Based on Biometrics
Standard fitness apps offer static plans that don't respond to the user's actual physiological state. The result: progress plateaus, overtraining, and injuries. We build AI systems that adapt load based on biometrics: HRV, heart rate, sleep quality, and training history. Our solutions increase adherence rates from 35–45% to 65–75% — a 1.7x improvement. Injury risk drops by 30–40% (Medicine & Science in Sports & Exercise). Personalized plans boost user LTV by 25–30% and reduce churn by 15–20%. We are a team of AI/ML engineers with 7 years of experience in sports physiology, with over 15 completed projects in workout personalization. Assess the implementation opportunity — contact us for a preliminary audit.
Why AI personalization outperforms static plans?
The key metric is not meeting a norm, but matching load to current recovery. Even the perfect plan becomes useless if the user has low HRV or poor sleep today. We deploy algorithms that compute a readiness score every morning (0–100). This score adjusts: workout type, intensity (via a load modifier), and recovery recommendations. This approach ensures smoother progress and reduces injury probability by 1.5x compared to static programs. Static plans typically achieve 40% adherence, while AI adaptation reaches 70% — 1.75x higher.
How does AI determine the optimal daily load?
The system considers four key factors: HRV, resting heart rate, sleep quality, and previous day's load. RecoveryMonitor computes a readiness score that directly influences training intensity. If the score is below 55 — only light recovery or rest is recommended. If above 75 — a heavy workout at 100% intensity is possible. Periodization is also applied: 3 weeks of increasing load, then a recovery week. Example: user wakes up with HRV 45ms (baseline 55ms), resting heart rate 62 (baseline 58), sleep 72 points, and had a heavy workout yesterday. RecoveryMonitor calculates: HRV dropped 18% → minus 25 points; RHR increased by 4 → minus 15 points; sleep 72 (above 60) → no penalty; high load yesterday → minus 10 points. Final readiness score = 50 — only light activity at 60% intensity.
Required data for personalization
Minimum set: 1–2 weeks of workout history and biometric indicators (HRV, resting heart rate, sleep) from a wearable tracker. If data is insufficient, we generate a profile based on anthropometry and goals, then calibrate it as real measurements come in. All major trackers are supported: Whoop, Oura, Apple Watch, Garmin, Polar.
Technical implementation: Python + Anthropic
Below is the real code for a plan generator we use in production systems. Core stack: Python 3.12, Anthropic Claude 3.5 (via Anthropic SDK), Pandas for analytics, NumPy for math.
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 plan"""
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 recovery week
# Determine current week of periodization 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:
"""Recovery monitoring from biometrics"""
def compute_readiness_score(self, biometrics: dict) -> dict:
"""
Readiness score (0-100).
Data: HRV, RHR, sleep_score, previous_day_load.
"""
score = 100.0
factors = []
# HRV (Heart Rate Variability) — main 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 low ({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's 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 = 'Great day for an intense workout'
intensity_modifier = 1.0
elif score > 55:
recommendation = 'Moderate workout — reduce intensity by 15%'
intensity_modifier = 0.85
elif score > 35:
recommendation = 'Only light recovery or rest'
intensity_modifier = 0.60
else:
recommendation = 'Active rest or day off'
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:
"""Progress analysis 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 for main 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)}
Comparison: static plan vs AI adaptation
| Characteristic | Static plan | AI adaptation (our system) |
|---|---|---|
| Uses biometrics | No | HRV, heart rate, sleep, load |
| Load adaptation | Once a month | Daily |
| Adherence rate | 35-45% | 65-75% |
| Injury risk | Baseline | 30-40% lower |
| ROI | — | 6-12 months |
What's included in the work?
| Component | Description |
|---|---|
| Solution architecture | Model selection (LLaMA 3, Claude), pipeline design for biometric collection and processing |
| Plan generation | RAG agent with vector search (ChromaDB) for exercises, contraindication handling |
| Analytics dashboard | Metrics: adherence rate, exercise progress, readiness score |
| Integration | REST API and SDK for iOS/Android, HealthKit, Google Fit, Polar, Garmin |
| Support | 3 months warranty support, team training, documentation |
Implementation process
- Analytics — study biometrics, workout types, current stack (1–2 days).
- Design — determine architecture: which LLMs, how to store exercise embeddings (pgvector).
- Implementation — write RecoveryMonitor, FitnessPlanGenerator, ProgressTracker modules (2–6 weeks).
- Testing — A/B test on 50–100 users, evaluate adherence improvement (1–2 weeks).
- Deploy — deploy microservices in Kubernetes with GPU nodes for inference (Triton Inference Server).
Economic efficiency
The cost of implementing a basic solution is recovered within 6–12 months by increasing user LTV by 25–30%. The average savings from developing a custom AI solution compared to buying a ready-made platform is 40%. Additionally, reducing churn by 15–20% directly increases revenue. Evaluate the economic effect for your product — contact us for a preliminary calculation.
Typical mistakes in AI personalization implementation
- Ignoring recovery data. A plan based only on goals (weight loss/muscle gain) without considering HRV and sleep leads to overtraining. We always include readiness score as a corrective factor.
- One model for all. General-purpose LLMs (basic GPT-4) give template advice. We use custom fine-tuned models based on LLaMA 3, trained on your data.
- No fallback mechanism. LLM failures (latency, toxic responses) should trigger a rule-based engine. Our architecture includes this.
Get a consultation on architecture and pipelines — contact us to discuss metrics and implementation plan for your fitness product.







