SetTempBasalTests.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. /// A direct port of the Javascript `set-temp-basal.test.js` tests
  5. @Suite("Set Temp Basal Tests") struct SetTempBasalTests {
  6. /// Helper to create a default profile for tests.
  7. private func createProfile(
  8. currentBasal: Decimal = 0.8,
  9. maxDailyBasal: Decimal = 1.3,
  10. maxBasal: Decimal = 3.0,
  11. skipNeutralTemps: Bool = false,
  12. maxDailySafetyMultiplier: Decimal = 3,
  13. currentBasalSafetyMultiplier: Decimal = 4,
  14. model: String? = nil
  15. ) -> Profile {
  16. var profile = Profile()
  17. profile.currentBasal = currentBasal
  18. profile.maxDailyBasal = maxDailyBasal
  19. profile.maxBasal = maxBasal
  20. profile.skipNeutralTemps = skipNeutralTemps
  21. profile.maxDailySafetyMultiplier = maxDailySafetyMultiplier
  22. profile.currentBasalSafetyMultiplier = currentBasalSafetyMultiplier
  23. profile.model = model
  24. return profile
  25. }
  26. /// Helper to create a default determination object.
  27. private func createDetermination() -> Determination {
  28. Determination(
  29. id: UUID(),
  30. reason: "",
  31. units: nil,
  32. insulinReq: nil,
  33. eventualBG: nil,
  34. sensitivityRatio: nil,
  35. rate: nil,
  36. duration: nil,
  37. iob: nil,
  38. cob: nil,
  39. predictions: nil,
  40. deliverAt: Date(),
  41. carbsReq: nil,
  42. temp: .absolute,
  43. bg: nil,
  44. reservoir: nil,
  45. isf: nil,
  46. timestamp: Date(),
  47. tdd: nil,
  48. current_target: nil,
  49. insulinForManualBolus: nil,
  50. manualBolusErrorString: nil,
  51. minDelta: nil,
  52. expectedDelta: nil,
  53. minGuardBG: nil,
  54. minPredBG: nil,
  55. threshold: nil,
  56. carbRatio: nil,
  57. received: false
  58. )
  59. }
  60. /// Helper to create a TempBasal object
  61. private func createCurrentTemp(rate: Decimal = 0, duration: Decimal = 0) -> TempBasal {
  62. TempBasal(
  63. duration: Int(truncating: duration as NSNumber),
  64. rate: rate,
  65. temp: .absolute,
  66. timestamp: Date()
  67. )
  68. }
  69. @Test("should cancel temp") func cancelTemp() throws {
  70. let profile = createProfile()
  71. let determination = createDetermination()
  72. let currentTemp = createCurrentTemp()
  73. let requestedTemp = try TempBasalFunctions.setTempBasal(
  74. rate: 0,
  75. duration: 0,
  76. profile: profile,
  77. determination: determination,
  78. currentTemp: currentTemp
  79. )
  80. #expect(requestedTemp.rate == 0)
  81. #expect(requestedTemp.duration == 0)
  82. }
  83. @Test("should set zero temp") func setZeroTemp() throws {
  84. let profile = createProfile()
  85. let determination = createDetermination()
  86. let currentTemp = createCurrentTemp()
  87. let requestedTemp = try TempBasalFunctions.setTempBasal(
  88. rate: 0,
  89. duration: 30,
  90. profile: profile,
  91. determination: determination,
  92. currentTemp: currentTemp
  93. )
  94. #expect(requestedTemp.rate == 0)
  95. #expect(requestedTemp.duration == 30)
  96. }
  97. @Test("should set high temp") func setHighTemp() throws {
  98. let profile = createProfile()
  99. let determination = createDetermination()
  100. let currentTemp = createCurrentTemp()
  101. let requestedTemp = try TempBasalFunctions.setTempBasal(
  102. rate: 2,
  103. duration: 30,
  104. profile: profile,
  105. determination: determination,
  106. currentTemp: currentTemp
  107. )
  108. #expect(requestedTemp.rate == 2)
  109. #expect(requestedTemp.duration == 30)
  110. }
  111. @Test("should not set basal on skip neutral mode") func skipNeutralMode() throws {
  112. // Test case 1: Current temp is active
  113. var profile = createProfile(currentBasal: 0.8, skipNeutralTemps: true)
  114. var determination = createDetermination()
  115. var currentTemp = createCurrentTemp(duration: 10)
  116. var requestedTemp = try TempBasalFunctions.setTempBasal(
  117. rate: 0.8,
  118. duration: 30,
  119. profile: profile,
  120. determination: determination,
  121. currentTemp: currentTemp
  122. )
  123. #expect(requestedTemp.duration == 0)
  124. // Test case 2: No current temp
  125. determination = createDetermination()
  126. currentTemp = createCurrentTemp() // duration = 0
  127. requestedTemp = try TempBasalFunctions.setTempBasal(
  128. rate: 0.8,
  129. duration: 30,
  130. profile: profile,
  131. determination: determination,
  132. currentTemp: currentTemp
  133. )
  134. #expect(requestedTemp.reason.contains("no temp basal is active, doing nothing") == true)
  135. }
  136. @Test("should limit high temp to max_basal") func limitToMaxBasal() throws {
  137. let profile = createProfile(maxBasal: 3.0)
  138. let determination = createDetermination()
  139. let currentTemp = createCurrentTemp()
  140. let requestedTemp = try TempBasalFunctions.setTempBasal(
  141. rate: 4,
  142. duration: 30,
  143. profile: profile,
  144. determination: determination,
  145. currentTemp: currentTemp
  146. )
  147. #expect(requestedTemp.rate == 3.0)
  148. #expect(requestedTemp.duration == 30)
  149. }
  150. @Test("should limit high temp to 3 * max_daily_basal") func limitToMaxDailyBasal() throws {
  151. let profile = createProfile(currentBasal: 1.0, maxDailyBasal: 1.3, maxBasal: 10.0)
  152. let determination = createDetermination()
  153. let currentTemp = createCurrentTemp()
  154. let requestedTemp = try TempBasalFunctions.setTempBasal(
  155. rate: 6,
  156. duration: 30,
  157. profile: profile,
  158. determination: determination,
  159. currentTemp: currentTemp
  160. )
  161. #expect(requestedTemp.rate == 3.9)
  162. #expect(requestedTemp.duration == 30)
  163. }
  164. @Test("should limit high temp to 4 * current_basal") func limitToCurrentBasal() throws {
  165. let profile = createProfile(currentBasal: 0.7, maxDailyBasal: 1.3, maxBasal: 10.0)
  166. let determination = createDetermination()
  167. let currentTemp = createCurrentTemp()
  168. let requestedTemp = try TempBasalFunctions.setTempBasal(
  169. rate: 6,
  170. duration: 30,
  171. profile: profile,
  172. determination: determination,
  173. currentTemp: currentTemp
  174. )
  175. #expect(requestedTemp.rate == 2.8)
  176. #expect(requestedTemp.duration == 30)
  177. }
  178. @Test("should temp to 0 when requested rate is less than 0") func rateLessThanZero() throws {
  179. let profile = createProfile(currentBasal: 0.7, maxDailyBasal: 1.3, maxBasal: 10.0)
  180. let determination = createDetermination()
  181. let currentTemp = createCurrentTemp()
  182. let requestedTemp = try TempBasalFunctions.setTempBasal(
  183. rate: -1,
  184. duration: 30,
  185. profile: profile,
  186. determination: determination,
  187. currentTemp: currentTemp
  188. )
  189. #expect(requestedTemp.rate == 0)
  190. #expect(requestedTemp.duration == 30)
  191. }
  192. @Test("should limit high temp to 4 * max_daily_basal when overridden") func limitWithOverrideMaxDaily() throws {
  193. let profile = createProfile(currentBasal: 2.0, maxDailyBasal: 1.3, maxBasal: 10.0, maxDailySafetyMultiplier: 4)
  194. let determination = createDetermination()
  195. let currentTemp = createCurrentTemp()
  196. let requestedTemp = try TempBasalFunctions.setTempBasal(
  197. rate: 6,
  198. duration: 30,
  199. profile: profile,
  200. determination: determination,
  201. currentTemp: currentTemp
  202. )
  203. #expect(requestedTemp.rate == 5.2)
  204. #expect(requestedTemp.duration == 30)
  205. }
  206. @Test("should limit high temp to 5 * current_basal when overridden") func limitWithOverrideCurrentBasal() throws {
  207. let profile = createProfile(currentBasal: 0.7, maxDailyBasal: 1.3, maxBasal: 10.0, currentBasalSafetyMultiplier: 5)
  208. let determination = createDetermination()
  209. let currentTemp = createCurrentTemp()
  210. let requestedTemp = try TempBasalFunctions.setTempBasal(
  211. rate: 6,
  212. duration: 30,
  213. profile: profile,
  214. determination: determination,
  215. currentTemp: currentTemp
  216. )
  217. #expect(requestedTemp.rate == 3.5)
  218. #expect(requestedTemp.duration == 30)
  219. }
  220. @Test("should allow small basal change when current temp is also small") func allowSmallChange() throws {
  221. let profile = createProfile(
  222. currentBasal: 0.075,
  223. maxDailyBasal: 1.3,
  224. maxBasal: 10.0,
  225. currentBasalSafetyMultiplier: 5,
  226. model: "523"
  227. )
  228. let determination = createDetermination()
  229. let currentTemp = createCurrentTemp(rate: 0.025, duration: 24)
  230. let requestedTemp = try TempBasalFunctions.setTempBasal(
  231. rate: 0,
  232. duration: 30,
  233. profile: profile,
  234. determination: determination,
  235. currentTemp: currentTemp
  236. )
  237. #expect(requestedTemp.rate == 0)
  238. #expect(requestedTemp.duration == 30)
  239. }
  240. @Test("should not allow small basal change when current temp is large") func disallowSmallChange() throws {
  241. let profile = createProfile(
  242. currentBasal: 10.075,
  243. maxDailyBasal: 11.3,
  244. maxBasal: 50.0,
  245. currentBasalSafetyMultiplier: 5,
  246. model: "523"
  247. )
  248. let determination = createDetermination()
  249. let currentTemp = createCurrentTemp(rate: 10.1, duration: 24)
  250. let requestedTemp = try TempBasalFunctions.setTempBasal(
  251. rate: 10.125,
  252. duration: 30,
  253. profile: profile,
  254. determination: determination,
  255. currentTemp: currentTemp
  256. )
  257. #expect(requestedTemp.reason.contains("no temp required") == true)
  258. }
  259. @Test("should set neutral temp") func setNeutralTemp() throws {
  260. let profile = createProfile(currentBasal: 0.8, skipNeutralTemps: false)
  261. let determination = createDetermination()
  262. let currentTemp = createCurrentTemp()
  263. let requestedTemp = try TempBasalFunctions.setTempBasal(
  264. rate: 0.8,
  265. duration: 30,
  266. profile: profile,
  267. determination: determination,
  268. currentTemp: currentTemp
  269. )
  270. #expect(requestedTemp.rate == 0.8)
  271. #expect(requestedTemp.duration == 30)
  272. #expect(requestedTemp.reason == "Setting neutral temp basal of 0.8U/hr")
  273. }
  274. }