Work About All work Resume GitHub LinkedIn Email
← All projects

Self-hosted · solo build

LinkRing —
self-hosted health tracker

Health technology · June 2026 — Present · v1 in production

A WHOOP without the subscription

WHOOP charges a monthly subscription for recovery scores computed on someone else's servers. LinkRing computes them from a $30 smart ring, on hardware I control, and the data never leaves the house.

The design decision that matters: every score is measured against my own 14-day rolling baseline, not a population average. Recovery (0–100) maps the night's resting heart rate — the 5th percentile of the overnight window — against that baseline as a logistic z-score. Strain (0–21) weights the day's heart-rate minutes across five effort zones; a day at 14 or above is classified hard. Sleep duration and quality are estimated from the overnight heart-rate dip. A number only means something compared to you.

It runs like real infrastructure, not a script: two launchd services on a Mac hub — a WSGI server for the dashboard, and a 15-minute cycle that syncs the ring, recomputes scores, exports a daily report to my Obsidian vault, and rotates database backups. The dashboard reaches my phone over Tailscale; nothing is ever exposed to the public internet.

The dashboard

Each day gets an end-of-day report with a plain-language verdict — "well recovered, green light to push" or "run down — favour rest" — and a one-tap export to my Obsidian vault. Metrics the hardware can't measure yet don't show fake numbers; the card says what's missing and why.

LinkRing dashboard — recovery ring at 32%, strain 13.5 of 21, resting heart rate 51 bpm, and a 30-day recovery versus resting-HR trend chart
Recovery ring, strain, and the trend — HRV, SpO₂, and body temperature honestly say "needs real-time PPG capture"
LinkRing report view — per-day strain, steps, active minutes, and calories with expandable end-of-day verdicts and send-to-Obsidian buttons
End-of-day reports — day classification, plain-language verdict, one-tap export to the Obsidian vault
LinkRing sleep view — last night 10 hours 55 minutes at quality 93 of 100, weekly averages, and a month of hours-slept and quality bars
Sleep from the overnight HR dip — the UI's own caption admits the estimation method

Key decisions

  • Personal rolling baseline over absolute thresholds — the scoring engine (~200 lines of NumPy/SciPy) ships with an assert-based self-check suite that runs before every deploy.
  • Production discipline on a hobby project: WSGI server, launchd auto-restart, rotating logs, daily database backups with 14-generation retention.
  • Reused the ring's existing open-source client instead of writing custom Bluetooth code — build the validation layer, not the plumbing.
  • Obsidian vault integration: daily scores sync into my knowledge base every 15 minutes as readable notes.
  • No faked metrics. HRV and SpO₂ math is written and self-tested but gated off until real-time pulse capture exists to feed it. No smart ring measures blood pressure, so LinkRing will never display one. v1 runs end-to-end today; first live ring pairing is the current step.

The whole system is about 625 lines of Python across five files, two SQLite databases, and zero cloud dependencies. It replaces a $360-a-year subscription with a $30 ring and code I can read.

v1 in production — ring pairing next