DynamicISFTests.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. /// The corresponding Javascript tests to confirm these numbers are here:
  5. /// - https://github.com/kingst/trio-oref/blob/dev-fixes-for-swift-comparison/tests/dynamic-isf.test.js
  6. @Suite("DynamicISF Calculation Tests") struct DynamicISFTests {
  7. // Helper to create common dependencies for tests
  8. private func createDependencies(
  9. useNewFormula: Bool = true,
  10. tdd: Decimal = 30,
  11. avgTDD: Decimal = 30,
  12. sensitivity: Decimal = 50,
  13. minAutosens: Decimal = 0.7,
  14. maxAutosens: Decimal = 1.2,
  15. useCustomPeakTime: Bool = false,
  16. insulinCurve: InsulinCurve = .rapidActing
  17. ) -> (Profile, Preferences, Decimal, TrioCustomOrefVariables) {
  18. var preferences = Preferences()
  19. preferences.useNewFormula = useNewFormula
  20. preferences.sigmoid = false
  21. preferences.adjustmentFactor = 0.8
  22. preferences.adjustmentFactorSigmoid = 0.5
  23. preferences.useCustomPeakTime = useCustomPeakTime
  24. preferences.curve = insulinCurve
  25. var profile = Profile()
  26. profile.sens = sensitivity
  27. profile.autosensMin = minAutosens
  28. profile.autosensMax = maxAutosens
  29. profile.minBg = 100
  30. profile.curve = insulinCurve
  31. profile.useCustomPeakTime = useCustomPeakTime
  32. profile.insulinPeakTime = 60 // For custom peak time test
  33. let glucose = Decimal(120)
  34. let trioVars = TrioCustomOrefVariables(
  35. average_total_data: avgTDD,
  36. weightedAverage: tdd,
  37. currentTDD: tdd,
  38. past2hoursAverage: 0,
  39. date: Date(),
  40. overridePercentage: 100,
  41. useOverride: false,
  42. duration: 0,
  43. unlimited: true,
  44. overrideTarget: 0,
  45. smbIsOff: false,
  46. advancedSettings: false,
  47. isfAndCr: false,
  48. isf: true,
  49. cr: true,
  50. smbIsScheduledOff: false,
  51. start: 0,
  52. end: 0,
  53. smbMinutes: 30,
  54. uamMinutes: 30,
  55. shouldProtectDueToHIGH: false
  56. )
  57. return (profile, preferences, glucose, trioVars)
  58. }
  59. @Test("Returns nil if dISF is disabled") func disabledReturnsNil() throws {
  60. let (profile, preferences, glucose, trioVars) = createDependencies(useNewFormula: false)
  61. let result = DynamicISF.calculate(
  62. profile: profile,
  63. preferences: preferences,
  64. currentGlucose: glucose,
  65. trioCustomOrefVariables: trioVars
  66. )
  67. #expect(result == nil)
  68. }
  69. @Test("Returns nil for invalid autosens limits") func invalidLimitsReturnsNil() throws {
  70. let (profile, preferences, glucose, trioVars) = createDependencies(minAutosens: 1.2, maxAutosens: 1.2)
  71. let result = DynamicISF.calculate(
  72. profile: profile,
  73. preferences: preferences,
  74. currentGlucose: glucose,
  75. trioCustomOrefVariables: trioVars
  76. )
  77. #expect(result == nil)
  78. }
  79. @Test("Logarithmic formula calculates all result fields correctly") func logarithmicFormula() throws {
  80. let (profile, preferences, glucose, trioVars) = createDependencies()
  81. let result = DynamicISF.calculate(
  82. profile: profile,
  83. preferences: preferences,
  84. currentGlucose: glucose,
  85. trioCustomOrefVariables: trioVars
  86. )!
  87. #expect(result.insulinFactor == 55)
  88. #expect(result.tddRatio.rounded(toPlaces: 2) == 1)
  89. #expect(result.ratio.rounded(toPlaces: 2) == 0.77)
  90. }
  91. @Test("Sigmoid formula calculates all result fields correctly") func sigmoidFormula() throws {
  92. var (profile, preferences, glucose, trioVars) = createDependencies()
  93. preferences.sigmoid = true
  94. let result = DynamicISF.calculate(
  95. profile: profile,
  96. preferences: preferences,
  97. currentGlucose: glucose,
  98. trioCustomOrefVariables: trioVars
  99. )!
  100. #expect(result.insulinFactor == 55)
  101. #expect(result.tddRatio == 1.0)
  102. #expect(result.ratio.rounded(scale: 2) == Decimal(string: "1.06"))
  103. }
  104. @Test("Uses default TDD ratio when average TDD is zero") func defaultTddRatio() throws {
  105. let (profile, preferences, glucose, trioVars) = createDependencies(avgTDD: 0)
  106. let result = DynamicISF.calculate(
  107. profile: profile,
  108. preferences: preferences,
  109. currentGlucose: glucose,
  110. trioCustomOrefVariables: trioVars
  111. )!
  112. #expect(result.tddRatio == 1.0)
  113. #expect(result.ratio.rounded(toPlaces: 2) == 0.77)
  114. }
  115. @Test("Uses custom peak time when enabled") func customPeakTime() throws {
  116. let (profile, preferences, glucose, trioVars) = createDependencies(useCustomPeakTime: true)
  117. let result = DynamicISF.calculate(
  118. profile: profile,
  119. preferences: preferences,
  120. currentGlucose: glucose,
  121. trioCustomOrefVariables: trioVars
  122. )!
  123. // 120 - profile.insulinPeakTime (60) = 60
  124. #expect(result.insulinFactor == 60)
  125. }
  126. @Test("Uses ultra-rapid insulin factor correctly") func ultraRapidInsulin() throws {
  127. let (profile, preferences, glucose, trioVars) = createDependencies(insulinCurve: .ultraRapid)
  128. let result = DynamicISF.calculate(
  129. profile: profile,
  130. preferences: preferences,
  131. currentGlucose: glucose,
  132. trioCustomOrefVariables: trioVars
  133. )!
  134. #expect(result.ratio.rounded(scale: 2) == Decimal(string: "0.7"))
  135. }
  136. @Test("Sigmoid handles maxLimit of 1 correctly") func sigmoidMaxLimitOne() throws {
  137. var (profile, preferences, glucose, trioVars) = createDependencies(maxAutosens: 1.0)
  138. preferences.sigmoid = true
  139. let result = DynamicISF.calculate(
  140. profile: profile,
  141. preferences: preferences,
  142. currentGlucose: glucose,
  143. trioCustomOrefVariables: trioVars
  144. )!
  145. #expect(result.insulinFactor == 55)
  146. #expect(result.tddRatio == 1.0)
  147. // BUG: you would expect this to be 1 but because of the fudge factor the
  148. // JS code uses to avoid divide by 0 it 0.99
  149. #expect(result.ratio.rounded(scale: 2) == Decimal(string: "0.99"))
  150. }
  151. @Test("Override with sigmoid adjusts target and ratio correctly") func overrideWithSigmoid() throws {
  152. var (profile, preferences, glucose, trioVars) = createDependencies()
  153. preferences.sigmoid = true
  154. trioVars.useOverride = true
  155. trioVars.overrideTarget = 80
  156. trioVars.overridePercentage = 80
  157. let result = DynamicISF.calculate(
  158. profile: profile,
  159. preferences: preferences,
  160. currentGlucose: glucose,
  161. trioCustomOrefVariables: trioVars
  162. )!
  163. #expect(result.ratio.rounded(toPlaces: 2) == Decimal(string: "1.11"))
  164. }
  165. }