DetermineBasalLowEventualGlucoseTests.swift 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. /// these tests should be an exact copy of the JS tests here:
  5. /// - https://github.com/kingst/trio-oref/blob/dev-fixes-for-swift-comparison/tests/determine-basal-low-eventual-glucose.test.js
  6. /// We had to extract the key functionality from JS and put it in a function to facilitate testing
  7. @Suite("DetermineBasal low eventual glucose") struct HandleLowEventualGlucoseTests {
  8. private func defaultProfile() -> Profile {
  9. var profile = Profile()
  10. profile.minBg = 100
  11. profile.targetBg = 100
  12. profile.currentBasal = 1.0
  13. profile.maxDailyBasal = 1.3
  14. profile.maxBasal = 3.5
  15. profile.sens = 50
  16. return profile
  17. }
  18. private func callHandleLowEventualGlucose(
  19. eventualGlucose: Decimal = 90,
  20. minGlucose: Decimal? = nil,
  21. targetGlucose: Decimal? = nil,
  22. minDelta: Decimal = 0,
  23. expectedDelta: Decimal = 0,
  24. carbsRequired: Decimal = 0,
  25. naiveEventualGlucose: Decimal = 90,
  26. glucoseStatus: GlucoseStatus = GlucoseStatus(
  27. delta: 0,
  28. glucose: 100,
  29. noise: 1,
  30. shortAvgDelta: 0,
  31. longAvgDelta: 0,
  32. date: Date(),
  33. lastCalIndex: nil,
  34. device: "test"
  35. ),
  36. currentTemp: TempBasal = TempBasal(duration: 0, rate: 0, temp: .absolute, timestamp: Date()),
  37. basal: Decimal? = nil,
  38. profile: Profile? = nil,
  39. determination: Determination? = nil,
  40. adjustedSensitivity: Decimal? = nil,
  41. overrideFactor: Decimal = 1
  42. ) throws -> (shouldSetTempBasal: Bool, determination: Determination) {
  43. let testProfile = profile ?? defaultProfile()
  44. let testDetermination = determination ?? Determination(
  45. id: nil,
  46. reason: "",
  47. units: nil,
  48. insulinReq: nil,
  49. eventualBG: nil,
  50. sensitivityRatio: nil,
  51. rate: nil,
  52. duration: nil,
  53. iob: nil,
  54. cob: nil,
  55. predictions: nil,
  56. deliverAt: nil,
  57. carbsReq: nil,
  58. temp: nil,
  59. bg: nil,
  60. reservoir: nil,
  61. isf: nil,
  62. timestamp: nil,
  63. tdd: nil,
  64. current_target: nil,
  65. minDelta: nil,
  66. expectedDelta: nil,
  67. minGuardBG: nil,
  68. minPredBG: nil,
  69. threshold: nil,
  70. carbRatio: nil,
  71. received: nil
  72. )
  73. return try DosingEngine.handleLowEventualGlucose(
  74. eventualGlucose: eventualGlucose,
  75. minGlucose: minGlucose ?? testProfile.minBg!,
  76. targetGlucose: targetGlucose ?? testProfile.targetBg!,
  77. minDelta: minDelta,
  78. expectedDelta: expectedDelta,
  79. carbsRequired: carbsRequired,
  80. naiveEventualGlucose: naiveEventualGlucose,
  81. glucoseStatus: glucoseStatus,
  82. currentTemp: currentTemp,
  83. basal: basal ?? testProfile.currentBasal!,
  84. profile: testProfile,
  85. determination: testDetermination,
  86. adjustedSensitivity: adjustedSensitivity ?? testProfile.sens!,
  87. overrideFactor: overrideFactor
  88. )
  89. }
  90. @Test("Guard: eventual glucose is not low") func testEventualGlucoseNotLow() throws {
  91. let (shouldSet, determination) = try callHandleLowEventualGlucose(eventualGlucose: 100, minGlucose: 100)
  92. #expect(shouldSet == false)
  93. #expect(determination.reason == "")
  94. }
  95. @Test("Naive eventual glucose below 40") func testNaiveEventualGlucoseBelow40() throws {
  96. let (shouldSet, determination) = try callHandleLowEventualGlucose(
  97. minDelta: 1,
  98. expectedDelta: 0,
  99. carbsRequired: 0,
  100. naiveEventualGlucose: 39
  101. )
  102. #expect(shouldSet == true)
  103. #expect(determination.rate == 0)
  104. #expect(determination.duration == 30)
  105. #expect(determination.reason.contains("naive_eventualBG < 40"))
  106. }
  107. @Test("Min delta > expected, but no carbs required") func testMinDeltaGreaterThanExpectedDeltaAndNoCarbs() throws {
  108. let (shouldSet, _) = try callHandleLowEventualGlucose(minDelta: 1, expectedDelta: 0, carbsRequired: 0)
  109. #expect(shouldSet == true)
  110. }
  111. @Test("Min delta < 0") func testMinDeltaLessThanZero() throws {
  112. let (shouldSet, determination) = try callHandleLowEventualGlucose(minDelta: -1, expectedDelta: -2, carbsRequired: 0)
  113. #expect(shouldSet == true)
  114. #expect(determination.rate == 0.6)
  115. }
  116. @Test("Current temp rate matches basal") func testCurrentTempRateMatchesBasal() throws {
  117. let profile = defaultProfile()
  118. let currentTemp = TempBasal(duration: 20, rate: profile.currentBasal!, temp: .absolute, timestamp: Date())
  119. let (shouldSet, determination) = try callHandleLowEventualGlucose(
  120. minDelta: 1,
  121. expectedDelta: 0,
  122. carbsRequired: 0,
  123. currentTemp: currentTemp,
  124. profile: profile
  125. )
  126. #expect(shouldSet == true)
  127. #expect(determination.rate == nil) // No change
  128. #expect(determination.reason.contains("temp \(currentTemp.rate) ~ req \(profile.currentBasal!)U/hr."))
  129. }
  130. @Test("Set basal as temp") func testSetBasalAsTemp() throws {
  131. let profile = defaultProfile()
  132. let (shouldSet, determination) = try callHandleLowEventualGlucose(
  133. minDelta: 1,
  134. expectedDelta: 0,
  135. carbsRequired: 0,
  136. profile: profile
  137. )
  138. #expect(shouldSet == true)
  139. #expect(determination.rate == profile.currentBasal)
  140. #expect(determination.duration == 30)
  141. #expect(determination.reason.contains("setting current basal of \(profile.currentBasal!) as temp."))
  142. }
  143. @Test("Insulin scheduled less than required") func testInsulinScheduledLessThanRequired() throws {
  144. let (shouldSet, determination) = try callHandleLowEventualGlucose(
  145. eventualGlucose: 80,
  146. naiveEventualGlucose: 70,
  147. currentTemp: TempBasal(duration: 120, rate: 0, temp: .absolute, timestamp: Date())
  148. )
  149. #expect(shouldSet == true)
  150. #expect(determination.rate == nil)
  151. #expect(determination.duration == nil)
  152. #expect(determination.reason.contains("is a lot less than needed"))
  153. }
  154. @Test("Rate similar to current temp") func testRateSimilarToCurrentTemp() throws {
  155. let currentTemp = TempBasal(duration: 10, rate: 0.1, temp: .absolute, timestamp: Date())
  156. let (shouldSet, determination) = try callHandleLowEventualGlucose(
  157. eventualGlucose: 99,
  158. targetGlucose: 110,
  159. currentTemp: currentTemp,
  160. adjustedSensitivity: 50
  161. )
  162. #expect(shouldSet == true)
  163. #expect(determination.rate == nil) // No change
  164. #expect(determination.reason.contains("temp \(currentTemp.rate) ~< req"))
  165. }
  166. @Test("Set zero temp") func testSetZeroTemp() throws {
  167. let (shouldSet, determination) = try callHandleLowEventualGlucose(eventualGlucose: 70, naiveEventualGlucose: 60)
  168. #expect(shouldSet == true)
  169. #expect(determination.rate == 0)
  170. #expect(determination.duration! > 0)
  171. #expect(determination.reason.contains("setting \(determination.duration!)m zero temp."))
  172. }
  173. }