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:

ElementScopeExample
phase.idUnique within plan"phase_1"
week.idUnique within phase"week_1"
day.idUnique within week"day_1"
block.idUnique within day"warmup_block"
activity.idUnique 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

UnitUsage
secondsRest periods, tempo, short durations
minutesWorkout durations, meditation
hoursLong activities
daysPhase/plan durations
weeksPhase/plan durations

Weight Units

UnitUsage
kgMetric weight (default)
lbsImperial weight
percentage_1rmPercentage of 1 rep max
percentage_bodyweightPercentage of athlete bodyweight (added v1.4.0)

Distance Units

UnitUsage
metersShort distances
kmLong distances (metric)
milesLong distances (imperial)

Intensity Units

UnitUsage
rpeRate of Perceived Exertion (1-10)
rirReps in Reserve (0-5)
heart_rate_zoneHR zones (1-5)
bpmBeats per minute
pacemin/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

CategoryDescription
weight_lossBody weight reduction
muscle_gainMuscle mass increase
enduranceCardiovascular fitness
flexibilityRange of motion
strengthMaximum force output
mental_wellnessStress, sleep, mindfulness
nutritionDietary habits
habitBehavioral changes
customTrainer-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:

PrefixSourceExample
icd10:ICD-10-CMicd10:O09 (supervision of high-risk pregnancy)
snomed:SNOMED CTsnomed:248152002 (low back pain)
acsm:ACSM Guidelinesacsm:cardiac_rehab_phase_2
acog:ACOG Committee Opinionsacog: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:

PrefixUseExample values
user.Static profile fieldsuser.age, user.experience_level, user.injuries, user.equipment.barbell
wellness.Daily/recent telemetrywellness.sleep_hours_last_night, wellness.hrv_rmssd_morning, wellness.session_rpe_yesterday, wellness.subjective_readiness, wellness.menstrual_phase
device.Wearable-derived metricsdevice.steps_today, device.battery_drain_24h
plan.Plan-state queriesplan.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:

ScopeDescription
activityOnly the specific activity
blockAll activities in the current block
dayAll activities in the current day
weekAll activities in the current week
phaseAll activities in the current phase
planAll 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

TypeDescription
warmupPreparation activities
mainPrimary workout content
cooldownRecovery activities
nutritionMeal/supplement timing
meditationMindfulness activities
educationLearning content
assessmentProgress 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):

FieldUnits
macros.{protein,carbs,fat}.unitg (default) — absolute grams \g_per_kg — grams per kg of bodyweight
calories.unitkcal (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:

ImplementationPackageSource
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 TypeRequired Sections
workoutgoals, phases, at least one exercise activity
nutritiongoals, nutrition activities
meditationgoals, meditation activities
hybridgoals, 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:

DurationWeeks ArrayValid?
2 weeks2 items✅ Yes
2 weeks3 items⚠️ Warning (weeks array takes precedence)
14 days2 items✅ Yes
Not specifiedAny✅ Yes (duration inferred from weeks)

Validation behavior:

  • If duration is specified but weeks array has different count, emit a warning
  • The weeks array is the source of truth for actual content
  • duration is 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_SUBPLAN Pass-2 rule (self-cycle detection); cross-plan cycle detection deferred until the validate API gains a sub_plans resolution map.

v1.4.0 (2026-05-03)

  • Added per-bodyweight scaling: MacroRange.unit accepts g_per_kg; Calories.unit accepts kcal | kcal_per_kg | multiplier_of_tdee; Weight.type accepts percentage_bodyweight. Lets the same authored plan ship to athletes of any bodyweight without rewriting numbers.
  • Documented controlled condition prefixes (icd10:, snomed:, acsm:, acog:) on Contraindication.condition for clinical use.
  • Documented controlled source prefixes (user., wellness., device., plan.*) on PersonalizationInput.source for adaptive plans driven by athlete state.

v1.3.0 (2026-05-03)

  • Added muscle-group taxonomy: optional primary_muscles[], secondary_muscles[], movement_pattern on ExerciseActivity with controlled MuscleGroup and MovementPattern enums. Lets analytics tools compute weekly sets per muscle group without an out-of-band exercise→muscle map.
  • Added intensity.zone_model enum 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.type widened with power and bpm.
  • 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.type periodization role (accumulation | intensification | realization | deload | base | build | peak | recovery | transition).
  • Added optional Week.is_deload boolean — independent of Phase.type so individual deload weeks can be tagged inside any phase.
  • Added structured Tempo shape 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 scope field 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