WPL - Wellness Plan Language Specification
Version: 1.1.0
Status: Draft
Last Updated: 2024-11-24
Overview
WPL (Wellness Plan Language) is a JSON-based markup language designed to describe comprehensive wellness plans that can include workouts, nutrition, meditation, recovery, and other wellness activities. It enables:
- Trainers: Create structured, reusable plans via a visual constructor
- Backend: Store, validate, and calculate personalized plans
- Frontend/Mobile: Render consistent visual representations and track progress
Design Principles
1. Platform Agnostic: JSON format parseable by any language
2. Extensible: New activity types can be added without breaking existing plans
3. Personalization: Plans adapt based on client input (age, injuries, goals)
4. Progress Tracking: Built-in checkpoints and progress measurement
5. Composable: Plans can include sub-plans and templates
ID Conventions
WPL uses two types of identifiers:
Local IDs (Scoped)
Local IDs must be unique within their parent scope:
| Element | Scope | Example |
|---|---|---|
phase.id | Unique within plan | "phase_1" |
week.id | Unique within phase | "week_1" |
day.id | Unique within week | "day_1" |
block.id | Unique within day | "warmup_block" |
activity.id | Unique within day | "exercise_1" |
Library References (Global)
Activities reference global library items via _ref fields:
{
"id": "exercise_1",
"type": "exercise",
"exercise_ref": "push_up",
"name": "Push-ups"
}
The exercise_ref maps to the exercise library's global ID. This separation allows:
- Multiple activities to reference the same exercise
- Clear distinction between plan-local and library-global identifiers
Standard Units
WPL uses standardized units across all measurements:
Time Units
| Unit | Usage |
|---|---|
seconds | Rest periods, tempo, short durations |
minutes | Workout durations, meditation |
hours | Long activities |
days | Phase/plan durations |
weeks | Phase/plan durations |
Weight Units
| Unit | Usage |
|---|---|
kg | Metric weight (default) |
lbs | Imperial weight |
percentage_1rm | Percentage of 1 rep max |
percentage_bodyweight | Percentage of athlete bodyweight (added v1.4.0) |
Distance Units
| Unit | Usage |
|---|---|
meters | Short distances |
km | Long distances (metric) |
miles | Long distances (imperial) |
Intensity Units
| Unit | Usage |
|---|---|
rpe | Rate of Perceived Exertion (1-10) |
rir | Reps in Reserve (0-5) |
heart_rate_zone | HR zones (1-5) |
bpm | Beats per minute |
pace | min/km or min/mile |
Tempo Format
ExercisePrescription.tempo accepts two forms (oneOf); pick whichever your tooling produces.
1. Conventional 4-digit string (eccentric-pause_bottom-concentric-pause_top):
"3-1-2-0" → 3s eccentric, 1s pause, 2s concentric, 0s pause top
"3-0-X-1" → 3s eccentric, 0s pause, explosive concentric, 1s pause
The character X in the concentric position means explosive / intent-maximal rather than a numeric duration.
2. Structured object (added v1.2.0) — equivalent shape consumers can compute time-under-tension from without parsing strings:
{
"eccentric": 3,
"pause_bottom": 1,
"concentric": 1,
"pause_top": 0,
"explosive_concentric": true
}
Reference validators accept either form. The reference compiler (@gymbile/wpl-ai) auto-normalizes the string form into the structured form at compile time when the input is recognizable.
Core Schema Structure
{
"$schema": "https://wpl.dev/schemas/wpl/v1.schema.json",
"version": "1.0.0",
"plan": {
"id": "uuid",
"name": "string",
"description": "string",
"type": "workout|nutrition|meditation|recovery|hybrid",
"visibility": "private|public|template",
"metadata": {
"created_by": "trainer_id",
"created_at": "iso8601",
"updated_at": "iso8601",
"tags": ["weight_loss", "beginner", "home_workout"],
"difficulty": "beginner|intermediate|advanced|adaptive",
"estimated_duration_days": 30,
"language": "en"
},
"goals": [...],
"requirements": {...},
"personalization": {...},
"phases": [...],
"progress": {...},
"notifications": {...}
}
}
1. Goals Definition
Goals define what the plan aims to achieve and how success is measured.
{
"goals": [
{
"id": "goal_1",
"type": "primary|secondary",
"category": "weight_loss|muscle_gain|endurance|flexibility|mental_wellness|nutrition|habit",
"name": "Lose 5kg",
"description": "Achieve healthy weight loss over 8 weeks",
"target": {
"metric": "weight",
"unit": "kg",
"start_value": null,
"target_value": -5,
"measurement_type": "absolute|relative|percentage"
},
"deadline": "2024-03-01",
"milestones": [
{
"id": "m1",
"name": "First kg lost",
"target_value": -1,
"reward_points": 100,
"badge_id": "first_kg_badge"
}
]
}
]
}
Goal Categories
| Category | Description |
|---|---|
weight_loss | Body weight reduction |
muscle_gain | Muscle mass increase |
endurance | Cardiovascular fitness |
flexibility | Range of motion |
strength | Maximum force output |
mental_wellness | Stress, sleep, mindfulness |
nutrition | Dietary habits |
habit | Behavioral changes |
custom | Trainer-defined goals |
2. Client Requirements & Personalization
Requirements (Plan Prerequisites)
{
"requirements": {
"min_age": 16,
"max_age": 65,
"fitness_level": ["beginner", "intermediate"],
"equipment": [
{
"id": "dumbbells",
"name": "Dumbbells",
"required": true,
"alternatives": ["resistance_bands", "water_bottles"]
},
{
"id": "yoga_mat",
"name": "Yoga Mat",
"required": false
}
],
"contraindications": [
{
"condition": "acog:pregnancy_t2",
"action": "exclude",
"message": "This plan is not suitable during pregnancy"
},
{
"condition": "snomed:248152002",
"action": "modify",
"affected_activities": ["deadlift", "squat"]
}
],
"time_commitment": {
"min_days_per_week": 3,
"max_days_per_week": 5,
"min_minutes_per_day": 30,
"max_minutes_per_day": 60
}
}
}
Controlled condition prefixes (added v1.4.0)
Contraindication.condition is a free string, but for clinical use prefer one of the recognized prefixes so consumers can match deterministically:
| Prefix | Source | Example |
|---|---|---|
icd10: | ICD-10-CM | icd10:O09 (supervision of high-risk pregnancy) |
snomed: | SNOMED CT | snomed:248152002 (low back pain) |
acsm: | ACSM Guidelines | acsm:cardiac_rehab_phase_2 |
acog: | ACOG Committee Opinions | acog:pregnancy_t2 |
Unprefixed strings (e.g. "lower_back_injury") are accepted for non-clinical content but cannot be matched deterministically.
Personalization Rules
Client input drives plan customization through conditional rules.
{
"personalization": {
"inputs": [
{
"id": "client_age",
"type": "number",
"source": "client_profile.age",
"label": "Age"
},
{
"id": "client_injuries",
"type": "array",
"source": "client_profile.injuries",
"label": "Current Injuries"
},
{
"id": "available_equipment",
"type": "array",
"source": "questionnaire",
"label": "Available Equipment"
},
{
"id": "fitness_level",
"type": "enum",
"source": "assessment",
"options": ["beginner", "intermediate", "advanced"]
},
{
"id": "weekly_availability",
"type": "number",
"source": "questionnaire",
"label": "Days per week available"
}
],
"rules": [
{
"id": "age_intensity_adjustment",
"condition": {
"operator": "and",
"conditions": [
{"field": "client_age", "op": ">=", "value": 50}
]
},
"actions": [
{"type": "modify_intensity", "factor": 0.8, "scope": "plan"},
{"type": "add_warmup_time", "minutes": 5, "scope": "day"},
{"type": "add_activity", "activity_id": "joint_mobility", "when": "before_workout"}
]
},
{
"id": "knee_injury_modification",
"condition": {
"field": "client_injuries",
"op": "contains",
"value": "knee"
},
"actions": [
{"type": "replace_exercise", "from": "squat", "to": "wall_sit", "scope": "plan"},
{"type": "replace_exercise", "from": "lunges", "to": "step_ups_low", "scope": "plan"},
{"type": "exclude_exercise", "exercise_id": "jump_squat", "scope": "plan"}
]
},
{
"id": "equipment_substitution",
"condition": {
"field": "available_equipment",
"op": "not_contains",
"value": "barbell"
},
"actions": [
{"type": "replace_exercise", "from": "barbell_squat", "to": "goblet_squat", "scope": "plan"}
]
}
]
}
}
Recognized source prefixes (added v1.4.0)
PersonalizationInput.source is a free string. Recognized prefixes let plans express adaptive behavior in a way every consumer understands:
| Prefix | Use | Example values |
|---|---|---|
user. | Static profile fields | user.age, user.experience_level, user.injuries, user.equipment.barbell |
wellness. | Daily/recent telemetry | wellness.sleep_hours_last_night, wellness.hrv_rmssd_morning, wellness.session_rpe_yesterday, wellness.subjective_readiness, wellness.menstrual_phase |
device. | Wearable-derived metrics | device.steps_today, device.battery_drain_24h |
plan. | Plan-state queries | plan.current_phase_type, plan.day_index_in_phase |
Unrecognized prefixes are accepted but consumers may ignore them.
Action Scope
The scope field defines where personalization actions apply:
| Scope | Description |
|---|---|
activity | Only the specific activity |
block | All activities in the current block |
day | All activities in the current day |
week | All activities in the current week |
phase | All activities in the current phase |
plan | All activities in the entire plan (default) |
Athlete Thresholds {#athlete-thresholds}
_(Added v1.3.0)_ Optional plan-level reference values for the target athlete. They let consumers resolve relative-intensity targets (HR zones, %FTP, %1RM, %BW, g/kg macros, ×TDEE calories) into absolute numbers downstream — and let the same authored plan ship to athletes of any bodyweight without rewriting numbers.
{
"athlete_thresholds": {
"hr_max_bpm": 188,
"lthr_bpm": 168,
"resting_hr_bpm": 48,
"ftp_watts": 285,
"vo2max_ml_kg_min": 58,
"critical_pace_seconds_per_km": 215,
"body_weight_kg": 72,
"one_rm": [
{ "exercise_ref": "back_squat", "value": 140, "unit": "kg" },
{ "exercise_ref": "bench_press", "value": 100, "unit": "kg" }
]
}
}
All fields are optional. If a plan uses weight: { type: "percentage_1rm" } for back_squat, a consumer with the matching one_rm entry can resolve it to absolute kg; without the threshold, the relative target is preserved as-authored for downstream tooling.
3. Plan Phases
Plans are organized into phases, each containing weeks, days, and activities.
{
"phases": [
{
"id": "phase_1",
"name": "Foundation",
"type": "accumulation",
"description": "Build base fitness and learn proper form",
"order": 1,
"duration": {
"value": 2,
"unit": "weeks"
},
"goals": ["goal_1"],
"unlock_condition": null,
"weeks": [
{
"id": "week_1",
"name": "Week 1",
"order": 1,
"theme": "Introduction",
"days": [...]
},
{
"id": "week_4",
"name": "Deload",
"order": 4,
"is_deload": true,
"days": [...]
}
]
},
{
"id": "phase_2",
"name": "Progression",
"type": "intensification",
"order": 2,
"duration": {"value": 4, "unit": "weeks"},
"unlock_condition": {
"type": "phase_complete",
"phase_id": "phase_1"
}
}
]
}
Phase.type — periodization role (added v1.2.0)
Optional enum that lets consumers surface where in a cycle the user is, without re-deriving it from volume/intensity numbers:
accumulation | intensification | realization | deload | base | build | peak | recovery | transition
Maps cleanly to common periodization vocabularies (block/linear/undulating). Pure metadata — does not affect schema validation of nested weeks/days.
Week.is_deload — deload week flag (added v1.2.0)
Optional boolean. Independent of Phase.type so individual deload weeks can be tagged inside any phase (e.g. a 4-week 5/3/1 cycle has its 4th week marked is_deload: true while the phase remains intensification).
4. Day Structure
Each day contains multiple activity blocks.
{
"days": [
{
"id": "day_1",
"day_of_week": 1,
"name": "Upper Body Focus",
"type": "training|rest|active_recovery|assessment",
"estimated_duration_minutes": 45,
"schedule": {
"preferred_time": "morning|afternoon|evening|any",
"flexibility": "strict|flexible"
},
"blocks": [
{
"id": "warmup_block",
"type": "warmup",
"order": 1,
"activities": [...]
},
{
"id": "main_block",
"type": "main",
"order": 2,
"structure": "circuit|straight_sets|superset|emom|amrap|tabata",
"rounds": 3,
"rest_between_rounds": {"value": 90, "unit": "seconds"},
"activities": [...]
},
{
"id": "cooldown_block",
"type": "cooldown",
"order": 3,
"activities": [...]
}
],
"nutrition_guidance": {...},
"notes": "Focus on controlled movements"
}
]
}
Block Types
| Type | Description |
|---|---|
warmup | Preparation activities |
main | Primary workout content |
cooldown | Recovery activities |
nutrition | Meal/supplement timing |
meditation | Mindfulness activities |
education | Learning content |
assessment | Progress checks |
5. Activity Types
5.1 Exercise Activity
{
"id": "exercise_1",
"type": "exercise",
"exercise_ref": "push_up",
"name": "Push-ups",
"primary_muscles": ["chest"],
"secondary_muscles": ["triceps", "front_delts"],
"movement_pattern": "push_horizontal",
"target_rpe": 8,
"target_rir": 2,
"prescription": {
"type": "sets_reps|time|distance|amrap|continuous|intervals",
"sets": 3,
"reps": {"min": 8, "max": 12, "target": 10},
"weight": {
"type": "bodyweight|absolute|percentage_1rm|percentage_bodyweight",
"value": null,
"unit": "kg"
},
"tempo": {
"eccentric": 3,
"pause_bottom": 1,
"concentric": 2,
"pause_top": 0
},
"rest": {"value": 60, "unit": "seconds"}
},
"progression": {
"type": "linear|wave|autoregulated",
"increment": {"value": 2, "unit": "reps", "per": "week"}
},
"alternatives": [
{
"exercise_ref": "knee_push_up",
"condition": "easier"
},
{
"exercise_ref": "diamond_push_up",
"condition": "harder"
}
],
"media": {
"video_url": "https://...",
"thumbnail_url": "https://...",
"instructions": ["Start in plank position", "Lower chest to ground", "Push back up"]
},
"tracking": {
"log_weight": true,
"log_reps": true,
"log_rpe": true,
"log_notes": true
}
}
5.2 Cardio Activity
{
"id": "cardio_1",
"type": "cardio",
"name": "Interval Running",
"modality": "running|cycling|swimming|rowing|elliptical|jump_rope",
"prescription": {
"type": "continuous|intervals|fartlek",
"duration": {"value": 20, "unit": "minutes"},
"intensity": {
"type": "heart_rate_zone|rpe|pace|power|bpm",
"zone_model": "hr_3_zone_seiler",
"target": {"zone": 3, "min": 130, "max": 150}
},
"intervals": {
"work": {"duration": 30},
"rest": {"duration": 30},
"repeat": 10
}
},
"tracking": {
"log_distance": true,
"log_duration": true,
"log_heart_rate": true,
"log_calories": true
}
}
intensity.zone_model (added v1.3.0)
Optional enum that disambiguates which zone numbering system a target uses. A "Zone 2" target is meaningless without the model — hr_3_zone_seiler (polarized) treats Z2 as threshold work, while hr_5_zone treats Z2 as easy aerobic. Recognized values:
hr_3_zone_seiler | hr_5_zone | hr_7_zone | power_coggan_7_zone | pace_critical_speed | rpe_borg_10 | rpe_borg_20
5.3 Nutrition Activity
{
"id": "nutrition_1",
"type": "nutrition",
"category": "meal|snack|supplement|hydration",
"name": "Post-Workout Protein",
"timing": {
"type": "relative|absolute",
"reference": "workout_end",
"offset": {"value": 30, "unit": "minutes"}
},
"prescription": {
"macros": {
"protein": {"min": 1.6, "max": 2.2, "unit": "g_per_kg"},
"carbs": {"min": 4.0, "max": 6.0, "unit": "g_per_kg"},
"fat": {"min": 0.7, "max": 1.0, "unit": "g_per_kg"}
},
"calories": {"min": 0.95, "max": 1.05, "unit": "multiplier_of_tdee"},
"suggestions": [
"Protein shake with banana",
"Greek yogurt with berries",
"Chicken breast with rice"
]
},
"tracking": {
"log_consumed": true,
"log_actual_macros": false,
"photo_required": false
}
}
Per-bodyweight scaling (added v1.4.0)
Both MacroRange.unit and calories.unit accept relative units so plans are portable across athletes of different bodyweights. Recommended for evidence-based prescriptions (e.g. Morton et al. 2018: 1.6–2.2 g/kg/day protein for resistance training):
| Field | Units | ||
|---|---|---|---|
macros.{protein,carbs,fat}.unit | g (default) — absolute grams \ | g_per_kg — grams per kg of bodyweight | |
calories.unit | kcal (default) \ | kcal_per_kg \ | multiplier_of_tdee (e.g. 0.95–1.05× of athlete's TDEE) |
When the unit is relative, consumers resolve to absolute numbers using the plan-level [athlete_thresholds.body_weight_kg](#athlete-thresholds).
5.4 Meditation Activity
{
"id": "meditation_1",
"type": "meditation",
"category": "breathing|mindfulness|visualization|body_scan|sleep",
"name": "Morning Mindfulness",
"prescription": {
"duration": {"value": 10, "unit": "minutes"},
"guided": true,
"audio_id": "meditation_audio_123"
},
"content": {
"introduction": "Find a comfortable seated position...",
"steps": [
{"duration": 60, "instruction": "Focus on your breath"},
{"duration": 120, "instruction": "Body scan from head to toe"},
{"duration": 60, "instruction": "Return awareness to the room"}
]
},
"tracking": {
"log_completed": true,
"log_mood_before": true,
"log_mood_after": true
}
}
5.5 Recovery Activity
{
"id": "recovery_1",
"type": "recovery",
"category": "stretching|foam_rolling|massage|cold_therapy|heat_therapy|sleep",
"name": "Evening Stretch Routine",
"prescription": {
"duration": {"value": 15, "unit": "minutes"},
"exercises": [
{
"name": "Hamstring Stretch",
"hold_time": {"value": 30, "unit": "seconds"},
"sides": "both",
"reps": 2
}
]
},
"tracking": {
"log_completed": true,
"log_soreness_level": true
}
}
5.6 Habit Activity
{
"id": "habit_1",
"type": "habit",
"category": "hydration|sleep|steps|screen_time|custom",
"name": "Daily Water Intake",
"prescription": {
"target": {"value": 8, "unit": "glasses"},
"frequency": "daily",
"reminders": [
{"time": "09:00", "message": "Morning hydration check"},
{"time": "14:00", "message": "Afternoon water reminder"}
]
},
"tracking": {
"log_count": true,
"streak_enabled": true
}
}
simple and recovery_exercise
These activity types are produced by the WPL-AI compiler as intermediate forms when the source DSL doesn't carry full structural information. Authored plans typically use the typed forms documented above (exercise, cardio, nutrition, meditation, recovery, habit); tools that consume compiler output should accept both.
sub_plan (preview, added v1.5.0) {#sub-plan-preview}
> Preview surface — do not lead with this in authored plans. Reserved for a future plan-marketplace / library-composition use case where one plan references another by id and the consumer resolves the reference at runtime. Most plans should compose by inlining content (a future DSL INCLUDE directive will handle this at compile time).
{
"id": "sub_plan_1",
"type": "sub_plan",
"name": "Standard warmup",
"sub_plan_ref": "plan_warmup_full_body"
}
The reference validators emit CYCLIC_SUBPLAN when a plan's sub_plan_ref equals its own id (self-cycle). Cross-plan cycles (A → B → A) are not yet detected; they require a future sub_plans resolution map at validate time.
6. Progress Tracking
{
"progress": {
"checkpoints": [
{
"id": "checkpoint_1",
"name": "Week 2 Check-in",
"trigger": {
"type": "time|completion|manual",
"value": {"week": 2, "day": 7}
},
"measurements": [
{"metric": "weight", "unit": "kg"},
{"metric": "body_fat", "unit": "percentage"},
{"metric": "photos", "views": ["front", "side", "back"]},
{"metric": "measurements", "areas": ["chest", "waist", "hips"]}
],
"questionnaire": [
{"question": "How is your energy level?", "type": "scale_1_10"},
{"question": "Any pain or discomfort?", "type": "text"}
]
}
],
"points_system": {
"enabled": true,
"rules": [
{"action": "complete_workout", "points": 10},
{"action": "complete_day", "points": 25},
{"action": "complete_week", "points": 100},
{"action": "log_meal", "points": 5},
{"action": "complete_meditation", "points": 15},
{"action": "streak_7_days", "points": 200}
]
},
"achievements": [
{
"id": "first_week",
"name": "First Week Champion",
"description": "Complete your first week",
"condition": {"type": "weeks_completed", "value": 1},
"badge_image": "badge_first_week.png",
"points": 500
}
],
"streaks": {
"enabled": true,
"types": ["daily_workout", "daily_nutrition", "daily_meditation"]
}
}
}
7. Notifications & Reminders
{
"notifications": {
"workout_reminder": {
"enabled": true,
"timing": {"value": 30, "unit": "minutes", "before": "scheduled_time"},
"message_template": "Time for {{workout_name}}! 💪"
},
"rest_day_motivation": {
"enabled": true,
"message_template": "Rest day! Your muscles are growing stronger 🌱"
},
"streak_at_risk": {
"enabled": true,
"trigger": {"hours_remaining": 4},
"message_template": "Don't break your {{streak_days}} day streak!"
},
"milestone_achieved": {
"enabled": true,
"message_template": "🎉 Milestone reached: {{milestone_name}}"
}
}
}
8. Exercise Library Reference
Plans reference exercises from a central library.
{
"exercise_library": {
"push_up": {
"id": "push_up",
"name": "Push-up",
"aliases": ["press-up"],
"category": "strength",
"equipment": ["none"],
"muscle_groups": {
"primary": ["chest", "triceps"],
"secondary": ["shoulders", "core"]
},
"difficulty": "beginner",
"instructions": [...],
"common_mistakes": [...],
"video_url": "...",
"thumbnail_url": "...",
"contraindications": ["wrist_injury", "shoulder_injury"]
}
}
}
9. Template System
Trainers can create reusable templates.
{
"template": {
"id": "template_1",
"name": "HIIT Circuit Template",
"type": "block",
"parameters": [
{"name": "exercises", "type": "exercise_list", "min": 4, "max": 8},
{"name": "work_time", "type": "duration", "default": 40},
{"name": "rest_time", "type": "duration", "default": 20},
{"name": "rounds", "type": "number", "default": 3}
],
"structure": {
"type": "circuit",
"rounds": "{{rounds}}",
"activities": "{{exercises}}",
"work_duration": "{{work_time}}",
"rest_duration": "{{rest_time}}"
}
}
}
10. Validation Rules
WPL has two reference validators and a shared conformance suite:
| Implementation | Package | Source |
|---|---|---|
| TypeScript | [@gymbile/wpl-validator](https://www.npmjs.com/package/@gymbile/wpl-validator) | [gymbile/wpl-validator-ts](https://github.com/gymbile/wpl-validator-ts) |
| Elixir | [wpl_validator](https://hex.pm/packages/wpl_validator) | [gymbile/wpl-validator-ex](https://github.com/gymbile/wpl-validator-ex) |
Both run a two-pass validation:
1. Pass 1 — JSON Schema validation against the canonical [v1.schema.json](https://github.com/gymbile/wpl/blob/main/schema/v1.schema.json). Pass 2 is skipped when Pass 1 fails.
2. Pass 2 — semantic invariants that JSON Schema can't express: cross-document uniqueness, ref resolution against an external catalog, prescription consistency, periodization warnings, and sub-plan cycle detection.
The shared [conformance suite](https://github.com/gymbile/wpl/tree/main/conformance) contains 44+ fixtures (valid plans + invalid plans with expected.json files) that both validators must agree on. When the two diverge, the fixture is the contract — the implementations are wrong. See [error-codes.md](https://github.com/gymbile/wpl/blob/main/conformance/error-codes.md) for the canonical error code catalog and path conventions.
Required Fields by Plan Type
| Plan Type | Required Sections |
|---|---|
workout | goals, phases, at least one exercise activity |
nutrition | goals, nutrition activities |
meditation | goals, meditation activities |
hybrid | goals, phases, at least 2 activity types |
Constraints
- Phase duration must be > 0
- Day activities must have unique IDs within the day
- Exercise references must exist in library or be inline-defined
- Personalization rules must reference valid input fields
- Progress checkpoints must fall within plan duration
Duration Consistency
When a phase specifies both duration and weeks array, they must be consistent:
| Duration | Weeks Array | Valid? |
|---|---|---|
| 2 weeks | 2 items | ✅ Yes |
| 2 weeks | 3 items | ⚠️ Warning (weeks array takes precedence) |
| 14 days | 2 items | ✅ Yes |
| Not specified | Any | ✅ Yes (duration inferred from weeks) |
Validation behavior:
- If
durationis specified butweeksarray has different count, emit a warning - The
weeksarray is the source of truth for actual content durationis used for display and progress calculations when weeks array is empty
11. Rendering Hints
For consistent UI rendering across platforms:
{
"rendering": {
"color_scheme": {
"primary": "#4F46E5",
"secondary": "#10B981",
"accent": "#F59E0B"
},
"activity_icons": {
"exercise": "dumbbell",
"cardio": "heart",
"nutrition": "utensils",
"meditation": "brain",
"recovery": "bed",
"habit": "check-circle"
},
"difficulty_colors": {
"beginner": "#10B981",
"intermediate": "#F59E0B",
"advanced": "#EF4444"
}
}
}
12. API Integration
Endpoints (Future)
POST /api/v1/plans # Create plan
GET /api/v1/plans/:id # Get plan
PUT /api/v1/plans/:id # Update plan
DELETE /api/v1/plans/:id # Delete plan
POST /api/v1/plans/:id/personalize # Generate personalized version
POST /api/v1/plans/:id/assign # Assign to client
GET /api/v1/plans/:id/progress # Get progress data
POST /api/v1/plans/:id/log # Log activity completion
13. Plan Assembly Pipeline
When assigning a plan to a client, the following pipeline is executed:
┌─────────────────────────────────────────────────────────────────┐
│ PLAN ASSEMBLY PIPELINE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. LOAD PLAN │
│ └─ Parse WPL JSON → Validate schema │
│ │
│ 2. COLLECT CLIENT DATA │
│ └─ Gather inputs: age, injuries, equipment, fitness level │
│ │
│ 3. EVALUATE PERSONALIZATION RULES │
│ └─ For each rule: │
│ ├─ Evaluate condition against client data │
│ └─ If true, queue actions for application │
│ │
│ 4. APPLY ACTIONS (in order) │
│ └─ modify_intensity → replace_exercise → exclude_exercise │
│ → reduce_sets → reduce_reps → increase_rest │
│ → add_warmup_time → add_activity │
│ │
│ 5. GENERATE PERSONALIZED INSTANCE │
│ └─ Create immutable copy with applied modifications │
│ │
│ 6. VALIDATE RESULT │
│ └─ Ensure personalized plan is still valid │
│ │
│ 7. ASSIGN TO CLIENT │
│ └─ Store assignment with start_date, personalized_plan │
│ │
└─────────────────────────────────────────────────────────────────┘
Key principles:
- Original plan is never modified (immutable)
- Personalized instance is stored separately per client
- Client can have multiple assignments of the same plan
- Progress is tracked against the personalized instance
14. Versioning
Plans include version information for backward compatibility:
{
"$schema": "https://wpl.dev/schemas/wpl/v1.schema.json",
"version": "1.0.0",
"min_app_version": "2.0.0"
}
Example: Complete 4-Week Weight Loss Plan
See examples/weight_loss_4_week.wpl.json for a full implementation.
Changelog
v1.5.0 (2026-05-03)
- Added
SubPlanActivity(type: "sub_plan",sub_plan_ref) — preview surface reserved for future plan-marketplace use cases. Most authored plans should compose by inlining content rather than referencing other plans at runtime. - Added
CYCLIC_SUBPLANPass-2 rule (self-cycle detection); cross-plan cycle detection deferred until the validate API gains asub_plansresolution map.
v1.4.0 (2026-05-03)
- Added per-bodyweight scaling:
MacroRange.unitacceptsg_per_kg;Calories.unitacceptskcal | kcal_per_kg | multiplier_of_tdee;Weight.typeacceptspercentage_bodyweight. Lets the same authored plan ship to athletes of any bodyweight without rewriting numbers. - Documented controlled
conditionprefixes (icd10:,snomed:,acsm:,acog:) onContraindication.conditionfor clinical use. - Documented controlled
sourceprefixes (user.,wellness.,device.,plan.*) onPersonalizationInput.sourcefor adaptive plans driven by athlete state.
v1.3.0 (2026-05-03)
- Added muscle-group taxonomy: optional
primary_muscles[],secondary_muscles[],movement_patternonExerciseActivitywith controlledMuscleGroupandMovementPatternenums. Lets analytics tools compute weekly sets per muscle group without an out-of-band exercise→muscle map. - Added
intensity.zone_modelenum on cardio prescriptions (hr_3_zone_seiler,hr_5_zone,hr_7_zone,power_coggan_7_zone,pace_critical_speed,rpe_borg_10,rpe_borg_20);intensity.typewidened withpowerandbpm. - Added plan-level
athlete_thresholds(HR max/LTHR/resting, FTP, VO2max, critical pace, body weight, 1RM list).
v1.2.0 (2026-05-03)
- Added optional
Phase.typeperiodization role (accumulation | intensification | realization | deload | base | build | peak | recovery | transition). - Added optional
Week.is_deloadboolean — independent ofPhase.typeso individual deload weeks can be tagged inside any phase. - Added structured
Temposhape alongside the existing 4-digit string form (oneOf:[string, StructuredTempo]); reference compiler auto-normalizes recognizable strings into the structured form at compile time.
v1.1.0 (2026-05-02)
- Added ID conventions section (local vs global IDs)
- Added standard units registry (time, weight, distance, intensity)
- Added tempo format specification
- Added
scopefield to personalization actions - Added duration consistency validation rules
- Added Plan Assembly Pipeline documentation
- Added RIR (Reps in Reserve) as intensity unit
v1.0.0 (2025-11-24)
- Initial specification
- Core activity types: exercise, cardio, nutrition, meditation, recovery, habit
- Personalization engine with conditional rules
- Progress tracking with points and achievements
- Template system for reusable components