Every marker on this page belongs to some part of you. Pick a region on the figure, or a system from the list, to see what the watch, the lab and the genome each have to say about it.
Daily values with a 7-day average through them. One note before you read the sleep chart: I charge my Apple Watch overnight most nights, so sleep is recorded on a minority of them, and that minority leans toward disrupted nights rather than typical ones. It is the least trustworthy series on this page.
Every stored value for the selected range, newest first. The same data the charts draw from.
Exercise pulled out of the general health metrics, because training load is a thing you steer week to week rather than a number you watch drift. Everything here comes from closed Apple Watch workouts and exercise minutes.
Pearson correlation across the selected range. Sleep and training are compared against the following day's recovery markers, since that is the direction the effect runs. Correlation is not causation and a small n is noisy, so treat these as prompts, not conclusions. Anything involving sleep is weaker still: I charge my watch overnight most nights, so those rows rest on far fewer readings than the others, and on a set of nights that is not representative.
Resting heart rate, HRV, breathing rate and wrist temperature tend to move together when the body is under load it did not choose: an infection, a bad week, too much training. This flags stretches where at least two of them broke away from my own 60-day baseline at once. It is pattern-spotting after the fact, not a diagnosis, and it cannot tell one cause from another.
Transcribed by hand from lab PDFs. Three draws so far, across two labs, which is why a few reference ranges shift between the first panel and the two after it. The shaded band on each chart is the lab's own reference range; the one exception is LDL, where the widely used desirable threshold of 100 is shaded instead of the lab's more permissive 130. I also take 7.5 g of creatine a day, which the kidney markers cannot be read without. The ⓘ on each of those three says why.
A phone that refuses to answer questions, a key/value store, and one HTML file. No database, no framework, no build step. If you only came for the numbers you can stop here. This is the part where I explain the plumbing to nobody in particular.
There is no API for Apple Health. Not a locked one, not a paid one. None.
HealthKit lives on the phone, in an encrypted store, and nothing on a server can reach in and ask it anything. Not with my password, not with an OAuth token, not at all. Apple made that call deliberately and I think they were right, which is inconvenient of them.
So the data can't be pulled. It has to be pushed, by the phone, on a schedule it decides. Every other decision here is downstream of that one fact: why there's an ingest endpoint at all, why it has to be idempotent, why a missing day means "the phone didn't call" rather than "nothing happened".
An iPhone app reads whatever HealthKit will hand over, wraps it in JSON and POSTs it to a serverless function that exists for the few hundred milliseconds it takes to run. That function writes to a key/value store I never had to configure. The page fetches the whole history back as one blob and draws every chart in the browser.
Total infrastructure: zero servers, zero databases, zero dollars a month. The most complex moving part is a phone automation with a toggle I got wrong for an entire evening (see below).
This is the one piece I'd be properly annoyed to get wrong, so it is the dullest code in the project. Four rules:
Workouts arrive carrying routes. GPS traces. Where I run, when, how often, starting from my front door, which is a fairly complete description of where I live and when I'm not in it.
None of it is written. The normalizer reduces a workout to three numbers (count, minutes, energy) and drops everything else on the floor. There's a test that feeds it a payload stuffed with location data and asserts none of it survives, because "I'm fairly sure I handled that" is not a security model for a page anyone on the internet can read.
index.html is around 2,900 lines and imports nothing. No React, no D3, no
bundler, no npm install to deploy. Every chart on this page is SVG assembled
by hand from the numbers.
That isn't asceticism, it's laziness with a long time horizon. I wanted to open one file in a year and understand all of it, and I wanted deploying to be "drag the folder, done". Colors are a palette checked for color-vision deficiency at every pair, and no status anywhere is signaled by color alone.
Workouts and health metrics sync on separate schedules, so days regularly landed with
workouts attached but no exercise-ring reading. The streak logic read
exerciseMinutes ?? 0, scored those days as zero minutes, and broke a
three-year streak on what was actually a sync gap.
The fix wasn't a better number, it was a third state. Every day is now worked, rest or unknown, and only a real ring reading can prove a short day. Missing data now looks like missing data everywhere on the page, which is the whole point.
Dates were built at UTC midnight and then formatted in the reader's own timezone. In New York that lands on 7pm the previous evening. Every label on the page (axes, tooltips, the streak, the lab dates) was silently off by one, and nothing looked broken.
I found it only because the streak tile said "since Jan 18" while the underlying record said Jan 19. These are calendar days, not instants, so they're now formatted in UTC, with tests that run under five timezones and check a year boundary specifically.
The body map above took four goes. Hand-authored SVG paths: the legs read as stilts. Second attempt: a "waistband" seam where the torso met the legs. Third attempt, and I want to be clear that this happened: a signed-distance-field raymarcher in numpy, which fused both thighs into a skirt and rendered every muscle groove as a machined trench.
Then I gave up and used an MIT-licensed anatomical asset, which took about ten minutes and looks like a human being. Knowing when to stop building something is a skill. I am still working on it.
125 checks across four suites, covering the normalizer, the ingest endpoint, the training
logic and the three derived sections. The front-end suites do something slightly unusual:
rather than copying the functions, they parse index.html, slice the real
source out of the script block by bracket-matching, and evaluate that. A copied test
happily passes while the live page is broken. This one can't.
I also test the tests. Flip a threshold, delete a guard, scramble a lookup table, then confirm the suite actually goes red. A test that still passes on deliberately broken code is worse than no test at all, because you believe it.
Not the code. Loading three years of history. Health Auto Export has a Batch Requests toggle, off by default, and for an hourly sync it should stay off. For a bulk backfill it has to be on, or the export fails with an empty response box and an error message containing no information whatsoever.
That cost me an evening and roughly forty attempts, and the answer was one switch. The
npm run check script exists entirely because of it: it reports coverage per
year and per automation, so I can see which of the two is behind instead of
guessing.
None of this is clever. It's a phone, a JSON file and one page. But it runs itself, costs nothing, and every number on it traces back to the thing that measured it, which is more than I can say for most health dashboards I've paid for.