Developer Change Plan
This document outlines where code changes are intended to land as the integration work continues. It is meant for internal engineering coordination.
Core Loop + Algorithm
BionicLoopCore/Sources/BionicLoopCore/Algorithms/- Extend
RealBUDosingAlgorithmto map additional inputs/outputs as the C++ API evolves. - Translation logic for meal announcements is implemented (
MealAnnouncement-> Algo2015mealTime/mealSize). - Add translation logic for pregnancy targets.
BionicLoopCore/Sources/BionicLoopCore/Runtime/- Enhance
LoopRuntimeCoordinatorscheduling, retry logic, and offline fallback tracking. - Persist last‑run timestamps and loop health state.
Marjorie I/O Parity Workstream
- Detailed comparison and extracted requirements are documented in:
Docs/Analysis/Marjorie_AlgorithmIO_GapAnalysis.md- Priority implementation slices:
- Add canonical per-step telemetry persistence (
input + output + applied delivery) as a single source of truth. - Expand bridge/output mapping to include key fields needed for audit and chart parity.
- Resolve policy decision for pump-unavailable step execution (skip vs execute-with-unavailable flags).
- Add test matrix coverage for degraded CGM/pump states and step continuity.
Algorithm Update Plan (Detailed)
- Input mapping:
- Build a single Swift “input model” that mirrors the C++ structs (glucose history, insulin history, device status, user weight, target selection, meal announcements).
- Centralize unit conversions (mg/dL, U/hr, minutes) and keep them in one file to prevent drift.
- Output mapping:
- Translate the C++ recommendation into
DosingRecommendationwith explicit 5‑minute dosing output (bolus/micro‑dose), plus any flags or status codes. - Preserve algorithm status details for logging and UI visibility (e.g., “insufficient data”, “using fallback basal”, “safety limited”).
- State handling:
- Maintain a stable per‑subject state (subject ID, last run time, persistent algorithm state if required by the C++ API).
- Add a versioned “algorithm state blob” store if the C++ interface needs to persist internal state across app restarts.
- Error handling:
- Normalize error codes from the C++ layer into a small set of Swift errors; log full raw codes for debugging.
- Ensure the loop fails closed (no dosing) on malformed inputs.
- Testing strategy:
- Add a golden‑input test harness that runs fixed inputs through the C++ wrapper and checks for deterministic outputs.
- Add “missing data” tests (no CGM, stale CGM, missing pump status).
- Add regression tests for target selection and meal sizing changes.
Algorithm Changes for Protocol Requirements
- Pregnancy targets:
- Add lower targets (90/100 mg/dL) to the target selection logic and surface them in the UI and input mapping.
- Meal fraction:
- Update meal controller fraction to 90% for pregnancy configuration.
- Ensure the change is configurable per study phase if needed.
- Initialization:
- Enforce weight‑only initialization (no carb ratios or correction factors).
- Ensure default tmax remains fixed at 65 minutes.
- CGM downtime rules:
- Accept fingerstick BG as CGM input during CGM downtime.
- Use weight‑based basal early, then adaptive basal after >= 24 hours of history.
Loop Timing + Step Semantics (doWork Plan)
Operational cadence assumptions (current hardware behavior)
- CGM cadence assumption: new G7 glucose values arrive approximately every
5 minutes. - Pump transport cadence assumption: Omnipod DASH BLE disconnect/reconnect occurs approximately every
3 minutes(pod-driven). - Implication:
- Current runtime execution is triggered by new CGM readings only.
- Pump reconnects are transport/state events and must not independently advance algorithm step.
- If both G7 and DASH are out of BLE range, the app has no reliable wake/data path and cannot safely execute algorithm steps.
Legacy BUMarjorie timing behavior to mirror
MasterControllercomputes next step wall-clock as:timeOfNextStep = firstStepTime + algorithmTime * 300s- Work is driven by
doWorkand split into: - Device reads (
CGM,insulin pump,glucagon pump) - Algorithm execution scheduling
- A single cycle trigger uses an early window:
- It schedules once when
timeUntilStep <= 300sand last schedule is older than273s(300 - 27). - Practical effect: one device/algorithm scheduling pass per 5-minute cycle, approximately 27 seconds before nominal boundary.
- Step skipping is wall-clock based:
- If late beyond grace, inferred step is
floor((now - firstStepTime) / 300s) + 1. algorithmTimeis advanced to inferred step before next run.AlgorithmControlleronly increments step after a successful run:- Input uses current
algorithmTimeasinputData.time. - After success, state saves and
algorithmTime = algorithmTime + 1. - Step 0 is blocked until either CGM or BG exists.
- BLE/device-driven wake behavior exists through device notifications:
DeviceController.deviceStateChangedtriggersscheduleWorkNow.- This supports "run when awakened by BLE" even without always-on silent audio.
Current BionicLoop behavior (implemented)
- Runtime path:
LoopRuntimeEnginein app layer receives G7 state updates and callsLoopRuntimeCoordinator.doWork(cause: .cgmUpdate).Start Algoarms runtime and waits for the next new G7 reading timestamp.- Step 0 executes on that next new reading.
- Timing/cadence:
LoopRuntimeCoordinatorcomputes due step with early window:expectedStep = floor((now + 27s - firstSuccessfulStepAt) / 300s)
firstSuccessfulStepAt,lastExecutedStep, and related runtime fields are persisted inUserDefaultsLoopRuntimeStateStore.- Duplicate wake attempts within an already-executed step index are skipped with
stepNotDue. - Gate behavior:
- Step 0 requires fresh usable CGM (
missingFreshGlucoseon failure). - Step > 0 can execute with unavailable CGM input (
CGM_VALUE_NONE,-1) when CGM is stale/missing/out of range. - Pump status refresh failure executes with unavailable pump input (
deliveryState = unknown) and blocks command application. - A single in-flight guard prevents concurrent execution overlap.
- Home loop badge age states (
Active/Aging/Stale) are based onlastSuccessfulRunAt(algorithm cadence), not raw CGM timestamp age. - Meal announce behavior:
LoopRuntimeCoordinator.announceMeal(...)supports early execution of future step slots.- Borrow is only allowed when:
- current slot is not due yet (
expectedStep < nextStep) - pump availability is known (
deliveryState != unknown) - pump is not currently delivering
- next scheduled slot is within
2 * 300sfrom now
- current slot is not due yet (
- Availability checks treat missing cached pump status (
nil) as a cache-miss, not immediate signal loss; signal-loss gating is driven by policy state or explicit unknown status. - On successful borrow, execution runs immediately as
nextStepand consumes that slot. - If request occurs after slot is due/missed (
expectedStep >= nextStep), execution uses current due step (expectedStep) with meal input. - Borrow remains rejected with
cannotBorrowfor pump delivering/unknown, no anchor, or out-of-window pre-due requests. tooLatewait messaging is bounded to the immediate due-step boundary (no forced extra +5 minute delay).- This due/missed meal behavior is provisional and requires explicit team review before release lock.
- Step synchronization:
- Runtime computes
expectedStep. RealBUDosingAlgorithmis synchronized toexpectedStepbefore recommendation generation.- Bridge call still increments step by one after successful
algorithmRunStep. - Session control:
- Manual test bolus UI is removed.
Reset Algoperforms full fresh-session reset:- clears persisted algorithm state (
UserDefaultsAlgoStateStore) - clears persisted runtime cadence state (
UserDefaultsLoopRuntimeStateStore) - clears pump adapter request/delivery carry-over metadata
- clears persisted recent-step timeline used by UI
- clears persisted algorithm state (
Pump-unavailable policy alignment (BUMarjorie parity review)
- Legacy BUMarjorie behavior (documented in
Docs/Analysis/BUMarjorie.md): - Step executes even when pump is unavailable.
- Algorithm input marks pump unavailable and uses invalid request/delivery fields.
- Delivery is skipped if unavailable, but step/time still advances.
- Current BionicLoop behavior:
doWorkskips entirely when pump status refresh fails (pumpStatusUnavailable).- Decision/work item:
- Policy selected and implemented:
POLICY-B(run step with unavailable pump input and gate delivery path only). - Implementation tasks after policy decision:
- Keep
step executedseparate fromdelivery appliedin persisted runtime telemetry. - Ensure next-step input carries last request/delivery fields only when they are valid.
- Keep reconnect/recovery path deterministic and visible in logs/UI.
- Required tests:
- Pump unavailable at step boundary.
- Reconnect before next step with valid status read.
- Delivery command failure after successful status read.
- Step counter behavior under each policy.
- CGM sanitization safety:
<39and>401map toCGM_VALUE_NONE(-1) before algorithm input.
Remaining implementation plan (no silent-audio dependency)
- Keep
doWorkCGM-driven for study closed-loop operation unless requirements change. - Add explicit fallback mode state machine (degraded/offline) and user status messaging.
- Add fingerstick BG fallback path for step 0 when protocol behavior is enabled.
- Evaluate additional wake paths (BGTask, reconnect-triggered run attempts) only after policy review.
BG check doWork policy (planned, now in scope)
- Goal:
- Allow manual BG entry to drive algorithm execution when CGM-triggered execution is not viable.
- Trigger model:
- Add a dedicated wake cause (
bgCheck) from UI submit action. bgCheckexecutes the current due step (expectedStep) only.- No borrowing semantics for BG check:
- never execute a future slot early,
- never consume
nextStepahead of schedule.
- Algorithm input mapping:
- Map manual fingerstick value into algorithm BG field (
BGval/ bridge BG input field). - Preserve CGM field behavior independently (
CGMmay still be-1when unavailable/unusable). - Persist source metadata so telemetry can distinguish
manual BGvsCGMdriven inputs. - Edge-case execution requirements:
- Case A: No CGM wake events (sensor not waking app, BLE out-of-range, etc.).
- BG submit must trigger doWork so cadence can progress when policy allows.
- Case B: CGM wake events occur, but CGM values are unusable for algorithm input (stale/out-of-range/invalid).
- BG submit must still trigger doWork using BG input path.
- Case C: Step already executed for current index.
- BG submit returns
stepNotDue; no duplicate run.
- BG submit returns
- Case D: Step 0 with no fresh usable CGM.
- BG path must define whether step 0 is allowed with BG-only input and be explicitly tested/approved.
- Safety/gating:
- Keep existing pump-ready gating and command-application gating.
- BG entry does not override pump unavailable command block behavior.
- Test matrix additions:
bgCheckexecutes due step when no CGM wake.bgCheckexecutes due step when CGM wake exists but CGM is unusable.bgCheckdoes not borrow future slot.bgCheckdoes not duplicate same-step execution.bgChecktelemetry captures BG source and value correctly.
Step increment and catch-up policy (implemented)
- Source of truth is persisted algorithm state (
timeStep) plus runtime anchor metadata. expectedStepis derived from wall-clock anchor and early window (+27slead).- If runtime is late, coordinator catches up by synchronizing algorithm step before running.
- If
expectedStep <= lastExecutedStep, algorithm execution is skipped for idempotency. - On successful algorithm call, bridge increments algorithm step by one.
Testable requirements for timing behavior
TIMING-01: Step 0 gate- Given no fresh CGM, step 0 is not executed and step counter does not advance.
TIMING-01a: Step > 0 degraded CGM execution- Given stale/missing/out-of-range CGM and step index > 0, runtime executes using
CGM_VALUE_NONE(-1) input. TIMING-02: Single-step increment- One successful run advances step exactly by 1.
TIMING-03: Wall-clock catch-up- If app wakes after missing >= 1 cycle, step aligns to inferred wall-clock step before next run.
TIMING-04: No duplicate execution in one cycle- Multiple wake events inside same cycle do not cause extra step increments.
TIMING-05: CGM-driven trigger- A new G7 reading timestamp triggers
doWorkattempt within bounded latency. TIMING-06: Restart continuity- After app quit/relaunch, scheduler restores prior step timeline and does not reset to step 0.
TIMING-07: Freshness gate (step 0)- If latest CGM exceeds configured max age before first successful step, run is skipped and reason is logged.
TIMING-08: Delivery feedback continuity- Next-step input contains prior request time/units requested/units delivered when available.
TIMING-09: Observability- Each attempt logs: cause, expected step, persisted step, run/skip reason, and post-run step.
TIMING-10: Pump reconnect non-trigger- Pump reconnect events alone do not trigger algorithm execution.
TIMING-11: Foreground non-trigger- App foreground/active transition alone does not trigger algorithm execution.
TIMING-12: CGM-step coupling- With CGM values at ~5-minute cadence, algorithm executes at most once per 5-minute step index.
TIMING-13: Reset fresh start- After
Reset Algo, next run starts at step 0 with no carried-over request/delivery metadata. TIMING-14: Out-of-range hard gate- Step 0 still requires usable CGM; for step > 0, unavailable CGM and unavailable pump execute in degraded mode with no command application.
TIMING-15: Fallback transition (planned)- After configured outage threshold (for example
>= 15 minuteswithout valid CGM + successful run), loop transitions to offline/degraded mode and emits user-visible status. TIMING-16: Fallback recovery (planned)- Once connectivity/data gates recover, loop exits offline/degraded mode and resumes normal step scheduling without resetting step timeline.
TIMING-17: Meal borrow window and consumption- Two immediate meal borrows are allowed when within 10 minutes of future slots, third is rejected.
- A borrowed step does not execute again at its original wall-clock slot.
TIMING-18: Meal borrow rejection paths- Borrow is rejected for pre-due out-of-window requests, pump delivering, pump unknown, or missing anchor.
TIMING-19: Meal announce on due/missed slot (provisional)- If meal announce is requested after a slot is due/missed, runtime executes meal on current due step (
expectedStep) rather than rejecting for late borrow. - Requires team/clinical sign-off before release behavior lock.
TIMING-20: Delivery-state auto-clear visibility- While pump reports
delivering, status is polled and Home/meal gating clear to non-delivering state without user entering pump settings. TIMING-21: BG check due-step execution only- BG-triggered doWork executes only current due step (
expectedStep) and never borrows a future slot. TIMING-22: BG check rescue path for CGM wake failure- If CGM wake does not occur, BG entry can still trigger execution (subject to other gates).
TIMING-23: BG check rescue path for invalid CGM data- If CGM wake occurs but CGM input is unusable, BG entry can still trigger execution with BG input mapping.
OmniBLE Integration
BionicLoopCore/Sources/BionicLoopCore/Ports/- Keep
PumpServiceinterface stable forOmniBLEPumpManageradapter use. BionicLoop/Integrations/Pump/- Maintain
PumpServiceAdapterand reconciliation of pump history usingdosesForStorage()after reconnect. BionicLoop/Features/Pump/- Wire Pump UI entry points, pairing flow, and status display.
G7 Integration
BionicLoopCore/Sources/BionicLoopCore/Ports/- Keep
CGMServiceinterface stable forG7SensorKitadapter use. BionicLoop/Integrations/CGM/- Maintain CGM manager adoption/restore flow and sensor lifecycle edge handling.
BionicLoop/Features/CGM/- Continue onboarding/settings UI integration and edge‑case handling.
Offline/Fallback Behavior
BionicLoopCore/Sources/BionicLoopCore/Runtime/- Add offline mode state machine (trigger at >= 15 min without valid CGM + algorithm).
- Define and enact offline basal via DASH.
BionicLoop/Features/Home/- Show offline mode banner and recovery messaging.
User Alerts (Planned)
BionicLoop/Runtime/- Add alert normalization service that consumes runtime/policy events and produces canonical alert objects.
BionicLoop/Integrations/Pump/- Map Omni alert/state events into normalized alert types with severity and dedupe keys.
BionicLoop/Integrations/CGM/- Map G7 alert/state events (sensor reliability, disconnect duration, critical glucose conditions) into normalized alert types.
BionicLoop/Features/Home/and settings/modals- Add consistent alert presentation surfaces and acknowledgment/clear interactions.
- Cross-cutting
- Add alert precedence/debounce policy and trace to
SRS-ALERT-*andTV-ALERT-*.
Persistence + State Restore
BionicLoopCore/Sources/BionicLoopCore/Persistence/- Persist loop state, last successful run, and last known device status.
BionicLoop/Integrations/- Restore device sessions on app launch and avoid unnecessary re‑pairing.
Home Charts (Scout Reuse Plan)
Legacy chart code review summary
- Source reviewed:
/Users/jcostik/Scout/evan2020/Charts/EGVChart.swift/Users/jcostik/Scout/evan2020/Charts/InsulinChart.swift- Keep:
- Lightweight SwiftUI rendering (no heavy chart framework dependency).
- Drag-to-scrub interaction pattern.
- Time-axis labels and glucose visual zones from
EGVChart. - Refactor:
- Remove dependency on Scout
ChartData/Egvmodel. - Remove legacy interpolation assumptions tied to remote API payload shape.
- Convert insulin bars from equal-spacing to time-based x positioning so insulin and CGM align on the same timeline.
Current implementation status
- Implemented in
HomeView: - Shared-range CGM + insulin overlay chart with aligned x-axis placement by timestamp.
- Time window selector (
4h,8h,12h,24h). - Scrub pills above chart (
CGM,Dose) driven by chart scrub position. Dosescrub output includes0.0Ucases and time output even when no nearby insulin step exists.- Recent step timeline moved into
Bionic Loop Settings -> Recent Dose Steps. - Styling/interaction state now implemented:
- Dashed rounded plot border with matching corner radius.
- Left/right y-axis labels anchored to chart container edges.
- Continuous scrub line tracking across chart area.
Single source of truth for Home chart/list data
- Implemented:
LoopTelemetryStorein app layer as Home chart/list projection source.- Persist rolling CGM and step telemetry windows in
UserDefaultsJSON. - Runtime now writes executed-step telemetry from
DoWorkResultand reconciles with pump status updates. - G7 view model ingests both current reading and persisted history into telemetry store.
- Canonical records:
CGMPoint: timestamp, glucose mg/dL, trend (optional), source (live,backfill).DosePoint: algorithm step, requested units, delivered units, request timestamp, delivered timestamp, status.StepContext: step, CGM used by algorithm (value + timestamp), loop cause.- Home UI (charts + step list) reads from this store.
- Runtime/pump/CGM adapters write to this store; Home must not compute independent state copies.
Ingestion paths
- CGM stream:
- Capture readings in
AppCGMManagerDelegate.cgmManager(_:hasNew:)fromCGMReadingResult. - Persist every valid sample used for display, not just the latest sample.
- Algorithm/runtime stream:
- On executed step, write
StepContext(step index, CGM used, execution timestamp, wake cause). - Pump stream:
- Continue reconciling delivery via
PumpStatusObserver+PumpServiceAdapter. - Upsert
DosePointby step ID so requested vs delivered can evolve as pod finalizes bolus.
UI composition plan
- Replace current "Recent Dose Steps" ad-hoc rendering with projection from telemetry store.
- Add two Home chart components backed by the same projection:
EGVChartView(ported/refactored from ScoutEGVChart).InsulinDoseChartView(ported/refactored from ScoutInsulinChartwith time-based x-axis).- Keep text list under charts for quick debugging:
- Step, requested/delivered units, step timestamp, CGM value/timestamp used.
- Scrubbing behavior:
- CGM chart scrub shows glucose + absolute timestamp.
- Insulin chart scrub shows delivered/requested units + step + absolute timestamp.
Acceptance requirements (testable)
CHART-01: Single-source projection- Home chart data and Home step list are generated from the same telemetry store snapshot.
CHART-02: CGM history persistence- After app relaunch, Home chart still renders recent CGM points without requiring a new read first.
CHART-03: Dose reconciliation update- When pod delivery status transitions (in-progress -> finalized), existing step point is updated in place (no duplicate step rows).
CHART-04: Step reset behaviorReset Algoclears telemetry tied to prior session; next session begins with step 0 and fresh chart/list history.CHART-05: Time-axis alignment- CGM and insulin charts place points/bars by timestamp, not by array index.
CHART-06: Zero-dose visibility- Step list/chart still include executed steps with
0.0Udelivery so skipped/no-dose behavior is visible.
Rollout order
- Done: Add telemetry store models and persistence schema.
- Done: Move Home chart/list reads to telemetry store snapshot.
- Done: Wire ingestion from CGM + loop runtime + pump reconciliation.
- Done: Add store unit tests (
LoopTelemetryStoreTests) and coordinator telemetry tests. - Pending: Add UI smoke tests for range switching and scrub behavior with sparse CGM/dose data.
Testing
BionicLoopTests/- Add unit tests for loop scheduling, offline fallback, and reconciliation logic.
BionicLoopUITests/- Add onboarding flow smoke tests for G7 and DASH.
Docs
Docs/Architecture/Architecture.mdandDocs/Requirements/Requirements.mdshould be updated alongside any new scheduling or device‑integration behavior.