Skip to content

Fix negative IOB on fresh installs (#898)#903

Merged
dnzxy merged 15 commits intodevfrom
fix-negative-iob-after-onboarding
Mar 20, 2026
Merged

Fix negative IOB on fresh installs (#898)#903
dnzxy merged 15 commits intodevfrom
fix-negative-iob-after-onboarding

Conversation

@dnzxy
Copy link
Copy Markdown
Contributor

@dnzxy dnzxy commented Dec 22, 2025

Summary

This PR fixes cases where negative IOB can occur when pump history contains a resume without a preceding suspend (e.g. fresh installs of newly onboarded Trio users).

Fixes: #898

Changes

The logic now detects resume-only states within the DIA (duration of insulin action) window and injects a simulated suspend event 1s before the resume only if no real suspend exists in the relevant history window. This gives oref a safe baseline without altering valid pump history.

@marionbarker
Copy link
Copy Markdown
Contributor

Test

Summary: TBD

Configuration

  • Use an older version of the pod rPi simulator (commit 4e2a622) that does not properly report pods as suspended in certain cases.
  • Use an older version of OmniBLE (commit b65dbfc2e) so can present resume without suspend.

Test 1

Delete Trio for SE 3rd gen phone running iOS 26.

  • build Trio dev (0.6.0.31) (with modified OmniBLE) to the phone
  • accept all default settings except set basal rates to 1 U/hr, CR to 10 g/U and ISF to 50 mg/dL/U
    • for the initial attempt I left basal at 0.1 U/hr and the negative IOB was -0.21; so started over with higher basal rates

Add Glucose simulator as CGM and rPi DASH as pump.
Observe IOB

Time IOB Comment
14:16 0 pod connected
14:19 -1.56 closed loop disabled
14:24 -1.56 flip from disable to enable
14:25 -1.51 first loop, TB set
14:32 -1.41 end test

Test 2

Delete Trio for SE 3rd gen phone running iOS 26.

  • build Trio fix-negative-iob-after-onboarding, commit 6e5e780, with modified OmniBLE to the phone
  • accept all default settings except set basal rates to 1 U/hr, CR to 10 g/U and ISF to 50 mg/dL/U

Add Glucose simulator as CGM and rPi DASH as pump.
Observe IOB

Time IOB Comment
14:42 0 pod connected
14:51 -1.56 closed loop disabled

no difference. Make sure I really have the new build.
Do a clean build folder, close and open workspace
rebuild and no difference.

Do another delete Trio and rebuild again.
Repeat the test.
Same result.

@dnzxy dnzxy marked this pull request as ready for review December 27, 2025 03:32
@dnzxy dnzxy requested a review from kingst December 27, 2025 03:32
@marionbarker
Copy link
Copy Markdown
Contributor

Test Summary

Initial look is good - no unexpected negative IOB after pump and CGM are attached to a fresh build.
I'll let this phone run in closed Loop for a while to see if unexpected negative IOB show up later in the history.

Test 3

Earlier tests were done before the PR was ready for testing.

Delete Trio from SE 3rd gen phone running iOS 26.

  • build Trio commit b7d42e3 (this pr) to the phone
  • accept all default settings except set basal rates to 1 U/hr, (SMB are disabled)

Add Glucose simulator as CGM and rPi DASH as pump.

  • TBR = temp basal rate
  • SBR = scheduled basal rate

Observe IOB

Time IOB Comment
19:46 0 rPi pod connected
19:50 0 first new CGM reading, closed loop still disabled
19:57 0 flip from closed loop from disable to enable
20:05 0 looping, TBR (0.55 U/hr) < SBR (1.0 U/hr) because of glucose history
20:08 -0.05 U looping, this small negative IOB is because TBR still 0.55 U/hr
20:13 -0.1 U looping, TBR is now same as SBR, 1.0 U/hr
20:23 -0.1 U looping, TBR still same as SBR, 1.0 U/hr

@dnzxy dnzxy marked this pull request as draft January 27, 2026 22:47
Copy link
Copy Markdown
Contributor

@kingst kingst left a comment

Choose a reason for hiding this comment

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

This review is based on code review only, I haven't tested this.

In general, this is a real bug and we should fix it, thank you for driving this.

I'd like to suggest two alternatives that might simplify things. Conceptually, I think it's better to remove orphaned resumes vs injecting a suspend since it'll simplify the logic.

One aspect that is important is ensuring that oref gets consistent behavior across all functions that operate on pump events: meal, autosens, and iob. So any time any of these functions pull pump events, the orphaned resume should be filtered out.

I would define an invariant that is simple, something like: "any resumes that don't have a preceding suspend within DIA or 24h time duration are orphaned resumes" and filter these out. It'll probably require pulling more pump events but I don't want to have a case where IoB doesn't have a resume and then it shows up later in meal.

In terms of the way to implement this, I can see two possibilities:

  • Filter in memory for all oref functions
  • Filter when new pump events arrive and don't store orphaned resumes

Neither is perfect so I don't have a strong opinion one way or the other about how we implement this.

@marionbarker
Copy link
Copy Markdown
Contributor

Test Summary

This test was performed on 2026-01-29. It was successful. Using code from this PR prevented negative IOB for a new installation.

Test Details

These are my raw notes:

Do a new test with dev.

Take a "pump break" for several hours.

To do this, must use a real pump. So set up 2 phones with MDT pumps.

2026-01-26 10:05 - end the Eversense Trio test on the 2nd gen SE phone.

was running experimental/medtrum-eversense branch from Mike Plante
ee490c6 (HEAD -> experimental/medtrum-eversense, origin/experimental/medtrum-eversense) Merge branch 'feat/dev-eversense' of https://github.com/bastiaanv/Trio into experimental/medtrum-eversense
ef1719a (origin/dev, origin/HEAD, dev) CI: Bump APP_DEV_VERSION to 0.6.0.44 [skip ci]
Eversense : 2204e62 (HEAD) wip

Set up Nightscout as CGM for both phones.

For red 2nd gen SE, do not allow any uploading to NS.
For black 3rd gen SE, allow uploading to NS.

Set up Therapy settings for both phones to be identical for Trio.
Check what I did for the PR and repeat that.

build fresh copies of dev on each phone and let the pump / phone / app come to equilibrium.
Modify the uploaded CGM values to be flat.

cgm cgm_steady_state_110_two_days.txt 299 $ns_url_test $api_secret;

Therapy settings: all default settings except set basal rates to 1 U/hr, CR to 10 g/U and ISF to 50 mg/dL/U

fiasp insulin selected
maxIOB = 10
maxbolus = 10
maxbasal = 2
target 110
basal rate 1.0 U/hr (single rate)
carb ratio 15
ISF 54
SMB & UAM; 90/90 minutes

at 10:30 build: 576d323 (HEAD -> dev, origin/dev) CI: Bump APP_DEV_VERSION to 0.6.0.46 [skip ci] on each phone.

disconnect each phone from xcode.

The 3rd gen phone has not been running anything for a long time.
It comes in at -1.5 U IOB intiially and starts bolusing

The 2nd gen phone was being used for an eversense test using a DASH rpi pump.

Let them come to equilibrium over the next 2 hours.

Got distracted with other things.

2nd gen - delete the pump at 16:00
3rd gen - suspend the pump at 16:00

Time 2nd gen 3rd gen
16:00 IOB -0.01 U IOB -0.01 U
16:02 delete pump suspend the pump
17:30 IOB -0.01 U IOB -1.04 U
20:40 IOB 0.00 U IOB -1.18 U
20:42 Pair DASH Resume MDT
Time IOB Dose IOB Dose
21:03 -0.15 TBR -1.01 0.18
21:08 -0.10 TBR -0.95 TBR, 0.175
21:15 -0.05 TBR -0.60 TBR, 0.075
21:20 rebuild PR 903
21:28 -0.19 rPi quit/restart -0.37 TBR

merge dev into fix-negative-iob-after-onboarding, local name: dev_merge_into_pr903

Build this onto the 2 phones and let them stablize overnight.
The rPi had quit for a couple hours before I checked this morning, so just restarted it.

2026-01-30

order: 2nd Gen (DASH), then 3rd Gen (MDT)

Time IOB Dose IOB Dose
06:08 -0.03 TBR 0.00 TBR
06:10 -0.03 suspend 0.00 delete pump
06:52 -0.50 suspended 0.00 no pump
08:19 -0.98 suspended 0.00 no pump
08:20 -0.98 resume 0.00 add DASH
10:40 -0.06 TBR+SMB in past -0.12 TBR
10:45 - - - delete app
10:56 - - - new app, pair pod & insert
11:00 -0.00 - TBR

Copy link
Copy Markdown
Contributor

@marionbarker marionbarker left a comment

Choose a reason for hiding this comment

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

Approved by test

@dnzxy dnzxy marked this pull request as ready for review February 13, 2026 15:10
@dnzxy dnzxy linked an issue Feb 24, 2026 that may be closed by this pull request
@dnzxy
Copy link
Copy Markdown
Contributor Author

dnzxy commented Feb 24, 2026

@kingst as discusses (privately), I have synced this PR with latest dev and extended the inline comments to document the remaining edge cases. This PR should be good to go then from my side. Feel free to review again and merge, if it's good.

@marionbarker no additional testing needed; we only synced this with dev and added comments.

This isn't a real algorithm yet, it's just a sketch to demonstrate a
concept. I think this change simplifies the logic and handles the edge
cases, but I don't know enough about the behavior of non omnipod pumps
to know if this change is the right one to make.

It definitely handles two cases:

  - new users

  - pump breaks
@marionbarker
Copy link
Copy Markdown
Contributor

Test Summary

Success
✅ The IOB for a new instance of Trio remains near 0

Configuration

SE 2nd gen phone running iOS 18.7

  • Delete Trio from the phone
  • Install a fresh build using the branch from this PR: commit 137fddf
  • Onboard the fresh app

Therapy settings (default except for basal rates) and maxIOB:

  • target 110 mg/dL
  • basal rate 2 U/hr
  • CR 15 g/U
  • ISF 200 mg/dL/U
  • maxIOB = 10
  • maxbolus = 10
  • maxbasal = 6
  • SMB & UAM; disabled

CGM: Nightscout: Use constant CGM value of 110 mg/dL
Pump: Connect to MDT 515 pump.

Note - this was my second test tonight. First one I did not have the flat rate (used glucose simulator) and I had SMB/UAM enabled.
Made it too hard to separate effects.
The history from that test was read from the MDT (SMB of 0.2 at 20:55).

Observe IOB

Time IOB TBR Reservoir Comment
21:10 0 - - MDT connected with signal loss
do a suspend/resume, then wait
21:14 0 - - see MDT data on main screen
enable closed loop
21:17 0.19 0.0 170.9 green loop
21:21 -0.01 2.0 170.9
21:27 0.04 2.0 170.7
21:32 0.03 2.05 170.6
21:37 0.03 2.05 170.4
21:42 0.03 2.05 170.2

Looking back at the initial test (see #903 (comment))

  • Without this fix, the negative insulin was about 1.5 times the scheduled basal rate after 10 minutes.
  • ✅ This test running essentially 0 IOB with a scheduled basal rate of 2 U/hr after 30 minutes

Copy link
Copy Markdown
Contributor

@marionbarker marionbarker left a comment

Choose a reason for hiding this comment

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

Approve by test

Copy link
Copy Markdown
Contributor

@marionbarker marionbarker left a comment

Choose a reason for hiding this comment

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

I did another test and am retracting my earlier approval. I'll post the test next.

@marionbarker
Copy link
Copy Markdown
Contributor

Test

❌ This test failed in that a significant negative IOB was reported.
❓ I did not see excessive amounts of insulin delivered but the rPi quit almost immediately
❓ I did not try entering carbs to see what recommended dose was
✅ It looks like the negative IOB was allowed to decay

Configuration

Continuing the test from #903 (comment)
@kingst requested a few more tests. When preparing for the additional testing, I ran into a significant report of negative IOB going from Medtronic to DASH.

SE 2nd gen phone running iOS 18.7

  • Continue with build using the branch from this PR: commit 137fddf
  • Pump MDT
  • CGM constant glucose trend of 110 mg/dL

Narrative

This test was performed 2026-02-27 starting just past 8 am PST.

Time Action IOB Comment
08:10 Delete MDT 0.0 Pump does not delete cleanly, took me to select insulin screen; I manually returned to add pump
08:15 Add DASH 0.0 pair, prime and insert using rPi DASH simulator
08:20 - -4.64 ❌ not desired behavior - on the other hand, this was allowed to decay
08:23 rPi quit did not notice rPi had quit but Trio had commanded 2 U/hr TB for 2 hours
11:02 rPi resume -0.54 the temp basal commanded was less than Scheduled Basal - continue for next half hour
12:38 Deactivate pod -0.15 Plan leave Trio running for 24 hours with no pod active for the next test

Screenshots

Graphic below shows

  • left: the main screen when MDT was deleted and DASH was added
  • middle: the history right after adding DASH
  • right: the history right after deactivating DASH
trio-pr903-bad-behavior

Log File

zip today's log file

20260227_add_DASH_after_MDT_pr903_log.txt.zip

@marionbarker
Copy link
Copy Markdown
Contributor

Test

✅ The 24 hour pump gap test and the long suspend test appear nominal.

Test 24 hour gap

Test results for adding a DASH pump more than 24 hours after last pod was deactivated.

Scheduled basal for therapy settings is 2.0 U/hr.

Time IOB comment / Time since last loop
13:48 0 TBR
13:53 0 -
13:59 0 -
14:04 0 2.15
14:08 0.05 2.15
14:14 0.05 2.15
14:20 0.05 2.15
14:22 0.05 2.10
14:29 0.05 2.10

Test Suspend

Suspend the pod.

  • Expect negative IOB to accumulate
  • When insulin delivery is resumed, expect high TBR to restore the missing insulin
Time IOB comment / Time since last loop
14:30 0.05 -
14:46 -0.46 -
15:02 -0.94 -
15:11 -1.13 resume, sch basal 2 U/hr
15:12 -1.26 3 U/hr
15:13 -1.26 3 U/hr

@marionbarker
Copy link
Copy Markdown
Contributor

marionbarker commented Mar 1, 2026

Test Summary

The odd behavior of massive negative IOB did not recur.

Medtronic Testing

Continue from the previous comment. This test took place on 2026-02-28.

For this deletion of the MDT pump, I came from the Devices, Pump, MDT screen to delete the pump instead of coming from the Main screen. I went straight to the add pump from there without returning to the main screen.

Time IOB basal rate comment / Time since last loop
15:13 -1.26 3 U/hr nominal
15:54 -0.58 2 U/hr pod connected
15:55 -0.01 2 U/hr manual bolus 0.55 U
15:57 0.0 2 U/hr deactivate pod
15:58 - - resume insulin delivery on MDT pump (suspended for >24 hours
15:59 0.0 no signal add MDT 515 pump
15:59 - - suspend, then resume
16:00 -0.08 2 U/hr <1 m
16:08 -0.01 2 U/hr 1 m, delete MDT, pump did delete cleanly this time
16:10 -0.01 2 U/hr just inserted cannula
16:20 0.02 1.9 ( U/hr pod disconnected at 2026-02-28T16:20:48
16:28 0.01 - deactivate pod, resume insulin on MDT
16:29 - - connect MDT
16:31 -0.09 sig loss 4 m

Let the MDT / Trio combination run overnight.

@marionbarker
Copy link
Copy Markdown
Contributor

Test Summary

I was once again not able to reproduce the error in #903 (comment)

Test Narrative

2026-02-28 at 21:48 - noticed signal loss, MDT pump not responsive, changed the battery, working again. Let test continue.

2026-03-01 08:55 Continue with the test where delete MDT and add DASH.
Tap on the MDT icon from main screen

  • delete MDT
  • returns to main screen with an add pump icon
  • tap on add pump icon
  • correctly takes me to the pump selection menu
Time IOB temp basal rate comment / Time since last loop
08:57 0.06 2.1 U/hr <1 m; delete MDT
09:00 insert cannula
09:01 0.06 2 U/hr (scheduled) return to main screen
09:01 0.05 2.1 U/hr (TBR) first loop
09:08 0.05 2.1 2 m

Once again - the odd negative IOB behavior was not repeated.
The MDT pump appeared to be deleted cleanly this time.

Start Over with Blank Slate

09:11 Deactivate the pod from the app

  • The MDT still has 15 minutes left of 2.1 U/hr TBR - allow the current TBR to complete and then scheduled basal to be restored.

09:13 Delete the Trio app from the phone.

  • Wait a couple of hours to let history progress on the MDT>

New Test, Fresh Trio build

oops - I planned to do the fresh trio build test but got busy on other tasks.

However, on 12-March-2026, I installed Trio dev onto a phone with no app for PR 975 and saw the large negative IOB.

@marionbarker
Copy link
Copy Markdown
Contributor

Test Summary

Delete Trio from test phone.
Build commit d6a122a on test phone.

✅ Initial test - new pump added to fresh install successful. No indication of negative IOB.

Configuration

  • iPhone SE 2nd gen running iOS 18
  • load settings from Nightscout when onboarding
    • target 90, basal rate 1.2, CR 15, ISF 50
    • all other settings at default (including IOB of 0)
  • use Nightscout as CGM with constant glucose upload to Nightscout

Omnipod DASH Testing

This test took place on 2026-03-19 at 13:00 PDT

Time IOB basal rate comment / Time since last loop
13:11 - - add rPi DASH pump - fresh build
13:12 - - insert cannula
13:13 0 1.2 U/hr initial view. scheduled basal
13:14 0 1.2 U/hr enable closed loop, set maxIOB to 10
13:16 0 1.2 U/hr the first temp basal command matches the scheduled basal rate
13:21 0 1.2 U/hr TBR

status

Let this test run for an hour and then test changing to another pump

@marionbarker
Copy link
Copy Markdown
Contributor

Test Continuation

✅ Successful test

Continue the test from comment: #903 (comment)

Configuration

update build to commit: 609bf1e

Test Narrative

Note

  • ✅ at start of this session, small positive IOB of 0.28 is expected because target is 90 mg/dL and glucose is steady around 110 mg/dL
  • ✅ after 1 hour with no pod, negative IOB is expected
  • ✅ adding new pump, expect some added insulin to be delivered
  • ✅ Observe history - see pump suspend when pod is deactivated and pump resume with MDT is connected
Time IOB basal rate comment / Time since last loop
15:39 0.28 1.05 update build, commit 609bf1e
15:40 0.28 1.05 deactivate pod, leave no pod connected
16:46 -0.85 - nominal behavior, switch to an MDT pump
MDT 515 was suspended until just before I connected it
16:50 -0.94 signal loss first main screen after connection was successful
issue a suspend then resume from the Trio app
16:51 -0.94 1.2 U/hr wait for first loop - that clears the signal loss
16:51 -0.93 2 U/hr nominal behavior to replace missing insulin for one hour without a pump
after this loop, modify max basal rate to 4 U/hr
16:56 -0.81 2 U/hr nominal behavior
17:03 -0.69 1.05 U/hr nominal behavior, successful test

Screenshots

trio-pr0903-again

Copy link
Copy Markdown
Contributor

@marionbarker marionbarker left a comment

Choose a reason for hiding this comment

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

Approve from test.

I did a cursory review and note that sonarclous wants changes. Leaving that up to the developers to handle.

@dnzxy dnzxy mentioned this pull request Mar 20, 2026
@dnzxy dnzxy merged commit 53ed629 into dev Mar 20, 2026
3 checks passed
@dnzxy dnzxy deleted the fix-negative-iob-after-onboarding branch March 20, 2026 00:26
mountrcg pushed a commit to mountrcg/Trio that referenced this pull request Apr 8, 2026
…ter-onboarding

Fix negative IOB on fresh installs (nightscout#898)
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.

Negative IOB on Initial Startup Can Lead to Excessive Insulin Delivery

3 participants