I have two fitness wearables and I was doing nothing useful with the data. I could see my resting heart rate, my HRV, my sleep stages, my VO2 Max estimate. I could see all of it in the native apps, but turning that into an actual daily plan required work I wasn’t doing. Every morning was a fresh start.
I wanted something that would synthesize the data and tell me what to do.
What It Does
Health Coach is a personal web app that runs on my home server. Every morning it generates a briefing: a short, specific coaching note based on my recovery score, recent training load, and sleep quality. Alongside the briefing is a workout adjusted to that day’s recovery. If I’m well-recovered, the session runs at full volume. If my HRV is low and my sleep was short, the weights come down and the rest periods go up. The adjustments come from Claude, which has access to the full context.
There’s also a chat coach: a conversation interface that knows my actual training history, not just generic fitness advice. I can ask it why it’s recommending a lighter session, or what my training load looks like over the past two weeks, and it gives answers based on what actually happened.
An analytics dashboard covers the last 7 to 30 days: sleep stages, HRV trend, workout heart rate zones, training load, weight history, blood oxygen. Everything is computed from real health data, not estimated from step counts.
The Data Layer
My primary data source is Apple Watch via Health Auto Export, an iOS app that pushes workouts, sleep data, and health metrics to a webhook as JSON. The server writes this directly to SQLite. Apple Watch data is accurate and immediate; it’s in HealthKit the moment a workout ends.
I also pull from Fitbit via the Google Health API, which fills gaps when Apple data isn’t available. This was originally the primary source, and the switch to Apple Watch primary came from a concrete failure: Fitbit’s automatic workout detection caught 15 minutes of a 58-minute kettlebell session. Google Health has the additional problem of hours-long sync delays. Apple Watch is just faster and more complete.
Getting Health Auto Export to reach the home server required configuring a Cloudflare Tunnel and setting up a Cloudflare Access service token so automated requests could authenticate without a browser session. The ingest endpoint accepts HAE’s JSON format, normalizes it (HAE sends workout duration in seconds; the rest of the app works in minutes), and writes to apple_workouts and apple_metrics tables.
The Recovery Score
Recovery is computed from four signals: sleep hours (30 points), HRV (35 points), resting heart rate (15 points), and training load (20 points). The original weighting had sleep at 40 and HRV at zero. That was wrong. HRV is the most predictive signal for overtraining and readiness; it got the largest weight in the rebalance.
Everything else runs off that number. Claude’s briefing prompt includes it. The workout adjustment prompt includes it. The chat coach includes it in the system context. Recovery score 85 and recovery score 42 produce meaningfully different mornings.
The AI Layer
Two Claude calls per day, both cached.
The briefing prompt includes the recovery score, yesterday’s workout, last night’s sleep stages, HRV, resting HR, and current training load. Claude returns a short coaching note, 3 to 5 sentences, specific to today’s numbers. The workout prompt includes the same context plus the 6-day kettlebell program JSON, and Claude returns the day’s session with volume and intensity adjusted to recovery.
The chat coach keeps conversation history in SQLite and includes a system prompt with 7 days of health context. It knows what I actually did last Tuesday, not a generic version of a person who works out.
One fix that mattered: the original briefing had a 300-token budget, which cut sentences mid-thought. Raising it to 600 was the whole fix.
Health data going to an external API is worth thinking about. The prompts contain no name, no location, no identifying information: just numbers (HRV in milliseconds, resting HR in bpm, sleep hours, workout duration). Anthropic doesn’t train on API data and doesn’t retain it long-term, which makes this meaningfully different from pasting health data into a consumer chat interface.
The Frontend
The interface is vanilla HTML, CSS, and JavaScript, about 3,300 lines, served directly by the Express backend. No framework, no build step.
I prototyped three directions before building anything, ranging from consumer to clinical. The clinical one (“Instrument”) was the right call. Flat charcoal surfaces, a single cyan accent, monospace labels, real SVG charts. The earlier version had gradient text, glow effects, and three competing accent colors. The redesign removed all of it.
The home screen has a 3-column layout on desktop that collapses to a single column on mobile. The center column cards (briefing and session) are reorderable. Desktop uses HTML5 drag-and-drop; mobile uses up/down buttons, because touchstart with preventDefault blocked scrolling, and drag handles on tall cards don’t work when the card fills the entire viewport.
Where It Is Now
The full loop is working: Apple Watch data comes in via Health Auto Export, the recovery score is computed, Claude generates the briefing and workout, the analytics dashboard shows the history. I’ve been using it daily.
The FUTURE_FEATURES.md has a list: HRV trend visualization, acute/chronic training load ratio, weekly summary emails. Nothing I need immediately, but things that would make the coaching better over time.
The code is private.