Repository navigation
Emit calibrations as manual glucose; stop marking display-only readings user-entered - #102
Merged
Merged
Conversation
ETolboom
force-pushed
the
g7-calibration-manual-glucose
branch
from
October 25, 2026 18:09
1b3f855 to
1db9e1e
Compare
calibrate(glucose:at:) now also emits the entered meter value as a user-entered NewGlucoseSample, timestamped when it was taken, so the app can store it as the manual blood glucose reading it is (Trio charts user-entered samples as MBG, per nightscout/Trio#1472). The bounds answer's lastGlucose is no source for it: captured traces (xDrip4iOS, DexcomG7CalibrationTests) show the field stores the most recent calibration's value through and beyond processing, but a record can be stale or another display's. Its documentation is corrected to match those traces (the DexKit layout's lastEGV name notwithstanding), and G7CalibrationRecord is documented as the keeper of the entered value.
ETolboom
force-pushed
the
g7-calibration-manual-glucose
branch
from
October 25, 2026 18:19
1db9e1e to
cc8a0f7
Compare
ETolboom
marked this pull request as ready for review
October 25, 2026 18:28
A reading the sensor shifts for display while settling a calibration is a device reading: isDisplayOnly carries that. Marking it user-entered mislabels it as a manual entry downstream (stored as MBG in Trio, excluded from CGM staleness in Loop, typed meter on Nightscout upload, HKMetadataKeyWasUserEntered in HealthKit).
This was referenced Oct 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two calibration-related fixes:
Emit entered calibrations as manual glucose readings.
calibrate(glucose:at:)now also emits the entered meter value as a user-enteredNewGlucoseSample, timestamped at the time it was taken, so apps can store it as the manual blood glucose reading it is. Loop stores it like a fingerstick entered in the UI; Trio charts user-entered samples as MBG (fix: store calibrations as manual fingersticks nightscout/Trio#1472). The sample is emitted at entry, before and regardless of the sensor's answer: the fingerstick is a measurement in its own right even when the sensor later refuses the calibration. Its sync identifier lives in its own namespace so it can never dedup-collide with sensor readings.Stop marking display-only readings as user-entered. Live and backfill readings now carry
wasUserEntered: false;isDisplayOnlyalready carries the "shifted for display while settling a calibration" meaning. The oldwasUserEntered = glucoseIsDisplayOnlymapping is a G5-era idiom that mislabels device readings as manual entries.Also included: the
G7CalibrationBoundsMessage.lastGlucosedocumentation now matches what the field actually holds (the stored value of the most recent calibration, from any display; never the source for what this app entered, since a record can be stale or another display's).Why
A Trio tester reported: entered a fingerstick of 72 as a G7 calibration, and two cycles later Trio showed 61 as an MBG: the sensor's post-calibration settling value, not the entered one. Two bugs combined:
0x10), and the old mapping turned those into user-entered samples, which Trio stores as MBG.Evidence
Field logs from the reporter's device
Logs from the tester's Trio install (pre-fix build), calibration of 163 mg/dL entered at 17:45:57:
This shows both bugs in one trace: nothing is recorded when the user enters the calibration, and the display-only-flagged backfill reading produced by the accepted calibration is what gets stored as a "calibration" (MBG), twice, because backfill re-delivers it on the next cycle. Here the settling value happened to equal the entered 163; in the original report (entered 72, MBG 61) it did not.
Supplementary: packet capture
The flag behavior is also visible on the wire, from a PacketLogger capture of a calibration session against a real sensor:
The reading right after an accepted calibration carries a transitional value (154, between the old 161 and the entered 146) flagged display-only. A control capture where the calibration got a different response (
34 0003 06) shows the next reading keeping0x0f, so the flagging tracks the accepted-calibration path.The kit's own test suite has carried such a frame since 2022:
testG7MessageDataWithCalibrationdecodes byte 18 =0x1f(bgTriggered) with a live glucose value. The new tests pin both behaviors: the entered calibration is emitted as a user-entered sample at entry time, and a display-only reading arrives asisDisplayOnly: true, wasUserEntered: false.What changes for Loop
The two fixes have very different blast radius.
Fix 2 (settling readings no longer marked user-entered) is dosing-neutral. Nothing in the dosing path filters on
wasUserEntered: momentum (GlucoseMath.linearMomentumEffect) and insulin counteraction effects (counteractionEffects) already exclude these readings viaisDisplayOnly, and the prediction start uses the latest sample regardless of origin. What changes is reporting and staleness:GlucoseStore.getLatestCGMGlucoseis the only consumer that filterswasUserEntered == NO; settling readings now count as CGM data, so a calibration no longer makes glucose look stale during its settling window.sgv) instead of meter (mbg);isCalibrationstill followsisDisplayOnly.HKMetadataKeyWasUserEntered.Fix 1 (the entered calibration is reported to the app) does affect dosing, identically to a fingerstick entered in the Loop UI. The sample joins the glucose history handed to the algorithm, which never filters on
wasUserEntered:hasSingleProvenancestill holds). In the common accepted-calibration case this is moot: the settling readings in that window are display-only, which already disables momentum viacontainsCalibrations(). If the sensor rejects the calibration there are no settling readings, and the entered value does move the regression slope.counteractionEffectsdoes not filter user-entered samples, so with RC enabled the calibration pairs with neighboring sensor readings (same provenance, more than 4 minutes apart) and shifts the observed discrepancy around calibration time.Whether dosing should consume the reported calibration is an app-level policy decision: the kit reports the event, the app decides what to do with it. Loop can exclude
wasUserEnteredsamples from its prediction input if it wants to dose purely on sensor data; Trio deliberately charts and doses on MBG entries (nightscout/Trio#1472), where this is the requested behavior.Tests
Full G7SensorKit suite green (198 tests) against LoopKit next-dev.