Ver código fonte

Fixes a Swift IoB bug when suspends last for a long period of time

Sam King 10 meses atrás
pai
commit
9224842ec8

+ 4 - 0
Trio.xcodeproj/project.pbxproj

@@ -217,6 +217,7 @@
 		3B1C5C452D68E269004E9273 /* IobTotalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B1C5C382D68E269004E9273 /* IobTotalTests.swift */; };
 		3B1C5C472D68E269004E9273 /* IobCalculateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B1C5C352D68E269004E9273 /* IobCalculateTests.swift */; };
 		3B1C5C482D68E269004E9273 /* IobHistoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B1C5C362D68E269004E9273 /* IobHistoryTests.swift */; };
+		3B2CE68B2E24ADF7005EF782 /* IobGenerateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2CE68A2E24ADF3005EF782 /* IobGenerateTests.swift */; };
 		3B2F77862D7E52ED005ED9FA /* TDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2F77852D7E52ED005ED9FA /* TDD.swift */; };
 		3B2F77882D7E5387005ED9FA /* CurrentTDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B2F77872D7E5387005ED9FA /* CurrentTDDSetup.swift */; };
 		3B31D5742E0E26C00047D32D /* ReplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B31D5732E0E26BB0047D32D /* ReplayTests.swift */; };
@@ -1156,6 +1157,7 @@
 		3B1C5C382D68E269004E9273 /* IobTotalTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IobTotalTests.swift; sourceTree = "<group>"; };
 		3B1C5C3D2D68E269004E9273 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
 		3B1C5C3E2D68E269004E9273 /* IobJsonTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IobJsonTypes.swift; sourceTree = "<group>"; };
+		3B2CE68A2E24ADF3005EF782 /* IobGenerateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IobGenerateTests.swift; sourceTree = "<group>"; };
 		3B2F77852D7E52ED005ED9FA /* TDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD.swift; sourceTree = "<group>"; };
 		3B2F77872D7E5387005ED9FA /* CurrentTDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentTDDSetup.swift; sourceTree = "<group>"; };
 		3B31D5732E0E26BB0047D32D /* ReplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplayTests.swift; sourceTree = "<group>"; };
@@ -2920,6 +2922,7 @@
 				DD30B9FF2E0745C400DA677C /* DetermineBasalDeltaCalculationTests.swift */,
 				DD30B9FD2E0742E200DA677C /* DetermineBasalSMBEnablementTests.swift */,
 				3B1C5C352D68E269004E9273 /* IobCalculateTests.swift */,
+				3B2CE68A2E24ADF3005EF782 /* IobGenerateTests.swift */,
 				3B1C5C362D68E269004E9273 /* IobHistoryTests.swift */,
 				3BC4053A2D931620006A03E9 /* IobJsonTests.swift */,
 				3BC26E542D7418830066ACD6 /* IobSuspendTests.swift */,
@@ -5209,6 +5212,7 @@
 				3B1C5C452D68E269004E9273 /* IobTotalTests.swift in Sources */,
 				3BE2F1EA2E031951009E2900 /* MealCobBucketingTests.swift in Sources */,
 				3B1C5C472D68E269004E9273 /* IobCalculateTests.swift in Sources */,
+				3B2CE68B2E24ADF7005EF782 /* IobGenerateTests.swift in Sources */,
 				3B1C5C482D68E269004E9273 /* IobHistoryTests.swift in Sources */,
 				3BC4053B2D931620006A03E9 /* IobJsonTests.swift in Sources */,
 				3BEF6AB72D9750780076089D /* MealJsonTests.swift in Sources */,

+ 5 - 0
Trio/Sources/APS/OpenAPSSwift/Extensions/PumpHistory+copy.swift

@@ -1,6 +1,11 @@
 import Foundation
 
 extension PumpHistoryEvent {
+    /// Helper function that we use when filtering pump history events
+    func isSuspendOrResume() -> Bool {
+        type == .pumpSuspend || type == .pumpResume
+    }
+
     func computedEvent() -> ComputedPumpHistoryEvent {
         ComputedPumpHistoryEvent(
             id: id,

+ 4 - 1
Trio/Sources/APS/OpenAPSSwift/Iob/IobGenerator.swift

@@ -12,7 +12,10 @@ struct IobGenerator {
         let diaAgo = Double(profile.dia ?? 10) * 60 * 60
         // add an extra two hours to the DIA to ensure we get all temp basals
         let lastDia = clock - diaAgo - 2.hoursToSeconds
-        let pumpHistory = history.filter({ $0.timestamp >= lastDia }).map({ $0.computedEvent() })
+
+        // we have to keep all of our suspend/resume events due to a hardcoded
+        // DIA value in dealing with suspended pumps in JS
+        let pumpHistory = history.filter({ $0.timestamp >= lastDia || $0.isSuspendOrResume() }).map({ $0.computedEvent() })
 
         let treatments = try IobHistory.calcTempTreatments(
             history: pumpHistory,

+ 37 - 0
TrioTests/OpenAPSSwiftTests/IobGenerateTests.swift

@@ -0,0 +1,37 @@
+import Foundation
+import Testing
+@testable import Trio
+
+@Suite("IoB generate tests") struct IobGenerateTests {
+    /// One of our performance optimizations where we filter old pump events has subtle interactions
+    /// with the JS implementation. In particular, JS will hardcode 8 hours for DIA in the suspend logic
+    /// when a pump history has a resume as the first suspend/resume event. This hard coded value
+    /// can cause some old netbasalinsulin to get dropped if DIA > 8 hours. We fixed this bug by
+    /// not filtering suspend and resume events, and this test case checks for the bug fix.
+    @Test("should test suspend filtering") func testSuspendFiltering() async throws {
+        let now = Calendar.current.startOfDay(for: Date()) + 20.hoursToSeconds
+
+        let history = [
+            PumpHistoryEvent(id: UUID().uuidString, type: .pumpSuspend, timestamp: now - 15.hoursToSeconds),
+            PumpHistoryEvent(id: UUID().uuidString, type: .pumpResume, timestamp: now - 1.hoursToSeconds)
+        ]
+
+        var profile = Profile()
+        profile.dia = 10
+        profile.currentBasal = 1
+        profile.maxDailyBasal = 1
+        profile.basalprofile = [
+            BasalProfileEntry(
+                start: "00:00:00",
+                minutes: 0,
+                rate: 1
+            )
+        ]
+        profile.suspendZerosIob = true
+
+        let iob = try IobGenerator.generate(history: history, profile: profile, clock: now, autosens: nil)
+
+        // Matches the long suspend test in JS iob.test.js
+        #expect(iob[0].netbasalinsulin == -8.95)
+    }
+}

+ 17 - 50
TrioTests/OpenAPSSwiftTests/IobJsonTests.swift

@@ -182,13 +182,19 @@ import Testing
         }
     }
 
+    @Test("Debug utility for checking one IOB error", .enabled(if: false)) func debugSignleIobError() async throws {
+        let algorithmComparison = try await HttpFiles.downloadFile(at: "/files/dd31e618-5023-40ca-ab7e-0fdd2475fbd9.2.json")
+        let iobInputs = algorithmComparison.iobInput!
+
+        timeZoneForTests.setTimezone(identifier: algorithmComparison.timezone)
+
+        try await checkFixedJsAgainstSwift(iobInputs: iobInputs)
+
+        timeZoneForTests.resetTimezone()
+    }
+
     @Test("Debug utility for checking iob-history", .enabled(if: false)) func debugIobHistory() async throws {
-        let testBundle = Bundle(for: BundleReference.self)
-        let path = testBundle.path(forResource: "iob-error-log", ofType: "json")!
-        let data = try Data(contentsOf: URL(fileURLWithPath: path))
-        let decoder = JSONDecoder()
-        decoder.dateDecodingStrategy = .secondsSince1970
-        let algorithmComparison = try decoder.decode(AlgorithmComparison.self, from: data)
+        let algorithmComparison = try await HttpFiles.downloadFile(at: "/files/dd31e618-5023-40ca-ab7e-0fdd2475fbd9.2.json")
         let iobInputs = algorithmComparison.iobInput!
 
         timeZoneForTests.setTimezone(identifier: algorithmComparison.timezone)
@@ -224,54 +230,15 @@ import Testing
         print("Writing to: \(outputURL.path)")
         try output.write(to: outputURL)
 
-        checkHistoryConsistency(swiftTreatments: swiftIobHistory, jsTreatments: jsIobHistory)
-        checkRunningBasal(swiftTreatments: swiftIobHistory, jsTreatments: jsIobHistory)
-
-        timeZoneForTests.resetTimezone()
-    }
-
-    /// simple utility for creating inputs for Javascript for use in testing
-    @Test("format inputs for Javascript", .enabled(if: false)) func generateJavascriptInputs() throws {
-        let testBundle = Bundle(for: BundleReference.self)
-        let path = testBundle.path(forResource: "iob-error-log", ofType: "json")!
-        let data = try Data(contentsOf: URL(fileURLWithPath: path))
-        let decoder = JSONDecoder()
-        decoder.dateDecodingStrategy = .secondsSince1970
-        let algorithmComparison = try decoder.decode(AlgorithmComparison.self, from: data)
-        let iobInputs = algorithmComparison.iobInput!
-
-        let encoder = JSONCoding.encoder
-        let output = try encoder.encode(iobInputs)
-
-        let sharedDir = FileManager.default.temporaryDirectory
-        let outputURL = sharedDir.appendingPathComponent("js_iob_input_error.json")
-
-        // Print the path so you can find it
+        output = try encoder.encode(iobInputs)
+        sharedDir = FileManager.default.temporaryDirectory
+        outputURL = sharedDir.appendingPathComponent("js_iob_input_error.json")
         print("Writing to: \(outputURL.path)")
-
         try output.write(to: outputURL)
 
-        timeZoneForTests.setTimezone(identifier: algorithmComparison.timezone)
-
-        let treatments = try IobHistory.calcTempTreatments(
-            history: iobInputs.history.map { $0.computedEvent() },
-            profile: iobInputs.profile,
-            clock: iobInputs.clock,
-            autosens: iobInputs.autosens,
-            zeroTempDuration: nil
-        )
-
-        let iobSomething = try IobCalculation.iobTotal(treatments: treatments, profile: iobInputs.profile, time: iobInputs.clock)
+        checkHistoryConsistency(swiftTreatments: swiftIobHistory, jsTreatments: jsIobHistory)
+        checkRunningBasal(swiftTreatments: swiftIobHistory, jsTreatments: jsIobHistory)
 
         timeZoneForTests.resetTimezone()
-
-        print(iobSomething.prettyPrintedJSON!)
-
-        let treatmentsOut = try encoder.encode(treatments)
-        let treatmentsUrl = sharedDir.appendingPathComponent("treatments.json")
-
-        print("Writing to: \(treatmentsUrl.path)")
-
-        try treatmentsOut.write(to: treatmentsUrl)
     }
 }

+ 1 - 1
TrioTests/OpenAPSSwiftTests/javascript/bundle/iob-history-prepare.js

@@ -9,5 +9,5 @@ function generate(pumphistory_data, profile_data, clock_data, autosens_data, zer
         inputs.autosens = autosens_data;
     }
     
-    return freeaps_iobHistory.calcTempTreatments(inputs, zeroTempDuration);
+    return trio_iobHistory.calcTempTreatments(inputs, zeroTempDuration);
 }