Skip to content

[Part 5/6] feat(calibration): show current reading, gate on fresh flat reading, warn on large deviations - #5

Open
ETolboom wants to merge 6 commits into
trioneer-dev:mainfrom
ETolboom:feat/calibration-safety
Open

ETolboom wants to merge 6 commits into
trioneer-dev:mainfrom
ETolboom:feat/calibration-safety

Conversation

@ETolboom

@ETolboom ETolboom commented Sep 24, 2026 •

Copy link
Copy Markdown

Add calibration-safety to the calibration screen with explicit gating:

  • Current reading shown next to the entry field, in the user's display unit ("—" when none).
  • Large-deviation warning: an entry ≥40 mg/dL away from the current reading asks for confirmation ("Send Anyway") before submitting.
  • Live updates via a new G6CalibrationViewModel observing manager state, plus a 30 s tick so the freshness gate can expire while the screen is open (state updates only arrive with a reading).

Stacked on #4; only the top commit is new.

Rules: when calibration is allowed

Calibration teaches the sensor from the difference between a fingerstick and the current sensor reading, so sending one only makes sense when that comparison is fair. The Send button is enabled only when all of these hold:

  1. A reading exists and is fresh: readings arrive every 5 minutes; one missed reading closes the gate (5.5-minute freshness window, the extra 30 s absorb delivery jitter so a slightly late reading doesn't flicker the gate).
  2. The trend is flat: |rate| < 1 mg/dL/min, via the shared G6TrendArrow bucketing from [Part 4/6] fix(glucose): match the official trend-arrow bucketing, classify transmitter model in core #4. A moving reading can't be compared fairly against a fingerstick.
  3. The entry parses and is within 40–400 mg/dL (pre-existing rule).

When blocked, an inline callout explains which rule fails ("No recent sensor reading" / "Glucose is changing, wait for a flat trend arrow (→)").

Screenshots

Calibration blocked (warmup) Calibration available BG Entry BG Deviation Warning
image image image image

Copilot AI lite review requested due to automatic review settings September 24, 2026 13:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ETolboom ETolboom changed the title [Part 5/N] feat(calibration): show current reading, gate on fresh flat reading, warn on large deviations [Part 5/6] feat(calibration): show current reading, gate on fresh flat reading, warn on large deviations Sep 24, 2026
OSLog.log handed the [CVarArg] array to os_log for 6+ arguments, which
reads garbage pointers for each %@ and segfaults in
_os_log_fmt_flatten_object_impl. The seven-argument "Backfill
acknowledged" line hit this on every backfill, crash-looping the app.

The fallback now asserts in debug and logs the pre-rendered line as a
single argument in release. The backfill ack line is collapsed to one
argument.
…ensor work in the simulator

Extract the pending-command logic into a pure CommandQueue value type
(supersede-on-enqueue, 5-minute stale-calibration drop, raw-value
persistence round-trip) with unit tests, and mirror didComplete into
enqueue on the simulator, where no link will ever drain the queue.

Also: stop simulated readings once no session is active, and mark
Locked.mutate @discardableResult (both copies).
…ard, discovery timeout

PeripheralManager.didUpdateValueFor: bind the condition's characteristic
and compare with == instead of shadowing the parameter (any
characteristic's update could satisfy a pending command's condition),
and forward unclaimed notifications to the delegate even while a
command is in flight (backfill frames were dropped mid-command).

didComplete(.stopSensor): emit a .sensorEnd persisted event before
clearing the session dates; the simulator drain path mirrors it.

transmitterExpirationDate stays nil until the first version read: the
fallback lifetime is a guess and a 180-day Anubis would alert as
expired at day 90.

Service discovery timeout 2s -> 10s. Battery re-arm reads the persisted
state.lastBatteryReadDate instead of a transient ivar that was nil after
every relaunch. UInt16(clamping:) for the persisted expiry-days Int
fallback, matching G6CGMManagerState.
…smitter model in core

The old thresholds (..<(-3) style) put a boundary rate like exactly
-3.0 mg/dL/min on the single-down arrow where the official apps and
CGMBLEKit put it on the steeper one, and mapped implausible rates
(|rate| > 8, reachable over the wire as Int8 tenths) to a triple arrow
where the apps show none.

The bucketing now lives in G6SensorCore as G6TrendArrow (LoopKit-free,
raw values matching GlucoseTrend for a one-line bridge) so it is covered
by swift test, and all three call sites (glucoseTrend(for:),
G6GlucoseDisplay.trendType, settings trend symbol) share it.

Also moves the transmitter-model prefix classification (8.. G6, 5../C..
ONE) into core as G6TransmitterModel with TransmitterID.model, so the
mapping is unit-tested; deviceModel keeps returning the display string.
…warn on large deviations

The calibration screen now shows the current sensor reading next to the
entry field and only allows sending a calibration when it can be
compared fairly: the latest reading must be fresh (one missed 5-minute
reading closes the gate, with 30 s of jitter tolerance) and the trend
must be flat (|rate| < 1 mg/dL/min, via the shared G6TrendArrow
bucketing). Blocked states explain themselves with a callout; the Send
button greys out.

A change of 40 mg/dL or more from the current reading asks for
confirmation before sending.

A new G6CalibrationViewModel observes manager state so the reading
updates live; a 30 s tick lets the freshness gate expire while the
screen is open (state updates only arrive with a reading). Entry
parsing is now a single computed property shared by the deviation math
and submit, so the warned-about value is exactly the value sent.
@ETolboom
ETolboom force-pushed the feat/calibration-safety branch from 4a82ec9 to 4347d13 Compare September 29, 2026 10:57
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.

2 participants