Hevy fitness pipeline¶
Built 2026-04-29 / 2026-04-30. Strong was retired in favor of Hevy Pro for its REST API.
All workout data flows: Hevy app → /v1/workouts → local JSONL cache → Notion Fitness · Workouts → wellness daily aggregator.
Auth + secrets¶
- API key in
~/.forge-secrets/hevy.envasHEVY_API_KEY=<uuid>. Pro account. - Header name:
api-key(UUID). Base:https://api.hevyapp.com/v1.
Notion IDs (also at data/hevy/notion_ids.json)¶
- Fitness page (under Life):
3510950b-d7a9-815a-bf64-f9b787390458 - Workouts DB:
3510950b-d7a9-812e-a23c-d20bf0b2f23a(one row per workout, NOT per set) - Wellness Daily DB:
3500950b-d7a9-811c-8e10-f3065f763b35(extended with Lift Volume / Working Sets / Workout Sessions / Top Set / Trained Muscles)
Scripts (all ~/forge/scripts/forge_hevy_*.py)¶
| Script | Purpose |
|---|---|
forge_hevy_dump.py |
Full paginated workout pull → data/hevy/raw_hevy_workouts.jsonl |
forge_hevy_templates_dump.py |
Exercise template cache → data/hevy/exercise_templates.json (template_id → muscle group) |
forge_hevy_anomalies.py |
Scan JSONL, emit anomaly review markdown (TIMER_FAIL_HUGE/LONG, STRONG_24H_DEFAULT, STUB_TINY, STUB_SINGLE_SET, SET_OUTLIER z>3) |
forge_hevy_fix.py |
--plan then --apply: PUT /v1/workouts/{id} duration fixes (60min for long/huge/24h, 30min for stubs) |
forge_hevy_notion_setup.py |
One-shot: create Fitness page + Workouts DB |
forge_hevy_backfill.py |
Idempotent backfill of 471 workouts → Notion (1 row per workout, query-by-Workout-ID dedup) |
forge_hevy_poll.py |
Incremental sync via /v1/workouts/events?since=<state>. Cron 17:00/00:00/04:30 UTC = 12:00/19:00/23:30 CT |
forge_hevy_read.py |
Library: workouts_on_date, workout_today, workout_summary, last_lift, sync_now, lift_volume_for_date. Auto-refreshes cache if older than 60min. |
Bot tools (registered in forge_telegram_inbox_brain.py, on both capture and coordinator personas)¶
sync_workouts(no args): trigger immediate Hevy → Notion syncworkout_today: today's session(s) with sets/volume/top set/musclesworkout_summary(days=7): per-day rollup with totalslast_lift(exercise): last 3 sessions for fuzzy-matched exercise
Hevy app stays the primary logging surface. Bots are read + sync only. No log_workout tool by design.
Wellness daily aggregator wire-up¶
forge_wellness_daily_summary.py calls forge_hevy_read.lift_volume_for_date(today) and writes Lift Volume / Working Sets / Workout Sessions / Top Set / Trained Muscles into the Wellness Daily row alongside HRV/sleep/stress. Same row, same date.
Bug squashed at the same time: aggregator was failing nightly since 2026-04-29 because the Notion refactor renamed the title prop from Date to Name. Fixed in this session.
State files¶
~/.forge-state/hevy_last_sync— ISO timestamp of last successful poll~/forge/logs/hevy/— poll_.log + cron.log ~/forge/data/hevy/— raw_hevy_workouts.jsonl, exercise_templates.json, hevy_anomalies_.md, hevy_fix_*.{jsonl,md}, hevy_set_outliers_ .md
Anomaly recap (2026-04-29 cleanup)¶
471 workouts scanned, 206 flagged, 165 duration anomalies auto-fixed via PUT (0 failures). 68 set outliers reviewed and marked all-keepers (real 1RMs and AMRAPs, not typos). z-score >3 detection per exercise.
Cadence design¶
Justin trains ~3x/week; hourly polling was rejected as wasteful. Three daily poll runs (noon/7pm/11:30pm CT) catch new workouts within ~5 hours, plus the sync_workouts bot tool for "I just finished, push now" intent.
[Claude Code, Hevy sessions 2026-04-29 + 2026-04-30]