BolusCalculatorTests.swift 26 KB

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