|
|
@@ -0,0 +1,196 @@
|
|
|
+import Foundation
|
|
|
+import Testing
|
|
|
+@testable import Trio
|
|
|
+
|
|
|
+/// The corresponding Javascript tests to confirm these numbers are here:
|
|
|
+/// - https://github.com/kingst/trio-oref/blob/dev-fixes-for-swift-comparison/tests/dynamic-isf.test.js
|
|
|
+@Suite("DynamicISF Calculation Tests") struct DynamicISFTests {
|
|
|
+ // Helper to create common dependencies for tests
|
|
|
+ private func createDependencies(
|
|
|
+ useNewFormula: Bool = true,
|
|
|
+ tdd: Decimal = 30,
|
|
|
+ avgTDD: Decimal = 30,
|
|
|
+ sensitivity: Decimal = 50,
|
|
|
+ minAutosens: Decimal = 0.7,
|
|
|
+ maxAutosens: Decimal = 1.2,
|
|
|
+ useCustomPeakTime: Bool = false,
|
|
|
+ insulinCurve: InsulinCurve = .rapidActing
|
|
|
+ ) -> (Profile, Preferences, Decimal, TrioCustomOrefVariables) {
|
|
|
+ var preferences = Preferences()
|
|
|
+ preferences.useNewFormula = useNewFormula
|
|
|
+ preferences.sigmoid = false
|
|
|
+ preferences.adjustmentFactor = 0.8
|
|
|
+ preferences.adjustmentFactorSigmoid = 0.5
|
|
|
+ preferences.useCustomPeakTime = useCustomPeakTime
|
|
|
+ preferences.curve = insulinCurve
|
|
|
+
|
|
|
+ var profile = Profile()
|
|
|
+ profile.sens = sensitivity
|
|
|
+ profile.autosensMin = minAutosens
|
|
|
+ profile.autosensMax = maxAutosens
|
|
|
+ profile.minBg = 100
|
|
|
+ profile.curve = insulinCurve
|
|
|
+ profile.useCustomPeakTime = useCustomPeakTime
|
|
|
+ profile.insulinPeakTime = 60 // For custom peak time test
|
|
|
+
|
|
|
+ let glucose = Decimal(120)
|
|
|
+
|
|
|
+ let trioVars = TrioCustomOrefVariables(
|
|
|
+ average_total_data: avgTDD,
|
|
|
+ weightedAverage: tdd,
|
|
|
+ currentTDD: tdd,
|
|
|
+ past2hoursAverage: 0,
|
|
|
+ date: Date(),
|
|
|
+ overridePercentage: 100,
|
|
|
+ useOverride: false,
|
|
|
+ duration: 0,
|
|
|
+ unlimited: true,
|
|
|
+ overrideTarget: 0,
|
|
|
+ smbIsOff: false,
|
|
|
+ advancedSettings: false,
|
|
|
+ isfAndCr: false,
|
|
|
+ isf: true,
|
|
|
+ cr: true,
|
|
|
+ smbIsScheduledOff: false,
|
|
|
+ start: 0,
|
|
|
+ end: 0,
|
|
|
+ smbMinutes: 30,
|
|
|
+ uamMinutes: 30,
|
|
|
+ shouldProtectDueToHIGH: false
|
|
|
+ )
|
|
|
+
|
|
|
+ return (profile, preferences, glucose, trioVars)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Returns nil if dISF is disabled") func disabledReturnsNil() throws {
|
|
|
+ let (profile, preferences, glucose, trioVars) = createDependencies(useNewFormula: false)
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )
|
|
|
+
|
|
|
+ #expect(result == nil)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Returns nil for invalid autosens limits") func invalidLimitsReturnsNil() throws {
|
|
|
+ let (profile, preferences, glucose, trioVars) = createDependencies(minAutosens: 1.2, maxAutosens: 1.2)
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )
|
|
|
+ #expect(result == nil)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Logarithmic formula calculates all result fields correctly") func logarithmicFormula() throws {
|
|
|
+ let (profile, preferences, glucose, trioVars) = createDependencies()
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )!
|
|
|
+
|
|
|
+ #expect(result.insulinFactor == 55)
|
|
|
+ #expect(result.tddRatio.rounded(toPlaces: 2) == 1)
|
|
|
+ #expect(result.ratio.rounded(toPlaces: 2) == 0.77)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Sigmoid formula calculates all result fields correctly") func sigmoidFormula() throws {
|
|
|
+ var (profile, preferences, glucose, trioVars) = createDependencies()
|
|
|
+ preferences.sigmoid = true
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )!
|
|
|
+
|
|
|
+ #expect(result.insulinFactor == 55)
|
|
|
+ #expect(result.tddRatio == 1.0)
|
|
|
+ #expect(result.ratio.rounded(scale: 2) == Decimal(string: "1.06"))
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Uses default TDD ratio when average TDD is zero") func defaultTddRatio() throws {
|
|
|
+ let (profile, preferences, glucose, trioVars) = createDependencies(avgTDD: 0)
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )!
|
|
|
+
|
|
|
+ #expect(result.tddRatio == 1.0)
|
|
|
+ #expect(result.ratio.rounded(toPlaces: 2) == 0.77)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Uses custom peak time when enabled") func customPeakTime() throws {
|
|
|
+ let (profile, preferences, glucose, trioVars) = createDependencies(useCustomPeakTime: true)
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )!
|
|
|
+
|
|
|
+ // 120 - profile.insulinPeakTime (60) = 60
|
|
|
+ #expect(result.insulinFactor == 60)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Uses ultra-rapid insulin factor correctly") func ultraRapidInsulin() throws {
|
|
|
+ let (profile, preferences, glucose, trioVars) = createDependencies(insulinCurve: .ultraRapid)
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )!
|
|
|
+
|
|
|
+ #expect(result.ratio.rounded(scale: 2) == Decimal(string: "0.7"))
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Sigmoid handles maxLimit of 1 correctly") func sigmoidMaxLimitOne() throws {
|
|
|
+ var (profile, preferences, glucose, trioVars) = createDependencies(maxAutosens: 1.0)
|
|
|
+ preferences.sigmoid = true
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )!
|
|
|
+
|
|
|
+ #expect(result.insulinFactor == 55)
|
|
|
+ #expect(result.tddRatio == 1.0)
|
|
|
+ // BUG: you would expect this to be 1 but because of the fudge factor the
|
|
|
+ // JS code uses to avoid divide by 0 it 0.99
|
|
|
+ #expect(result.ratio.rounded(scale: 2) == Decimal(string: "0.99"))
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("Override with sigmoid adjusts target and ratio correctly") func overrideWithSigmoid() throws {
|
|
|
+ var (profile, preferences, glucose, trioVars) = createDependencies()
|
|
|
+ preferences.sigmoid = true
|
|
|
+ trioVars.useOverride = true
|
|
|
+ trioVars.overrideTarget = 80
|
|
|
+ trioVars.overridePercentage = 80
|
|
|
+
|
|
|
+ let result = DynamicISF.calculate(
|
|
|
+ profile: profile,
|
|
|
+ preferences: preferences,
|
|
|
+ currentGlucose: glucose,
|
|
|
+ trioCustomOrefVariables: trioVars
|
|
|
+ )!
|
|
|
+
|
|
|
+ #expect(result.ratio.rounded(toPlaces: 2) == Decimal(string: "1.11"))
|
|
|
+ }
|
|
|
+}
|