Skip to content

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.env as HEVY_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 sync
  • workout_today: today's session(s) with sets/volume/top set/muscles
  • workout_summary (days=7): per-day rollup with totals
  • last_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]