BolusCalculatorTests.swift 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. import Foundation
  2. import Testing
  3. @testable import Trio
  4. @Suite("Bolus Calculator Tests") struct BolusCalculatorTests: Injectable {
  5. @Injected() var calculator: BolusCalculationManager!
  6. @Injected() var settingsManager: SettingsManager!
  7. @Injected() var fileStorage: FileStorage!
  8. @Injected() var apsManager: APSManager!
  9. let resolver = TrioApp().resolver
  10. init() {
  11. injectServices(resolver)
  12. }
  13. @Test("Calculator is correctly initialized") func testCalculatorInitialization() {
  14. #expect(calculator != nil, "BolusCalculationManager should be injected")
  15. #expect(calculator is BaseBolusCalculationManager, "Calculator should be of type BaseBolusCalculationManager")
  16. }
  17. @Test("Calculate insulin for standard meal") func testStandardMealCalculation() async throws {
  18. // STEP 1: Setup test scenario
  19. // We need to provide a CalculationInput struct
  20. let carbs: Decimal = 80
  21. let currentBG: Decimal = 180 // 80 points above target, should result in 2U correction
  22. let deltaBG: Decimal = 5 // Rising trend, should add small correction
  23. let target: Decimal = 100
  24. let isf: Decimal = 40
  25. let carbRatio: Decimal = 10 // Should result in 8U for carbs
  26. let iob: Decimal = 1.0 // Should subtract from final result
  27. let cob: Int16 = 20
  28. let useFattyMealCorrectionFactor: Bool = false
  29. let useSuperBolus: Bool = false
  30. let fattyMealFactor: Decimal = 0.8
  31. let sweetMealFactor: Decimal = 2
  32. let basal: Decimal = 1.5
  33. let fraction: Decimal = 0.8
  34. let maxBolus: Decimal = 10
  35. let maxIOB: Decimal = 15.0
  36. let maxCOB: Decimal = 120.0
  37. let minPredBG: Decimal = 80.0
  38. // STEP 2: Create calculation input
  39. let input = CalculationInput(
  40. carbs: carbs,
  41. currentBG: currentBG,
  42. deltaBG: deltaBG,
  43. target: target,
  44. isf: isf,
  45. carbRatio: carbRatio,
  46. iob: iob,
  47. cob: cob,
  48. useFattyMealCorrectionFactor: useFattyMealCorrectionFactor,
  49. fattyMealFactor: fattyMealFactor,
  50. useSuperBolus: useSuperBolus,
  51. sweetMealFactor: sweetMealFactor,
  52. basal: basal,
  53. fraction: fraction,
  54. maxBolus: maxBolus,
  55. maxIOB: maxIOB,
  56. maxCOB: maxCOB,
  57. minPredBG: minPredBG
  58. )
  59. // STEP 3: Calculate insulin
  60. let result = await calculator.calculateInsulin(input: input)
  61. // STEP 4: Verify results
  62. // Expected calculation breakdown:
  63. // wholeCob = 80g + 20g COB = 100g
  64. // wholeCobInsulin = 100g ÷ 10 g/U = 10U
  65. // targetDifference = currentBG - target = 180 - 100 = 80 mg/dL
  66. // targetDifferenceInsulin = 80 mg/dL ÷ 40 mg/dL/U = 2U
  67. // fifteenMinutesInsulin = 5 mg/dL ÷ 40 mg/dL/U = 0.125U
  68. // correctionInsulin = targetDifferenceInsulin = 2U
  69. // iobInsulinReduction = 1U
  70. // superBolusInsulin = 0U (disabled)
  71. // no adjustment for fatty meals (disabled)
  72. // wholeCalc = round(wholeCobInsulin + correctionInsulin + fifteenMinutesInsulin - iobInsulinReduction, 3) = 11.125U
  73. // insulinCalculated = round(wholeCalc × fraction, 3) = 8.9U
  74. // Calculate expected values with proper rounding using roundBolus method from the apsManager
  75. let wholeCobInsulin = apsManager.roundBolus(amount: Decimal(100) / Decimal(10)) // 10U
  76. let targetDifferenceInsulin = apsManager.roundBolus(amount: Decimal(80) / Decimal(40)) // 2U
  77. let fifteenMinutesInsulin = apsManager.roundBolus(amount: Decimal(5) / Decimal(40)) // 0.125U
  78. let wholeCalc = wholeCobInsulin + targetDifferenceInsulin + fifteenMinutesInsulin - Decimal(1) // 11.125U
  79. let expectedInsulinCalculated = apsManager.roundBolus(amount: wholeCalc * fraction) // 8.9U
  80. #expect(
  81. result.insulinCalculated == expectedInsulinCalculated,
  82. """
  83. Incorrect insulin calculation
  84. Expected: \(expectedInsulinCalculated)U
  85. Actual: \(result.insulinCalculated)U
  86. Components from CalculationResult:
  87. - insulinCalculated: \(result.insulinCalculated)U (expected: \(expectedInsulinCalculated)U)
  88. - wholeCalc: \(result.wholeCalc)U (expected: \(wholeCalc)U)
  89. - correctionInsulin: \(result.correctionInsulin)U (expected: \(targetDifferenceInsulin)U)
  90. - iobInsulinReduction: \(result.iobInsulinReduction)U (expected: 1U)
  91. - superBolusInsulin: \(result.superBolusInsulin)U (expected: 0U)
  92. - targetDifference: \(result.targetDifference) mg/dL (expected: 80 mg/dL)
  93. - targetDifferenceInsulin: \(result.targetDifferenceInsulin)U (expected: \(targetDifferenceInsulin)U)
  94. - fifteenMinutesInsulin: \(result.fifteenMinutesInsulin)U (expected: \(fifteenMinutesInsulin)U)
  95. - wholeCob: \(result.wholeCob)g (expected: 100g)
  96. - wholeCobInsulin: \(result.wholeCobInsulin)U (expected: \(wholeCobInsulin)U)
  97. """
  98. )
  99. // Verify each component from CalculationResult struct with rounded values
  100. #expect(
  101. result.insulinCalculated == expectedInsulinCalculated,
  102. "Final calculated insulin amount should be \(expectedInsulinCalculated)U"
  103. )
  104. #expect(result.wholeCalc == wholeCalc, "Total calculation before fraction should be \(wholeCalc)U")
  105. #expect(
  106. result.correctionInsulin == targetDifferenceInsulin,
  107. "Insulin for BG correction should be \(targetDifferenceInsulin)U"
  108. )
  109. #expect(result.iobInsulinReduction == -1.0, "Absolute IOB reduction amount should be 1U, hence -1U")
  110. #expect(result.superBolusInsulin == 0, "Additional insulin for super bolus should be 0U")
  111. #expect(result.targetDifference == 80, "Difference from target BG should be 80 mg/dL")
  112. #expect(
  113. result.targetDifferenceInsulin == targetDifferenceInsulin,
  114. "Insulin needed for target difference should be \(targetDifferenceInsulin)U"
  115. )
  116. #expect(
  117. result.fifteenMinutesInsulin == fifteenMinutesInsulin,
  118. "Trend-based insulin adjustment should be \(fifteenMinutesInsulin)U"
  119. )
  120. #expect(result.wholeCob == 100, "Total carbs (COB + new carbs) should be 100g")
  121. #expect(result.wholeCobInsulin == wholeCobInsulin, "Insulin for total carbs should be \(wholeCobInsulin)U")
  122. }
  123. @Test("Calculate insulin for fatty meal") func testFattyMealCalculation() async throws {
  124. // STEP 1: Setup test scenario
  125. // We need to provide a CalculationInput struct
  126. let carbs: Decimal = 80
  127. let currentBG: Decimal = 180 // 80 points above target, should result in 2U correction
  128. let deltaBG: Decimal = 5 // Rising trend, should add small correction
  129. let target: Decimal = 100
  130. let isf: Decimal = 40
  131. let carbRatio: Decimal = 10 // Should result in 8U for carbs
  132. let iob: Decimal = 1.0 // Should subtract from final result
  133. let cob: Int16 = 20
  134. let useFattyMealCorrectionFactor: Bool = true // now set to true
  135. let useSuperBolus: Bool = false
  136. let fattyMealFactor: Decimal = 0.8
  137. let sweetMealFactor: Decimal = 2
  138. let basal: Decimal = 1.5
  139. let fraction: Decimal = 0.8
  140. let maxBolus: Decimal = 10
  141. let maxIOB: Decimal = 15.0
  142. let maxCOB: Decimal = 120.0
  143. let minPredBG: Decimal = 80.0
  144. // STEP 2: Create calculation input
  145. let input = CalculationInput(
  146. carbs: carbs,
  147. currentBG: currentBG,
  148. deltaBG: deltaBG,
  149. target: target,
  150. isf: isf,
  151. carbRatio: carbRatio,
  152. iob: iob,
  153. cob: cob,
  154. useFattyMealCorrectionFactor: useFattyMealCorrectionFactor,
  155. fattyMealFactor: fattyMealFactor,
  156. useSuperBolus: useSuperBolus,
  157. sweetMealFactor: sweetMealFactor,
  158. basal: basal,
  159. fraction: fraction,
  160. maxBolus: maxBolus,
  161. maxIOB: maxIOB,
  162. maxCOB: maxCOB,
  163. minPredBG: minPredBG
  164. )
  165. // STEP 3: Calculate insulin with fatty meal enabled
  166. let fattyMealResult = await calculator.calculateInsulin(input: input)
  167. // STEP 4: Calculate insulin with fatty meal disabled for comparison
  168. let standardInput = CalculationInput(
  169. carbs: carbs,
  170. currentBG: currentBG,
  171. deltaBG: deltaBG,
  172. target: target,
  173. isf: isf,
  174. carbRatio: carbRatio,
  175. iob: iob,
  176. cob: cob,
  177. useFattyMealCorrectionFactor: false, // Disabled for comparison
  178. fattyMealFactor: fattyMealFactor,
  179. useSuperBolus: useSuperBolus,
  180. sweetMealFactor: sweetMealFactor,
  181. basal: basal,
  182. fraction: fraction,
  183. maxBolus: maxBolus,
  184. maxIOB: maxIOB,
  185. maxCOB: maxCOB,
  186. minPredBG: minPredBG
  187. )
  188. let standardResult = await calculator.calculateInsulin(input: standardInput)
  189. // STEP 5: Verify results
  190. // Fatty meal should reduce the insulin amount by the fatty meal factor (0.8)
  191. let expectedReduction = fattyMealFactor
  192. let actualReduction = Decimal(
  193. (Double(fattyMealResult.insulinCalculated) / Double(standardResult.insulinCalculated) * 10.0).rounded() / 10.0
  194. )
  195. #expect(
  196. actualReduction == expectedReduction,
  197. """
  198. Fatty meal calculation incorrect
  199. Expected reduction factor: \(expectedReduction)
  200. Actual reduction factor: \(actualReduction)
  201. Standard calculation: \(standardResult.insulinCalculated)U
  202. Fatty meal calculation: \(fattyMealResult.insulinCalculated)U
  203. """
  204. )
  205. }
  206. @Test("Calculate insulin with super bolus") func testSuperBolusCalculation() async throws {
  207. // STEP 1: Setup test scenario
  208. // We need to provide a CalculationInput struct
  209. let carbs: Decimal = 80
  210. let currentBG: Decimal = 180 // 80 points above target, should result in 2U correction
  211. let deltaBG: Decimal = 5 // Rising trend, should add small correction
  212. let target: Decimal = 100
  213. let isf: Decimal = 40
  214. let carbRatio: Decimal = 10 // Should result in 8U for carbs
  215. let iob: Decimal = 1.0 // Should subtract from final result
  216. let cob: Int16 = 20
  217. let useFattyMealCorrectionFactor: Bool = false
  218. let useSuperBolus: Bool = true // Super bolus enabled
  219. let fattyMealFactor: Decimal = 0.8
  220. let sweetMealFactor: Decimal = 2
  221. let basal: Decimal = 1.5 // Will be added to insulin calculation when super bolus is enabled
  222. let fraction: Decimal = 0.8
  223. let maxBolus: Decimal = 10
  224. let maxIOB: Decimal = 15.0
  225. let maxCOB: Decimal = 120.0
  226. let minPredBG: Decimal = 80.0
  227. // STEP 2: Create calculation input with super bolus enabled
  228. let input = CalculationInput(
  229. carbs: carbs,
  230. currentBG: currentBG,
  231. deltaBG: deltaBG,
  232. target: target,
  233. isf: isf,
  234. carbRatio: carbRatio,
  235. iob: iob,
  236. cob: cob,
  237. useFattyMealCorrectionFactor: useFattyMealCorrectionFactor,
  238. fattyMealFactor: fattyMealFactor,
  239. useSuperBolus: useSuperBolus,
  240. sweetMealFactor: sweetMealFactor,
  241. basal: basal,
  242. fraction: fraction,
  243. maxBolus: maxBolus,
  244. maxIOB: maxIOB,
  245. maxCOB: maxCOB,
  246. minPredBG: minPredBG
  247. )
  248. // STEP 3: Calculate insulin with super bolus enabled
  249. let superBolusResult = await calculator.calculateInsulin(input: input)
  250. // STEP 4: Calculate insulin with super bolus disabled for comparison
  251. let standardInput = CalculationInput(
  252. carbs: carbs,
  253. currentBG: currentBG,
  254. deltaBG: deltaBG,
  255. target: target,
  256. isf: isf,
  257. carbRatio: carbRatio,
  258. iob: iob,
  259. cob: cob,
  260. useFattyMealCorrectionFactor: useFattyMealCorrectionFactor,
  261. fattyMealFactor: fattyMealFactor,
  262. useSuperBolus: false, // Disabled for comparison
  263. sweetMealFactor: sweetMealFactor,
  264. basal: basal,
  265. fraction: fraction,
  266. maxBolus: maxBolus,
  267. maxIOB: maxIOB,
  268. maxCOB: maxCOB,
  269. minPredBG: minPredBG
  270. )
  271. let standardResult = await calculator.calculateInsulin(input: standardInput)
  272. // STEP 5: Verify results
  273. // Super bolus should add basal rate * sweetMealFactor to the insulin calculation
  274. let expectedSuperBolusInsulin = basal * sweetMealFactor
  275. #expect(
  276. superBolusResult.superBolusInsulin == expectedSuperBolusInsulin,
  277. """
  278. Super bolus insulin incorrect
  279. Expected: \(expectedSuperBolusInsulin)U (basal \(basal)U × sweetMealFactor \(sweetMealFactor))
  280. Actual: \(superBolusResult.superBolusInsulin)U
  281. """
  282. )
  283. #expect(
  284. superBolusResult.insulinCalculated > standardResult.insulinCalculated,
  285. """
  286. Super bolus calculation incorrect
  287. Expected super bolus calculation to be higher than standard
  288. Super bolus: \(superBolusResult.insulinCalculated)U
  289. Standard: \(standardResult.insulinCalculated)U
  290. Difference: \(superBolusResult.insulinCalculated - standardResult.insulinCalculated)U
  291. """
  292. )
  293. // The difference should be the difference of super bolus (= standard dose + the basal rate * sweetMealFactor) limited by max bolus, and the standard dose.
  294. let actualDifference = (superBolusResult.insulinCalculated - standardResult.insulinCalculated)
  295. let expectedDifference = min(superBolusResult.insulinCalculated, maxBolus) - standardResult.insulinCalculated
  296. #expect(
  297. actualDifference == expectedDifference,
  298. """
  299. Super bolus difference incorrect
  300. Expected difference: min(\(expectedSuperBolusInsulin), \(maxBolus)) U (basal \(basal)U × sweetMealFactor \(sweetMealFactor) + standard dose \(standardResult
  301. .insulinCalculated)) - standard dose \(standardResult.insulinCalculated)
  302. Actual difference: \(actualDifference)U
  303. Standard result: \(standardResult)
  304. SuperBolus result: \(superBolusResult)
  305. """
  306. )
  307. }
  308. @Test("Calculate insulin with zero carbs") func testZeroCarbsCalculation() async throws {
  309. // Given
  310. let carbs: Decimal = 0
  311. // When
  312. let result = await calculator.handleBolusCalculation(
  313. carbs: carbs,
  314. useFattyMealCorrection: false,
  315. useSuperBolus: false,
  316. minPredBG: nil
  317. )
  318. // Then
  319. #expect(result.wholeCobInsulin == 0, "Zero carbs should require no insulin for carbs")
  320. }
  321. @Test("Verify settings retrieval") func testGetSettings() async throws {
  322. // Given - Save original settings to restore later
  323. let originalSettings = settingsManager.settings
  324. // Setup test settings
  325. let expectedUnits = GlucoseUnits.mgdL
  326. let expectedFraction: Decimal = 0.7
  327. let expectedFattyMealFactor: Decimal = 0.8
  328. let expectedSweetMealFactor: Decimal = 2
  329. let expectedMaxCarbs: Decimal = 150
  330. // Update settings through settings manager
  331. settingsManager.settings.units = expectedUnits
  332. settingsManager.settings.overrideFactor = expectedFraction
  333. settingsManager.settings.fattyMealFactor = expectedFattyMealFactor
  334. settingsManager.settings.sweetMealFactor = expectedSweetMealFactor
  335. settingsManager.settings.maxCarbs = expectedMaxCarbs
  336. // Save settings to storage
  337. fileStorage.save(settingsManager.settings, as: OpenAPS.Settings.settings)
  338. // When
  339. let (units, fraction, fattyMealFactor, sweetMealFactor, maxCarbs) = await getSettings()
  340. // Then
  341. #expect(units == expectedUnits, "Units should match settings")
  342. #expect(fraction == expectedFraction, "Override factor should match settings")
  343. #expect(fattyMealFactor == expectedFattyMealFactor, "Fatty meal factor should match settings")
  344. #expect(sweetMealFactor == expectedSweetMealFactor, "Sweet meal factor should match settings")
  345. #expect(maxCarbs == expectedMaxCarbs, "Max carbs should match settings")
  346. // Cleanup - Restore original settings
  347. settingsManager.settings = originalSettings
  348. fileStorage.save(originalSettings, as: OpenAPS.Settings.settings)
  349. }
  350. @Test("Verify getCurrentSettingValue returns correct values based on time") func testGetCurrentSettingValue() async throws {
  351. // STEP 1: Backup current settings
  352. let originalBasalProfile = await fileStorage.retrieveAsync(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self)
  353. let originalCarbRatios = await fileStorage.retrieveAsync(OpenAPS.Settings.carbRatios, as: CarbRatios.self)
  354. let originalBGTargets = await fileStorage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self)
  355. let originalISFValues = await fileStorage.retrieveAsync(
  356. OpenAPS.Settings.insulinSensitivities,
  357. as: InsulinSensitivities.self
  358. )
  359. // STEP 2: Setup test data with known values
  360. // Note: Entries must be sorted by time for the algorithm to work correctly
  361. let basalProfile = [
  362. BasalProfileEntry(start: "00:00", minutes: 0, rate: 1.0), // 12:00 AM - 6:00 AM: 1.0
  363. BasalProfileEntry(start: "06:00", minutes: 360, rate: 1.2), // 6:00 AM - 12:00 PM: 1.2
  364. BasalProfileEntry(start: "12:00", minutes: 720, rate: 1.1), // 12:00 PM - 6:00 PM: 1.1
  365. BasalProfileEntry(start: "18:00", minutes: 1080, rate: 0.9) // 6:00 PM - 12:00 AM: 0.9
  366. ]
  367. let carbRatios = CarbRatios(
  368. units: .grams,
  369. schedule: [
  370. CarbRatioEntry(start: "00:00", offset: 0, ratio: 10), // 12:00 AM - 12:00 PM: 10
  371. CarbRatioEntry(start: "12:00", offset: 720, ratio: 12) // 12:00 PM - 12:00 AM: 12
  372. ]
  373. )
  374. let bgTargets = BGTargets(
  375. units: .mgdL,
  376. userPreferredUnits: .mgdL,
  377. targets: [
  378. BGTargetEntry(low: 100, high: 120, start: "00:00", offset: 0), // 12:00 AM - 8:00 AM: 100
  379. BGTargetEntry(low: 90, high: 110, start: "08:00", offset: 480) // 8:00 AM - 12:00 AM: 90
  380. ]
  381. )
  382. let isfValues = InsulinSensitivities(
  383. units: .mgdL,
  384. userPreferredUnits: .mgdL,
  385. sensitivities: [
  386. InsulinSensitivityEntry(sensitivity: 40, offset: 0, start: "00:00"), // 12:00 AM - 2:00 PM: 40
  387. InsulinSensitivityEntry(sensitivity: 45, offset: 840, start: "14:00") // 2:00 PM - 12:00 AM: 45
  388. ]
  389. )
  390. // STEP 3: Store test data
  391. fileStorage.save(basalProfile, as: OpenAPS.Settings.basalProfile)
  392. fileStorage.save(carbRatios, as: OpenAPS.Settings.carbRatios)
  393. fileStorage.save(bgTargets, as: OpenAPS.Settings.bgTargets)
  394. fileStorage.save(isfValues, as: OpenAPS.Settings.insulinSensitivities)
  395. // STEP 4: Define test cases with specific times and expected values
  396. // Format: (hour, minute, [setting type: expected value])
  397. let testTimes: [(hour: Int, minute: Int, expected: [SettingType: Decimal])] = [
  398. // Test midnight values (00:00)
  399. (
  400. hour: 0, minute: 0,
  401. expected: [
  402. .basal: 1.0, // First basal rate
  403. .carbRatio: 10, // First carb ratio
  404. .bgTarget: 100, // First target
  405. .isf: 40 // First ISF
  406. ]
  407. ),
  408. // Test mid-morning values (7:00)
  409. (
  410. hour: 7, minute: 0,
  411. expected: [
  412. .basal: 1.2, // Second basal rate (after 6:00)
  413. .carbRatio: 10, // Still first carb ratio
  414. .bgTarget: 100, // Still first target
  415. .isf: 40 // Still first ISF
  416. ]
  417. ),
  418. // Test afternoon values (15:00)
  419. (
  420. hour: 15, minute: 0,
  421. expected: [
  422. .basal: 1.1, // Third basal rate (after 12:00)
  423. .carbRatio: 12, // Second carb ratio (after 12:00)
  424. .bgTarget: 90, // Second target
  425. .isf: 45 // Second ISF (after 14:00)
  426. ]
  427. )
  428. ]
  429. // STEP 5: Test each time point
  430. for testTime in testTimes {
  431. // Create a date object for the test time
  432. let calendar = Calendar.current
  433. var components = calendar.dateComponents([.year, .month, .day], from: Date())
  434. components.hour = testTime.hour
  435. components.minute = testTime.minute
  436. components.second = 0
  437. guard let testDate = calendar.date(from: components) else {
  438. throw TestError("Failed to create test date")
  439. }
  440. // Test each setting type at this time
  441. for (type, expectedValue) in testTime.expected {
  442. // Get the actual value for this setting at the test time
  443. let value = await getCurrentSettingValue(for: type, at: testDate)
  444. // Compare with expected value
  445. #expect(
  446. value == expectedValue,
  447. """
  448. Failed at \(testTime.hour):\(String(format: "%02d", testTime.minute))
  449. Setting: \(type)
  450. Expected: \(expectedValue)
  451. Actual: \(value)
  452. """
  453. )
  454. }
  455. }
  456. // STEP 6: Cleanup - Restore original settings
  457. if let originalBasalProfile = originalBasalProfile {
  458. fileStorage.save(originalBasalProfile, as: OpenAPS.Settings.basalProfile)
  459. }
  460. if let originalCarbRatios = originalCarbRatios {
  461. fileStorage.save(originalCarbRatios, as: OpenAPS.Settings.carbRatios)
  462. }
  463. if let originalBGTargets = originalBGTargets {
  464. fileStorage.save(originalBGTargets, as: OpenAPS.Settings.bgTargets)
  465. }
  466. if let originalISFValues = originalISFValues {
  467. fileStorage.save(originalISFValues, as: OpenAPS.Settings.insulinSensitivities)
  468. }
  469. }
  470. }
  471. // Copied over from BolusCalculationManager as they are not included in the protocol definition (and I don´t want them to be included)
  472. extension BolusCalculatorTests {
  473. private enum SettingType {
  474. case basal
  475. case carbRatio
  476. case bgTarget
  477. case isf
  478. }
  479. /// Retrieves current settings from the SettingsManager
  480. /// - Returns: Tuple containing units, fraction, fattyMealFactor, sweetMealFactor, and maxCarbs settings
  481. private func getSettings() async -> (
  482. units: GlucoseUnits,
  483. fraction: Decimal,
  484. fattyMealFactor: Decimal,
  485. sweetMealFactor: Decimal,
  486. maxCarbs: Decimal
  487. ) {
  488. return (
  489. units: settingsManager.settings.units,
  490. fraction: settingsManager.settings.overrideFactor,
  491. fattyMealFactor: settingsManager.settings.fattyMealFactor,
  492. sweetMealFactor: settingsManager.settings.sweetMealFactor,
  493. maxCarbs: settingsManager.settings.maxCarbs
  494. )
  495. }
  496. /// Gets the current setting value for a specific setting type based on the time of day
  497. /// - Parameter type: The type of setting to retrieve (basal, carbRatio, bgTarget, or isf)
  498. /// - Returns: The current decimal value for the specified setting type
  499. private func getCurrentSettingValue(for type: SettingType, at date: Date) async -> Decimal {
  500. let calendar = Calendar.current
  501. let midnight = calendar.startOfDay(for: date)
  502. let minutesSinceMidnight = calendar.dateComponents([.minute], from: midnight, to: date).minute ?? 0
  503. switch type {
  504. case .basal:
  505. let profile = await getBasalProfile()
  506. return profile.last { $0.minutes <= minutesSinceMidnight }?.rate ?? 0
  507. case .carbRatio:
  508. let ratios = await getCarbRatios()
  509. return ratios.schedule.last { $0.offset <= minutesSinceMidnight }?.ratio ?? 0
  510. case .bgTarget:
  511. let targets = await getBGTargets()
  512. return targets.targets.last { $0.offset <= minutesSinceMidnight }?.low ?? 0
  513. case .isf:
  514. let sensitivities = await getISFValues()
  515. return sensitivities.sensitivities.last { $0.offset <= minutesSinceMidnight }?.sensitivity ?? 0
  516. }
  517. }
  518. /// Retrieves the pump settings from storage
  519. /// - Returns: PumpSettings object containing pump configuration
  520. private func getPumpSettings() async -> PumpSettings {
  521. await fileStorage.retrieveAsync(OpenAPS.Settings.settings, as: PumpSettings.self)
  522. ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
  523. ?? PumpSettings(insulinActionCurve: 10, maxBolus: 10, maxBasal: 2)
  524. }
  525. /// Retrieves the basal profile from storage
  526. /// - Returns: Array of BasalProfileEntry objects
  527. private func getBasalProfile() async -> [BasalProfileEntry] {
  528. await fileStorage.retrieveAsync(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self)
  529. ?? [BasalProfileEntry](from: OpenAPS.defaults(for: OpenAPS.Settings.basalProfile))
  530. ?? []
  531. }
  532. /// Retrieves carb ratios from storage
  533. /// - Returns: CarbRatios object containing carb ratio schedule
  534. private func getCarbRatios() async -> CarbRatios {
  535. await fileStorage.retrieveAsync(OpenAPS.Settings.carbRatios, as: CarbRatios.self)
  536. ?? CarbRatios(from: OpenAPS.defaults(for: OpenAPS.Settings.carbRatios))
  537. ?? CarbRatios(units: .grams, schedule: [])
  538. }
  539. /// Retrieves blood glucose targets from storage
  540. /// - Returns: BGTargets object containing target schedule
  541. private func getBGTargets() async -> BGTargets {
  542. await fileStorage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self)
  543. ?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
  544. ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
  545. }
  546. /// Retrieves insulin sensitivity factors from storage
  547. /// - Returns: InsulinSensitivities object containing sensitivity schedule
  548. private func getISFValues() async -> InsulinSensitivities {
  549. await fileStorage.retrieveAsync(OpenAPS.Settings.insulinSensitivities, as: InsulinSensitivities.self)
  550. ?? InsulinSensitivities(from: OpenAPS.defaults(for: OpenAPS.Settings.insulinSensitivities))
  551. ?? InsulinSensitivities(
  552. units: .mgdL,
  553. userPreferredUnits: .mgdL,
  554. sensitivities: []
  555. )
  556. }
  557. }