DynamicISFTests.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. )
  56. return (profile, preferences, glucose, trioVars)
  57. }
  58. @Test("Returns nil if dISF is disabled") func disabledReturnsNil() throws {
  59. let (profile, preferences, glucose, trioVars) = createDependencies(useNewFormula: false)
  60. let result = DynamicISF.calculate(
  61. profile: profile,
  62. preferences: preferences,
  63. currentGlucose: glucose,
  64. trioCustomOrefVariables: trioVars
  65. )
  66. #expect(result == nil)
  67. }
  68. @Test("Returns nil for invalid autosens limits") func invalidLimitsReturnsNil() throws {
  69. let (profile, preferences, glucose, trioVars) = createDependencies(minAutosens: 1.2, maxAutosens: 1.2)
  70. let result = DynamicISF.calculate(
  71. profile: profile,
  72. preferences: preferences,
  73. currentGlucose: glucose,
  74. trioCustomOrefVariables: trioVars
  75. )
  76. #expect(result == nil)
  77. }
  78. @Test("Logarithmic formula calculates all result fields correctly") func logarithmicFormula() throws {
  79. let (profile, preferences, glucose, trioVars) = createDependencies()
  80. let result = DynamicISF.calculate(
  81. profile: profile,
  82. preferences: preferences,
  83. currentGlucose: glucose,
  84. trioCustomOrefVariables: trioVars
  85. )!
  86. #expect(result.insulinFactor == 55)
  87. #expect(result.tddRatio.rounded(toPlaces: 2) == 1)
  88. #expect(result.ratio.rounded(toPlaces: 2) == 0.77)
  89. }
  90. @Test("Sigmoid formula calculates all result fields correctly") func sigmoidFormula() throws {
  91. var (profile, preferences, glucose, trioVars) = createDependencies()
  92. preferences.sigmoid = true
  93. let result = DynamicISF.calculate(
  94. profile: profile,
  95. preferences: preferences,
  96. currentGlucose: glucose,
  97. trioCustomOrefVariables: trioVars
  98. )!
  99. #expect(result.insulinFactor == 55)
  100. #expect(result.tddRatio == 1.0)
  101. #expect(result.ratio.rounded(scale: 2) == Decimal(string: "1.06"))
  102. }
  103. @Test("Uses default TDD ratio when average TDD is zero") func defaultTddRatio() throws {
  104. let (profile, preferences, glucose, trioVars) = createDependencies(avgTDD: 0)
  105. let result = DynamicISF.calculate(
  106. profile: profile,
  107. preferences: preferences,
  108. currentGlucose: glucose,
  109. trioCustomOrefVariables: trioVars
  110. )!
  111. #expect(result.tddRatio == 1.0)
  112. #expect(result.ratio.rounded(toPlaces: 2) == 0.77)
  113. }
  114. @Test("Uses custom peak time when enabled") func customPeakTime() throws {
  115. let (profile, preferences, glucose, trioVars) = createDependencies(useCustomPeakTime: true)
  116. let result = DynamicISF.calculate(
  117. profile: profile,
  118. preferences: preferences,
  119. currentGlucose: glucose,
  120. trioCustomOrefVariables: trioVars
  121. )!
  122. // 120 - profile.insulinPeakTime (60) = 60
  123. #expect(result.insulinFactor == 60)
  124. }
  125. @Test("Uses ultra-rapid insulin factor correctly") func ultraRapidInsulin() throws {
  126. let (profile, preferences, glucose, trioVars) = createDependencies(insulinCurve: .ultraRapid)
  127. let result = DynamicISF.calculate(
  128. profile: profile,
  129. preferences: preferences,
  130. currentGlucose: glucose,
  131. trioCustomOrefVariables: trioVars
  132. )!
  133. #expect(result.ratio.rounded(scale: 2) == Decimal(string: "0.7"))
  134. }
  135. @Test("Sigmoid handles maxLimit of 1 correctly") func sigmoidMaxLimitOne() throws {
  136. var (profile, preferences, glucose, trioVars) = createDependencies(maxAutosens: 1.0)
  137. preferences.sigmoid = true
  138. let result = DynamicISF.calculate(
  139. profile: profile,
  140. preferences: preferences,
  141. currentGlucose: glucose,
  142. trioCustomOrefVariables: trioVars
  143. )!
  144. #expect(result.insulinFactor == 55)
  145. #expect(result.tddRatio == 1.0)
  146. // BUG: you would expect this to be 1 but because of the fudge factor the
  147. // JS code uses to avoid divide by 0 it 0.99
  148. #expect(result.ratio.rounded(scale: 2) == Decimal(string: "0.99"))
  149. }
  150. @Test("Override with sigmoid adjusts target and ratio correctly") func overrideWithSigmoid() throws {
  151. var (profile, preferences, glucose, trioVars) = createDependencies()
  152. preferences.sigmoid = true
  153. trioVars.useOverride = true
  154. trioVars.overrideTarget = 80
  155. trioVars.overridePercentage = 80
  156. let result = DynamicISF.calculate(
  157. profile: profile,
  158. preferences: preferences,
  159. currentGlucose: glucose,
  160. trioCustomOrefVariables: trioVars
  161. )!
  162. #expect(result.ratio.rounded(toPlaces: 2) == Decimal(string: "1.11"))
  163. }
  164. }