Skip to content

Commit 364c712

Browse files
Don't snooze high alarm when there is no IOB (#6348)
The Bolus Wizard Preview plugin snoozes a high alarm when its `bolusEstimate` is below the snooze threshold, treating that as "enough insulin on board to cover the high". But `bolusEstimate` is 0 in two cases that have nothing to do with IOB coverage: - the current BG sits below the profile's high target, so neither the high nor low branch of calc() runs (the exact scenario in #6348: complete profile, no insulin treatments), and - calc() bails out early (missing profile fields, missing IOB property, or stale data), leaving bolusEstimate at its initial 0. In both cases, with no insulin on board, a genuine high alarm is silenced. Because the snooze is requested at URGENT level and snoozedBy() matches by level across the group, it can also suppress other urgent alarms (including lows) for the snooze duration. Guard the snooze on `prop.iob > 0` so a high is only snoozed when there is actually insulin on board to cover it. Legitimate "enough IOB" snoozing is unchanged. Adds regression tests covering the report scenario (BG below profile high target), the stale-data bail-out path, and the unchanged enough-IOB snooze. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 17283fe commit 364c712

2 files changed

Lines changed: 65 additions & 1 deletion

File tree

lib/plugins/boluswizardpreview.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,10 @@ function init (ctx) {
107107
var ar2EventType = sbx.properties.ar2 && sbx.properties.ar2.eventType;
108108
var high = ar2EventType === 'high' || prop.scaledSGV >= sbx.scaleMgdl(sbx.settings.thresholds.bgTargetTop);
109109

110-
return high && prop.bolusEstimate < settings.snoozeBWP;
110+
// Require real IOB: a bolusEstimate of 0 also means no insulin is covering
111+
// the high (or calc bailed out on missing profile/IOB or stale data), which
112+
// must not be read as "enough IOB" and silence a genuine high alarm. See #6348.
113+
return high && prop.iob > 0 && prop.bolusEstimate < settings.snoozeBWP;
111114
};
112115

113116

tests/boluswizardpreview.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ describe('boluswizardpreview', function ( ) {
1515
var ar2 = require('../lib/plugins/ar2')(ctx);
1616
var iob = require('../lib/plugins/iob')(ctx);
1717
var bgnow = require('../lib/plugins/bgnow')(ctx);
18+
var simplealarms = require('../lib/plugins/simplealarms')(ctx);
1819

1920
function prepareSandbox ( ) {
2021
var sbx = require('../lib/sandbox')().serverInit(env, ctx);
@@ -297,6 +298,66 @@ describe('boluswizardpreview', function ( ) {
297298

298299
});
299300

301+
it('does not snooze a high alarm with no IOB when the BG sits below the profile high target', function (done) {
302+
// Faithful to #6348: a complete profile whose target_high is above the
303+
// current high reading leaves bolusEstimate at 0 even though calc succeeds.
304+
// With no insulin on board that must not be read as "enough IOB".
305+
ctx.notifications.resetStateForTests();
306+
ctx.notifications.initRequests();
307+
ctx.ddata.sgvs = [{mills: before, mgdl: 270}, {mills: now, mgdl: 273}];
308+
ctx.ddata.treatments = []; // no insulin -> no IOB
309+
ctx.ddata.profiles = [{ dia: 3, units: ctx.settings.units, sens: 100, target_high: 280, target_low: 100 }];
310+
311+
var sbx = prepareSandbox();
312+
313+
simplealarms.checkNotifications(sbx);
314+
boluswizardpreview.checkNotifications(sbx);
315+
316+
var highest = ctx.notifications.findHighestAlarm('default');
317+
should.exist(highest);
318+
should(ctx.notifications.snoozedBy(highest)).not.be.ok();
319+
320+
done();
321+
});
322+
323+
it('does not request a high snooze when calc bails out with no IOB', function (done) {
324+
// #6348: an incomplete profile (or stale data) makes calc bail out, leaving
325+
// bolusEstimate at 0 and iob unset. The snooze guard must cover that too.
326+
ctx.notifications.resetStateForTests();
327+
ctx.notifications.initRequests();
328+
var stale = now - (30 * 60 * 1000);
329+
ctx.ddata.sgvs = [{mills: stale - (5 * 60 * 1000), mgdl: 295}, {mills: stale, mgdl: 300}];
330+
ctx.ddata.treatments = []; // no insulin -> no IOB
331+
ctx.ddata.profiles = [profile];
332+
333+
var sbx = prepareSandbox();
334+
var prop = sbx.properties.bwp;
335+
336+
prop.errors.should.containEql('Data isn\'t current');
337+
should(prop.iob).not.be.ok();
338+
boluswizardpreview.highSnoozedByIOB(prop, {snoozeBWP: 0.10}, sbx).should.equal(false);
339+
340+
done();
341+
});
342+
343+
it('still snoozes a high alarm when real IOB covers it', function (done) {
344+
// Contract contrast for #6348: the guard discriminates on iob > 0, so a
345+
// genuine "enough IOB" high is still snoozed.
346+
ctx.notifications.resetStateForTests();
347+
ctx.notifications.initRequests();
348+
ctx.ddata.sgvs = [{mills: before, mgdl: 295}, {mills: now, mgdl: 300}];
349+
ctx.ddata.treatments = [{mills: before, insulin: '5.0'}];
350+
ctx.ddata.profiles = [profile];
351+
352+
var sbx = prepareSandbox();
353+
var prop = sbx.properties.bwp;
354+
355+
prop.iob.should.be.greaterThan(0);
356+
boluswizardpreview.highSnoozedByIOB(prop, {snoozeBWP: 0.10}, sbx).should.equal(true);
357+
358+
done();
359+
});
360+
300361
it('set a pill to the BWP with infos', function (done) {
301362
// BWP-TIME-001: Use fixed timestamp for deterministic IOB calculation
302363
// Using `now` instead of `Date.now()` prevents timing drift between

0 commit comments

Comments
 (0)