Building a Personal Athlete Dashboard — Where Things Stand
I’ve been quietly building something I’m quite proud of, and it’s reached a point where it’s worth talking more about.
The project started simply enough: I have 8.6 years of continuous health and activity data — every run, every walk, every night’s sleep, daily resting heart rate, HRV, Body Battery, VO2Max estimates, the lot — and I wanted to actually use it to learn more about performance and health. That meant a pipeline to pull it, a database to hold it, and a report to make sense of it.
What’s been built – The tech bits
The core is a four-script Python pipeline:
- A Python script pulls data from Garmin Connect — biostats, activity files (TCX/GPX), HRV measurements from FIT files — and writes everything to a local SQLite database. It handles multi-athlete support, rate limiting, MFA authentication, and has a full flag set for targeted backfills by date range, year, or month.
- An SQLite backing store with upsert logic across four tables: biostats, activities, HRV, and wellness. It has its own CLI with import, export, query, and stats commands.
- A second python script which builds a self-contained HTML file with Plotly charts — Resting Heart Rate, Sleep (stacked deep/light/REM), HRV (RMSSD), and VO2Max. along with summary stat cards at the top and annotated statistics panels on each chart showing averages, bests, and ±1 standard deviation bands.
- A wrapper script that chains all three together, cronjob-ready, athlete-aware. One command to update any athlete’s data and regenerate their report.
Multi-athlete capability
This is where it turned from a personal project into a product. The pipeline now handles multiple athletes cleanly — each with their own Garmin credentials, their own database, and their own report. A friend ‘Client A’ was the first external athlete connected; a second accomplished masters athlete with MFA enabled was added after that. The wrapper script handles athlete switching via a simple argument.
There are also utilities to quickly check database record counts per athlete, and a `health-upload` script that SCPs ( i.e. sends ) the latest report to the web server via cron.
Recently Implemented
- AI narrative integration — an `–ai` flag gives the option to call the Anthropic API at generation time and embed a plain-English physiological commentary and advice in each report. This doesn’t exist in any Garmin analytics tool I’ve found.
- Direct DB writes – the pipeline now writes directly to the DB, eliminating the intermediate CSV layer and the stale-file conflicts it caused.
- Mobile layout — the chart/stats-panel layout has been reworked for screens under 780px, moving annotations below charts rather than beside them. Still iterating.
See It Live
Reports ( anonymised ) are publicly accessible at https://urbanlegend.co.nz/vitals — three athletes’ data, multiple date windows, quarterly views and rolling windows.
My own dataset goes back to September 2017 and includes the full longitudinal story: the peak fitness years in New Zealand, the 18-month period in Thailand and Vietnam when training was on the back-burner and RHR rocketed, the long recovery arc, and the current return-to-running phase using walk/burst intervals under MAF 124.
That longitudinal story is a goldmine. Eight and a half years of real data showing exactly what chronic stress does to an athlete’s biometrics — and what the recovery looks like — is more compelling than any sample dataset.
Join The Beta
The pipeline is ready. I’m onboarding athletes now at no cost. You get full personalised reports for the life of the product; I get a real-world test case, and feedback. If you’ve been wearing a Garmin for a year or more, your data already has a story — I can show you what it is.