Skip to content

fix(bg-model): cap fuel-rate recommendations#195

Merged
psjostrom merged 2 commits into
mainfrom
worktree-fuel-rate-investigation
May 14, 2026
Merged

fix(bg-model): cap fuel-rate recommendations#195
psjostrom merged 2 commits into
mainfrom
worktree-fuel-rate-investigation

Conversation

@psjostrom
Copy link
Copy Markdown
Owner

Summary

Bug 3 from the original screenshots — model recommending 90 g/h Easy when current is 62. Two research-grounded changes to lib/bgModel.ts:

Spread guard — regression now requires tested fuel rates to differ by ≥ 10 g/h (one CHO titration step). Below that, the slope fits noise across a narrow window and solving for the model's "ideal drop" extrapolates absurd targets. Real example from production data: 4 g/h fuel spread (60-64), drop nearly identical across groups, regression returned raw target 143 g/h capped to 90. After fix, falls through to extrapolation which moves one step at a time → 65 g/h.

Step capMAX_FUEL_MULTIPLIER = 1.5 (50% jump per cycle) replaced with current + 10 g/h (one CHO step max increment). The 50% multiplier had no clinical basis and violated incremental CHO titration guidance — sudden CHO increases cause GI distress (Costa et al. 2023 systematic review). The 10 g/h step matches the standard sports-nutrition gut-training increment (Jeukendrup & Killer 2010) and the "Rule of 15" magnitude from the Riddell et al. 2017 T1D exercise consensus.

MAX_FUEL_ABSOLUTE = 90 kept — it's the gut absorption ceiling for an untrained gut.

End-to-end verified against real activity data (67 runs)

Category Before After Method Why
Easy 90 g/h (capped) 65 g/h extrapolation spread guard kicked in (4 g/h spread < 10)
Long 56 g/h 56 g/h regression unchanged — 12 g/h spread, real slope, target under cap
Interval 60 g/h (default) 60 g/h (default) n/a drop doesn't meet MIN_DROP_TO_SUGGEST

Side discovery while debugging — phase-aware modeling

Per-time-bucket analysis of the same 67 activities surfaced something the current model can't see: drop rate has two distinct phases. CHO ingested at minute 10 enters the bloodstream around minute 25, and the data reflects this clearly:

Bucket Easy Long
0-15 min -1.78 mmol/hr -2.67
15-30 min -3.23 -3.47
30-45 min -2.13 -0.75
45+ min -1.69 -0.71

Easy runs (median 44 min) spend ~70% of their time in pre-fuel territory; Long runs (median 76 min) spend ~40%. So the average drop rate Easy reports is dominated by pre-fuel kinetics that no amount of fuel during the run will fix. The current model's "Easy needs more fuel" diagnosis is directionally wrong.

The fix in this PR limits the damage of that misdiagnosis (incremental titration). The full fix is phase-aware modeling, queued in IDEAS.md as the next focused PR after this lands.

Also noted in TODO.md: the post-run hyper part of the timing hypothesis remains untested (only 1 spike sample because the investigation script disabled runBGContext to skip Scout; needs a follow-up with full BG context).

Test plan

🤖 Generated with Claude Code

…ep; add spread guard

PR #193 fixed the BG drop display unit but left the model recommending
absurd fuel rates (e.g. 90 g/h Easy from a 62 g/h baseline). Two
research-grounded changes:

1. **Spread guard:** regression requires tested fuel rates to differ
   by ≥ FUEL_STEP_GH (10 g/h). Below that, the slope fits noise across
   a narrow window and solving for "ideal drop" extrapolates absurd
   targets. Real example from Per's data: 4 g/h spread (60-64), drop
   nearly identical across groups, regression returned raw target
   143 g/h capped to 90. Falls through to extrapolation, which moves
   one step at a time.

2. **Step cap:** `MAX_FUEL_MULTIPLIER = 1.5` (50% jump per cycle)
   replaced with `current + FUEL_STEP_GH` (10 g/h max increment per
   cycle). The 50% multiplier had no clinical basis and violated
   incremental CHO titration guidance — sudden CHO increases cause GI
   distress (Costa et al. 2023 systematic review). The 10 g/h step
   matches the standard sports-nutrition gut-training increment
   (Jeukendrup & Killer 2010) and the "Rule of 15" magnitude from the
   Riddell et al. 2017 T1D exercise consensus.

`MAX_FUEL_ABSOLUTE = 90` kept — it's the gut absorption ceiling for an
untrained gut (single-source SGLT1 limit ~60 g/h, 60-90 with
glucose-fructose blends and gut adaptation; >90 only with elite-level
adaptation).

End-to-end verified against 67 real activities:
- Easy:     90 → 65 g/h (regression skipped, extrapolation result)
- Long:     56 → 56 g/h (regression still triggers, real signal across
                         12 g/h spread)
- Interval: 60 → 60 g/h (default, drop doesn't meet MIN_DROP_TO_SUGGEST)

IDEAS.md: added "Phase-Aware Fuel Modeling" — investigation while
implementing this fix surfaced that the model's bigger problem is
averaging drop rate across both pre-fuel (minutes 0-25, no fuel
landed yet) and post-fuel (minutes 25+, fuel kicking in) phases. Easy
runs are dominated by pre-fuel kinetics that more fuel cannot fix.
Worth a separate focused PR.

TODO.md: queued post-run hyper investigation — needs runBGContext data
which the debug script disabled to skip Scout.

Tests: 1431 → 1431 pass (existing regression test updated for the new
cap value 66 → 55; two new tests cover the spread guard and step cap).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
springa Ready Ready Preview, Comment May 14, 2026 0:47am

Comment thread lib/bgModel.ts Outdated
Comment thread lib/bgModel.ts Outdated
Comment thread lib/__tests__/bgModel.test.ts
Comment thread lib/__tests__/bgModel.test.ts
Copy link
Copy Markdown
Owner Author

@psjostrom psjostrom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Math is solid — traced the spread guard, step cap, and spike-penalty interaction against the new test cases, all check out. End-to-end verification against 67 activities is a strong signal.

Four polish notes inline: two comment-rot issues (suggestions provided) and two non-blocking test-coverage gaps (cap-vs-penalty pinning, spread-guard boundary at exactly 10 g/h).

… tests

Address review feedback on PR #195:

- Drop `MAX_FUEL_MULTIPLIER` reference from `capFuel` comment — the constant
  no longer exists in the codebase, so the reference would rot on merge.
- Drop the trailing "Falls through to the extrapolation path" sentence from
  the spread-guard comment — it narrates the other branch and would go stale
  if extrapolation semantics change.
- Add boundary test: spread of exactly `FUEL_STEP_GH` (10 g/h) qualifies for
  regression. Guards against a future `>=` → `>` flip.
- Add cap-vs-penalty ordering test: with raw target above the step cap,
  spike penalty subtracts from raw before the cap clamps (penalty-then-cap),
  not the other way around.
@psjostrom psjostrom merged commit e61075e into main May 14, 2026
5 checks passed
@psjostrom psjostrom deleted the worktree-fuel-rate-investigation branch May 14, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant