Viran
All projects

In progress

Gym Dashboard

A data platform that turns freeform gym notes into progress insights. Log a workout on my phone and the live dashboard updates overnight.

Open the dashboard (opens in a new tab)

The live dashboard on my real training data, refreshed every night. Opens in a new tab.

viran.uk/demos/gym(opens in a new tab)
Preview of the dashboard: sessions, sets and personal bests, with strength gains charted by muscle

Overview

I log every workout as freeform text, so this project turns those notes into a tested star schema and a public dashboard that keeps itself current. A Shortcut on my iPhone drops each workout into an iCloud folder. Every night at 03:00 a scheduled job files it into my notes, runs a Dagster pipeline (a Python parser, dbt models and tests on DuckDB, and a guarded public export) and pushes the result to git, which redeploys the site on Vercel. The site is fully static, with no database, no server and no per-visitor cost, and the raw notes never leave my machine.

Highlights

  • Phone to dashboard, hands-free: an iPhone Shortcut saves each workout to an iCloud folder that acts as a landing bucket. A nightly launchd job (catching up on wake if the Mac was asleep) files new entries into the notes with exact dates, runs the pipeline and pushes the update, so the live site refreshes overnight
  • Orchestrated with Dagster: one asset graph (bronze, dbt, public export) with blocking checks, run on my private notes nightly and on a public sample in CI for every change. Exports are deterministic byte for byte, so a night with no new workouts produces no commit, and any failure stops before publishing and sends a notification
  • Messy notes to structured rows: a parser for my own shorthand (plates per side, partial reps, failures, eccentric overload), with aliases, exercises and day types kept as data rather than code. All of the roughly 2,000 sets resolve to one of 58 canonical exercises
  • Dates from almost nothing: nearly two thirds of sessions were never dated. I estimate each date from the entries around it, spread the estimates across distinct days with an assignment solver, and flag every one. The method is backtested by hiding each known date in turn and re-running it: median error is one day, and over 90% land within three. Apple Health workouts cross-check the result
  • Progress maths that holds up: a load model turns machine and bar weights into comparable total kg, progress is weight first with reps as the tiebreaker, warm-ups are excluded, and each lift's first sessions count as a learning period
  • Quality gates and a human in the loop: dbt tests and contract checks fail the build on bad data, with a published data-quality scorecard. A private audit page shows every raw line beside what was parsed, flags suspect dates and weights with the reasoning, and keeps before and after snapshots as I fix the source notes
  • Ask my training log with no model at runtime: visitors type a question in plain English and get an instant answer, with no API route and no per-visitor cost. A closed set of questions is precomputed in SQL over the published data, and typed text only selects one answer. Fuzzy matching handles typos, and out-of-scope requests get a polite no. It is scored on a tuned set and a held-out set, and the held-out set is rewritten after every look so the score stays honest
  • Private by default: raw notes, health remarks included, stay on my machine. Public output is limited to explicit aggregate columns, and forbidden columns are blocked in code and by a failing check

Built with

  • Python
  • Dagster
  • dbt
  • DuckDB
  • launchd
  • iOS Shortcuts
  • GitHub Actions
  • Next.js
  • TypeScript
  • Tailwind CSS
  • d3
  • Vercel

Architecture

Architecture: an iPhone Shortcut drops each workout into an iCloud folder. A nightly job on my Mac files it into the private notes, runs a Dagster pipeline (parser, dbt on DuckDB with tests, guarded export) and pushes aggregates to git. Vercel redeploys the static Next.js site. GitHub Actions runs the same pipeline on a public sample for every change.