Skip to content

Garmin + Hevy Integration Design

URL: https://mkdocs.justinsforge.com/memory/general/reference_garmin_workout_integration/

Both data domains are already landing in the same Notion row (πŸ’ͺ Wellness Daily, id 3500950b-d7a9-811c-8e10-f3065f763b35) and the same SQLite store (data/context.db facts_wellness). The wiring exists; what is missing is anything that acts on the join. This doc maps what is available, where the leverage points are, and what to build next.

Related references:


1. Available Data Sources and Fields

Garmin (via forge_garmin_poll.py, 28 polls/day, 14:00-03:00 UTC window)

Lands in facts_wellness with source=garmin, user_side=justin, then surfaced via /context?about=wellness.

Field (metric name) Cadence Notes
sleep_score nightly 0-100 Garmin sleep score
sleep_deep_min, sleep_light_min, sleep_rem_min, sleep_awake_min nightly Stage breakdown in minutes
hrv_overnight nightly Last-night HRV in ms
hrv_status nightly Balanced / Unbalanced / Low / High
hrv_weekly_avg nightly Garmin's own 7-day baseline
rhr continuous Resting heart rate, bpm
respiration_avg, spo2_avg nightly Sleep-window averages
body_battery continuous 0-100 energy reserve, drains across day
stress_avg continuous 0-100 daily average
training_readiness morning 0-100, Garmin's composite (sleep + HRV + recovery time + load + stress)
steps, kcal_active, intensity_minutes, distance rolling Daily user-summary block
vo2_max weekly-ish Surfaced by connectapi, not currently captured by poller (gap)
training_status, training_load, acute_load, load_focus daily Surfaced by connectapi, not currently captured (gap)
recovery_time post-workout Hours until "fully recovered" per Garmin, not currently captured (gap)

Eight Sleep (HA native integration) supplies hrv, rhr, sleep_fitness_score, plus stage data; the wellness summarizer prefers Eight Sleep HRV when present and falls back to Garmin overnight.

Hevy (via forge_hevy_poll.py, 3 polls/day)

Lands in Notion Fitness Β· Workouts (id 3510950b-d7a9-812e-a23c-d20bf0b2f23a), one row per session. Per-set granularity stays in data/hevy/raw_hevy_workouts.jsonl. Library access via forge_hevy_read.py.

Field Source / shape
Session date + start/end + duration_min /v1/workouts
Title (template name) per session
Volume (kg or lb total) sum of weight Γ— reps across working sets
Working sets count excludes warm-ups
Top set (exercise / weight / reps) heaviest 1RM-equivalent set
Trained muscles resolved via exercise_templates.json template_id β†’ muscle group
Per-exercise sets (weight, reps, rpe, rest, set_type) JSONL only, not in Notion
Rolling per-exercise history last_lift(exercise) library call

Already aggregated daily into Wellness Daily as: Lift Volume, Working Sets, Workout Sessions, Top Set, Trained Muscles (wired by forge_wellness_daily_summary.py).

What the join looks like today

A Wellness Daily row already contains both halves on the same date. What is missing is any logic that reads them together.


2. Integration Opportunities

Ranked by signal-to-effort. Each is a question Justin would actually ask out loud.

2A. Readiness-aware training suggestion (highest value, lowest effort)

"Should I lift hard today, do mobility, or rest?"

Inputs Justin already has, today, before he opens the gym: training_readiness, hrv_overnight + hrv_status + Ξ”7d, body_battery (morning value), stress_avg (rolling), recovery_time from Garmin (gap, easy add), days since last hard session, last session's trained muscles.

Decision rule (first cut, tunable):

Readiness HRV vs 7d Recommendation
β‰₯ 80 β‰₯ avg Push: heavy compound day, target undertrained muscle groups
60-79 within Β±5 Maintain: planned session, normal load
40-59 down 5-15 Pull back: skip top sets, cap RPE 7, swap conditioning for mobility
< 40 down β‰₯ 15 OR Low Rest: walk + sauna + sleep priority; suggest active recovery only

Surface as a coordinator-bot tool (training_recommendation_today) and as a line in the morning brief.

2B. Volume vs recovery correlation (medium value, medium effort)

"Does heavy leg day actually wreck my next-day HRV?"

The Wellness Daily DB has been collecting joined rows since 2026-04-29. With ~30 days of data it is worth running a simple lag-1 correlation between Lift Volume (and Working Sets per muscle group) on day N and HRV / sleep score / readiness on day N+1.

This is one Sonnet pass + a Python script, not a real ML pipeline. Output goes into Justin's weekly review as plain English: "Pulling sessions cost you 8ms HRV the next morning; pushing sessions cost 3ms; legs cost 12ms (n=4)." Single insight per muscle group is enough.

2C. Auto workout recommendation (medium value, higher effort)

"What should today's session look like?"

Combines 2A (readiness) with: Hevy last_lift history per muscle group, days-since-trained per muscle group (already derivable from trained_muscles field on each session row), Justin's preferred templates. Output is a session skeleton, not a Hevy workout creation (Hevy app stays the primary logging surface, per existing feedback memory).

Format: "Push day, RPE cap 8. Bench 4Γ—6 around 185, OHP 3Γ—8 around 115, dips 3Γ—AMRAP. Last bench was 6 days ago at 180Γ—6, progress 5lb." Posted to coordinator chat or wellness dashboard.

2D. Recovery-time honesty check (low effort, defensive)

Garmin's recovery_time is a hours-until-recovered estimate that Justin currently ignores. Capture it, then warn when the upcoming workout slot violates it: "Garmin says 18h recovery left from yesterday's session. Today's planned push day is 4h early."

2E. Workout impact retro (low value standalone, high value with 2B)

After each Hevy sync, write a one-line retrospective into the workout's Notion row: "Volume 12,400 lb, 18 sets. Following morning HRV: 78 (-7 vs avg)." Lets Justin browse Hevy history with biological cost attached.

2F. VO2 max / training status trend overlay (low effort, mostly informational)

Capture VO2 max, training status, training load, load focus from Garmin. These are the multi-week strategic signals (productive vs unproductive vs overreaching). Surface trend, not daily value.


Sequencing is chosen so each step is independently useful and the next step compounds on it.

Build 1: Garmin field coverage gap-fill

Effort: ~30 min What: Extend forge_garmin_poll.py to capture vo2_max, training_status, training_load, acute_load, load_focus, recovery_time. All available via connectapi calls already used; add a few more endpoint reads + ha_poller mapping rows. Why first: Cheap. Unblocks Builds 2, 3, 5. Even if no consumer is built, the data starts accruing for future correlation work. Risks: Some endpoints rate-limit; throttle to once/poll-cycle, not 28Γ—/day.

Build 2: training_recommendation_today bot tool (Opportunity 2A)

Effort: ~2 hours What: New Python script forge_training_recommendation.py reading /context?about=wellness&window=24h + last 3 Hevy sessions via forge_hevy_read. Apply decision table. Returns structured dict + 1-2 sentence Sonnet-written rationale. Register as tool_training_recommendation_today on coordinator persona. Where it lives in Justin's day: Morning brief at 7am CT calls it, prepends a "Today's training" line. Coordinator can also call on-demand ("how should I train today?"). Risks: Decision thresholds will be wrong on first pass. Build the rule table as a JSON config (data/training_thresholds.json) so tuning is a one-line edit, not a code change.

Build 3: Volume vs recovery weekly retro (Opportunity 2B)

Effort: ~3 hours What: forge_fitness_weekly_retro.py, runs Sunday night. Pulls last 30 Wellness Daily rows. Computes lag-1 correlation between (Working Sets, Lift Volume, per-muscle-group flags) on day N and (HRV Ξ”7d, Sleep Score, Readiness) on day N+1. Sonnet writes a 3-bullet recap into a new Notion page under Fitness, plus a coordinator notify. Why second: Needs Build 1 data ideally, but works on what exists today. Output is a tunable, learnable signal Justin can verify against his lived experience. Risks: Small-n (30-40 sessions) means correlations are noisy. Caveat the language ("seems to cost ~X ms HRV, n=4 sessions").

Build 4: Workout-row biological retro (Opportunity 2E)

Effort: ~1 hour What: Extend forge_hevy_poll.py (or a paired post-poll hook) to enrich each new Notion workout row with the next-morning HRV / Sleep Score / Readiness once those are available (24h delay). Single Notion property update per row. Why third: Cheap, makes Hevy DB self-documenting for future review.

Build 5: Auto session skeleton (Opportunity 2C)

Effort: ~5 hours What: forge_session_planner.py. Combines Build 2 readiness output + days-since-muscle-group + last-lift progressions into a structured session skeleton. Outputs a markdown block (not a Hevy template) posted to coordinator chat in the morning, or on /plan-workout skill invocation. Why later: Needs Build 1 + Build 2 done so the inputs are reliable. Most opinionated build, highest chance of "Sonnet wrote a generic gym-bro plan" failure mode. Defer until 2-3 are proven useful. Risks: Justin already knows how he likes to train. Frame as "draft I can ignore," not "the plan."

Build 6 (deferred): Recovery-time guardrail (Opportunity 2D)

Effort: ~1 hour What: When Justin opens coordinator and asks about today's training, if Garmin recovery_time > 0 hours and previous session was hard, flag it. Trivial once Build 1 ships. Deferable until it's clear Justin actually defers to recovery_time.

Build 7 (deferred): VO2/training status trend card (Opportunity 2F)

Effort: ~1 hour What: Add a Wellness Daily column for Training Status + monthly trend line on the dashboard. Mostly observational. Build only after a month of Build-1 data accumulates.


Summary

  • Plumbing already solved: Garmin β†’ SQLite β†’ Context API β†’ Wellness Daily, Hevy β†’ JSONL β†’ Notion β†’ Wellness Daily, both joined per-day.
  • Missing piece: anything that reads them together and acts on it.
  • Highest leverage first move: Build 1 (field gap-fill) plus Build 2 (training_recommendation_today) gives Justin a daily, actionable, readiness-aware suggestion in the morning brief inside half a day of work.
  • Compounding payoff: Build 3 (weekly retro) starts learning Justin's personal volume-recovery curve from real data, getting smarter over the next 60-90 days as session count climbs.

[Claude Code]