Repository navigation
Take plain numbers in fixed units at the public API - #17
Merged
Merged
Conversation
LoopKit currently depends on LoopAlgorithm for LoopQuantity and LoopUnit, so apps that want LoopKit without the algorithm (Trio) still have to build it. With this change LoopAlgorithm's public API uses Doubles in fixed units, so LoopKit can own its unit types and the two packages no longer need each other. - Glucose values are mg/dL (GlucoseValue.mgdl), carbs are grams (CarbEntry.grams), sensitivity is mg/dL per unit, targets and the suspend threshold are mg/dL, velocities are mg/dL per second. - LoopQuantity and LoopUnit are internal; the math is unchanged. - The LoopAlgorithm struct is renamed Algorithm, so LoopAlgorithm.X always names the module. - GlucoseSampleValue drops trendRate and condition, which the algorithm never read. - Helpers that apps don't call are internal. - LoopKit's tests of LoopMath and date rounding move here.
- AlgorithmInput's sensitivity, target and suspendThreshold become sensitivityMgdlPerUnit, targetMgdl and suspendThresholdMgdl, and the public overloads that take plain numbers label them the same way (momentumVelocityMaximumMgdlPerMinute, insulinSensitivityHistoryMgdlPerUnit, ...). A value in the wrong unit no longer passes unnoticed at the call site. Fixture JSON keys are unchanged. - run converts the schedules to quantities once instead of on each access. - Remove FixtureGlucoseSample.condition and the now unused GlucoseCondition.
- Units on every AlgorithmInput property, Algorithm.run(input:) and both generatePrediction overloads, including the parameters that had none. - README gains a usage section with the units table. - Fix doc comments that no longer matched the code: generatePrediction never returns nil, annotated(with:) takes basalHistory, filterDateInterval takes an interval, useLegacyIntegration is described, and the sensitivity window note no longer refers to mid-absorption ISF as future work.
This was referenced Nov 1, 2026
ps2
added a commit
to LoopKit/LoopKit
that referenced
this pull request
Nov 1, 2026
The dose annotation and IOB tests moved to LoopAlgorithm (LoopKit/LoopAlgorithm#17). The reconcile step of testIOBFromSuspend tests LoopKit's own reconciled(), so it stays here as testReconcileSuspend with its two fixtures.
ps2
added a commit
to LoopKit/Loop
that referenced
this pull request
Nov 1, 2026
They test LoopAlgorithm's dose annotation and insulin on board, so they now live there with their fixtures (LoopKit/LoopAlgorithm#17). The override tests stay here because they cross from LoopKit's override schedules into the algorithm.
ps2
added a commit
to LoopKit/LoopKit
that referenced
this pull request
Nov 1, 2026
* Own the shared value types instead of depending on LoopAlgorithm LoopKit depended on LoopAlgorithm only for LoopQuantity, LoopUnit and a set of small value types, so apps that use LoopKit without the algorithm (Trio) still had to build it. LoopAlgorithm now takes plain numbers in fixed units (LoopKit/LoopAlgorithm#17), so LoopKit can own these types itself. - LoopKit has its own LoopQuantity, LoopUnit, AbsoluteScheduleValue, glucose, carb and insulin value types, insulin models and dose recommendations, under the same names, so code that used them through LoopAlgorithm only changes its import. - No LoopAlgorithm package dependency in the project, Package.swift or CI; LoopKit builds and tests on its own. - LegacyDoseChart takes a ChartedDose instead of the algorithm's dose type. - Tests of algorithm code move to LoopAlgorithm (LoopMath, date rounding) and Loop (dose annotation, IOB, an override run). * Keep the suspend reconcile check in LoopKit The dose annotation and IOB tests moved to LoopAlgorithm (LoopKit/LoopAlgorithm#17). The reconcile step of testIOBFromSuspend tests LoopKit's own reconciled(), so it stays here as testReconcileSuspend with its two fixtures.
ps2
added a commit
to LoopKit/Loop
that referenced
this pull request
Nov 1, 2026
* Use LoopKit's value types and bridge to LoopAlgorithm LoopKit no longer depends on LoopAlgorithm, and LoopAlgorithm takes plain numbers in fixed units (LoopKit/LoopAlgorithm#17). Loop is now the one place that converts between the two. - AlgorithmBridge.swift: StoredGlucoseSample and StoredCarbEntry conform to the algorithm's protocols; LoopKit insulin models are wrapped for the algorithm; schedules convert to mg/dL; algorithm results convert back to LoopKit values for storage, display and the watch. - LoopKitTypeAliases.swift makes the unqualified value-type names mean LoopKit's in each target; algorithm types are written LoopAlgorithm.X. - LoopAlgorithm is linked once, in LoopCore; the widget, watch app and LoopCore's own code no longer import it. - LoopCore gets the two constants the UI used from LoopAlgorithm. - Name the new slow retrospective correction experiment in Current Setup. - Tests: an end-to-end test that mmol/L settings dose the same as mg/dL, and the dose annotation, IOB and override tests moved from LoopKit. * Map the dose direction exhaustively and test mmol/L manual boluses - The automatic dose direction maps by switch instead of rawValue, so a case added on either side fails to compile instead of crashing the loop. - Test that mmol/L settings give the same manual bolus recommendation and bolus preview as mg/dL with a pending carb entry. - Test that LoopCore's copies of the input recency interval and maximum carb absorption time match LoopAlgorithm's. - Fix the indentation of a predictGlucose call. * Move the dose history tests to LoopAlgorithm They test LoopAlgorithm's dose annotation and insulin on board, so they now live there with their fixtures (LoopKit/LoopAlgorithm#17). The override tests stay here because they cross from LoopKit's override schedules into the algorithm.
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.
LoopKit depends on LoopAlgorithm today only for
LoopQuantityandLoopUnit, so an app that wants LoopKit without the algorithm (Trio) still has to build LoopAlgorithm. This changes LoopAlgorithm's public API to plain Doubles in fixed units. LoopKit can then own its unit types, and neither package depends on the other, with no new shared package.This is the first of a set of changes. LoopKit and Loop PRs follow, and Loop becomes the only place that converts between LoopKit's types and LoopAlgorithm's (see the plan below).
API changes
quantity: LoopQuantitymgdl: Double(GlucoseValue,GlucoseEffect,PredictedGlucoseValue,GlucoseChange)quantitygrams: Double(CarbEntry,CarbStatus,AbsorbedCarbValue.observedGramsetc.)quantitymgdlPerSecond: Doublesensitivity: [AbsoluteScheduleValue<LoopQuantity>]sensitivityMgdlPerUnit: [AbsoluteScheduleValue<Double>]target: [AbsoluteScheduleValue<ClosedRange<LoopQuantity>>]targetMgdl: GlucoseRangeTimeline(ClosedRange<Double>)suspendThreshold: LoopQuantity?suspendThresholdMgdl: Double?momentumVelocityMaximum: LoopQuantity?momentumVelocityMaximumMgdlPerMinute: Double?The plain-number inputs carry their unit in the name, both on
AlgorithmInputand in the public overloads (generatePrediction,PrecomputedInsulinInput.withEffects/build, theglucoseEffectsand carb-effect overloads). That way an mmol/L value passed by mistake stands out at the call site instead of compiling unnoticed. The Loop PR adds an end-to-end mmol/L test as well.LoopQuantityandLoopUnitare now internal. Internally the math still uses them, so results are unchanged.LoopAlgorithmstruct is renamedAlgorithm, soLoopAlgorithm.Xalways names the module. Call sites becomeAlgorithm.run(input:)andAlgorithm.generatePrediction(...).GlucoseSampleValuedropstrendRateandcondition; the algorithm never read them.FixtureGlucoseSample.conditionand the internalGlucoseConditiongo too.runconverts the input schedules to quantities once at the start.insulinCorrection,recommendTempBasal, etc.) are internal. The fixture JSON format is unchanged.Plan
LoopWorkspace next-dev pins LoopKit/LoopAlgorithm e3f5e3c, so merging this to main doesn't affect it. The workspace bump that adopts this change needs these PRs too:
LoopQuantity,LoopUnitand the shared value types under the same names. It drops its LoopAlgorithm dependency and builds and tests on its own.import LoopAlgorithm. LoopKit keeps the names, so each PR just switches toimport LoopKit:Testing
swift test: 214 tests pass (main's tests plus the 26 moved from LoopKit).