fitops workouts
Markdown-based workout definitions with activity linking and segment compliance scoring.
Commands print human-readable output by default. Add --json for raw JSON (useful for scripting or AI agents).
Workouts are plain .md files you write and store in ~/.fitops/workouts/. When you link a workout to a Strava activity, FitOps captures a physiology snapshot (CTL, ATL, TSB, VO2max, LT1/LT2) and — if the workout has named segments — scores each segment against the activity's heart rate stream.
Workout File Format
Create .md files in ~/.fitops/workouts/:
---
name: Threshold Tuesday
sport: Run
target_duration_min: 60
tags: [threshold, quality]
---
## Warmup
10 min easy (Z1–Z2)
## Main Set
4 × 8 min @ Z4
2 min Z1 recovery jog between reps
## Cooldown
8 min easy Z1Frontmatter fields (all optional):
| Field | Type | Description |
|---|---|---|
name | string | Display name (defaults to filename stem) |
sport | string | Run, Ride, etc. |
target_duration_min | int | Total target duration |
tags | list | [threshold, quality, run] |
Segments are defined with ## headings in the body. Each heading becomes a scoreable segment when the workout is linked to an activity. Duration (10 min, 4 × 8 min) and target zone (Z4, Zone 3, Z2–Z3) are parsed automatically from the text below each heading.
Commands
fitops workouts list
List all workout files in ~/.fitops/workouts/.
fitops workouts listfitops workouts show <name>
Display the contents and parsed metadata of a workout file.
fitops workouts show threshold-tuesday
fitops workouts show "Threshold Tuesday"
fitops workouts show threshold-tuesday.mdfitops workouts link <name> <activity_id>
Link a workout to a synced activity and capture a physiology snapshot.
fitops workouts link threshold-tuesday 12345678901
fitops workouts link threshold-tuesday 12345678901 --notes "Felt strong, legs fresh"What gets stored:
- Raw markdown content of the workout file at link time
- Parsed frontmatter metadata
- Physiology snapshot: CTL, ATL, TSB, VO2max, LT1/LT2, zones method and values
If the activity already has a linked workout, it is overwritten.
Options:
| Flag | Description |
|---|---|
--notes TEXT | Optional notes for this workout instance |
fitops workouts get <activity_id>
Retrieve the workout and physiology snapshot linked to a specific activity.
fitops workouts get 12345678901fitops workouts compliance <activity_id>
Score each workout segment against the activity's heart rate stream.
fitops workouts compliance 12345678901
fitops workouts compliance 12345678901 --recalculateRequires:
- A workout linked to the activity (
fitops workouts linkfirst) - Heart rate stream data (fetched from Strava automatically if not cached)
- Zone settings configured (
fitops analytics zones --set-lthr 165)
Each ## segment in the workout file is matched to a time slice of the HR stream, proportional to the durations specified. For example, a 10 min warmup + 32 min main set + 2 min recovery + 8 min cooldown (52 min total) maps onto the actual moving time of the activity.
Options:
| Flag | Description |
|---|---|
--recalculate | Force re-score even if segment results are already cached |
See Output Examples → Workouts for sample output.
fitops workouts unlink <activity_id>
Remove the workout–activity association for an activity.
fitops workouts unlink 12345678901The workout file itself is not deleted — only the DB record linking it to the activity is removed.
fitops workouts create <name> <source>
Create a workout markdown file from a JSON definition.
fitops workouts create "Threshold Tuesday" workout.json
cat workout.json | fitops workouts create "Threshold Tuesday" -Arguments:
| Argument | Description |
|---|---|
NAME | Display name for the workout (e.g. "10 Mar Intervals") |
SOURCE | Path to a JSON file, or - to read from stdin |
JSON format:
{
"sport": "run",
"segments": [
{ "name": "Warmup", "type": "warmup", "duration_min": 10, "zone": 2 },
{ "name": "Intervals", "type": "interval", "reps": 5, "duration_min": 4, "zone": 4 },
{ "name": "Recovery", "type": "recovery", "reps": 5, "duration_min": 2, "zone": 1 },
{ "name": "Cooldown", "type": "cooldown", "duration_min": 10, "zone": 1 }
]
}The command generates a .md file in ~/.fitops/workouts/ with YAML frontmatter and human-readable segment headings that the compliance scorer can parse.
fitops workouts edit <workout> <source>
Replace an existing workout definition from a JSON file.
fitops workouts edit 12 updated-workout.json
fitops workouts edit "Threshold Tuesday" updated-workout.json --name "Threshold Progression"
cat updated-workout.json | fitops workouts edit threshold-tuesday.md - --jsonArguments:
| Argument | Description |
|---|---|
WORKOUT | Workout id, exact workout name, or workout filename |
SOURCE | Path to replacement JSON, or - to read from stdin |
Options:
| Flag | Default | Description |
|---|---|---|
--name TEXT | existing name | Rename the workout while saving |
--sport TYPE | existing sport | Replace the workout sport |
--json | false | Output raw JSON |
Editing updates the DB row and the matching markdown file in ~/.fitops/workouts/. Because the segment structure may have changed, FitOps clears cached segment compliance rows and per-link compliance scores for that workout. Re-run fitops workouts compliance <activity_id> --recalculate for linked executions you want to score again.
fitops workouts delete <workout>
Delete a workout definition, its workout links, cached segment scores, and its markdown file.
fitops workouts delete 12 --yes
fitops workouts delete threshold-tuesday.md --yes --jsonOptions:
| Flag | Default | Description |
|---|---|---|
--yes | false | Required confirmation for deletion |
--json | false | Output raw JSON |
If another DB workout row references the same markdown file, FitOps keeps the file and only removes the selected row and its dependent cached data.
fitops workouts simulate <name>
Simulate a workout on a course or past activity route, applying terrain and weather adjustments per segment.
fitops workouts simulate threshold-tuesday --course 3
fitops workouts simulate tempo-run --activity 12345678901 --base-pace 5:10
fitops workouts simulate long-run --course 1 --date 2026-04-20 --hour 8
fitops workouts simulate tempo-run --course 2 --temp 28 --humidity 70Arguments:
| Argument | Description |
|---|---|
NAME | Workout filename or name (e.g. threshold-tuesday) |
Options:
| Flag | Default | Description |
|---|---|---|
--course ID | — | RaceCourse ID (from fitops race courses) |
--activity ID | — | Strava activity ID — uses cached GPS streams as the course |
--base-pace MM:SS | — | Base pace per km for HR-zone segments with no explicit pace target |
--temp C | — | Temperature °C (manual override) |
--humidity PCT | — | Relative humidity % (manual override) |
--wind MS | — | Wind speed m/s |
--wind-dir DEG | — | Wind direction degrees (0=N) |
--date YYYY-MM-DD | — | Fetch weather for this date (future = forecast, past = archive) |
--hour N | 9 | Start hour local time (0–23) for weather fetch |
Either --course or --activity is required (not both). Manual --temp/--humidity override auto-fetched weather when both are provided.
Output: Per-segment plan showing segment name, target zone, planned duration, GAP/WAP-adjusted pace, estimated HR, and cumulative time.
fitops workouts history
List all workouts that have been linked to activities.
fitops workouts history
fitops workouts history --limit 10 --sport RunOptions:
| Flag | Default | Description |
|---|---|---|
--limit N | 20 | Max entries to return |
--sport TYPE | all | Filter by sport type |
fitops workouts summary
Show a stored-data summary of completed workout sessions.
fitops workouts summary
fitops workouts summary --period month --sport run
fitops workouts summary --period all --jsonThe summary reads from saved workout_activity_links, workout_segments, and linked activities rows. It does not rescore workouts, fetch streams, or recompute compliance during the read.
Options:
| Flag | Default | Description |
|---|---|---|
--period week|month|year|all | month | Time window for completed linked activities |
--sport TYPE | total | run, cycle, total, or an exact Strava sport type |
The JSON output includes _meta, a summary object, and a data_availability block that marks recomputed as false.
fitops workouts analytics <workout>
Show contribution analytics for one workout definition.
fitops workouts analytics "Threshold Tuesday"
fitops workouts analytics 12 --period year
fitops workouts analytics threshold-tuesday.md --period all --jsonThe command accepts a workout id, exact workout name, or workout filename. It reads stored workout links, stored activity TSS, stored true pace, and stored segment scores. It does not fetch streams, rescore compliance, compute true pace, or estimate missing TSS on the read path.
Options:
| Flag | Default | Description |
|---|---|---|
--period week|month|year|all | year | Time window for contribution and trend metrics |
The JSON output includes _meta, workout_analytics.summary, workout_analytics.trend, workout_analytics.segment_summary, and a data_availability block. Rows without stored activities.training_stress_score are counted in missing_tss_sessions and excluded from load contribution metrics. Rows without stored activity_weather.true_pace_s_per_km are counted in missing_true_pace_sessions and omitted from the true pace trend.
If older activities are missing cached TSS, run fitops analytics recalculate-scores once to backfill activity score and TSS columns.
Compliance Scoring
Each segment gets a compliance_score (0.0–1.0) computed as:
compliance_score = time_in_target_pct × 0.6 + (1 − |deviation| / 2) × 0.4Where:
time_in_target_pct— fraction of the segment spent in the target zonedeviation— how many zones above/below the target your average HR landed
Being below target is penalised the same as being above (symmetric formula). A score ≥ 0.8 is generally considered a successful segment.
The overall_compliance_score is a duration-weighted average across all scored segments.
See Also
- Concepts → Zones — zone methods and thresholds
- Output Examples → Workouts — sample output
fitops analytics zones— configure your HR zones