Procházet zdrojové kódy

Tests for replaying IoB errors + minor IoB fixes

Sam King před 1 rokem
rodič
revize
27ceb61e34

+ 68 - 0
Trio.xcodeproj/project.pbxproj

@@ -240,6 +240,7 @@
 		3BAD36B22D7CDC1A00CC298D /* MainLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */; };
 		3BAD36B22D7CDC1A00CC298D /* MainLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */; };
 		3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */; };
 		3BAD36CC2D7D420E00CC298D /* CoreDataInitializationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */; };
 		3BC26E552D7418830066ACD6 /* IobSuspendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC26E542D7418830066ACD6 /* IobSuspendTests.swift */; };
 		3BC26E552D7418830066ACD6 /* IobSuspendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC26E542D7418830066ACD6 /* IobSuspendTests.swift */; };
+		3BC4053B2D931620006A03E9 /* IobJsonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC4053A2D931620006A03E9 /* IobJsonTests.swift */; };
 		3BCE75B32D4B38AE009E9453 /* InsulinSensitivities+Convert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BCE75B22D4B38A0009E9453 /* InsulinSensitivities+Convert.swift */; };
 		3BCE75B32D4B38AE009E9453 /* InsulinSensitivities+Convert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BCE75B22D4B38A0009E9453 /* InsulinSensitivities+Convert.swift */; };
 		3BCE75B52D4B391F009E9453 /* Decimal+rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BCE75B42D4B3917009E9453 /* Decimal+rounding.swift */; };
 		3BCE75B52D4B391F009E9453 /* Decimal+rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BCE75B42D4B3917009E9453 /* Decimal+rounding.swift */; };
 		3BEA3AE02D58F79700A67A1D /* OrefFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEA3ADE2D58F79700A67A1D /* OrefFunction.swift */; };
 		3BEA3AE02D58F79700A67A1D /* OrefFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEA3ADE2D58F79700A67A1D /* OrefFunction.swift */; };
@@ -248,6 +249,18 @@
 		3BEA3AE32D58F79700A67A1D /* JSONCompare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEA3ADC2D58F79700A67A1D /* JSONCompare.swift */; };
 		3BEA3AE32D58F79700A67A1D /* JSONCompare.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BEA3ADC2D58F79700A67A1D /* JSONCompare.swift */; };
 		3BF8D0C12D5175BE001B3F84 /* ProfileJsNativeCompareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF8D0C02D5175B3001B3F84 /* ProfileJsNativeCompareTests.swift */; };
 		3BF8D0C12D5175BE001B3F84 /* ProfileJsNativeCompareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF8D0C02D5175B3001B3F84 /* ProfileJsNativeCompareTests.swift */; };
 		3BF8D14B2D530397001B3F84 /* JSONCompareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF8D14A2D530397001B3F84 /* JSONCompareTests.swift */; };
 		3BF8D14B2D530397001B3F84 /* JSONCompareTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF8D14A2D530397001B3F84 /* JSONCompareTests.swift */; };
+		3BF92F2D2D86DEE9006B545A /* autosens.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F212D86DEE9006B545A /* autosens.js */; };
+		3BF92F2E2D86DEE9006B545A /* autotune-prep.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F232D86DEE9006B545A /* autotune-prep.js */; };
+		3BF92F2F2D86DEE9006B545A /* profile.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F2A2D86DEE9006B545A /* profile.js */; };
+		3BF92F302D86DEE9006B545A /* determine-basal.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F252D86DEE9006B545A /* determine-basal.js */; };
+		3BF92F312D86DEE9006B545A /* meal.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F292D86DEE9006B545A /* meal.js */; };
+		3BF92F322D86DEE9006B545A /* glucose-get-last.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F262D86DEE9006B545A /* glucose-get-last.js */; };
+		3BF92F332D86DEE9006B545A /* iob.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F272D86DEE9006B545A /* iob.js */; };
+		3BF92F342D86DEE9006B545A /* iob-history.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F282D86DEE9006B545A /* iob-history.js */; };
+		3BF92F352D86DEE9006B545A /* basal-set-temp.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F242D86DEE9006B545A /* basal-set-temp.js */; };
+		3BF92F362D86DEE9006B545A /* autotune-core.js in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F222D86DEE9006B545A /* autotune-core.js */; };
+		3BF92F382D86E10B006B545A /* OpenAPSFixed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BF92F372D86E106006B545A /* OpenAPSFixed.swift */; };
+		3BF92F3A2D86F1AA006B545A /* iob-error-log.json in Resources */ = {isa = PBXBuildFile; fileRef = 3BF92F392D86F1AA006B545A /* iob-error-log.json */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
 		45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */; };
 		45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */; };
 		491D6FBD2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBC2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift */; };
 		491D6FBD2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBC2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift */; };
@@ -1003,6 +1016,7 @@
 		3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoadingView.swift; sourceTree = "<group>"; };
 		3BAD36B12D7CDC1400CC298D /* MainLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoadingView.swift; sourceTree = "<group>"; };
 		3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataInitializationCoordinator.swift; sourceTree = "<group>"; };
 		3BAD36CB2D7D420500CC298D /* CoreDataInitializationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataInitializationCoordinator.swift; sourceTree = "<group>"; };
 		3BC26E542D7418830066ACD6 /* IobSuspendTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IobSuspendTests.swift; sourceTree = "<group>"; };
 		3BC26E542D7418830066ACD6 /* IobSuspendTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IobSuspendTests.swift; sourceTree = "<group>"; };
+		3BC4053A2D931620006A03E9 /* IobJsonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IobJsonTests.swift; sourceTree = "<group>"; };
 		3BCE75B22D4B38A0009E9453 /* InsulinSensitivities+Convert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InsulinSensitivities+Convert.swift"; sourceTree = "<group>"; };
 		3BCE75B22D4B38A0009E9453 /* InsulinSensitivities+Convert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "InsulinSensitivities+Convert.swift"; sourceTree = "<group>"; };
 		3BCE75B42D4B3917009E9453 /* Decimal+rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+rounding.swift"; sourceTree = "<group>"; };
 		3BCE75B42D4B3917009E9453 /* Decimal+rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+rounding.swift"; sourceTree = "<group>"; };
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
@@ -1013,6 +1027,18 @@
 		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
 		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
 		3BF8D0C02D5175B3001B3F84 /* ProfileJsNativeCompareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileJsNativeCompareTests.swift; sourceTree = "<group>"; };
 		3BF8D0C02D5175B3001B3F84 /* ProfileJsNativeCompareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileJsNativeCompareTests.swift; sourceTree = "<group>"; };
 		3BF8D14A2D530397001B3F84 /* JSONCompareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONCompareTests.swift; sourceTree = "<group>"; };
 		3BF8D14A2D530397001B3F84 /* JSONCompareTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONCompareTests.swift; sourceTree = "<group>"; };
+		3BF92F212D86DEE9006B545A /* autosens.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = autosens.js; sourceTree = "<group>"; };
+		3BF92F222D86DEE9006B545A /* autotune-core.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "autotune-core.js"; sourceTree = "<group>"; };
+		3BF92F232D86DEE9006B545A /* autotune-prep.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "autotune-prep.js"; sourceTree = "<group>"; };
+		3BF92F242D86DEE9006B545A /* basal-set-temp.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "basal-set-temp.js"; sourceTree = "<group>"; };
+		3BF92F252D86DEE9006B545A /* determine-basal.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "determine-basal.js"; sourceTree = "<group>"; };
+		3BF92F262D86DEE9006B545A /* glucose-get-last.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "glucose-get-last.js"; sourceTree = "<group>"; };
+		3BF92F272D86DEE9006B545A /* iob.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = iob.js; sourceTree = "<group>"; };
+		3BF92F282D86DEE9006B545A /* iob-history.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "iob-history.js"; sourceTree = "<group>"; };
+		3BF92F292D86DEE9006B545A /* meal.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = meal.js; sourceTree = "<group>"; };
+		3BF92F2A2D86DEE9006B545A /* profile.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = profile.js; sourceTree = "<group>"; };
+		3BF92F372D86E106006B545A /* OpenAPSFixed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAPSFixed.swift; sourceTree = "<group>"; };
+		3BF92F392D86F1AA006B545A /* iob-error-log.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "iob-error-log.json"; sourceTree = "<group>"; };
 		3F60E97100041040446F44E7 /* PumpConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigStateModel.swift; sourceTree = "<group>"; };
 		3F60E97100041040446F44E7 /* PumpConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigStateModel.swift; sourceTree = "<group>"; };
 		3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorDataFlow.swift; sourceTree = "<group>"; };
 		3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorDataFlow.swift; sourceTree = "<group>"; };
 		42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorProvider.swift; sourceTree = "<group>"; };
 		42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorProvider.swift; sourceTree = "<group>"; };
@@ -2444,6 +2470,7 @@
 		3B1C5C3C2D68E269004E9273 /* json */ = {
 		3B1C5C3C2D68E269004E9273 /* json */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				3BF92F392D86F1AA006B545A /* iob-error-log.json */,
 			);
 			);
 			path = json;
 			path = json;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -2451,6 +2478,7 @@
 		3B1C5C3F2D68E269004E9273 /* utils */ = {
 		3B1C5C3F2D68E269004E9273 /* utils */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				3BF92F372D86E106006B545A /* OpenAPSFixed.swift */,
 				3B1C5C3D2D68E269004E9273 /* Extensions.swift */,
 				3B1C5C3D2D68E269004E9273 /* Extensions.swift */,
 				3B1C5C3E2D68E269004E9273 /* IobJsonTypes.swift */,
 				3B1C5C3E2D68E269004E9273 /* IobJsonTypes.swift */,
 			);
 			);
@@ -2522,10 +2550,12 @@
 		3B5CD2C72D4AECD500CE213C /* OpenAPSSwiftTests */ = {
 		3B5CD2C72D4AECD500CE213C /* OpenAPSSwiftTests */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				3BF92F2C2D86DEE9006B545A /* javascript */,
 				3B1C5C3C2D68E269004E9273 /* json */,
 				3B1C5C3C2D68E269004E9273 /* json */,
 				3B1C5C3F2D68E269004E9273 /* utils */,
 				3B1C5C3F2D68E269004E9273 /* utils */,
 				3B1C5C352D68E269004E9273 /* IobCalculateTests.swift */,
 				3B1C5C352D68E269004E9273 /* IobCalculateTests.swift */,
 				3B1C5C362D68E269004E9273 /* IobHistoryTests.swift */,
 				3B1C5C362D68E269004E9273 /* IobHistoryTests.swift */,
+				3BC4053A2D931620006A03E9 /* IobJsonTests.swift */,
 				3BC26E542D7418830066ACD6 /* IobSuspendTests.swift */,
 				3BC26E542D7418830066ACD6 /* IobSuspendTests.swift */,
 				3B1C5C382D68E269004E9273 /* IobTotalTests.swift */,
 				3B1C5C382D68E269004E9273 /* IobTotalTests.swift */,
 				3BF8D14A2D530397001B3F84 /* JSONCompareTests.swift */,
 				3BF8D14A2D530397001B3F84 /* JSONCompareTests.swift */,
@@ -2550,6 +2580,31 @@
 			path = Logging;
 			path = Logging;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
 		};
 		};
+		3BF92F2B2D86DEE9006B545A /* bundle */ = {
+			isa = PBXGroup;
+			children = (
+				3BF92F212D86DEE9006B545A /* autosens.js */,
+				3BF92F222D86DEE9006B545A /* autotune-core.js */,
+				3BF92F232D86DEE9006B545A /* autotune-prep.js */,
+				3BF92F242D86DEE9006B545A /* basal-set-temp.js */,
+				3BF92F252D86DEE9006B545A /* determine-basal.js */,
+				3BF92F262D86DEE9006B545A /* glucose-get-last.js */,
+				3BF92F272D86DEE9006B545A /* iob.js */,
+				3BF92F282D86DEE9006B545A /* iob-history.js */,
+				3BF92F292D86DEE9006B545A /* meal.js */,
+				3BF92F2A2D86DEE9006B545A /* profile.js */,
+			);
+			path = bundle;
+			sourceTree = "<group>";
+		};
+		3BF92F2C2D86DEE9006B545A /* javascript */ = {
+			isa = PBXGroup;
+			children = (
+				3BF92F2B2D86DEE9006B545A /* bundle */,
+			);
+			path = javascript;
+			sourceTree = "<group>";
+		};
 		4E8C7B59F8065047ECE20965 /* View */ = {
 		4E8C7B59F8065047ECE20965 /* View */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
@@ -3780,6 +3835,17 @@
 			isa = PBXResourcesBuildPhase;
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			buildActionMask = 2147483647;
 			files = (
 			files = (
+				3BF92F2D2D86DEE9006B545A /* autosens.js in Resources */,
+				3BF92F2E2D86DEE9006B545A /* autotune-prep.js in Resources */,
+				3BF92F2F2D86DEE9006B545A /* profile.js in Resources */,
+				3BF92F302D86DEE9006B545A /* determine-basal.js in Resources */,
+				3BF92F312D86DEE9006B545A /* meal.js in Resources */,
+				3BF92F322D86DEE9006B545A /* glucose-get-last.js in Resources */,
+				3BF92F332D86DEE9006B545A /* iob.js in Resources */,
+				3BF92F342D86DEE9006B545A /* iob-history.js in Resources */,
+				3BF92F352D86DEE9006B545A /* basal-set-temp.js in Resources */,
+				3BF92F362D86DEE9006B545A /* autotune-core.js in Resources */,
+				3BF92F3A2D86F1AA006B545A /* iob-error-log.json in Resources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 		};
@@ -4469,10 +4535,12 @@
 				CE1F6DD92BADF4620064EB8D /* PluginManagerTests.swift in Sources */,
 				CE1F6DD92BADF4620064EB8D /* PluginManagerTests.swift in Sources */,
 				3B1C5C432D68E269004E9273 /* Extensions.swift in Sources */,
 				3B1C5C432D68E269004E9273 /* Extensions.swift in Sources */,
 				3BC26E552D7418830066ACD6 /* IobSuspendTests.swift in Sources */,
 				3BC26E552D7418830066ACD6 /* IobSuspendTests.swift in Sources */,
+				3BF92F382D86E10B006B545A /* OpenAPSFixed.swift in Sources */,
 				3B1C5C442D68E269004E9273 /* IobJsonTypes.swift in Sources */,
 				3B1C5C442D68E269004E9273 /* IobJsonTypes.swift in Sources */,
 				3B1C5C452D68E269004E9273 /* IobTotalTests.swift in Sources */,
 				3B1C5C452D68E269004E9273 /* IobTotalTests.swift in Sources */,
 				3B1C5C472D68E269004E9273 /* IobCalculateTests.swift in Sources */,
 				3B1C5C472D68E269004E9273 /* IobCalculateTests.swift in Sources */,
 				3B1C5C482D68E269004E9273 /* IobHistoryTests.swift in Sources */,
 				3B1C5C482D68E269004E9273 /* IobHistoryTests.swift in Sources */,
+				3BC4053B2D931620006A03E9 /* IobJsonTests.swift in Sources */,
 				38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */,
 				38FCF3F925E902C20078B0D1 /* FileStorageTests.swift in Sources */,
 				BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */,
 				BD8FC0602D6619DB00B95AED /* CarbsStorageTests.swift in Sources */,
 				BD8FC05E2D6618CE00B95AED /* BolusCalculatorTests.swift in Sources */,
 				BD8FC05E2D6618CE00B95AED /* BolusCalculatorTests.swift in Sources */,

+ 32 - 6
Trio/Sources/APS/OpenAPSSwift/Extensions/PumpHistory+copy.swift

@@ -39,7 +39,8 @@ extension ComputedPumpHistoryEvent {
             note: note,
             note: note,
             isSMB: isSMB,
             isSMB: isSMB,
             isExternal: isExternal,
             isExternal: isExternal,
-            insulin: insulin
+            insulin: insulin,
+            omitFromTempHistory: omitFromTempHistory
         )
         )
     }
     }
 
 
@@ -59,7 +60,29 @@ extension ComputedPumpHistoryEvent {
             note: note,
             note: note,
             isSMB: isSMB,
             isSMB: isSMB,
             isExternal: isExternal,
             isExternal: isExternal,
-            insulin: insulin
+            insulin: insulin,
+            omitFromTempHistory: omitFromTempHistory
+        )
+    }
+
+    func copyWith(duration: Decimal, timestamp: Date, omitFromTempHistory: Bool) -> ComputedPumpHistoryEvent {
+        ComputedPumpHistoryEvent(
+            id: id,
+            type: type,
+            timestamp: timestamp,
+            amount: amount,
+            duration: duration,
+            durationMin: durationMin,
+            rate: rate,
+            temp: temp,
+            carbInput: carbInput,
+            fatInput: fatInput,
+            proteinInput: proteinInput,
+            note: note,
+            isSMB: isSMB,
+            isExternal: isExternal,
+            insulin: insulin,
+            omitFromTempHistory: omitFromTempHistory
         )
         )
     }
     }
 
 
@@ -79,7 +102,8 @@ extension ComputedPumpHistoryEvent {
             note: note,
             note: note,
             isSMB: isSMB,
             isSMB: isSMB,
             isExternal: isExternal,
             isExternal: isExternal,
-            insulin: insulin
+            insulin: insulin,
+            omitFromTempHistory: omitFromTempHistory
         )
         )
     }
     }
 
 
@@ -99,7 +123,8 @@ extension ComputedPumpHistoryEvent {
             note: note,
             note: note,
             isSMB: isSMB,
             isSMB: isSMB,
             isExternal: isExternal,
             isExternal: isExternal,
-            insulin: insulin
+            insulin: insulin,
+            omitFromTempHistory: omitFromTempHistory
         )
         )
     }
     }
 
 
@@ -125,7 +150,7 @@ extension ComputedPumpHistoryEvent {
         )
         )
     }
     }
 
 
-    static func zeroTempBasal(timestamp: Date, duration: Decimal) -> ComputedPumpHistoryEvent {
+    static func zeroTempBasal(timestamp: Date, duration: Decimal, omitFromTempHistory: Bool) -> ComputedPumpHistoryEvent {
         ComputedPumpHistoryEvent(
         ComputedPumpHistoryEvent(
             id: UUID().uuidString,
             id: UUID().uuidString,
             type: .tempBasal,
             type: .tempBasal,
@@ -141,7 +166,8 @@ extension ComputedPumpHistoryEvent {
             note: nil,
             note: nil,
             isSMB: nil,
             isSMB: nil,
             isExternal: nil,
             isExternal: nil,
-            insulin: nil
+            insulin: nil,
+            omitFromTempHistory: omitFromTempHistory
         )
         )
     }
     }
 
 

+ 51 - 24
Trio/Sources/APS/OpenAPSSwift/Iob/IobHistory.swift

@@ -78,7 +78,8 @@ struct IobHistory {
         // this stops the most recent temp basal, the 1m comes from Javascript
         // this stops the most recent temp basal, the 1m comes from Javascript
         let zeroTempBasal = ComputedPumpHistoryEvent.zeroTempBasal(
         let zeroTempBasal = ComputedPumpHistoryEvent.zeroTempBasal(
             timestamp: clock + 1.minutesToSeconds,
             timestamp: clock + 1.minutesToSeconds,
-            duration: zeroTempDuration ?? 0
+            duration: zeroTempDuration ?? 0,
+            omitFromTempHistory: false
         )
         )
 
 
         // match temp basal entries to their duration entry
         // match temp basal entries to their duration entry
@@ -184,7 +185,10 @@ struct IobHistory {
                     // the temp basal starts during the suspend but goes on
                     // the temp basal starts during the suspend but goes on
                     // past, adjust the start date
                     // past, adjust the start date
                     let newDuration = tempBasalEnd.timeIntervalSince(suspend.end).secondsToMinutes
                     let newDuration = tempBasalEnd.timeIntervalSince(suspend.end).secondsToMinutes
-                    let newTempBasal = tempBasal.copyWith(duration: newDuration, timestamp: suspend.end)
+                    let newTempBasal = tempBasal.copyWith(
+                        duration: newDuration,
+                        timestamp: suspend.end
+                    )
                     return modifyTempBasalDuringSuspend(tempBasal: newTempBasal, suspends: Array(suspends.dropFirst(index + 1)))
                     return modifyTempBasalDuringSuspend(tempBasal: newTempBasal, suspends: Array(suspends.dropFirst(index + 1)))
                 case (true, true):
                 case (true, true):
                     // the suspend is completely within the temp basal
                     // the suspend is completely within the temp basal
@@ -192,7 +196,11 @@ struct IobHistory {
                     let firstDuration = suspend.timestamp.timeIntervalSince(tempBasal.timestamp).secondsToMinutes
                     let firstDuration = suspend.timestamp.timeIntervalSince(tempBasal.timestamp).secondsToMinutes
                     let firstTempBasal = tempBasal.copyWith(duration: firstDuration)
                     let firstTempBasal = tempBasal.copyWith(duration: firstDuration)
                     let secondDuration = tempBasalEnd.timeIntervalSince(suspend.end).secondsToMinutes
                     let secondDuration = tempBasalEnd.timeIntervalSince(suspend.end).secondsToMinutes
-                    let secondTempBasal = tempBasal.copyWith(duration: secondDuration, timestamp: suspend.end)
+                    let secondTempBasal = tempBasal.copyWith(
+                        duration: secondDuration,
+                        timestamp: suspend.end,
+                        omitFromTempHistory: true
+                    )
                     return [firstTempBasal] +
                     return [firstTempBasal] +
                         modifyTempBasalDuringSuspend(tempBasal: secondTempBasal, suspends: Array(suspends.dropFirst(index + 1)))
                         modifyTempBasalDuringSuspend(tempBasal: secondTempBasal, suspends: Array(suspends.dropFirst(index + 1)))
                 }
                 }
@@ -210,21 +218,29 @@ struct IobHistory {
             return tempBasals
             return tempBasals
         }
         }
 
 
-        let lastSuspendTime = lastSuspend.timestamp
-        return tempBasals.map { event in
-            let duration = event.duration ?? 0
-            let eventEnd = event.timestamp + duration.minutesToSeconds
-            guard eventEnd <= lastSuspendTime else {
-                return event
-            }
-
-            if event.timestamp > lastSuspendTime {
-                return event.copyWith(duration: 0)
-            } else {
-                let newDuration = duration - lastSuspendTime.timeIntervalSince(event.timestamp).secondsToMinutes
-                return event.copyWith(duration: newDuration)
-            }
-        }
+        return tempBasals
+        // This logic in Javascript never runs because it's in an `if`
+        // statement that compares a date (number) with a timestamp (string)
+        // which will always evaluate to false.
+        //
+        // Although I think this logic is what the algorithm is trying
+        // to do, this will get rid of zero duration temp, so I don't
+        // think we should use it
+        /*
+         let lastSuspendTime = lastSuspend.timestamp
+         return tempBasals.map { event in
+             guard event.end > lastSuspendTime else {
+                 return event
+             }
+
+             if event.timestamp > lastSuspendTime {
+                 return event.copyWith(duration: 0)
+             } else {
+                 let newDuration = lastSuspendTime.timeIntervalSince(event.timestamp).secondsToMinutes
+                 return event.copyWith(duration: newDuration)
+             }
+         }
+         */
     }
     }
 
 
     private static func adjustForSuspendedPrior(
     private static func adjustForSuspendedPrior(
@@ -242,17 +258,24 @@ struct IobHistory {
                 return event
                 return event
             }
             }
 
 
-            let duration = event.duration ?? 0
-            let eventEnd = event.timestamp + duration.minutesToSeconds
-            if eventEnd < firstResumeDate {
+            if event.end < firstResumeDate {
                 return event.copyWith(duration: 0)
                 return event.copyWith(duration: 0)
             } else {
             } else {
-                let newDuration = duration - eventEnd.timeIntervalSince(firstResumeDate).secondsToMinutes
+                let duration = event.duration ?? 0
+                let newDuration = duration - event.end.timeIntervalSince(firstResumeDate).secondsToMinutes
                 return event.copyWith(duration: newDuration, timestamp: firstResumeDate)
                 return event.copyWith(duration: newDuration, timestamp: firstResumeDate)
             }
             }
         }
         }
     }
     }
 
 
+    /// Split up temp basals that overlap with suspends
+    ///
+    /// In Javascript, the algorithm mutates the original tempBasal and includes the mutated
+    /// entry in the tempHistory that it returns. But, it omits any zero temp basals it injects
+    /// or for temp basals that it splits into multiple parts it only includes the original temp basal
+    /// in the temp history even though it accounts for these with the IoB calculation. To signify
+    /// these entries that are just for accounting, we mark them as
+    /// `omitFromTempHistory == true`.
     private static func splitAroundSuspends(
     private static func splitAroundSuspends(
         tempBasals: [ComputedPumpHistoryEvent],
         tempBasals: [ComputedPumpHistoryEvent],
         suspends: [PumpSuspended]
         suspends: [PumpSuspended]
@@ -261,7 +284,9 @@ struct IobHistory {
         tempBasals = adjustForCurrentlySuspended(tempBasals: tempBasals, suspends: suspends)
         tempBasals = adjustForCurrentlySuspended(tempBasals: tempBasals, suspends: suspends)
         tempBasals = tempBasals.flatMap { modifyTempBasalDuringSuspend(tempBasal: $0, suspends: suspends) }
         tempBasals = tempBasals.flatMap { modifyTempBasalDuringSuspend(tempBasal: $0, suspends: suspends) }
         let zeroTempBasals = suspends
         let zeroTempBasals = suspends
-            .map { ComputedPumpHistoryEvent.zeroTempBasal(timestamp: $0.timestamp, duration: $0.durationInMinutes) }
+            .map {
+                ComputedPumpHistoryEvent
+                    .zeroTempBasal(timestamp: $0.timestamp, duration: $0.durationInMinutes, omitFromTempHistory: true) }
 
 
         return (tempBasals + zeroTempBasals).sorted { $0.timestamp < $1.timestamp
         return (tempBasals + zeroTempBasals).sorted { $0.timestamp < $1.timestamp
         }
         }
@@ -429,7 +454,7 @@ struct IobHistory {
         let suspends = try getSuspends(pumpHistory: pumpHistory, clock: clock)
         let suspends = try getSuspends(pumpHistory: pumpHistory, clock: clock)
         let boluses = pumpHistory.filter({ $0.type == .bolus }).map { $0.copyWith(insulin: $0.amount) }
         let boluses = pumpHistory.filter({ $0.type == .bolus }).map { $0.copyWith(insulin: $0.amount) }
 
 
-        let tempHistory: [ComputedPumpHistoryEvent]
+        var tempHistory: [ComputedPumpHistoryEvent]
         if profile.suspendZerosIob {
         if profile.suspendZerosIob {
             tempHistory = splitAroundSuspends(tempBasals: tempBasals, suspends: suspends)
             tempHistory = splitAroundSuspends(tempBasals: tempBasals, suspends: suspends)
         } else {
         } else {
@@ -442,6 +467,8 @@ struct IobHistory {
             autosens: autosens
             autosens: autosens
         )
         )
 
 
+        tempHistory = tempHistory.filter { !$0.omitFromTempHistory }
+
         return (boluses + tempBoluses + tempHistory).sorted { $0.timestamp < $1.timestamp }
         return (boluses + tempBoluses + tempHistory).sorted { $0.timestamp < $1.timestamp }
     }
     }
 }
 }

+ 2 - 0
Trio/Sources/APS/OpenAPSSwift/Logging/AlgorithmComparison.swift

@@ -90,6 +90,7 @@ struct AlgorithmComparison: Codable {
     let jsException: AlgorithmException?
     let jsException: AlgorithmException?
     let swiftException: AlgorithmException?
     let swiftException: AlgorithmException?
     let comparisonError: AlgorithmException?
     let comparisonError: AlgorithmException?
+    let version: String?
 
 
     // Inputs for mismatches
     // Inputs for mismatches
     let iobInput: IobInputs?
     let iobInput: IobInputs?
@@ -131,5 +132,6 @@ struct AlgorithmComparison: Codable {
         self.comparisonError = comparisonError
         self.comparisonError = comparisonError
         iobInput = iobInputs
         iobInput = iobInputs
         timezone = TimeZone.current.identifier
         timezone = TimeZone.current.identifier
+        version = "1"
     }
     }
 }
 }

+ 5 - 1
Trio/Sources/APS/OpenAPSSwift/Models/ComputedPumpHistoryEvent.swift

@@ -17,6 +17,7 @@ struct ComputedPumpHistoryEvent: Codable, Equatable, Identifiable {
     let isExternal: Bool?
     let isExternal: Bool?
     let insulin: Decimal?
     let insulin: Decimal?
     let isTempBolus: Bool
     let isTempBolus: Bool
+    let omitFromTempHistory: Bool
 
 
     // Make these non-computed properties to ensure they're always set
     // Make these non-computed properties to ensure they're always set
     let started_at: Date
     let started_at: Date
@@ -42,7 +43,8 @@ struct ComputedPumpHistoryEvent: Codable, Equatable, Identifiable {
         isSMB: Bool?,
         isSMB: Bool?,
         isExternal: Bool?,
         isExternal: Bool?,
         insulin: Decimal?,
         insulin: Decimal?,
-        isTempBolus: Bool = false
+        isTempBolus: Bool = false,
+        omitFromTempHistory: Bool = false
     ) {
     ) {
         self.id = id
         self.id = id
         self.type = type
         self.type = type
@@ -60,6 +62,7 @@ struct ComputedPumpHistoryEvent: Codable, Equatable, Identifiable {
         self.isExternal = isExternal
         self.isExternal = isExternal
         self.insulin = insulin
         self.insulin = insulin
         self.isTempBolus = isTempBolus
         self.isTempBolus = isTempBolus
+        self.omitFromTempHistory = omitFromTempHistory
 
 
         // Explicitly set started_at and date as required by history.js
         // Explicitly set started_at and date as required by history.js
         started_at = timestamp // This matches behavior of new Date(tz(timestamp))
         started_at = timestamp // This matches behavior of new Date(tz(timestamp))
@@ -87,5 +90,6 @@ extension ComputedPumpHistoryEvent {
         case date
         case date
         case insulin
         case insulin
         case isTempBolus
         case isTempBolus
+        case omitFromTempHistory
     }
     }
 }
 }

+ 66 - 0
TrioTests/OpenAPSSwiftTests/IobHistoryTests.swift

@@ -456,4 +456,70 @@ import Testing
         // Total: -1U
         // Total: -1U
         #expect(treatments.netInsulin().isWithin(0.01, of: -1))
         #expect(treatments.netInsulin().isWithin(0.01, of: -1))
     }
     }
+
+    @Test("should omit zero temp and split temp basal around suspend") func splitTempBasalFromSuspend() async throws {
+        let basalprofile = [
+            BasalProfileEntry(
+                start: "00:00:00",
+                minutes: 0,
+                rate: 1.2
+            )
+        ]
+
+        let now = Calendar.current.startOfDay(for: Date()) + 30.minutesToSeconds
+        let timestamp30mAgo = now - 30.minutesToSeconds
+        let timestamp20mAgo = now - 20.minutesToSeconds
+        let timestamp10mAgo = now - 10.minutesToSeconds
+
+        let pumpHistory = [
+            ComputedPumpHistoryEvent.forTest(
+                type: .tempBasal,
+                timestamp: timestamp30mAgo,
+                duration: nil,
+                rate: 2.4,
+                temp: .absolute
+            ),
+            ComputedPumpHistoryEvent.forTest(
+                type: .tempBasalDuration,
+                timestamp: timestamp30mAgo,
+                durationMin: 30
+            ),
+            ComputedPumpHistoryEvent.forTest(
+                type: .pumpSuspend,
+                timestamp: timestamp20mAgo
+            ),
+            ComputedPumpHistoryEvent.forTest(
+                type: .pumpResume,
+                timestamp: timestamp10mAgo
+            )
+        ]
+
+        var profile = Profile()
+        profile.dia = 3
+        profile.basalprofile = basalprofile
+        profile.currentBasal = 1.2
+        profile.maxDailyBasal = 1.2
+        profile.suspendZerosIob = true
+
+        let treatments = try IobHistory.calcTempTreatments(
+            history: pumpHistory,
+            profile: profile,
+            clock: now,
+            autosens: nil,
+            zeroTempDuration: nil
+        )
+
+        let tempBasals = treatments.filter { $0.type == .tempBasal }
+        #expect(tempBasals[0].duration == 10)
+        #expect(tempBasals[0].timestamp == timestamp30mAgo)
+        #expect(tempBasals[0].rate == 2.4)
+        #expect(tempBasals[1].rate == 0)
+        #expect(tempBasals.count == 2) // the original temp basal + last zero
+
+        // 10m at 2.4U/h - 1.2U/h -> 0.2U
+        // 10m at 0U/h - 1.2U/h -> -0.2U
+        // 10m at 2.4U/h - 1.2U/h -> 0.2U
+        // Total: 0.2
+        #expect(treatments.netInsulin().isWithin(0.01, of: 0.2))
+    }
 }
 }

+ 179 - 0
TrioTests/OpenAPSSwiftTests/IobJsonTests.swift

@@ -0,0 +1,179 @@
+import Foundation
+import Testing
+@testable import Trio
+
+class BundleReference {}
+
+/// This test suite is to help us debug and verify iob errors from Trio devices
+///
+/// There are two key components. First, we have a version of the Javascript that has a number
+/// of bugs fixed. We don't want to fix the real Javascript, so we put this fixed Javascript in the
+/// testing bundle and use it to run comparisons. If the error we see in the field is one that we know
+/// about and have fixed in JS, the Swift and JS implementations will produce the same results. You
+/// can find the fixed JS here:
+///  https://github.com/kingst/trio-oref/tree/tcd-fixes-for-swift-comparison
+///
+/// Second, we have a server that runs (part of `trio-oref-logs`) to serve error logs captured
+/// from the field. This server needs to run on the same machine as the simulator where this test runs.
+/// You can find more information about it from the `trio-oref-logs` repo.
+@Suite("IoB using real pump history JSON", .serialized) struct IobJsonTests {
+    private var originalTZ: String? = ProcessInfo.processInfo.environment["TZ"]
+
+    // Helper function to set timezone
+    private func setTimezone(identifier: String) {
+        setenv("TZ", identifier, 1)
+        tzset() // Make the change take effect
+    }
+
+    // Helper function to reset timezone
+    private func resetTimezone() {
+        // Restore system timezone
+        if let originalTZ = originalTZ {
+            setenv("TZ", originalTZ, 1)
+        } else {
+            unsetenv("TZ")
+        }
+        tzset()
+    }
+
+    // Note: This test case has a memory leak so limit your inputs
+    // to about 250 files at a time
+    @Test(
+        "should produce same results for fixed JS and different for bundle JS",
+        .enabled(if: false)
+    ) func replayErrorInputs() async throws {
+        let url = URL(string: "http://localhost:8123/list")!
+        let (data, _) = try await URLSession.shared.data(from: url)
+        let files = try JSONDecoder().decode([String].self, from: data)
+        let fileDataDecoder = JSONDecoder()
+        fileDataDecoder.dateDecodingStrategy = .secondsSince1970
+        for filePath in files {
+            let dataUrl = URL(string: "http://localhost:8123\(filePath)")!
+            let (data, _) = try await URLSession.shared.data(from: dataUrl)
+            let algorithmComparison = try fileDataDecoder.decode(AlgorithmComparison.self, from: data)
+            print("Checking \(filePath) @ \(algorithmComparison.createdAt)")
+            guard let iobInputs = algorithmComparison.iobInput else {
+                print("Skipping, no iobInputs found")
+                if let str = algorithmComparison.comparisonError {
+                    print(str)
+                }
+                if let str = algorithmComparison.swiftException {
+                    print(str)
+                }
+                continue
+            }
+
+            setTimezone(identifier: algorithmComparison.timezone)
+
+            try await checkFixedJsAgainstSwift(iobInputs: algorithmComparison.iobInput!)
+            try await checkBundleJsAgainstSwift(iobInputs: algorithmComparison.iobInput!)
+
+            resetTimezone()
+        }
+    }
+
+    func checkFixedJsAgainstSwift(iobInputs: IobInputs) async throws {
+        let openAps = OpenAPSFixed()
+        let (iobResultSwift, _) = OpenAPSSwift.iob(
+            pumphistory: iobInputs.history,
+            profile: try JSONBridge.to(iobInputs.profile),
+            clock: iobInputs.clock,
+            autosens: try JSONBridge.to(iobInputs.autosens)
+        )
+
+        let iobResultJavascript = await openAps.iobJavascript(
+            pumphistory: iobInputs.history,
+            profile: try JSONBridge.to(iobInputs.profile),
+            clock: iobInputs.clock,
+            autosens: try JSONBridge.to(iobInputs.autosens)
+        )
+
+        let comparison = JSONCompare.createComparison(
+            function: .iob,
+            swift: iobResultSwift,
+            swiftDuration: 0.1,
+            javascript: iobResultJavascript,
+            javascriptDuration: 0.1,
+            iobInputs: nil
+        )
+
+        if comparison.resultType == .valueDifference {
+            print(comparison.differences!.prettyPrintedJSON!)
+        }
+
+        if comparison.resultType != .matching {
+            print("REPLAY ERROR: Fixed JS didn't match")
+        }
+
+        #expect(comparison.resultType == .matching)
+    }
+
+    func checkBundleJsAgainstSwift(iobInputs: IobInputs) async throws {
+        let openAps = OpenAPS(storage: BaseFileStorage())
+        let (iobResultSwift, _) = OpenAPSSwift.iob(
+            pumphistory: iobInputs.history,
+            profile: try JSONBridge.to(iobInputs.profile),
+            clock: iobInputs.clock,
+            autosens: try JSONBridge.to(iobInputs.autosens)
+        )
+
+        let iobResultJavascript = await openAps.iobJavascript(
+            pumphistory: iobInputs.history,
+            profile: try JSONBridge.to(iobInputs.profile),
+            clock: iobInputs.clock,
+            autosens: try JSONBridge.to(iobInputs.autosens)
+        )
+
+        let comparison = JSONCompare.createComparison(
+            function: .iob,
+            swift: iobResultSwift,
+            swiftDuration: 0.1,
+            javascript: iobResultJavascript,
+            javascriptDuration: 0.1,
+            iobInputs: nil
+        )
+
+        if comparison.resultType != .valueDifference {
+            print("REPLAY ERROR: bundle JS did't produce value difference")
+        }
+
+        #expect(comparison.resultType == .valueDifference)
+    }
+
+    /// 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
+        print("Writing to: \(outputURL.path)")
+
+        try output.write(to: outputURL)
+
+        let treatments = try IobHistory.calcTempTreatments(
+            history: iobInputs.history.map { $0.computedEvent() },
+            profile: iobInputs.profile,
+            clock: iobInputs.clock,
+            autosens: iobInputs.autosens,
+            zeroTempDuration: nil
+        )
+
+        let treatmentsOut = try encoder.encode(treatments)
+        let treatmentsUrl = sharedDir.appendingPathComponent("treatments.json")
+
+        print("Writing to: \(treatmentsUrl.path)")
+
+        try treatmentsOut.write(to: treatmentsUrl)
+    }
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/autosens.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/autotune-core.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/autotune-prep.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/basal-set-temp.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/determine-basal.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/glucose-get-last.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/iob-history.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/iob.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/meal.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 0
TrioTests/OpenAPSSwiftTests/javascript/bundle/profile.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 3424 - 0
TrioTests/OpenAPSSwiftTests/json/iob-error-log.json


+ 13 - 0
TrioTests/OpenAPSSwiftTests/utils/Extensions.swift

@@ -71,3 +71,16 @@ extension IobResult {
         }
         }
     }
     }
 }
 }
+
+extension ComputedPumpHistoryEvent {
+    func contains(tempBolus: ComputedPumpHistoryEvent) -> Bool {
+        guard type == .tempBasal, tempBolus.isTempBolus else {
+            fatalError("invalid type for computed pump history event")
+        }
+
+        let start = timestamp
+        let end = start + duration!.minutesToSeconds
+
+        return start <= tempBolus.timestamp && end > tempBolus.timestamp
+    }
+}

+ 61 - 0
TrioTests/OpenAPSSwiftTests/utils/OpenAPSFixed.swift

@@ -0,0 +1,61 @@
+import Combine
+import Foundation
+import JavaScriptCore
+
+@testable import Trio
+
+/// This class provides us with an implementation of trio-oref with a number of iob bugs that are fixed.
+/// We can use this during testing to confirm that for an input that generated an error that a corrected
+/// Javascript implementation would have produced the same results
+final class OpenAPSFixed {
+    private let jsWorker = JavaScriptWorker()
+
+    func sortPumpHistory(pumpHistory: JSON) throws -> JSON {
+        let pumpHistorySwift = try JSONBridge.pumpHistory(from: pumpHistory)
+        return try JSONBridge.to(pumpHistorySwift.sorted(by: { $0.timestamp > $1.timestamp }))
+    }
+
+    func iobJavascript(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON) async -> OrefFunctionResult {
+        do {
+            let testBundle = Bundle(for: OpenAPSFixed.self)
+            let pumphistory: JSON = try! sortPumpHistory(pumpHistory: pumphistory)
+            let result = try await withCheckedThrowingContinuation { continuation in
+                jsWorker.inCommonContext { worker in
+                    worker.evaluateBatch(scripts: [
+                        Script(name: "prepare/log.js"),
+                        Script.fromTestingBundle(name: "iob.js", bundle: testBundle),
+                        Script(name: "prepare/iob.js")
+                    ])
+                    let result = worker.call(function: "generate", with: [
+                        pumphistory,
+                        profile,
+                        clock,
+                        autosens
+                    ])
+                    continuation.resume(returning: result)
+                }
+            }
+            return .success(result)
+        } catch {
+            return .failure(error)
+        }
+    }
+}
+
+extension Script {
+    static func fromTestingBundle(name: String, bundle: Bundle) -> Script {
+        let body: String
+        if let url = bundle.url(forResource: "\(name)", withExtension: "") {
+            do {
+                body = try String(contentsOf: url)
+            } catch {
+                print("Error loading script: \(error.localizedDescription)")
+                body = "Error loading script"
+            }
+        } else {
+            print("Resource not found: javascript/\(name)")
+            body = "Resource not found"
+        }
+        return Script(name: name, body: body)
+    }
+}

+ 0 - 5
scripts/run_ios_tests.sh

@@ -1,5 +0,0 @@
-#!/bin/bash
-xcodebuild test -workspace Trio.xcworkspace \
-	   -scheme Trio \
-	   -destination 'platform=iOS Simulator,name=iPhone 16' \
-    | xcpretty