NextRep
A daily “what should I train next?” recommender on my own Garmin data, with a built-in check on whether the advice helps.
- Role
- Solo: metric design, data pipeline, modeling, evaluation, product
- Timeline
- From idea to live app
- Stack
- Python, SQL, Postgres with row-level security, GitHub Actions, TypeScript/React
- Built with
- Lovable (app), Claude (research, pipeline code, data checks)
The problem
My watch records everything, but Garmin Connect only shows the data back to me. It never tells me what to do next. Each morning I wanted three answers:
- How recovered am I, overall and per muscle group?
- Am I on track this week for strength and cardio?
- Given both, what should tomorrow's workout be?
Constraint
The official Garmin developer API was closed to new applicants and paid aggregators started in the hundreds of dollars a month. Requirements: free to run, close to live, covering workouts, sleep, stress, and Body Battery, and private by default.
Approach
Replace the paid API with a free scheduled job
- 1
Garmin watch + Garmin Connect
workouts, sleep, stress, Body Battery
- 2
Hourly Python sync
GitHub Actions job pulls the last two days
- 3
Token-checked ingest route
public to GitHub, rejects any call without the secret
- 4
Postgres with row-level security
raw payload kept so new metrics need no re-sync
- 5
Rule-based insight engines + React UI
next workout and muscle fatigue, with the numbers behind each
A free hourly sync feeds a private, explainable app
GitHub Actions instead of a server
Free, reliable scheduling, plus a manual trigger for backfilling history.
Rule-based engines, not a black-box score
Every recommendation shows the numbers behind it, so it earns trust.
Keep Garmin's raw payload
New metrics can be added later without re-syncing history.
Hard problems
Making messy wearable data trustworthy
Garmin's “no data” values look like real numbers
Stress returns -1 or -2 when the watch lacks data, and resting heart rate on nights without the watch read as high as 108 bpm against a true value near 59. I treat negative stress as missing and only trust resting heart rate on days with recorded sleep.
Garmin doesn't know which exercises I did
10 of 11 strength sessions recorded a single exercise as UNKNOWN. The fatigue model maps named categories to primary and secondary muscles, falls back to a full-body estimate, and labels those cards “estimated” so the app never overstates what it knows.
The endpoint has to be public, but the data can't be
The ingest route rejects any request without a shared secret token, the tables are locked by row-level security to my account alone, and demo mode never touches the database.
Metric definitions
Every number has a data-quality rule
- Recovery (Body Battery)
- Overnight values only count on nights with recorded sleep
- Resting heart rate
- Trusted only on days with sleep data, otherwise flagged unreliable
- Stress
- Negative values (-1, -2) treated as missing
- Muscle fatigue
- Sets × reps by muscle (primary 100%, secondary 50%) plus cardio leg load, decaying over 36–48h
- Adherence
- Followed, partial, swapped, or skipped, classified automatically from that day's activities
Results
30 days of my own data changed how I train
I'd stopped doing cardio without noticing
I lifted 8 times in 15 days while my treadmill walk-run program stalled, the biggest gap for a weight-loss goal.
I wasn't wearing my watch to bed
Only 3 of 30 nights had sleep data. Body Battery recharged to 87–92 on those nights and rarely passed 40 otherwise.
Garmin couldn't see my lifting
Almost every strength session was one unnamed set, which led me to start using Garmin's named strength workouts.
Evaluation
Do the recommendations work?
- Log each recommendation at the time it's made, frozen with the rule that fired and every input.
- Classify adherence automatically from the next day's Garmin activities, with no manual logging to bias the data.
- Measure next-day outcomes: Body Battery, resting heart rate (sleep nights only), stress, and weekly target attainment.
- Compare followed vs not-followed days with a 90% bootstrap confidence interval; below 8 days per group, show “not enough data yet.”
- Break results down by rule to see which rules earn their place.
It's one person, observational and not randomized, so results describe associations, not causal effects, and the app says so on screen. Next step: randomly show the recommended session or a default plan on eligible days.
What I learned
- When the official door closes, the constraint becomes the design.
- Real data is the spec. Every important decision came from looking at actual rows.
- Explainable beats clever. A rule-based recommendation that shows its numbers earns more trust.
- A recommender isn't done when it ships. It's done when it's measured.