IobTotalTests.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. @Suite("Calculate Total IOB Tests") struct CalculateIobTotalTests {
  5. // Helper function to create a basic treatment
  6. func createTreatment(insulin: Decimal, timestamp: Date) -> ComputedPumpHistoryEvent {
  7. ComputedPumpHistoryEvent.forTest(
  8. type: .bolus,
  9. timestamp: timestamp,
  10. insulin: insulin
  11. )
  12. }
  13. // Helper function to create a basic profile
  14. func createProfile(
  15. dia: Decimal = 5,
  16. curve: InsulinCurve = .rapidActing,
  17. useCustomPeakTime: Bool = false,
  18. insulinPeakTime: Decimal = 0
  19. ) -> Profile {
  20. var profile = Profile()
  21. profile.curve = curve
  22. profile.useCustomPeakTime = useCustomPeakTime
  23. profile.insulinPeakTime = insulinPeakTime
  24. profile.dia = dia
  25. return profile
  26. }
  27. @Test("should return zero values when no treatments provided") func returnZeroForNoTreatments() async throws {
  28. let now = Date()
  29. let result = try IobCalculation.iobTotal(
  30. treatments: [],
  31. profile: createProfile(),
  32. time: now
  33. )
  34. #expect(result.iob == 0)
  35. #expect(result.activity == 0)
  36. #expect(result.basaliob == 0)
  37. #expect(result.bolusiob == 0)
  38. }
  39. @Test("should calculate total IOB with rapid-acting insulin bolus") func calculateTotalRapidActing() async throws {
  40. let now = Date()
  41. let thirtyMinsAgo = now - 30.minutesToSeconds
  42. let treatments = [createTreatment(insulin: 2, timestamp: thirtyMinsAgo)]
  43. let result = try IobCalculation.iobTotal(
  44. treatments: treatments,
  45. profile: createProfile(dia: 5, curve: .rapidActing),
  46. time: now
  47. )
  48. #expect(result.iob.isWithin(0.1, of: 1.8))
  49. #expect(result.bolusiob.isWithin(0.1, of: 1.8))
  50. #expect(result.basaliob == 0)
  51. }
  52. @Test("should calculate total IOB with ultra-rapid insulin bolus") func calculateTotalUltraRapid() async throws {
  53. let now = Date()
  54. let thirtyMinsAgo = now - 30.minutesToSeconds
  55. let treatments = [createTreatment(insulin: 2, timestamp: thirtyMinsAgo)]
  56. let result = try IobCalculation.iobTotal(
  57. treatments: treatments,
  58. profile: createProfile(dia: 5, curve: .ultraRapid),
  59. time: now
  60. )
  61. #expect(result.iob.isWithin(0.001, of: 1.769))
  62. #expect(result.bolusiob.isWithin(0.001, of: 1.769))
  63. #expect(result.basaliob == 0)
  64. }
  65. @Test("should calculate total IOB with basal insulin") func calculateTotalBasal() async throws {
  66. let now = Date()
  67. let thirtyMinsAgo = now - 30.minutesToSeconds
  68. let treatments = [createTreatment(insulin: -0.05, timestamp: thirtyMinsAgo)]
  69. let result = try IobCalculation.iobTotal(
  70. treatments: treatments,
  71. profile: createProfile(dia: 5, curve: .rapidActing),
  72. time: now
  73. )
  74. #expect(result.basaliob.isWithin(0.001, of: -0.046))
  75. #expect(result.bolusiob == 0)
  76. }
  77. @Test("should handle multiple treatments of different types") func handleMultipleTreatments() async throws {
  78. let now = Date()
  79. let treatments = [
  80. createTreatment(insulin: 2.0, timestamp: now - 30.minutesToSeconds),
  81. createTreatment(insulin: 0.05, timestamp: now - 20.minutesToSeconds),
  82. createTreatment(insulin: 1.0, timestamp: now - 10.minutesToSeconds)
  83. ]
  84. let result = try IobCalculation.iobTotal(
  85. treatments: treatments,
  86. profile: createProfile(dia: 5, curve: .rapidActing),
  87. time: now
  88. )
  89. #expect(result.basaliob.isWithin(0.001, of: 0.048))
  90. #expect(result.bolusinsulin == 3.0)
  91. #expect(result.netbasalinsulin == 0.05)
  92. }
  93. @Test("should handle custom peak times for rapid-acting insulin") func handleCustomPeakRapidActing() async throws {
  94. let now = Date()
  95. let thirtyMinsAgo = now - 30.minutesToSeconds
  96. let treatments = [createTreatment(insulin: 2.0, timestamp: thirtyMinsAgo)]
  97. let result = try IobCalculation.iobTotal(
  98. treatments: treatments,
  99. profile: createProfile(
  100. dia: 5,
  101. curve: .rapidActing,
  102. useCustomPeakTime: true,
  103. insulinPeakTime: 100
  104. ),
  105. time: now
  106. )
  107. #expect(result.iob.isWithin(0.001, of: 1.898))
  108. }
  109. @Test("should handle custom peak times for ultra-rapid insulin") func handleCustomPeakUltraRapid() async throws {
  110. let now = Date()
  111. let thirtyMinsAgo = now - 30.minutesToSeconds
  112. let treatments = [createTreatment(insulin: 2.0, timestamp: thirtyMinsAgo)]
  113. let result = try IobCalculation.iobTotal(
  114. treatments: treatments,
  115. profile: createProfile(
  116. dia: 5,
  117. curve: .ultraRapid,
  118. useCustomPeakTime: true,
  119. insulinPeakTime: 80
  120. ),
  121. time: now
  122. )
  123. #expect(result.iob.isWithin(0.001, of: 1.863))
  124. }
  125. @Test("should ignore future treatments") func ignoreFutureTreatments() async throws {
  126. let now = Date()
  127. let treatments = [
  128. createTreatment(insulin: 2.0, timestamp: now + 30.minutesToSeconds),
  129. createTreatment(insulin: 1.0, timestamp: now - 10.minutesToSeconds)
  130. ]
  131. let result = try IobCalculation.iobTotal(
  132. treatments: treatments,
  133. profile: createProfile(dia: 5, curve: .rapidActing),
  134. time: now
  135. )
  136. #expect(result.bolusinsulin == 1.0)
  137. }
  138. @Test("should ignore treatments older than DIA") func ignoreOldTreatments() async throws {
  139. let now = Date()
  140. let sixHoursAgo = now - 6.hoursToSeconds
  141. let treatments = [createTreatment(insulin: 2.0, timestamp: sixHoursAgo)]
  142. let result = try IobCalculation.iobTotal(
  143. treatments: treatments,
  144. profile: createProfile(dia: 5, curve: .rapidActing),
  145. time: now
  146. )
  147. #expect(result.iob == 0)
  148. #expect(result.activity == 0)
  149. }
  150. @Test("should enforce minimum DIA of 5 hours for both insulin types") func enforceMinimumDIA() async throws {
  151. let now = Date()
  152. let fourHoursAgo = now - 4.hoursToSeconds
  153. let treatments = [createTreatment(insulin: 2.0, timestamp: fourHoursAgo)]
  154. // Test rapid-acting
  155. let rapidResult = try IobCalculation.iobTotal(
  156. treatments: treatments,
  157. profile: createProfile(dia: 4, curve: .rapidActing),
  158. time: now
  159. )
  160. #expect(rapidResult.iob > 0)
  161. // Test ultra-rapid
  162. let ultraResult = try IobCalculation.iobTotal(
  163. treatments: treatments,
  164. profile: createProfile(dia: 4, curve: .ultraRapid),
  165. time: now
  166. )
  167. #expect(ultraResult.iob > 0)
  168. }
  169. }