|
|
@@ -0,0 +1,446 @@
|
|
|
+import CoreData
|
|
|
+import Foundation
|
|
|
+import Swinject
|
|
|
+
|
|
|
+protocol BolusCalculationManager {
|
|
|
+ func calculateInsulin(input: CalculationInput) async -> CalculationResult
|
|
|
+ func handleBolusCalculation(carbs: Decimal, useFattyMealCorrection: Bool, useSuperBolus: Bool) async -> CalculationResult
|
|
|
+}
|
|
|
+
|
|
|
+final class BaseBolusCalculationManager: BolusCalculationManager, Injectable {
|
|
|
+ @Injected() private var apsManager: APSManager!
|
|
|
+ @Injected() private var settingsManager: SettingsManager!
|
|
|
+ @Injected() private var fileStorage: FileStorage!
|
|
|
+ @Injected() private var determinationStorage: DeterminationStorage!
|
|
|
+
|
|
|
+ let glucoseFetchContext = CoreDataStack.shared.newTaskContext()
|
|
|
+ let determinationFetchContext = CoreDataStack.shared.newTaskContext()
|
|
|
+
|
|
|
+ init(resolver: Resolver) {
|
|
|
+ injectServices(resolver)
|
|
|
+ }
|
|
|
+
|
|
|
+ // MARK: - Types
|
|
|
+
|
|
|
+ private struct GlucoseVariables {
|
|
|
+ var currentBG: Decimal
|
|
|
+ var deltaBG: Decimal
|
|
|
+ }
|
|
|
+
|
|
|
+ private struct BolusCalculatorVariables {
|
|
|
+ var insulinRequired: Decimal
|
|
|
+ var evBG: Decimal
|
|
|
+ var insulin: Decimal
|
|
|
+ var target: Decimal
|
|
|
+ var isf: Decimal
|
|
|
+ var cob: Int16
|
|
|
+ var iob: Decimal
|
|
|
+ var basal: Decimal
|
|
|
+ var carbRatio: Decimal
|
|
|
+ var insulinCalculated: Decimal
|
|
|
+ }
|
|
|
+
|
|
|
+ private enum SettingType {
|
|
|
+ case basal
|
|
|
+ case carbRatio
|
|
|
+ case bgTarget
|
|
|
+ case isf
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Retrieves current settings from the SettingsManager
|
|
|
+ /// - Returns: Tuple containing units, fraction, fattyMealFactor, sweetMealFactor, and maxCarbs settings
|
|
|
+ private func getSettings() async -> (
|
|
|
+ units: GlucoseUnits,
|
|
|
+ fraction: Decimal,
|
|
|
+ fattyMealFactor: Decimal,
|
|
|
+ sweetMealFactor: Decimal,
|
|
|
+ maxCarbs: Decimal
|
|
|
+ ) {
|
|
|
+ return (
|
|
|
+ units: settingsManager.settings.units,
|
|
|
+ fraction: settingsManager.settings.overrideFactor,
|
|
|
+ fattyMealFactor: settingsManager.settings.fattyMealFactor,
|
|
|
+ sweetMealFactor: settingsManager.settings.sweetMealFactor,
|
|
|
+ maxCarbs: settingsManager.settings.maxCarbs
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Gets the current setting value for a specific setting type based on the time of day
|
|
|
+ /// - Parameter type: The type of setting to retrieve (basal, carbRatio, bgTarget, or isf)
|
|
|
+ /// - Returns: The current decimal value for the specified setting type
|
|
|
+ private func getCurrentSettingValue(for type: SettingType) async -> Decimal {
|
|
|
+ let now = Date()
|
|
|
+ let calendar = Calendar.current
|
|
|
+ let dateFormatter = DateFormatter()
|
|
|
+ dateFormatter.timeZone = TimeZone.current
|
|
|
+
|
|
|
+ let regexWithSeconds = #"^\d{2}:\d{2}:\d{2}$"#
|
|
|
+
|
|
|
+ let entries: [(start: String, value: Decimal)]
|
|
|
+
|
|
|
+ switch type {
|
|
|
+ case .basal:
|
|
|
+ let basalEntries = await getBasalProfile()
|
|
|
+ entries = basalEntries.map { ($0.start, $0.rate) }
|
|
|
+ case .carbRatio:
|
|
|
+ let carbRatios = await getCarbRatios()
|
|
|
+ entries = carbRatios.schedule.map { ($0.start, $0.ratio) }
|
|
|
+ case .bgTarget:
|
|
|
+ let bgTargets = await getBGTargets()
|
|
|
+ entries = bgTargets.targets.map { ($0.start, $0.low) }
|
|
|
+ case .isf:
|
|
|
+ let isfValues = await getISFValues()
|
|
|
+ entries = isfValues.sensitivities.map { ($0.start, $0.sensitivity) }
|
|
|
+ }
|
|
|
+
|
|
|
+ for (index, entry) in entries.enumerated() {
|
|
|
+ // Dynamically set the format based on whether it matches the regex
|
|
|
+ if entry.start.range(of: regexWithSeconds, options: .regularExpression) != nil {
|
|
|
+ dateFormatter.dateFormat = "HH:mm:ss"
|
|
|
+ } else {
|
|
|
+ dateFormatter.dateFormat = "HH:mm"
|
|
|
+ }
|
|
|
+
|
|
|
+ guard let entryTime = dateFormatter.date(from: entry.start) else {
|
|
|
+ print("Invalid entry start time: \(entry.start)")
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ let entryComponents = calendar.dateComponents([.hour, .minute, .second], from: entryTime)
|
|
|
+ let entryStartTime = calendar.date(
|
|
|
+ bySettingHour: entryComponents.hour!,
|
|
|
+ minute: entryComponents.minute!,
|
|
|
+ second: entryComponents.second ?? 0, // Set seconds to 0 if not provided
|
|
|
+ of: now
|
|
|
+ )!
|
|
|
+
|
|
|
+ let entryEndTime: Date
|
|
|
+ if index < entries.count - 1 {
|
|
|
+ // Dynamically set the format again for the next element
|
|
|
+ if entries[index + 1].start.range(of: regexWithSeconds, options: .regularExpression) != nil {
|
|
|
+ dateFormatter.dateFormat = "HH:mm:ss"
|
|
|
+ } else {
|
|
|
+ dateFormatter.dateFormat = "HH:mm"
|
|
|
+ }
|
|
|
+
|
|
|
+ if let nextEntryTime = dateFormatter.date(from: entries[index + 1].start) {
|
|
|
+ let nextEntryComponents = calendar.dateComponents([.hour, .minute, .second], from: nextEntryTime)
|
|
|
+ entryEndTime = calendar.date(
|
|
|
+ bySettingHour: nextEntryComponents.hour!,
|
|
|
+ minute: nextEntryComponents.minute!,
|
|
|
+ second: nextEntryComponents.second ?? 0,
|
|
|
+ of: now
|
|
|
+ )!
|
|
|
+ } else {
|
|
|
+ entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ entryEndTime = calendar.date(byAdding: .day, value: 1, to: entryStartTime)!
|
|
|
+ }
|
|
|
+
|
|
|
+ if now >= entryStartTime, now < entryEndTime {
|
|
|
+ return entry.value
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Retrieves the pump settings from storage
|
|
|
+ /// - Returns: PumpSettings object containing pump configuration
|
|
|
+ private func getPumpSettings() async -> PumpSettings {
|
|
|
+ await fileStorage.retrieveAsync(OpenAPS.Settings.settings, as: PumpSettings.self)
|
|
|
+ ?? PumpSettings(from: OpenAPS.defaults(for: OpenAPS.Settings.settings))
|
|
|
+ ?? PumpSettings(insulinActionCurve: 10, maxBolus: 10, maxBasal: 2)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Retrieves the basal profile from storage
|
|
|
+ /// - Returns: Array of BasalProfileEntry objects
|
|
|
+ private func getBasalProfile() async -> [BasalProfileEntry] {
|
|
|
+ await fileStorage.retrieveAsync(OpenAPS.Settings.basalProfile, as: [BasalProfileEntry].self)
|
|
|
+ ?? [BasalProfileEntry](from: OpenAPS.defaults(for: OpenAPS.Settings.basalProfile))
|
|
|
+ ?? []
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Retrieves carb ratios from storage
|
|
|
+ /// - Returns: CarbRatios object containing carb ratio schedule
|
|
|
+ private func getCarbRatios() async -> CarbRatios {
|
|
|
+ await fileStorage.retrieveAsync(OpenAPS.Settings.carbRatios, as: CarbRatios.self)
|
|
|
+ ?? CarbRatios(from: OpenAPS.defaults(for: OpenAPS.Settings.carbRatios))
|
|
|
+ ?? CarbRatios(units: .grams, schedule: [])
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Retrieves blood glucose targets from storage
|
|
|
+ /// - Returns: BGTargets object containing target schedule
|
|
|
+ private func getBGTargets() async -> BGTargets {
|
|
|
+ await fileStorage.retrieveAsync(OpenAPS.Settings.bgTargets, as: BGTargets.self)
|
|
|
+ ?? BGTargets(from: OpenAPS.defaults(for: OpenAPS.Settings.bgTargets))
|
|
|
+ ?? BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: [])
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Retrieves insulin sensitivity factors from storage
|
|
|
+ /// - Returns: InsulinSensitivities object containing sensitivity schedule
|
|
|
+ private func getISFValues() async -> InsulinSensitivities {
|
|
|
+ await fileStorage.retrieveAsync(OpenAPS.Settings.insulinSensitivities, as: InsulinSensitivities.self)
|
|
|
+ ?? InsulinSensitivities(from: OpenAPS.defaults(for: OpenAPS.Settings.insulinSensitivities))
|
|
|
+ ?? InsulinSensitivities(
|
|
|
+ units: .mgdL,
|
|
|
+ userPreferredUnits: .mgdL,
|
|
|
+ sensitivities: []
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Fetches recent glucose readings from CoreData
|
|
|
+ /// - Returns: Array of NSManagedObjectIDs for glucose readings
|
|
|
+ private func fetchGlucose() async -> [NSManagedObjectID] {
|
|
|
+ let results = await CoreDataStack.shared.fetchEntitiesAsync(
|
|
|
+ ofType: GlucoseStored.self,
|
|
|
+ onContext: glucoseFetchContext,
|
|
|
+ predicate: NSPredicate.glucose,
|
|
|
+ key: "date",
|
|
|
+ ascending: false,
|
|
|
+ fetchLimit: 288
|
|
|
+ )
|
|
|
+
|
|
|
+ return await glucoseFetchContext.perform {
|
|
|
+ guard let fetchedResults = results as? [GlucoseStored] else { return [] }
|
|
|
+ return fetchedResults.map(\.objectID)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Updates glucose-related variables based on recent readings
|
|
|
+ /// - Parameter objects: Array of GlucoseStored objects
|
|
|
+ /// - Returns: GlucoseVariables containing current blood glucose and delta
|
|
|
+ private func updateGlucoseVariables(with objects: [GlucoseStored]) -> GlucoseVariables {
|
|
|
+ let lastGlucose = objects.first?.glucose ?? 0
|
|
|
+ let thirdLastGlucose = objects.dropFirst(2).first?.glucose ?? 0
|
|
|
+ let delta = Decimal(lastGlucose) - Decimal(thirdLastGlucose)
|
|
|
+
|
|
|
+ return GlucoseVariables(currentBG: Decimal(lastGlucose), deltaBG: delta)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Updates bolus calculator variables based on recent determinations and current settings
|
|
|
+ /// - Parameters:
|
|
|
+ /// - objects: Array of OrefDetermination objects
|
|
|
+ /// - currentBGTarget: Current blood glucose target
|
|
|
+ /// - currentISF: Current insulin sensitivity factor
|
|
|
+ /// - currentCarbRatio: Current carb ratio
|
|
|
+ /// - currentBasal: Current basal rate
|
|
|
+ /// - Returns: BolusCalculatorVariables containing updated calculation parameters
|
|
|
+ private func updateBolusCalculatorVariables(
|
|
|
+ with objects: [OrefDetermination],
|
|
|
+ currentBGTarget: Decimal,
|
|
|
+ currentISF: Decimal,
|
|
|
+ currentCarbRatio: Decimal,
|
|
|
+ currentBasal: Decimal
|
|
|
+ ) -> BolusCalculatorVariables {
|
|
|
+ guard let mostRecentDetermination = objects.first else {
|
|
|
+ return BolusCalculatorVariables(
|
|
|
+ insulinRequired: 0,
|
|
|
+ evBG: 0,
|
|
|
+ insulin: 0,
|
|
|
+ target: currentBGTarget,
|
|
|
+ isf: currentISF,
|
|
|
+ cob: 0,
|
|
|
+ iob: 0,
|
|
|
+ basal: currentBasal,
|
|
|
+ carbRatio: currentCarbRatio,
|
|
|
+ insulinCalculated: 0
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return BolusCalculatorVariables(
|
|
|
+ insulinRequired: (mostRecentDetermination.insulinReq ?? 0) as Decimal,
|
|
|
+ evBG: (mostRecentDetermination.eventualBG ?? 0) as Decimal,
|
|
|
+ insulin: (mostRecentDetermination.insulinForManualBolus ?? 0) as Decimal,
|
|
|
+ target: (mostRecentDetermination.currentTarget ?? currentBGTarget as NSDecimalNumber) as Decimal,
|
|
|
+ isf: (mostRecentDetermination.insulinSensitivity ?? NSDecimalNumber(decimal: currentISF)) as Decimal,
|
|
|
+ cob: mostRecentDetermination.cob as Int16,
|
|
|
+ iob: (mostRecentDetermination.iob ?? 0) as Decimal,
|
|
|
+ basal: (mostRecentDetermination.tempBasal ?? currentBasal as NSDecimalNumber) as Decimal,
|
|
|
+ carbRatio: (mostRecentDetermination.carbRatio ?? NSDecimalNumber(decimal: currentCarbRatio)) as Decimal,
|
|
|
+ insulinCalculated: 0
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ private func prepareCalculationInput(
|
|
|
+ carbs: Decimal,
|
|
|
+ useFattyMealCorrection: Bool,
|
|
|
+ useSuperBolus: Bool
|
|
|
+ ) async -> CalculationInput {
|
|
|
+ // Get settings
|
|
|
+ let settings = await getSettings()
|
|
|
+
|
|
|
+ // Get max bolus
|
|
|
+ let maxBolus = await getPumpSettings().maxBolus
|
|
|
+
|
|
|
+ // Get current profile values
|
|
|
+ let currentBasal = await getCurrentSettingValue(for: .basal)
|
|
|
+ let currentCarbRatio = await getCurrentSettingValue(for: .carbRatio)
|
|
|
+ let currentBGTarget = await getCurrentSettingValue(for: .bgTarget)
|
|
|
+ let currentISF = await getCurrentSettingValue(for: .isf)
|
|
|
+
|
|
|
+ // Fetch glucose data
|
|
|
+ let glucoseIds = await fetchGlucose()
|
|
|
+ let glucoseObjects: [GlucoseStored] = await CoreDataStack.shared.getNSManagedObject(
|
|
|
+ with: glucoseIds,
|
|
|
+ context: glucoseFetchContext
|
|
|
+ )
|
|
|
+ let glucoseVars = await glucoseFetchContext.perform {
|
|
|
+ self.updateGlucoseVariables(with: glucoseObjects)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fetch determination data
|
|
|
+ let determinationIds = await determinationStorage.fetchLastDeterminationObjectID(
|
|
|
+ predicate: NSPredicate.predicateFor30MinAgoForDetermination
|
|
|
+ )
|
|
|
+ let determinationObjects: [OrefDetermination] = await CoreDataStack.shared.getNSManagedObject(
|
|
|
+ with: determinationIds,
|
|
|
+ context: determinationFetchContext
|
|
|
+ )
|
|
|
+ let bolusVars = await determinationFetchContext.perform {
|
|
|
+ self.updateBolusCalculatorVariables(
|
|
|
+ with: determinationObjects,
|
|
|
+ currentBGTarget: currentBGTarget,
|
|
|
+ currentISF: currentISF,
|
|
|
+ currentCarbRatio: currentCarbRatio,
|
|
|
+ currentBasal: currentBasal
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return CalculationInput(
|
|
|
+ carbs: carbs,
|
|
|
+ currentBG: glucoseVars.currentBG,
|
|
|
+ deltaBG: glucoseVars.deltaBG,
|
|
|
+ target: bolusVars.target,
|
|
|
+ isf: bolusVars.isf,
|
|
|
+ carbRatio: bolusVars.carbRatio,
|
|
|
+ iob: bolusVars.iob,
|
|
|
+ cob: bolusVars.cob,
|
|
|
+ useFattyMealCorrectionFactor: useFattyMealCorrection,
|
|
|
+ fattyMealFactor: settings.fattyMealFactor,
|
|
|
+ useSuperBolus: useSuperBolus,
|
|
|
+ sweetMealFactor: settings.sweetMealFactor,
|
|
|
+ basal: bolusVars.basal,
|
|
|
+ fraction: settings.fraction,
|
|
|
+ maxBolus: maxBolus
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Calculates the recommended insulin dose based on various parameters
|
|
|
+ /// - Parameter input: CalculationInput containing all necessary parameters
|
|
|
+ /// - Returns: CalculationResult with detailed breakdown of the calculation
|
|
|
+ func calculateInsulin(input: CalculationInput) async -> CalculationResult {
|
|
|
+ // insulin needed for the current blood glucose
|
|
|
+ let targetDifference = input.currentBG - input.target
|
|
|
+ let targetDifferenceInsulin = apsManager.roundBolus(amount: targetDifference / input.isf)
|
|
|
+
|
|
|
+ // more or less insulin because of bg trend in the last 15 minutes
|
|
|
+ let fifteenMinutesInsulin = apsManager.roundBolus(amount: input.deltaBG / input.isf)
|
|
|
+
|
|
|
+ // determine whole COB for which we want to dose insulin for and then determine insulin for wholeCOB
|
|
|
+ let wholeCob = Decimal(input.cob) + input.carbs
|
|
|
+ let wholeCobInsulin = apsManager.roundBolus(amount: wholeCob / input.carbRatio)
|
|
|
+
|
|
|
+ // determine how much the calculator reduces/ increases the bolus because of IOB
|
|
|
+ let iobInsulinReduction = (-1) * input.iob
|
|
|
+
|
|
|
+ // adding everything together
|
|
|
+ // add a calc for the case that no fifteenMinInsulin is available
|
|
|
+ let wholeCalc: Decimal
|
|
|
+ if input.deltaBG != 0 {
|
|
|
+ wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin + fifteenMinutesInsulin)
|
|
|
+ } else {
|
|
|
+ // add (rare) case that no glucose value is available -> maybe display warning?
|
|
|
+ // if no bg is available, ?? sets its value to 0
|
|
|
+ if input.currentBG == 0 {
|
|
|
+ wholeCalc = (iobInsulinReduction + wholeCobInsulin)
|
|
|
+ } else {
|
|
|
+ wholeCalc = (targetDifferenceInsulin + iobInsulinReduction + wholeCobInsulin)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // apply custom factor at the end of the calculations
|
|
|
+ let result = wholeCalc * input.fraction
|
|
|
+
|
|
|
+ // apply custom factor if fatty meal toggle in bolus calc config settings is on and the box for fatty meals is checked (in RootView)
|
|
|
+ var insulinCalculated: Decimal
|
|
|
+ var superBolusInsulin: Decimal = 0
|
|
|
+ if input.useFattyMealCorrectionFactor {
|
|
|
+ insulinCalculated = result * input.fattyMealFactor
|
|
|
+ } else if input.useSuperBolus {
|
|
|
+ superBolusInsulin = input.sweetMealFactor * input.basal
|
|
|
+ insulinCalculated = result + superBolusInsulin
|
|
|
+ } else {
|
|
|
+ insulinCalculated = result
|
|
|
+ }
|
|
|
+
|
|
|
+ // display no negative insulinCalculated
|
|
|
+ insulinCalculated = max(insulinCalculated, 0)
|
|
|
+ insulinCalculated = min(insulinCalculated, input.maxBolus)
|
|
|
+
|
|
|
+ // round calculated recommendation to allowed bolus increment
|
|
|
+ insulinCalculated = apsManager.roundBolus(amount: insulinCalculated)
|
|
|
+
|
|
|
+ return CalculationResult(
|
|
|
+ insulinCalculated: insulinCalculated,
|
|
|
+ wholeCalc: wholeCalc,
|
|
|
+ correctionInsulin: targetDifferenceInsulin,
|
|
|
+ iobInsulinReduction: iobInsulinReduction,
|
|
|
+ superBolusInsulin: superBolusInsulin,
|
|
|
+ targetDifference: targetDifference,
|
|
|
+ targetDifferenceInsulin: targetDifferenceInsulin,
|
|
|
+ fifteenMinutesInsulin: fifteenMinutesInsulin,
|
|
|
+ wholeCob: wholeCob,
|
|
|
+ wholeCobInsulin: wholeCobInsulin
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Handles the complete bolus calculation process
|
|
|
+ /// - Parameters:
|
|
|
+ /// - carbs: Amount of carbohydrates to be consumed
|
|
|
+ /// - useFattyMealCorrection: Whether to apply fatty meal correction
|
|
|
+ /// - useSuperBolus: Whether to use super bolus calculation
|
|
|
+ /// - Returns: CalculationResult containing the calculated insulin dose and details
|
|
|
+ func handleBolusCalculation(carbs: Decimal, useFattyMealCorrection: Bool, useSuperBolus: Bool) async -> CalculationResult {
|
|
|
+ let input = await prepareCalculationInput(
|
|
|
+ carbs: carbs,
|
|
|
+ useFattyMealCorrection: useFattyMealCorrection,
|
|
|
+ useSuperBolus: useSuperBolus
|
|
|
+ )
|
|
|
+ let result = await calculateInsulin(input: input)
|
|
|
+ return result
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Input parameters required for bolus calculation
|
|
|
+struct CalculationInput: Sendable {
|
|
|
+ let carbs: Decimal // Carbohydrates to be consumed (in grams)
|
|
|
+ let currentBG: Decimal // Current blood glucose level
|
|
|
+ let deltaBG: Decimal // Blood glucose change in last 15 minutes
|
|
|
+ let target: Decimal // Target blood glucose level
|
|
|
+ let isf: Decimal // Insulin Sensitivity Factor
|
|
|
+ let carbRatio: Decimal // Carb to insulin ratio
|
|
|
+ let iob: Decimal // Insulin on Board
|
|
|
+ let cob: Int16 // Carbs on Board
|
|
|
+ let useFattyMealCorrectionFactor: Bool // Whether to apply fatty meal correction
|
|
|
+ let fattyMealFactor: Decimal // Factor for fatty meal adjustment
|
|
|
+ let useSuperBolus: Bool // Whether to use super bolus calculation
|
|
|
+ let sweetMealFactor: Decimal // Factor for sweet meal adjustment
|
|
|
+ let basal: Decimal // Current basal rate
|
|
|
+ let fraction: Decimal // General correction factor
|
|
|
+ let maxBolus: Decimal // Maximum allowed bolus
|
|
|
+}
|
|
|
+
|
|
|
+/// Results of the bolus calculation
|
|
|
+struct CalculationResult: Sendable {
|
|
|
+ let insulinCalculated: Decimal // Final calculated insulin amount
|
|
|
+ let wholeCalc: Decimal // Total calculation before adjustments
|
|
|
+ let correctionInsulin: Decimal // Insulin for BG correction
|
|
|
+ let iobInsulinReduction: Decimal // IOB reduction amount
|
|
|
+ let superBolusInsulin: Decimal // Additional insulin for super bolus
|
|
|
+ let targetDifference: Decimal // Difference from target BG
|
|
|
+ let targetDifferenceInsulin: Decimal // Insulin needed for target difference
|
|
|
+ let fifteenMinutesInsulin: Decimal // Trend-based insulin adjustment
|
|
|
+ let wholeCob: Decimal // Total carbs (COB + new carbs)
|
|
|
+ let wholeCobInsulin: Decimal // Insulin needed for total carbs
|
|
|
+}
|