|
|
@@ -0,0 +1,305 @@
|
|
|
+import Foundation
|
|
|
+import Testing
|
|
|
+@testable import Trio
|
|
|
+
|
|
|
+/// A direct port of the Javascript `set-temp-basal.test.js` tests
|
|
|
+@Suite("Set Temp Basal Tests") struct SetTempBasalTests {
|
|
|
+ /// Helper to create a default profile for tests.
|
|
|
+ private func createProfile(
|
|
|
+ currentBasal: Decimal = 0.8,
|
|
|
+ maxDailyBasal: Decimal = 1.3,
|
|
|
+ maxBasal: Decimal = 3.0,
|
|
|
+ skipNeutralTemps: Bool = false,
|
|
|
+ maxDailySafetyMultiplier: Decimal = 3,
|
|
|
+ currentBasalSafetyMultiplier: Decimal = 4,
|
|
|
+ model: String? = nil
|
|
|
+ ) -> Profile {
|
|
|
+ var profile = Profile()
|
|
|
+ profile.currentBasal = currentBasal
|
|
|
+ profile.maxDailyBasal = maxDailyBasal
|
|
|
+ profile.maxBasal = maxBasal
|
|
|
+ profile.skipNeutralTemps = skipNeutralTemps
|
|
|
+ profile.maxDailySafetyMultiplier = maxDailySafetyMultiplier
|
|
|
+ profile.currentBasalSafetyMultiplier = currentBasalSafetyMultiplier
|
|
|
+ profile.model = model
|
|
|
+ return profile
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Helper to create a default determination object.
|
|
|
+ private func createDetermination() -> Determination {
|
|
|
+ Determination(
|
|
|
+ id: UUID(),
|
|
|
+ reason: "",
|
|
|
+ units: nil,
|
|
|
+ insulinReq: nil,
|
|
|
+ eventualBG: nil,
|
|
|
+ sensitivityRatio: nil,
|
|
|
+ rate: nil,
|
|
|
+ duration: nil,
|
|
|
+ iob: nil,
|
|
|
+ cob: nil,
|
|
|
+ predictions: nil,
|
|
|
+ deliverAt: Date(),
|
|
|
+ carbsReq: nil,
|
|
|
+ temp: .absolute,
|
|
|
+ bg: nil,
|
|
|
+ reservoir: nil,
|
|
|
+ isf: nil,
|
|
|
+ timestamp: Date(),
|
|
|
+ tdd: nil,
|
|
|
+ current_target: nil,
|
|
|
+ insulinForManualBolus: nil,
|
|
|
+ manualBolusErrorString: nil,
|
|
|
+ minDelta: nil,
|
|
|
+ expectedDelta: nil,
|
|
|
+ minGuardBG: nil,
|
|
|
+ minPredBG: nil,
|
|
|
+ threshold: nil,
|
|
|
+ carbRatio: nil,
|
|
|
+ received: false
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Helper to create a TempBasal object
|
|
|
+ private func createCurrentTemp(rate: Decimal = 0, duration: Decimal = 0) -> TempBasal {
|
|
|
+ TempBasal(
|
|
|
+ duration: Int(truncating: duration as NSNumber),
|
|
|
+ rate: rate,
|
|
|
+ temp: .absolute,
|
|
|
+ timestamp: Date()
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should cancel temp") func cancelTemp() throws {
|
|
|
+ let profile = createProfile()
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 0,
|
|
|
+ duration: 0,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 0)
|
|
|
+ #expect(requestedTemp.duration == 0)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should set zero temp") func setZeroTemp() throws {
|
|
|
+ let profile = createProfile()
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 0,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 0)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should set high temp") func setHighTemp() throws {
|
|
|
+ let profile = createProfile()
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 2,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 2)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should not set basal on skip neutral mode") func skipNeutralMode() throws {
|
|
|
+ // Test case 1: Current temp is active
|
|
|
+ var profile = createProfile(currentBasal: 0.8, skipNeutralTemps: true)
|
|
|
+ var determination = createDetermination()
|
|
|
+ var currentTemp = createCurrentTemp(duration: 10)
|
|
|
+
|
|
|
+ var requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 0.8,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.duration == 0)
|
|
|
+
|
|
|
+ // Test case 2: No current temp
|
|
|
+ determination = createDetermination()
|
|
|
+ currentTemp = createCurrentTemp() // duration = 0
|
|
|
+ requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 0.8,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.reason.contains("no temp basal is active, doing nothing") == true)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should limit high temp to max_basal") func limitToMaxBasal() throws {
|
|
|
+ let profile = createProfile(maxBasal: 3.0)
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 4,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 3.0)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should limit high temp to 3 * max_daily_basal") func limitToMaxDailyBasal() throws {
|
|
|
+ let profile = createProfile(currentBasal: 1.0, maxDailyBasal: 1.3, maxBasal: 10.0)
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 6,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 3.9)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should limit high temp to 4 * current_basal") func limitToCurrentBasal() throws {
|
|
|
+ let profile = createProfile(currentBasal: 0.7, maxDailyBasal: 1.3, maxBasal: 10.0)
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 6,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 2.8)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should temp to 0 when requested rate is less than 0") func rateLessThanZero() throws {
|
|
|
+ let profile = createProfile(currentBasal: 0.7, maxDailyBasal: 1.3, maxBasal: 10.0)
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: -1,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 0)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should limit high temp to 4 * max_daily_basal when overridden") func limitWithOverrideMaxDaily() throws {
|
|
|
+ let profile = createProfile(currentBasal: 2.0, maxDailyBasal: 1.3, maxBasal: 10.0, maxDailySafetyMultiplier: 4)
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 6,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 5.2)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should limit high temp to 5 * current_basal when overridden") func limitWithOverrideCurrentBasal() throws {
|
|
|
+ let profile = createProfile(currentBasal: 0.7, maxDailyBasal: 1.3, maxBasal: 10.0, currentBasalSafetyMultiplier: 5)
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 6,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 3.5)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should allow small basal change when current temp is also small") func allowSmallChange() throws {
|
|
|
+ let profile = createProfile(
|
|
|
+ currentBasal: 0.075,
|
|
|
+ maxDailyBasal: 1.3,
|
|
|
+ maxBasal: 10.0,
|
|
|
+ currentBasalSafetyMultiplier: 5,
|
|
|
+ model: "523"
|
|
|
+ )
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp(rate: 0.025, duration: 24)
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 0,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.rate == 0)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should not allow small basal change when current temp is large") func disallowSmallChange() throws {
|
|
|
+ let profile = createProfile(
|
|
|
+ currentBasal: 10.075,
|
|
|
+ maxDailyBasal: 11.3,
|
|
|
+ maxBasal: 50.0,
|
|
|
+ currentBasalSafetyMultiplier: 5,
|
|
|
+ model: "523"
|
|
|
+ )
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp(rate: 10.1, duration: 24)
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 10.125,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+ #expect(requestedTemp.reason.contains("no temp required") == true)
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test("should set neutral temp") func setNeutralTemp() throws {
|
|
|
+ let profile = createProfile(currentBasal: 0.8, skipNeutralTemps: false)
|
|
|
+ let determination = createDetermination()
|
|
|
+ let currentTemp = createCurrentTemp()
|
|
|
+
|
|
|
+ let requestedTemp = try TempBasalFunctions.setTempBasal(
|
|
|
+ rate: 0.8,
|
|
|
+ duration: 30,
|
|
|
+ profile: profile,
|
|
|
+ determination: determination,
|
|
|
+ currentTemp: currentTemp
|
|
|
+ )
|
|
|
+
|
|
|
+ #expect(requestedTemp.rate == 0.8)
|
|
|
+ #expect(requestedTemp.duration == 30)
|
|
|
+ #expect(requestedTemp.reason == "Setting neutral temp basal of 0.8U/hr")
|
|
|
+ }
|
|
|
+}
|