From 94fb603037bf146e611d038fcd9dd3f5026e0b6c Mon Sep 17 00:00:00 2001 From: marionbarker Date: Wed, 26 Apr 2023 11:48:31 -0700 Subject: [PATCH 01/22] Add Dosing Strategy: automaticBolusSlidingScale --- Loop/Managers/LoopDataManager.swift | 33 ++++++++++++++++++++ Loop/Models/LoopConstants.swift | 7 +++++ Loop/Views/DosingStrategySelectionView.swift | 2 ++ LoopCore/LoopSettings.swift | 2 ++ 4 files changed, 44 insertions(+) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 18a0816621..38673bfdc0 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -1712,6 +1712,39 @@ extension LoopDataManager { rateRounder: rateRounder, isBasalRateScheduleOverrideActive: settings.scheduleOverride?.isBasalRateScheduleOverriden(at: startDate) == true ) + case .automaticBolusSlidingScale: + let volumeRounder = { (_ units: Double) in + return self.delegate?.roundBolusVolume(units: units) ?? units + } + + // slidingPartialBolusApplicationFactor calculated based on LoopConstants + // and current glucose and current target range + let currentGlucose = glucose.quantity.doubleValue(for: settings.glucoseUnit ?? .milligramsPerDeciliter) + let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() + let correctionRange = correctionRangeSchedule!.quantityRange(at: now()) + let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: settings.glucoseUnit ?? .milligramsPerDeciliter) + let minGlucoseSlidingScale = LoopConstants.minGlucoseDeltaSlidingScale + lowerBoundTarget + let scalingFraction = LoopConstants.scalingNumerator/(LoopConstants.maxGlucoseSlidingScale-minGlucoseSlidingScale) + let scalingGlucose = max(currentGlucose - minGlucoseSlidingScale, 0.0) + let slidingPartialBolusApplicationFactor = min(LoopConstants.minPartialApplicationFactor + scalingGlucose * scalingFraction, LoopConstants.maxPartialApplicationFactor) + + // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus + let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(slidingPartialBolusApplicationFactor, 1.0)) + + dosingRecommendation = predictedGlucose.recommendedAutomaticDose( + to: glucoseTargetRange!, + at: predictedGlucose[0].startDate, + suspendThreshold: settings.suspendThreshold?.quantity, + sensitivity: insulinSensitivity!, + model: doseStore.insulinModelProvider.model(for: pumpInsulinType), + basalRates: basalRateSchedule!, + maxAutomaticBolus: maxAutomaticBolus, + partialApplicationFactor: slidingPartialBolusApplicationFactor * self.timeBasedDoseApplicationFactor, + lastTempBasal: lastTempBasal, + volumeRounder: volumeRounder, + rateRounder: rateRounder, + isBasalRateScheduleOverrideActive: settings.scheduleOverride?.isBasalRateScheduleOverriden(at: startDate) == true + ) case .tempBasalOnly: let temp = predictedGlucose.recommendedTempBasal( diff --git a/Loop/Models/LoopConstants.swift b/Loop/Models/LoopConstants.swift index a62fc13849..2cca3438fe 100644 --- a/Loop/Models/LoopConstants.swift +++ b/Loop/Models/LoopConstants.swift @@ -75,4 +75,11 @@ enum LoopConstants { static let simpleBolusCalculatorMinGlucoseBolusRecommendation = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) static let simpleBolusCalculatorMinGlucoseMealBolusRecommendation = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 55) static let simpleBolusCalculatorGlucoseWarningLimit = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) + + // Dosing Stategy: Automatic Bolus: Sliding Scale + static let minPartialApplicationFactor = 0.15 // similar to temp basal strategy + static let maxPartialApplicationFactor = 1.00 // full correction + static let scalingNumerator = maxPartialApplicationFactor - minPartialApplicationFactor + static let maxGlucoseSlidingScale = 250.0 // mg/dL value at which full correction is applied + static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL delta from low end of correction range for minPartialApplicationFactor } diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index f447b032b6..931423d6d7 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -59,6 +59,8 @@ extension AutomaticDosingStrategy { return NSLocalizedString("Loop will set temporary basal rates to increase and decrease insulin delivery.", comment: "Description string for temp basal only dosing strategy") case .automaticBolus: return NSLocalizedString("Loop will automatically bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") + case .automaticBolusSlidingScale: + return NSLocalizedString("Loop will automatically bolus using partial bolus factor that increases with glucose level when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for sliding scale automatic bolus dosing strategy") } } diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 63418ad4aa..8f9084a1a2 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -15,6 +15,8 @@ public extension AutomaticDosingStrategy { return LocalizedString("Temp Basal Only", comment: "Title string for temp basal only dosing strategy") case .automaticBolus: return LocalizedString("Automatic Bolus", comment: "Title string for automatic bolus dosing strategy") + case .automaticBolusSlidingScale: + return LocalizedString("Automatic Bolus: Sliding Scale", comment: "Title string for automatic bolus with sliding scale dosing strategy") } } } From 4ef6ff12ea5e59b60f96053ae547fd18718cbe90 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Wed, 26 Apr 2023 12:57:22 -0700 Subject: [PATCH 02/22] Reverse order of two new constants --- Loop/Models/LoopConstants.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Models/LoopConstants.swift b/Loop/Models/LoopConstants.swift index 2cca3438fe..cc77c4be22 100644 --- a/Loop/Models/LoopConstants.swift +++ b/Loop/Models/LoopConstants.swift @@ -80,6 +80,6 @@ enum LoopConstants { static let minPartialApplicationFactor = 0.15 // similar to temp basal strategy static let maxPartialApplicationFactor = 1.00 // full correction static let scalingNumerator = maxPartialApplicationFactor - minPartialApplicationFactor - static let maxGlucoseSlidingScale = 250.0 // mg/dL value at which full correction is applied static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL delta from low end of correction range for minPartialApplicationFactor + static let maxGlucoseSlidingScale = 250.0 // mg/dL value at which full correction is applied } From ff1bb4061588088821decb1efc03841bca884060 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Thu, 27 Apr 2023 09:23:07 -0700 Subject: [PATCH 03/22] revert to dev for dosing strategy selection --- Loop/Views/DosingStrategySelectionView.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index 931423d6d7..f447b032b6 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -59,8 +59,6 @@ extension AutomaticDosingStrategy { return NSLocalizedString("Loop will set temporary basal rates to increase and decrease insulin delivery.", comment: "Description string for temp basal only dosing strategy") case .automaticBolus: return NSLocalizedString("Loop will automatically bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") - case .automaticBolusSlidingScale: - return NSLocalizedString("Loop will automatically bolus using partial bolus factor that increases with glucose level when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for sliding scale automatic bolus dosing strategy") } } From 8ff48a22f8e80c217d88ea9ad0283778afe1fecc Mon Sep 17 00:00:00 2001 From: marionbarker Date: Thu, 27 Apr 2023 09:24:34 -0700 Subject: [PATCH 04/22] revert to dev for dosing strategy selection --- LoopCore/LoopSettings.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 8f9084a1a2..63418ad4aa 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -15,8 +15,6 @@ public extension AutomaticDosingStrategy { return LocalizedString("Temp Basal Only", comment: "Title string for temp basal only dosing strategy") case .automaticBolus: return LocalizedString("Automatic Bolus", comment: "Title string for automatic bolus dosing strategy") - case .automaticBolusSlidingScale: - return LocalizedString("Automatic Bolus: Sliding Scale", comment: "Title string for automatic bolus with sliding scale dosing strategy") } } } From 5a5b6a89b8b7cd3f7e20299e33d2794121a72d58 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Thu, 27 Apr 2023 10:30:58 -0700 Subject: [PATCH 05/22] Rename to effectiveBolusApplicationFactor, change to flag inside existing automaticBolus case --- Loop/Managers/LoopDataManager.swift | 56 +++++++++++++---------------- Loop/Models/LoopConstants.swift | 11 +++--- 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 38673bfdc0..bad33919aa 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -67,6 +67,21 @@ final class LoopDataManager { private var insulinOnBoard: InsulinValue? + // Calculate the effectiveBolusApplicationFactor + private func calculateEffectiveBolusApplicationFactor(glucose: HKQuantity, correctionRangeSchedule: GlucoseRangeSchedule, settings: LoopSettings) -> Double { + // Calculate current glucose and lower bound target + let currentGlucose = glucose.doubleValue(for: .milligramsPerDeciliter) + let correctionRange = correctionRangeSchedule.quantityRange(at: now()) + let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: .milligramsPerDeciliter) + // Calculate minimum glucose sliding scale and scaling fraction + let minGlucoseSlidingScale = LoopConstants.minGlucoseDeltaSlidingScale + lowerBoundTarget + let scalingFraction = (LoopConstants.maxPartialApplicationFactor-LoopConstants.minPartialApplicationFactor) / (LoopConstants.maxGlucoseSlidingScale - minGlucoseSlidingScale) + let scalingGlucose = max(currentGlucose - minGlucoseSlidingScale, 0.0) + // Calculate effectiveBolusApplicationFactor + let effectiveBolusApplicationFactor = min(LoopConstants.minPartialApplicationFactor + scalingGlucose * scalingFraction, LoopConstants.maxPartialApplicationFactor) + return effectiveBolusApplicationFactor + } + deinit { for observer in notificationObservers { NotificationCenter.default.removeObserver(observer) @@ -1696,40 +1711,19 @@ extension LoopDataManager { return self.delegate?.roundBolusVolume(units: units) ?? units } - let maxAutomaticBolus = min(iobHeadroom, maxBolus! * LoopConstants.bolusPartialApplicationFactor) + var effectiveBolusApplicationFactor = LoopConstants.bolusPartialApplicationFactor - dosingRecommendation = predictedGlucose.recommendedAutomaticDose( - to: glucoseTargetRange!, - at: predictedGlucose[0].startDate, - suspendThreshold: settings.suspendThreshold?.quantity, - sensitivity: insulinSensitivity!, - model: doseStore.insulinModelProvider.model(for: pumpInsulinType), - basalRates: basalRateSchedule!, - maxAutomaticBolus: maxAutomaticBolus, - partialApplicationFactor: LoopConstants.bolusPartialApplicationFactor * self.timeBasedDoseApplicationFactor, - lastTempBasal: lastTempBasal, - volumeRounder: volumeRounder, - rateRounder: rateRounder, - isBasalRateScheduleOverrideActive: settings.scheduleOverride?.isBasalRateScheduleOverriden(at: startDate) == true - ) - case .automaticBolusSlidingScale: - let volumeRounder = { (_ units: Double) in - return self.delegate?.roundBolusVolume(units: units) ?? units - } + // TO DO: make this a selectable flag in Dosing Strategy screen + let flagSlidingScale = true - // slidingPartialBolusApplicationFactor calculated based on LoopConstants - // and current glucose and current target range - let currentGlucose = glucose.quantity.doubleValue(for: settings.glucoseUnit ?? .milligramsPerDeciliter) - let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() - let correctionRange = correctionRangeSchedule!.quantityRange(at: now()) - let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: settings.glucoseUnit ?? .milligramsPerDeciliter) - let minGlucoseSlidingScale = LoopConstants.minGlucoseDeltaSlidingScale + lowerBoundTarget - let scalingFraction = LoopConstants.scalingNumerator/(LoopConstants.maxGlucoseSlidingScale-minGlucoseSlidingScale) - let scalingGlucose = max(currentGlucose - minGlucoseSlidingScale, 0.0) - let slidingPartialBolusApplicationFactor = min(LoopConstants.minPartialApplicationFactor + scalingGlucose * scalingFraction, LoopConstants.maxPartialApplicationFactor) + if flagSlidingScale { + let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() + var effectiveBolusApplicationFactor = calculateEffectiveBolusApplicationFactor(glucose: glucose.quantity, correctionRangeSchedule: correctionRangeSchedule!, settings: settings) + print(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, effectiveBolusApplicationFactor) + } // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus - let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(slidingPartialBolusApplicationFactor, 1.0)) + let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(effectiveBolusApplicationFactor, 1.0)) dosingRecommendation = predictedGlucose.recommendedAutomaticDose( to: glucoseTargetRange!, @@ -1739,7 +1733,7 @@ extension LoopDataManager { model: doseStore.insulinModelProvider.model(for: pumpInsulinType), basalRates: basalRateSchedule!, maxAutomaticBolus: maxAutomaticBolus, - partialApplicationFactor: slidingPartialBolusApplicationFactor * self.timeBasedDoseApplicationFactor, + partialApplicationFactor: effectiveBolusApplicationFactor * self.timeBasedDoseApplicationFactor, lastTempBasal: lastTempBasal, volumeRounder: volumeRounder, rateRounder: rateRounder, diff --git a/Loop/Models/LoopConstants.swift b/Loop/Models/LoopConstants.swift index cc77c4be22..0bab490b65 100644 --- a/Loop/Models/LoopConstants.swift +++ b/Loop/Models/LoopConstants.swift @@ -77,9 +77,10 @@ enum LoopConstants { static let simpleBolusCalculatorGlucoseWarningLimit = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) // Dosing Stategy: Automatic Bolus: Sliding Scale - static let minPartialApplicationFactor = 0.15 // similar to temp basal strategy - static let maxPartialApplicationFactor = 1.00 // full correction - static let scalingNumerator = maxPartialApplicationFactor - minPartialApplicationFactor - static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL delta from low end of correction range for minPartialApplicationFactor - static let maxGlucoseSlidingScale = 250.0 // mg/dL value at which full correction is applied + static let minPartialApplicationFactor = 0.15 // min fraction of correction when glucose > minGlucoseSlidingScale + static let maxPartialApplicationFactor = 1.00 // max fraction of correction when glucose > maxGlucoseSlidingScale + // set minGlucoseSlidingScale based on user setting for correction range + // use mg/dL for calculations + static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL + static let maxGlucoseSlidingScale = 250.0 // mg/dL } From 08e6fa22a5092b58df8be7bcc5cc01bb3646d5b7 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Fri, 28 Apr 2023 07:27:16 -0700 Subject: [PATCH 06/22] Add applyLinearRamp slider to Dosing Strategy --- Loop/Managers/LoopDataManager.swift | 7 ++-- Loop/Managers/SettingsManager.swift | 4 ++- .../StatusTableViewController.swift | 7 ++++ Loop/View Models/SettingsViewModel.swift | 10 ++++++ Loop/Views/DosingStrategySelectionView.swift | 34 ++++++++++++++++--- Loop/Views/SettingsView.swift | 2 +- LoopCore/LoopSettings.swift | 11 +++++- 7 files changed, 64 insertions(+), 11 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index bad33919aa..e16c8cfb6b 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -1713,14 +1713,13 @@ extension LoopDataManager { var effectiveBolusApplicationFactor = LoopConstants.bolusPartialApplicationFactor - // TO DO: make this a selectable flag in Dosing Strategy screen - let flagSlidingScale = true + let flagSlidingScale = settings.applyLinearRampToBolusApplicationFactor if flagSlidingScale { let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() - var effectiveBolusApplicationFactor = calculateEffectiveBolusApplicationFactor(glucose: glucose.quantity, correctionRangeSchedule: correctionRangeSchedule!, settings: settings) - print(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, effectiveBolusApplicationFactor) + effectiveBolusApplicationFactor = calculateEffectiveBolusApplicationFactor(glucose: glucose.quantity, correctionRangeSchedule: correctionRangeSchedule!, settings: settings) } + print(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, Double(Int(100.0*effectiveBolusApplicationFactor))/100.0) // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(effectiveBolusApplicationFactor, 1.0)) diff --git a/Loop/Managers/SettingsManager.swift b/Loop/Managers/SettingsManager.swift index e52cada0d1..ded3e7fc5e 100644 --- a/Loop/Managers/SettingsManager.swift +++ b/Loop/Managers/SettingsManager.swift @@ -109,6 +109,7 @@ class SettingsManager { maximumBolus: latestSettings.maximumBolus, suspendThreshold: latestSettings.suspendThreshold, automaticDosingStrategy: latestSettings.automaticDosingStrategy, + applyLinearRampToBolusApplicationFactor: latestSettings.applyLinearRampToBolusApplicationFactor, defaultRapidActingModel: latestSettings.defaultRapidActingModel?.presetForRapidActingInsulin) } } @@ -140,7 +141,8 @@ class SettingsManager { cgmDevice: deviceStatusProvider?.cgmManagerStatus?.device, pumpDevice: deviceStatusProvider?.pumpManagerStatus?.device, bloodGlucoseUnit: displayGlucoseUnitObservable?.displayGlucoseUnit, - automaticDosingStrategy: newLoopSettings.automaticDosingStrategy) + automaticDosingStrategy: newLoopSettings.automaticDosingStrategy, + applyLinearRampToBolusApplicationFactor: newLoopSettings.applyLinearRampToBolusApplicationFactor) } func storeSettings(newLoopSettings: LoopSettings? = nil, notificationSettings: NotificationSettings? = nil) { diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 904f64cabd..221498c0dd 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -1464,6 +1464,7 @@ final class StatusTableViewController: LoopChartsTableViewController { isClosedLoopAllowed: closedLoopStatus.$isClosedLoopAllowed, supportInfoProvider: deviceManager, automaticDosingStrategy: deviceManager.loopManager.settings.automaticDosingStrategy, + applyLinearRampToBolusApplicationFactor: deviceManager.loopManager.settings.applyLinearRampToBolusApplicationFactor, availableSupports: supportManager.availableSupports, isOnboardingComplete: onboardingManager.isComplete, therapySettingsViewModelDelegate: deviceManager, @@ -2066,6 +2067,12 @@ extension StatusTableViewController: SettingsViewModelDelegate { } } + func applyLinearRampToBolusApplicationFactorChanged(_ value: Bool) { + self.deviceManager.loopManager.mutateSettings { settings in + settings.applyLinearRampToBolusApplicationFactor = value + } + } + func didTapIssueReport(title: String) { // TODO: this dismiss here is temporary, until we know exactly where // we want this screen to belong in the navigation flow diff --git a/Loop/View Models/SettingsViewModel.swift b/Loop/View Models/SettingsViewModel.swift index 16e58743b9..c36162894b 100644 --- a/Loop/View Models/SettingsViewModel.swift +++ b/Loop/View Models/SettingsViewModel.swift @@ -52,6 +52,7 @@ public typealias PumpManagerViewModel = DeviceViewModel public protocol SettingsViewModelDelegate: AnyObject { func dosingEnabledChanged(_: Bool) func dosingStrategyChanged(_: AutomaticDosingStrategy) + func applyLinearRampToBolusApplicationFactorChanged(_: Bool) func didTapIssueReport(title: String) var closedLoopDescriptiveText: String? { get } } @@ -94,6 +95,12 @@ public class SettingsViewModel: ObservableObject { } } + @Published var applyLinearRampToBolusApplicationFactor: Bool { + didSet { + delegate?.applyLinearRampToBolusApplicationFactorChanged(applyLinearRampToBolusApplicationFactor) + } + } + var closedLoopPreference: Bool { didSet { delegate?.dosingEnabledChanged(closedLoopPreference) @@ -115,6 +122,7 @@ public class SettingsViewModel: ObservableObject { isClosedLoopAllowed: Published.Publisher, supportInfoProvider: SupportInfoProvider, automaticDosingStrategy: AutomaticDosingStrategy, + applyLinearRampToBolusApplicationFactor: Bool, availableSupports: [SupportUI], isOnboardingComplete: Bool, therapySettingsViewModelDelegate: TherapySettingsViewModelDelegate?, @@ -132,6 +140,7 @@ public class SettingsViewModel: ObservableObject { self.closedLoopPreference = initialDosingEnabled self.isClosedLoopAllowed = false self.automaticDosingStrategy = automaticDosingStrategy + self.applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor self.supportInfoProvider = supportInfoProvider self.availableSupports = availableSupports self.isOnboardingComplete = isOnboardingComplete @@ -194,6 +203,7 @@ extension SettingsViewModel { isClosedLoopAllowed: FakeClosedLoopAllowedPublisher().$mockIsClosedLoopAllowed, supportInfoProvider: MockSupportInfoProvider(), automaticDosingStrategy: .automaticBolus, + applyLinearRampToBolusApplicationFactor: false, availableSupports: [], isOnboardingComplete: false, therapySettingsViewModelDelegate: nil, diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index f447b032b6..97ff6a3298 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -14,11 +14,13 @@ import LoopKitUI public struct DosingStrategySelectionView: View { @Binding private var automaticDosingStrategy: AutomaticDosingStrategy - + @Binding private var applyLinearRampToBolusApplicationFactor: Bool + @State private var internalDosingStrategy: AutomaticDosingStrategy - public init(automaticDosingStrategy: Binding) { + public init(automaticDosingStrategy: Binding, applyLinearRampToBolusApplicationFactor: Binding) { self._automaticDosingStrategy = automaticDosingStrategy + self._applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor self._internalDosingStrategy = State(initialValue: automaticDosingStrategy.wrappedValue) } @@ -45,13 +47,34 @@ public struct DosingStrategySelectionView: View { self.internalDosingStrategy = strategy // Hack to force update. :( } } - ) + ), + trailingView: strategy.isAutomaticBolus ? linearRampVolusApplicationFactorSection : nil ) .padding(.vertical, 4) } } } +extension DosingStrategySelectionView { + var linearRampVolusApplicationFactorSection: AnyView { + return AnyView( + Toggle(isOn: $applyLinearRampToBolusApplicationFactor) { + VStack(alignment: .leading) { + Text("Linear Bolus Ramp", comment: "The title text for the Apply Linear Ramp to Bolus Application Factor toggle") + .padding(.vertical, 0.5) + Text("Loop will apply a linear ramp to the bolus application factor, gradually increasing the partial bolus factor as glucose levels rise. ", comment: "Description string for Apply Linear Ramp to Bolus Application Factor toggle") + .font(.footnote) + .foregroundColor(.secondary) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(.leading) + } + .fixedSize(horizontal: false, vertical: true) + } + .disabled(!automaticDosingStrategy.isAutomaticBolus) + ) + } +} + extension AutomaticDosingStrategy { var informationalText: String { switch self { @@ -62,10 +85,13 @@ extension AutomaticDosingStrategy { } } + var isAutomaticBolus: Bool { + return self == .automaticBolus + } } struct DosingStrategySelectionView_Previews: PreviewProvider { static var previews: some View { - DosingStrategySelectionView(automaticDosingStrategy: .constant(.automaticBolus)) + DosingStrategySelectionView(automaticDosingStrategy: .constant(.automaticBolus), applyLinearRampToBolusApplicationFactor: .constant(false)) } } diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 90859273e3..a6fdaff1d6 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -125,7 +125,7 @@ extension SettingsView { private var dosingStrategySection: some View { Section(header: SectionHeader(label: NSLocalizedString("Dosing Strategy", comment: "The title of the Dosing Strategy section in settings"))) { - NavigationLink(destination: DosingStrategySelectionView(automaticDosingStrategy: $viewModel.automaticDosingStrategy)) + NavigationLink(destination: DosingStrategySelectionView(automaticDosingStrategy: $viewModel.automaticDosingStrategy, applyLinearRampToBolusApplicationFactor: $viewModel.applyLinearRampToBolusApplicationFactor)) { HStack { Text(viewModel.automaticDosingStrategy.title) diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 63418ad4aa..9dbbfcca51 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -73,6 +73,8 @@ public struct LoopSettings: Equatable { public var automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly + public var applyLinearRampToBolusApplicationFactor: Bool = false + public var defaultRapidActingModel: ExponentialInsulinModelPreset? public var glucoseUnit: HKUnit? { @@ -94,6 +96,7 @@ public struct LoopSettings: Equatable { maximumBolus: Double? = nil, suspendThreshold: GlucoseThreshold? = nil, automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly, + applyLinearRampToBolusApplicationFactor: Bool = false, defaultRapidActingModel: ExponentialInsulinModelPreset? = nil ) { self.dosingEnabled = dosingEnabled @@ -110,6 +113,7 @@ public struct LoopSettings: Equatable { self.maximumBolus = maximumBolus self.suspendThreshold = suspendThreshold self.automaticDosingStrategy = automaticDosingStrategy + self.applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor self.defaultRapidActingModel = defaultRapidActingModel } } @@ -277,13 +281,18 @@ extension LoopSettings: RawRepresentable { { self.automaticDosingStrategy = automaticDosingStrategy } + + if let applyLinearRampToBolusApplicationFactor = rawValue["applyLinearRampToBolusApplicationFactor"] as? Bool { + self.applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor + } } public var rawValue: RawValue { var raw: RawValue = [ "version": LoopSettings.version, "dosingEnabled": dosingEnabled, - "overridePresets": overridePresets.map { $0.rawValue } + "overridePresets": overridePresets.map { $0.rawValue }, + "applyLinearRampToBolusApplicationFactor": applyLinearRampToBolusApplicationFactor ] raw["glucoseTargetRangeSchedule"] = glucoseTargetRangeSchedule?.rawValue From c60bb404a5932d472f36ec7d031577d9ba59dd7c Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sat, 6 May 2023 15:35:34 -0700 Subject: [PATCH 07/22] Update constants --- Loop/Models/LoopConstants.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Loop/Models/LoopConstants.swift b/Loop/Models/LoopConstants.swift index 0bab490b65..2dc64f182f 100644 --- a/Loop/Models/LoopConstants.swift +++ b/Loop/Models/LoopConstants.swift @@ -77,10 +77,10 @@ enum LoopConstants { static let simpleBolusCalculatorGlucoseWarningLimit = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) // Dosing Stategy: Automatic Bolus: Sliding Scale - static let minPartialApplicationFactor = 0.15 // min fraction of correction when glucose > minGlucoseSlidingScale - static let maxPartialApplicationFactor = 1.00 // max fraction of correction when glucose > maxGlucoseSlidingScale + static let minPartialApplicationFactor = 0.20 // min fraction of correction when glucose > minGlucoseSlidingScale + static let maxPartialApplicationFactor = 0.80 // max fraction of correction when glucose > maxGlucoseSlidingScale // set minGlucoseSlidingScale based on user setting for correction range // use mg/dL for calculations static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL - static let maxGlucoseSlidingScale = 250.0 // mg/dL + static let maxGlucoseSlidingScale = 200.0 // mg/dL } From 289ab56bc0919cca3dd3d5c72a2e423f426cf672 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Mon, 22 May 2023 14:15:31 -0700 Subject: [PATCH 08/22] Modify explanation seen by user --- Loop/Views/DosingStrategySelectionView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index 97ff6a3298..03892f3bb6 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -60,9 +60,9 @@ extension DosingStrategySelectionView { return AnyView( Toggle(isOn: $applyLinearRampToBolusApplicationFactor) { VStack(alignment: .leading) { - Text("Linear Bolus Ramp", comment: "The title text for the Apply Linear Ramp to Bolus Application Factor toggle") + Text("Modify Bolus Percentage", comment: "The title text for the Apply Linear Ramp to Bolus Application Factor toggle") .padding(.vertical, 0.5) - Text("Loop will apply a linear ramp to the bolus application factor, gradually increasing the partial bolus factor as glucose levels rise. ", comment: "Description string for Apply Linear Ramp to Bolus Application Factor toggle") + Text("Modify Automatic Bolus behavior: The percentage of recommended bolus delivered each cycle varies with glucose level: near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).", comment: "Description string for Modify Bolus Percentage toggle") .font(.footnote) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -81,7 +81,7 @@ extension AutomaticDosingStrategy { case .tempBasalOnly: return NSLocalizedString("Loop will set temporary basal rates to increase and decrease insulin delivery.", comment: "Description string for temp basal only dosing strategy") case .automaticBolus: - return NSLocalizedString("Loop will automatically bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") + return NSLocalizedString("Loop will automatically bolus 40% of recommended bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") } } From 373e9382e1c07269a2683303fe02cb72b4359109 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Mon, 22 May 2023 14:19:09 -0700 Subject: [PATCH 09/22] missed one phrase --- Loop/Views/DosingStrategySelectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index 03892f3bb6..5846654a52 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -60,7 +60,7 @@ extension DosingStrategySelectionView { return AnyView( Toggle(isOn: $applyLinearRampToBolusApplicationFactor) { VStack(alignment: .leading) { - Text("Modify Bolus Percentage", comment: "The title text for the Apply Linear Ramp to Bolus Application Factor toggle") + Text("Modify Bolus Percentage", comment: "The title text for the Modify Bolus Percentage toggle") .padding(.vertical, 0.5) Text("Modify Automatic Bolus behavior: The percentage of recommended bolus delivered each cycle varies with glucose level: near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).", comment: "Description string for Modify Bolus Percentage toggle") .font(.footnote) From e058d50ec562428284289770dd94d44b7b75721b Mon Sep 17 00:00:00 2001 From: marionbarker Date: Wed, 31 May 2023 21:03:34 -0700 Subject: [PATCH 10/22] Update explanation seen by user, modify font --- Loop/Views/DosingStrategySelectionView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index 5846654a52..8981f96e6f 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -62,7 +62,8 @@ extension DosingStrategySelectionView { VStack(alignment: .leading) { Text("Modify Bolus Percentage", comment: "The title text for the Modify Bolus Percentage toggle") .padding(.vertical, 0.5) - Text("Modify Automatic Bolus behavior: The percentage of recommended bolus delivered each cycle varies with glucose level: near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).", comment: "Description string for Modify Bolus Percentage toggle") + .font(.subheadline) + Text("Modify Automatic Bolus behavior: This experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description string for Modify Bolus Percentage toggle") .font(.footnote) .foregroundColor(.secondary) .fixedSize(horizontal: false, vertical: true) From ab1569560feac5b494e5842d8d58e488a6403bc4 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sat, 3 Jun 2023 08:32:23 -0700 Subject: [PATCH 11/22] Enable swapping of original and replacment protocols, minimize LoopKit changes --- Loop.xcodeproj/project.pbxproj | 16 ++++++++ Loop/Managers/DosingStrategy.swift | 20 ++++++++++ Loop/Managers/LinearRampDosingStrategy.swift | 36 ++++++++++++++++++ Loop/Managers/LoopDataManager.swift | 22 ++++++++++- Loop/Managers/OriginalDosingStrategy.swift | 23 ++++++++++++ Loop/Models/LoopConstants.swift | 8 ++++ .../SettingsViewModel+ApplyLinearRamp.swift | 20 ++++++++++ Loop/Views/DosingStrategySelectionView.swift | 37 ++++++++++++++++--- Loop/Views/SettingsView.swift | 2 +- 9 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 Loop/Managers/DosingStrategy.swift create mode 100644 Loop/Managers/LinearRampDosingStrategy.swift create mode 100644 Loop/Managers/OriginalDosingStrategy.swift create mode 100644 Loop/View Models/SettingsViewModel+ApplyLinearRamp.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 360db94c06..3132501709 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -516,6 +516,10 @@ C1FB428D21791D2500FAB378 /* PumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */; }; C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; + DDC389F42A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F32A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift */; }; + DDC389F62A2B61750066E2E8 /* DosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F52A2B61750066E2E8 /* DosingStrategy.swift */; }; + DDC389F82A2B620B0066E2E8 /* LinearRampDosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* LinearRampDosingStrategy.swift */; }; + DDC389FA2A2B62470066E2E8 /* OriginalDosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* OriginalDosingStrategy.swift */; }; E90909D124E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */; }; E90909D224E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CD24E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json */; }; E90909D324E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CE24E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json */; }; @@ -1683,6 +1687,10 @@ C1FF3D4B29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1FF3D4C29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1FF3D4D29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoPlist.strings; sourceTree = ""; }; + DDC389F32A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsViewModel+ApplyLinearRamp.swift"; sourceTree = ""; }; + DDC389F52A2B61750066E2E8 /* DosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DosingStrategy.swift; sourceTree = ""; }; + DDC389F72A2B620B0066E2E8 /* LinearRampDosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinearRampDosingStrategy.swift; sourceTree = ""; }; + DDC389F92A2B62470066E2E8 /* OriginalDosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OriginalDosingStrategy.swift; sourceTree = ""; }; E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_momentum_effect.json; sourceTree = ""; }; E90909CD24E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_insulin_effect.json; sourceTree = ""; }; E90909CE24E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_predicted_glucose.json; sourceTree = ""; }; @@ -2436,6 +2444,9 @@ C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */, A96DAC2B2838F31200D94E38 /* SharedLogging.swift */, 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, + DDC389F52A2B61750066E2E8 /* DosingStrategy.swift */, + DDC389F72A2B620B0066E2E8 /* LinearRampDosingStrategy.swift */, + DDC389F92A2B62470066E2E8 /* OriginalDosingStrategy.swift */, ); path = Managers; sourceTree = ""; @@ -2716,6 +2727,7 @@ 1D49795724E7289700948F05 /* ServicesViewModel.swift */, C174233B259BEB0F00399C9D /* ManualEntryDoseViewModel.swift */, 1DB619AB270BAD3D006C9D07 /* VersionUpdateViewModel.swift */, + DDC389F32A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift */, ); path = "View Models"; sourceTree = ""; @@ -3825,6 +3837,7 @@ 43A51E1F1EB6D62A000736CC /* CarbAbsorptionViewController.swift in Sources */, 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */, 4372E48B213CB5F00068E043 /* Double.swift in Sources */, + DDC389F42A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift in Sources */, 430B29932041F5B300BA9F93 /* UserDefaults+Loop.swift in Sources */, 43CE7CDE1CA8B63E003CC1B0 /* Data.swift in Sources */, E9BB27AB23B85C3500FB4987 /* SleepStore.swift in Sources */, @@ -3866,6 +3879,7 @@ C1201E2C23ECDBD0002DA84A /* WatchContextRequestUserInfo.swift in Sources */, 1D49795824E7289700948F05 /* ServicesViewModel.swift in Sources */, 1D4A3E2D2478628500FD601B /* StoredAlert+CoreDataClass.swift in Sources */, + DDC389FA2A2B62470066E2E8 /* OriginalDosingStrategy.swift in Sources */, 892D7C5123B54A15008A9656 /* CarbEntryViewController.swift in Sources */, B4E202302661063E009421B5 /* AutomaticDosingStatus.swift in Sources */, C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, @@ -3897,6 +3911,7 @@ 1DB619AC270BAD3D006C9D07 /* VersionUpdateViewModel.swift in Sources */, A9C62D882331703100535612 /* Service.swift in Sources */, 89CAB36324C8FE96009EE3CE /* PredictedGlucoseChartView.swift in Sources */, + DDC389F82A2B620B0066E2E8 /* LinearRampDosingStrategy.swift in Sources */, 4F6663941E905FD2009E74FC /* ChartColorPalette+Loop.swift in Sources */, A9F703732489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift in Sources */, 4328E0351CFC0AE100E199AA /* WatchDataManager.swift in Sources */, @@ -3960,6 +3975,7 @@ A97F250825E056D500F0EE19 /* OnboardingManager.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */, + DDC389F62A2B61750066E2E8 /* DosingStrategy.swift in Sources */, 4F70C2101DE8FAC5006380B7 /* ExtensionDataManager.swift in Sources */, 43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */, A9FB75F1252BE320004C7D3F /* BolusDosingDecision.swift in Sources */, diff --git a/Loop/Managers/DosingStrategy.swift b/Loop/Managers/DosingStrategy.swift new file mode 100644 index 0000000000..cbe529b3a4 --- /dev/null +++ b/Loop/Managers/DosingStrategy.swift @@ -0,0 +1,20 @@ +// +// DosingStrategy.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-03. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore + +protocol DosingStrategy { + func calculateDosingFactor( + for glucose: HKQuantity, + correctionRangeSchedule: GlucoseRangeSchedule, + settings: LoopSettings + ) -> Double +} diff --git a/Loop/Managers/LinearRampDosingStrategy.swift b/Loop/Managers/LinearRampDosingStrategy.swift new file mode 100644 index 0000000000..79613c84a3 --- /dev/null +++ b/Loop/Managers/LinearRampDosingStrategy.swift @@ -0,0 +1,36 @@ +// +// LinearRampDosingStrategy.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-03. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore + +struct LinearRampDosingStrategy: DosingStrategy { + func calculateDosingFactor( + for glucose: HKQuantity, + correctionRangeSchedule: GlucoseRangeSchedule, + settings: LoopSettings + ) -> Double { + // Calculate current glucose and lower bound target + let currentGlucose = glucose.doubleValue(for: .milligramsPerDeciliter) + let correctionRange = correctionRangeSchedule.quantityRange(at: Date()) + let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: .milligramsPerDeciliter) + + // Calculate minimum glucose sliding scale and scaling fraction + let minGlucoseSlidingScale = LoopConstants.minGlucoseDeltaSlidingScale + lowerBoundTarget + let scalingFraction = (LoopConstants.maxPartialApplicationFactor - LoopConstants.minPartialApplicationFactor) / (LoopConstants.maxGlucoseSlidingScale - minGlucoseSlidingScale) + let scalingGlucose = max(currentGlucose - minGlucoseSlidingScale, 0.0) + + // Calculate effectiveBolusApplicationFactor + let effectiveBolusApplicationFactor = min(LoopConstants.minPartialApplicationFactor + scalingGlucose * scalingFraction, LoopConstants.maxPartialApplicationFactor) + + return effectiveBolusApplicationFactor + } +} + diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index ffc66ee314..ac428e49da 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -67,6 +67,8 @@ final class LoopDataManager { private var insulinOnBoard: InsulinValue? + var dosingStrategy: DosingStrategy = OriginalDosingStrategy() // default to original strategy + deinit { for observer in notificationObservers { NotificationCenter.default.removeObserver(observer) @@ -1698,7 +1700,23 @@ extension LoopDataManager { return self.delegate?.roundBolusVolume(units: units) ?? units } - let maxAutomaticBolus = min(iobHeadroom, maxBolus! * LoopConstants.bolusPartialApplicationFactor) + // Create dosing strategy based on user setting + let dosingStrategy: DosingStrategy = UserDefaults.standard.bool(forKey: "applyLinearRampToBolusApplicationFactor") + ? LinearRampDosingStrategy() + : OriginalDosingStrategy() + + let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() + + let effectiveBolusApplicationFactor = dosingStrategy.calculateDosingFactor( + for: glucose.quantity, + correctionRangeSchedule: correctionRangeSchedule!, + settings: settings + ) + + print(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, Double(Int(100.0*effectiveBolusApplicationFactor))/100.0) + + // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus + let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(effectiveBolusApplicationFactor, 1.0)) dosingRecommendation = predictedGlucose.recommendedAutomaticDose( to: glucoseTargetRange!, @@ -1708,7 +1726,7 @@ extension LoopDataManager { model: doseStore.insulinModelProvider.model(for: pumpInsulinType), basalRates: basalRateSchedule!, maxAutomaticBolus: maxAutomaticBolus, - partialApplicationFactor: LoopConstants.bolusPartialApplicationFactor * self.timeBasedDoseApplicationFactor, + partialApplicationFactor: effectiveBolusApplicationFactor * self.timeBasedDoseApplicationFactor, lastTempBasal: lastTempBasal, volumeRounder: volumeRounder, rateRounder: rateRounder, diff --git a/Loop/Managers/OriginalDosingStrategy.swift b/Loop/Managers/OriginalDosingStrategy.swift new file mode 100644 index 0000000000..88cb723223 --- /dev/null +++ b/Loop/Managers/OriginalDosingStrategy.swift @@ -0,0 +1,23 @@ +// +// OriginalDosingStrategy.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-03. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore + +struct OriginalDosingStrategy: DosingStrategy { + func calculateDosingFactor( + for glucose: HKQuantity, + correctionRangeSchedule: GlucoseRangeSchedule, + settings: LoopSettings + ) -> Double { + // The original strategy uses a constant dosing factor. + return LoopConstants.bolusPartialApplicationFactor + } +} diff --git a/Loop/Models/LoopConstants.swift b/Loop/Models/LoopConstants.swift index a62fc13849..2dc64f182f 100644 --- a/Loop/Models/LoopConstants.swift +++ b/Loop/Models/LoopConstants.swift @@ -75,4 +75,12 @@ enum LoopConstants { static let simpleBolusCalculatorMinGlucoseBolusRecommendation = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) static let simpleBolusCalculatorMinGlucoseMealBolusRecommendation = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 55) static let simpleBolusCalculatorGlucoseWarningLimit = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) + + // Dosing Stategy: Automatic Bolus: Sliding Scale + static let minPartialApplicationFactor = 0.20 // min fraction of correction when glucose > minGlucoseSlidingScale + static let maxPartialApplicationFactor = 0.80 // max fraction of correction when glucose > maxGlucoseSlidingScale + // set minGlucoseSlidingScale based on user setting for correction range + // use mg/dL for calculations + static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL + static let maxGlucoseSlidingScale = 200.0 // mg/dL } diff --git a/Loop/View Models/SettingsViewModel+ApplyLinearRamp.swift b/Loop/View Models/SettingsViewModel+ApplyLinearRamp.swift new file mode 100644 index 0000000000..2bbff3882e --- /dev/null +++ b/Loop/View Models/SettingsViewModel+ApplyLinearRamp.swift @@ -0,0 +1,20 @@ +// +// SettingsViewModel+ApplyLinearRamp.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-03. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import SwiftUI + +extension SettingsViewModel { + + var applyLinearRampToBolusApplicationFactorBinding: Binding { + Binding( + get: { UserDefaults.standard.bool(forKey: "applyLinearRampToBolusApplicationFactor") }, + set: { UserDefaults.standard.set($0, forKey: "applyLinearRampToBolusApplicationFactor") } + ) + } +} diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index f447b032b6..8981f96e6f 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -14,11 +14,13 @@ import LoopKitUI public struct DosingStrategySelectionView: View { @Binding private var automaticDosingStrategy: AutomaticDosingStrategy - + @Binding private var applyLinearRampToBolusApplicationFactor: Bool + @State private var internalDosingStrategy: AutomaticDosingStrategy - public init(automaticDosingStrategy: Binding) { + public init(automaticDosingStrategy: Binding, applyLinearRampToBolusApplicationFactor: Binding) { self._automaticDosingStrategy = automaticDosingStrategy + self._applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor self._internalDosingStrategy = State(initialValue: automaticDosingStrategy.wrappedValue) } @@ -45,27 +47,52 @@ public struct DosingStrategySelectionView: View { self.internalDosingStrategy = strategy // Hack to force update. :( } } - ) + ), + trailingView: strategy.isAutomaticBolus ? linearRampVolusApplicationFactorSection : nil ) .padding(.vertical, 4) } } } +extension DosingStrategySelectionView { + var linearRampVolusApplicationFactorSection: AnyView { + return AnyView( + Toggle(isOn: $applyLinearRampToBolusApplicationFactor) { + VStack(alignment: .leading) { + Text("Modify Bolus Percentage", comment: "The title text for the Modify Bolus Percentage toggle") + .padding(.vertical, 0.5) + .font(.subheadline) + Text("Modify Automatic Bolus behavior: This experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description string for Modify Bolus Percentage toggle") + .font(.footnote) + .foregroundColor(.secondary) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(.leading) + } + .fixedSize(horizontal: false, vertical: true) + } + .disabled(!automaticDosingStrategy.isAutomaticBolus) + ) + } +} + extension AutomaticDosingStrategy { var informationalText: String { switch self { case .tempBasalOnly: return NSLocalizedString("Loop will set temporary basal rates to increase and decrease insulin delivery.", comment: "Description string for temp basal only dosing strategy") case .automaticBolus: - return NSLocalizedString("Loop will automatically bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") + return NSLocalizedString("Loop will automatically bolus 40% of recommended bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") } } + var isAutomaticBolus: Bool { + return self == .automaticBolus + } } struct DosingStrategySelectionView_Previews: PreviewProvider { static var previews: some View { - DosingStrategySelectionView(automaticDosingStrategy: .constant(.automaticBolus)) + DosingStrategySelectionView(automaticDosingStrategy: .constant(.automaticBolus), applyLinearRampToBolusApplicationFactor: .constant(false)) } } diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index dc3f6ec3c8..f30ee4d170 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -165,7 +165,7 @@ extension SettingsView { private var dosingStrategySection: some View { Section(header: SectionHeader(label: NSLocalizedString("Dosing Strategy", comment: "The title of the Dosing Strategy section in settings"))) { - NavigationLink(destination: DosingStrategySelectionView(automaticDosingStrategy: $viewModel.automaticDosingStrategy)) + NavigationLink(destination: DosingStrategySelectionView(automaticDosingStrategy: $viewModel.automaticDosingStrategy, applyLinearRampToBolusApplicationFactor: viewModel.applyLinearRampToBolusApplicationFactorBinding)) { HStack { Text(viewModel.automaticDosingStrategy.title) From 89e1512abe4ca458c2063368f3a583f620aecade Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sat, 3 Jun 2023 08:48:04 -0700 Subject: [PATCH 12/22] Restore to dev branch version prior to pr1988_update merge --- Loop/Managers/LoopDataManager.swift | 30 +-------------- Loop/Managers/SettingsManager.swift | 4 +- Loop/Models/LoopConstants.swift | 8 ---- .../StatusTableViewController.swift | 7 ---- Loop/View Models/SettingsViewModel.swift | 10 ----- Loop/Views/DosingStrategySelectionView.swift | 37 +++---------------- Loop/Views/SettingsView.swift | 2 +- LoopCore/LoopSettings.swift | 11 +----- 8 files changed, 10 insertions(+), 99 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 2d2f1deda2..ffc66ee314 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -67,21 +67,6 @@ final class LoopDataManager { private var insulinOnBoard: InsulinValue? - // Calculate the effectiveBolusApplicationFactor - private func calculateEffectiveBolusApplicationFactor(glucose: HKQuantity, correctionRangeSchedule: GlucoseRangeSchedule, settings: LoopSettings) -> Double { - // Calculate current glucose and lower bound target - let currentGlucose = glucose.doubleValue(for: .milligramsPerDeciliter) - let correctionRange = correctionRangeSchedule.quantityRange(at: now()) - let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: .milligramsPerDeciliter) - // Calculate minimum glucose sliding scale and scaling fraction - let minGlucoseSlidingScale = LoopConstants.minGlucoseDeltaSlidingScale + lowerBoundTarget - let scalingFraction = (LoopConstants.maxPartialApplicationFactor-LoopConstants.minPartialApplicationFactor) / (LoopConstants.maxGlucoseSlidingScale - minGlucoseSlidingScale) - let scalingGlucose = max(currentGlucose - minGlucoseSlidingScale, 0.0) - // Calculate effectiveBolusApplicationFactor - let effectiveBolusApplicationFactor = min(LoopConstants.minPartialApplicationFactor + scalingGlucose * scalingFraction, LoopConstants.maxPartialApplicationFactor) - return effectiveBolusApplicationFactor - } - deinit { for observer in notificationObservers { NotificationCenter.default.removeObserver(observer) @@ -1713,18 +1698,7 @@ extension LoopDataManager { return self.delegate?.roundBolusVolume(units: units) ?? units } - var effectiveBolusApplicationFactor = LoopConstants.bolusPartialApplicationFactor - - let flagSlidingScale = settings.applyLinearRampToBolusApplicationFactor - - if flagSlidingScale { - let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() - effectiveBolusApplicationFactor = calculateEffectiveBolusApplicationFactor(glucose: glucose.quantity, correctionRangeSchedule: correctionRangeSchedule!, settings: settings) - } - print(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, Double(Int(100.0*effectiveBolusApplicationFactor))/100.0) - - // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus - let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(effectiveBolusApplicationFactor, 1.0)) + let maxAutomaticBolus = min(iobHeadroom, maxBolus! * LoopConstants.bolusPartialApplicationFactor) dosingRecommendation = predictedGlucose.recommendedAutomaticDose( to: glucoseTargetRange!, @@ -1734,7 +1708,7 @@ extension LoopDataManager { model: doseStore.insulinModelProvider.model(for: pumpInsulinType), basalRates: basalRateSchedule!, maxAutomaticBolus: maxAutomaticBolus, - partialApplicationFactor: effectiveBolusApplicationFactor * self.timeBasedDoseApplicationFactor, + partialApplicationFactor: LoopConstants.bolusPartialApplicationFactor * self.timeBasedDoseApplicationFactor, lastTempBasal: lastTempBasal, volumeRounder: volumeRounder, rateRounder: rateRounder, diff --git a/Loop/Managers/SettingsManager.swift b/Loop/Managers/SettingsManager.swift index ded3e7fc5e..e52cada0d1 100644 --- a/Loop/Managers/SettingsManager.swift +++ b/Loop/Managers/SettingsManager.swift @@ -109,7 +109,6 @@ class SettingsManager { maximumBolus: latestSettings.maximumBolus, suspendThreshold: latestSettings.suspendThreshold, automaticDosingStrategy: latestSettings.automaticDosingStrategy, - applyLinearRampToBolusApplicationFactor: latestSettings.applyLinearRampToBolusApplicationFactor, defaultRapidActingModel: latestSettings.defaultRapidActingModel?.presetForRapidActingInsulin) } } @@ -141,8 +140,7 @@ class SettingsManager { cgmDevice: deviceStatusProvider?.cgmManagerStatus?.device, pumpDevice: deviceStatusProvider?.pumpManagerStatus?.device, bloodGlucoseUnit: displayGlucoseUnitObservable?.displayGlucoseUnit, - automaticDosingStrategy: newLoopSettings.automaticDosingStrategy, - applyLinearRampToBolusApplicationFactor: newLoopSettings.applyLinearRampToBolusApplicationFactor) + automaticDosingStrategy: newLoopSettings.automaticDosingStrategy) } func storeSettings(newLoopSettings: LoopSettings? = nil, notificationSettings: NotificationSettings? = nil) { diff --git a/Loop/Models/LoopConstants.swift b/Loop/Models/LoopConstants.swift index 2dc64f182f..a62fc13849 100644 --- a/Loop/Models/LoopConstants.swift +++ b/Loop/Models/LoopConstants.swift @@ -75,12 +75,4 @@ enum LoopConstants { static let simpleBolusCalculatorMinGlucoseBolusRecommendation = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) static let simpleBolusCalculatorMinGlucoseMealBolusRecommendation = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 55) static let simpleBolusCalculatorGlucoseWarningLimit = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) - - // Dosing Stategy: Automatic Bolus: Sliding Scale - static let minPartialApplicationFactor = 0.20 // min fraction of correction when glucose > minGlucoseSlidingScale - static let maxPartialApplicationFactor = 0.80 // max fraction of correction when glucose > maxGlucoseSlidingScale - // set minGlucoseSlidingScale based on user setting for correction range - // use mg/dL for calculations - static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL - static let maxGlucoseSlidingScale = 200.0 // mg/dL } diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index e3b4e1469e..da7072498b 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -1547,7 +1547,6 @@ final class StatusTableViewController: LoopChartsTableViewController { initialDosingEnabled: deviceManager.loopManager.settings.dosingEnabled, isClosedLoopAllowed: automaticDosingStatus.$isAutomaticDosingAllowed, automaticDosingStrategy: deviceManager.loopManager.settings.automaticDosingStrategy, - applyLinearRampToBolusApplicationFactor: deviceManager.loopManager.settings.applyLinearRampToBolusApplicationFactor, availableSupports: supportManager.availableSupports, isOnboardingComplete: onboardingManager.isComplete, therapySettingsViewModelDelegate: deviceManager, @@ -2146,12 +2145,6 @@ extension StatusTableViewController: SettingsViewModelDelegate { } } - func applyLinearRampToBolusApplicationFactorChanged(_ value: Bool) { - self.deviceManager.loopManager.mutateSettings { settings in - settings.applyLinearRampToBolusApplicationFactor = value - } - } - func didTapIssueReport() { // TODO: this dismiss here is temporary, until we know exactly where // we want this screen to belong in the navigation flow diff --git a/Loop/View Models/SettingsViewModel.swift b/Loop/View Models/SettingsViewModel.swift index 13ff5556ec..3e3c20be24 100644 --- a/Loop/View Models/SettingsViewModel.swift +++ b/Loop/View Models/SettingsViewModel.swift @@ -52,7 +52,6 @@ public typealias PumpManagerViewModel = DeviceViewModel public protocol SettingsViewModelDelegate: AnyObject { func dosingEnabledChanged(_: Bool) func dosingStrategyChanged(_: AutomaticDosingStrategy) - func applyLinearRampToBolusApplicationFactorChanged(_: Bool) func didTapIssueReport() var closedLoopDescriptiveText: String? { get } } @@ -94,12 +93,6 @@ public class SettingsViewModel: ObservableObject { } } - @Published var applyLinearRampToBolusApplicationFactor: Bool { - didSet { - delegate?.applyLinearRampToBolusApplicationFactorChanged(applyLinearRampToBolusApplicationFactor) - } - } - var closedLoopPreference: Bool { didSet { delegate?.dosingEnabledChanged(closedLoopPreference) @@ -120,7 +113,6 @@ public class SettingsViewModel: ObservableObject { initialDosingEnabled: Bool, isClosedLoopAllowed: Published.Publisher, automaticDosingStrategy: AutomaticDosingStrategy, - applyLinearRampToBolusApplicationFactor: Bool, availableSupports: [SupportUI], isOnboardingComplete: Bool, therapySettingsViewModelDelegate: TherapySettingsViewModelDelegate?, @@ -138,7 +130,6 @@ public class SettingsViewModel: ObservableObject { self.closedLoopPreference = initialDosingEnabled self.isClosedLoopAllowed = false self.automaticDosingStrategy = automaticDosingStrategy - self.applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor self.availableSupports = availableSupports self.isOnboardingComplete = isOnboardingComplete self.therapySettingsViewModelDelegate = therapySettingsViewModelDelegate @@ -187,7 +178,6 @@ extension SettingsViewModel { initialDosingEnabled: true, isClosedLoopAllowed: FakeClosedLoopAllowedPublisher().$mockIsClosedLoopAllowed, automaticDosingStrategy: .automaticBolus, - applyLinearRampToBolusApplicationFactor: false, availableSupports: [], isOnboardingComplete: false, therapySettingsViewModelDelegate: nil, diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index 8981f96e6f..f447b032b6 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -14,13 +14,11 @@ import LoopKitUI public struct DosingStrategySelectionView: View { @Binding private var automaticDosingStrategy: AutomaticDosingStrategy - @Binding private var applyLinearRampToBolusApplicationFactor: Bool - + @State private var internalDosingStrategy: AutomaticDosingStrategy - public init(automaticDosingStrategy: Binding, applyLinearRampToBolusApplicationFactor: Binding) { + public init(automaticDosingStrategy: Binding) { self._automaticDosingStrategy = automaticDosingStrategy - self._applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor self._internalDosingStrategy = State(initialValue: automaticDosingStrategy.wrappedValue) } @@ -47,52 +45,27 @@ public struct DosingStrategySelectionView: View { self.internalDosingStrategy = strategy // Hack to force update. :( } } - ), - trailingView: strategy.isAutomaticBolus ? linearRampVolusApplicationFactorSection : nil + ) ) .padding(.vertical, 4) } } } -extension DosingStrategySelectionView { - var linearRampVolusApplicationFactorSection: AnyView { - return AnyView( - Toggle(isOn: $applyLinearRampToBolusApplicationFactor) { - VStack(alignment: .leading) { - Text("Modify Bolus Percentage", comment: "The title text for the Modify Bolus Percentage toggle") - .padding(.vertical, 0.5) - .font(.subheadline) - Text("Modify Automatic Bolus behavior: This experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description string for Modify Bolus Percentage toggle") - .font(.footnote) - .foregroundColor(.secondary) - .fixedSize(horizontal: false, vertical: true) - .multilineTextAlignment(.leading) - } - .fixedSize(horizontal: false, vertical: true) - } - .disabled(!automaticDosingStrategy.isAutomaticBolus) - ) - } -} - extension AutomaticDosingStrategy { var informationalText: String { switch self { case .tempBasalOnly: return NSLocalizedString("Loop will set temporary basal rates to increase and decrease insulin delivery.", comment: "Description string for temp basal only dosing strategy") case .automaticBolus: - return NSLocalizedString("Loop will automatically bolus 40% of recommended bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") + return NSLocalizedString("Loop will automatically bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") } } - var isAutomaticBolus: Bool { - return self == .automaticBolus - } } struct DosingStrategySelectionView_Previews: PreviewProvider { static var previews: some View { - DosingStrategySelectionView(automaticDosingStrategy: .constant(.automaticBolus), applyLinearRampToBolusApplicationFactor: .constant(false)) + DosingStrategySelectionView(automaticDosingStrategy: .constant(.automaticBolus)) } } diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 7c9b217e08..dc3f6ec3c8 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -165,7 +165,7 @@ extension SettingsView { private var dosingStrategySection: some View { Section(header: SectionHeader(label: NSLocalizedString("Dosing Strategy", comment: "The title of the Dosing Strategy section in settings"))) { - NavigationLink(destination: DosingStrategySelectionView(automaticDosingStrategy: $viewModel.automaticDosingStrategy, applyLinearRampToBolusApplicationFactor: $viewModel.applyLinearRampToBolusApplicationFactor)) + NavigationLink(destination: DosingStrategySelectionView(automaticDosingStrategy: $viewModel.automaticDosingStrategy)) { HStack { Text(viewModel.automaticDosingStrategy.title) diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 9dbbfcca51..63418ad4aa 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -73,8 +73,6 @@ public struct LoopSettings: Equatable { public var automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly - public var applyLinearRampToBolusApplicationFactor: Bool = false - public var defaultRapidActingModel: ExponentialInsulinModelPreset? public var glucoseUnit: HKUnit? { @@ -96,7 +94,6 @@ public struct LoopSettings: Equatable { maximumBolus: Double? = nil, suspendThreshold: GlucoseThreshold? = nil, automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly, - applyLinearRampToBolusApplicationFactor: Bool = false, defaultRapidActingModel: ExponentialInsulinModelPreset? = nil ) { self.dosingEnabled = dosingEnabled @@ -113,7 +110,6 @@ public struct LoopSettings: Equatable { self.maximumBolus = maximumBolus self.suspendThreshold = suspendThreshold self.automaticDosingStrategy = automaticDosingStrategy - self.applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor self.defaultRapidActingModel = defaultRapidActingModel } } @@ -281,18 +277,13 @@ extension LoopSettings: RawRepresentable { { self.automaticDosingStrategy = automaticDosingStrategy } - - if let applyLinearRampToBolusApplicationFactor = rawValue["applyLinearRampToBolusApplicationFactor"] as? Bool { - self.applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor - } } public var rawValue: RawValue { var raw: RawValue = [ "version": LoopSettings.version, "dosingEnabled": dosingEnabled, - "overridePresets": overridePresets.map { $0.rawValue }, - "applyLinearRampToBolusApplicationFactor": applyLinearRampToBolusApplicationFactor + "overridePresets": overridePresets.map { $0.rawValue } ] raw["glucoseTargetRangeSchedule"] = glucoseTargetRangeSchedule?.rawValue From a515b6cd6286ca4eeb5092a758c8c16773bd98b2 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sun, 4 Jun 2023 06:40:41 -0700 Subject: [PATCH 13/22] Restore to dev branch version prior to update for PR 1988 --- Loop.xcodeproj/project.pbxproj | 16 -------- Loop/Managers/DosingStrategy.swift | 20 ---------- Loop/Managers/LinearRampDosingStrategy.swift | 36 ------------------ Loop/Managers/LoopDataManager.swift | 22 +---------- Loop/Managers/OriginalDosingStrategy.swift | 23 ------------ Loop/Models/LoopConstants.swift | 8 ---- .../SettingsViewModel+ApplyLinearRamp.swift | 20 ---------- Loop/Views/DosingStrategySelectionView.swift | 37 +++---------------- Loop/Views/SettingsView.swift | 2 +- 9 files changed, 8 insertions(+), 176 deletions(-) delete mode 100644 Loop/Managers/DosingStrategy.swift delete mode 100644 Loop/Managers/LinearRampDosingStrategy.swift delete mode 100644 Loop/Managers/OriginalDosingStrategy.swift delete mode 100644 Loop/View Models/SettingsViewModel+ApplyLinearRamp.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 3132501709..360db94c06 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -516,10 +516,6 @@ C1FB428D21791D2500FAB378 /* PumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */; }; C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; - DDC389F42A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F32A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift */; }; - DDC389F62A2B61750066E2E8 /* DosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F52A2B61750066E2E8 /* DosingStrategy.swift */; }; - DDC389F82A2B620B0066E2E8 /* LinearRampDosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* LinearRampDosingStrategy.swift */; }; - DDC389FA2A2B62470066E2E8 /* OriginalDosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* OriginalDosingStrategy.swift */; }; E90909D124E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */; }; E90909D224E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CD24E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json */; }; E90909D324E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CE24E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json */; }; @@ -1687,10 +1683,6 @@ C1FF3D4B29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1FF3D4C29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1FF3D4D29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoPlist.strings; sourceTree = ""; }; - DDC389F32A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsViewModel+ApplyLinearRamp.swift"; sourceTree = ""; }; - DDC389F52A2B61750066E2E8 /* DosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DosingStrategy.swift; sourceTree = ""; }; - DDC389F72A2B620B0066E2E8 /* LinearRampDosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinearRampDosingStrategy.swift; sourceTree = ""; }; - DDC389F92A2B62470066E2E8 /* OriginalDosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OriginalDosingStrategy.swift; sourceTree = ""; }; E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_momentum_effect.json; sourceTree = ""; }; E90909CD24E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_insulin_effect.json; sourceTree = ""; }; E90909CE24E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_predicted_glucose.json; sourceTree = ""; }; @@ -2444,9 +2436,6 @@ C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */, A96DAC2B2838F31200D94E38 /* SharedLogging.swift */, 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, - DDC389F52A2B61750066E2E8 /* DosingStrategy.swift */, - DDC389F72A2B620B0066E2E8 /* LinearRampDosingStrategy.swift */, - DDC389F92A2B62470066E2E8 /* OriginalDosingStrategy.swift */, ); path = Managers; sourceTree = ""; @@ -2727,7 +2716,6 @@ 1D49795724E7289700948F05 /* ServicesViewModel.swift */, C174233B259BEB0F00399C9D /* ManualEntryDoseViewModel.swift */, 1DB619AB270BAD3D006C9D07 /* VersionUpdateViewModel.swift */, - DDC389F32A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift */, ); path = "View Models"; sourceTree = ""; @@ -3837,7 +3825,6 @@ 43A51E1F1EB6D62A000736CC /* CarbAbsorptionViewController.swift in Sources */, 43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */, 4372E48B213CB5F00068E043 /* Double.swift in Sources */, - DDC389F42A2B05280066E2E8 /* SettingsViewModel+ApplyLinearRamp.swift in Sources */, 430B29932041F5B300BA9F93 /* UserDefaults+Loop.swift in Sources */, 43CE7CDE1CA8B63E003CC1B0 /* Data.swift in Sources */, E9BB27AB23B85C3500FB4987 /* SleepStore.swift in Sources */, @@ -3879,7 +3866,6 @@ C1201E2C23ECDBD0002DA84A /* WatchContextRequestUserInfo.swift in Sources */, 1D49795824E7289700948F05 /* ServicesViewModel.swift in Sources */, 1D4A3E2D2478628500FD601B /* StoredAlert+CoreDataClass.swift in Sources */, - DDC389FA2A2B62470066E2E8 /* OriginalDosingStrategy.swift in Sources */, 892D7C5123B54A15008A9656 /* CarbEntryViewController.swift in Sources */, B4E202302661063E009421B5 /* AutomaticDosingStatus.swift in Sources */, C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, @@ -3911,7 +3897,6 @@ 1DB619AC270BAD3D006C9D07 /* VersionUpdateViewModel.swift in Sources */, A9C62D882331703100535612 /* Service.swift in Sources */, 89CAB36324C8FE96009EE3CE /* PredictedGlucoseChartView.swift in Sources */, - DDC389F82A2B620B0066E2E8 /* LinearRampDosingStrategy.swift in Sources */, 4F6663941E905FD2009E74FC /* ChartColorPalette+Loop.swift in Sources */, A9F703732489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift in Sources */, 4328E0351CFC0AE100E199AA /* WatchDataManager.swift in Sources */, @@ -3975,7 +3960,6 @@ A97F250825E056D500F0EE19 /* OnboardingManager.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */, - DDC389F62A2B61750066E2E8 /* DosingStrategy.swift in Sources */, 4F70C2101DE8FAC5006380B7 /* ExtensionDataManager.swift in Sources */, 43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */, A9FB75F1252BE320004C7D3F /* BolusDosingDecision.swift in Sources */, diff --git a/Loop/Managers/DosingStrategy.swift b/Loop/Managers/DosingStrategy.swift deleted file mode 100644 index cbe529b3a4..0000000000 --- a/Loop/Managers/DosingStrategy.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// DosingStrategy.swift -// Loop -// -// Created by Jonas Björkert on 2023-06-03. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import Foundation -import HealthKit -import LoopKit -import LoopCore - -protocol DosingStrategy { - func calculateDosingFactor( - for glucose: HKQuantity, - correctionRangeSchedule: GlucoseRangeSchedule, - settings: LoopSettings - ) -> Double -} diff --git a/Loop/Managers/LinearRampDosingStrategy.swift b/Loop/Managers/LinearRampDosingStrategy.swift deleted file mode 100644 index 79613c84a3..0000000000 --- a/Loop/Managers/LinearRampDosingStrategy.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// LinearRampDosingStrategy.swift -// Loop -// -// Created by Jonas Björkert on 2023-06-03. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import Foundation -import HealthKit -import LoopKit -import LoopCore - -struct LinearRampDosingStrategy: DosingStrategy { - func calculateDosingFactor( - for glucose: HKQuantity, - correctionRangeSchedule: GlucoseRangeSchedule, - settings: LoopSettings - ) -> Double { - // Calculate current glucose and lower bound target - let currentGlucose = glucose.doubleValue(for: .milligramsPerDeciliter) - let correctionRange = correctionRangeSchedule.quantityRange(at: Date()) - let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: .milligramsPerDeciliter) - - // Calculate minimum glucose sliding scale and scaling fraction - let minGlucoseSlidingScale = LoopConstants.minGlucoseDeltaSlidingScale + lowerBoundTarget - let scalingFraction = (LoopConstants.maxPartialApplicationFactor - LoopConstants.minPartialApplicationFactor) / (LoopConstants.maxGlucoseSlidingScale - minGlucoseSlidingScale) - let scalingGlucose = max(currentGlucose - minGlucoseSlidingScale, 0.0) - - // Calculate effectiveBolusApplicationFactor - let effectiveBolusApplicationFactor = min(LoopConstants.minPartialApplicationFactor + scalingGlucose * scalingFraction, LoopConstants.maxPartialApplicationFactor) - - return effectiveBolusApplicationFactor - } -} - diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index ac428e49da..ffc66ee314 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -67,8 +67,6 @@ final class LoopDataManager { private var insulinOnBoard: InsulinValue? - var dosingStrategy: DosingStrategy = OriginalDosingStrategy() // default to original strategy - deinit { for observer in notificationObservers { NotificationCenter.default.removeObserver(observer) @@ -1700,23 +1698,7 @@ extension LoopDataManager { return self.delegate?.roundBolusVolume(units: units) ?? units } - // Create dosing strategy based on user setting - let dosingStrategy: DosingStrategy = UserDefaults.standard.bool(forKey: "applyLinearRampToBolusApplicationFactor") - ? LinearRampDosingStrategy() - : OriginalDosingStrategy() - - let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() - - let effectiveBolusApplicationFactor = dosingStrategy.calculateDosingFactor( - for: glucose.quantity, - correctionRangeSchedule: correctionRangeSchedule!, - settings: settings - ) - - print(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, Double(Int(100.0*effectiveBolusApplicationFactor))/100.0) - - // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus - let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(effectiveBolusApplicationFactor, 1.0)) + let maxAutomaticBolus = min(iobHeadroom, maxBolus! * LoopConstants.bolusPartialApplicationFactor) dosingRecommendation = predictedGlucose.recommendedAutomaticDose( to: glucoseTargetRange!, @@ -1726,7 +1708,7 @@ extension LoopDataManager { model: doseStore.insulinModelProvider.model(for: pumpInsulinType), basalRates: basalRateSchedule!, maxAutomaticBolus: maxAutomaticBolus, - partialApplicationFactor: effectiveBolusApplicationFactor * self.timeBasedDoseApplicationFactor, + partialApplicationFactor: LoopConstants.bolusPartialApplicationFactor * self.timeBasedDoseApplicationFactor, lastTempBasal: lastTempBasal, volumeRounder: volumeRounder, rateRounder: rateRounder, diff --git a/Loop/Managers/OriginalDosingStrategy.swift b/Loop/Managers/OriginalDosingStrategy.swift deleted file mode 100644 index 88cb723223..0000000000 --- a/Loop/Managers/OriginalDosingStrategy.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// OriginalDosingStrategy.swift -// Loop -// -// Created by Jonas Björkert on 2023-06-03. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import Foundation -import HealthKit -import LoopKit -import LoopCore - -struct OriginalDosingStrategy: DosingStrategy { - func calculateDosingFactor( - for glucose: HKQuantity, - correctionRangeSchedule: GlucoseRangeSchedule, - settings: LoopSettings - ) -> Double { - // The original strategy uses a constant dosing factor. - return LoopConstants.bolusPartialApplicationFactor - } -} diff --git a/Loop/Models/LoopConstants.swift b/Loop/Models/LoopConstants.swift index 2dc64f182f..a62fc13849 100644 --- a/Loop/Models/LoopConstants.swift +++ b/Loop/Models/LoopConstants.swift @@ -75,12 +75,4 @@ enum LoopConstants { static let simpleBolusCalculatorMinGlucoseBolusRecommendation = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) static let simpleBolusCalculatorMinGlucoseMealBolusRecommendation = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 55) static let simpleBolusCalculatorGlucoseWarningLimit = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 70) - - // Dosing Stategy: Automatic Bolus: Sliding Scale - static let minPartialApplicationFactor = 0.20 // min fraction of correction when glucose > minGlucoseSlidingScale - static let maxPartialApplicationFactor = 0.80 // max fraction of correction when glucose > maxGlucoseSlidingScale - // set minGlucoseSlidingScale based on user setting for correction range - // use mg/dL for calculations - static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL - static let maxGlucoseSlidingScale = 200.0 // mg/dL } diff --git a/Loop/View Models/SettingsViewModel+ApplyLinearRamp.swift b/Loop/View Models/SettingsViewModel+ApplyLinearRamp.swift deleted file mode 100644 index 2bbff3882e..0000000000 --- a/Loop/View Models/SettingsViewModel+ApplyLinearRamp.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// SettingsViewModel+ApplyLinearRamp.swift -// Loop -// -// Created by Jonas Björkert on 2023-06-03. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import Foundation -import SwiftUI - -extension SettingsViewModel { - - var applyLinearRampToBolusApplicationFactorBinding: Binding { - Binding( - get: { UserDefaults.standard.bool(forKey: "applyLinearRampToBolusApplicationFactor") }, - set: { UserDefaults.standard.set($0, forKey: "applyLinearRampToBolusApplicationFactor") } - ) - } -} diff --git a/Loop/Views/DosingStrategySelectionView.swift b/Loop/Views/DosingStrategySelectionView.swift index 8981f96e6f..f447b032b6 100644 --- a/Loop/Views/DosingStrategySelectionView.swift +++ b/Loop/Views/DosingStrategySelectionView.swift @@ -14,13 +14,11 @@ import LoopKitUI public struct DosingStrategySelectionView: View { @Binding private var automaticDosingStrategy: AutomaticDosingStrategy - @Binding private var applyLinearRampToBolusApplicationFactor: Bool - + @State private var internalDosingStrategy: AutomaticDosingStrategy - public init(automaticDosingStrategy: Binding, applyLinearRampToBolusApplicationFactor: Binding) { + public init(automaticDosingStrategy: Binding) { self._automaticDosingStrategy = automaticDosingStrategy - self._applyLinearRampToBolusApplicationFactor = applyLinearRampToBolusApplicationFactor self._internalDosingStrategy = State(initialValue: automaticDosingStrategy.wrappedValue) } @@ -47,52 +45,27 @@ public struct DosingStrategySelectionView: View { self.internalDosingStrategy = strategy // Hack to force update. :( } } - ), - trailingView: strategy.isAutomaticBolus ? linearRampVolusApplicationFactorSection : nil + ) ) .padding(.vertical, 4) } } } -extension DosingStrategySelectionView { - var linearRampVolusApplicationFactorSection: AnyView { - return AnyView( - Toggle(isOn: $applyLinearRampToBolusApplicationFactor) { - VStack(alignment: .leading) { - Text("Modify Bolus Percentage", comment: "The title text for the Modify Bolus Percentage toggle") - .padding(.vertical, 0.5) - .font(.subheadline) - Text("Modify Automatic Bolus behavior: This experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description string for Modify Bolus Percentage toggle") - .font(.footnote) - .foregroundColor(.secondary) - .fixedSize(horizontal: false, vertical: true) - .multilineTextAlignment(.leading) - } - .fixedSize(horizontal: false, vertical: true) - } - .disabled(!automaticDosingStrategy.isAutomaticBolus) - ) - } -} - extension AutomaticDosingStrategy { var informationalText: String { switch self { case .tempBasalOnly: return NSLocalizedString("Loop will set temporary basal rates to increase and decrease insulin delivery.", comment: "Description string for temp basal only dosing strategy") case .automaticBolus: - return NSLocalizedString("Loop will automatically bolus 40% of recommended bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") + return NSLocalizedString("Loop will automatically bolus when insulin needs are above scheduled basal, and will use temporary basal rates when needed to reduce insulin delivery below scheduled basal.", comment: "Description string for automatic bolus dosing strategy") } } - var isAutomaticBolus: Bool { - return self == .automaticBolus - } } struct DosingStrategySelectionView_Previews: PreviewProvider { static var previews: some View { - DosingStrategySelectionView(automaticDosingStrategy: .constant(.automaticBolus), applyLinearRampToBolusApplicationFactor: .constant(false)) + DosingStrategySelectionView(automaticDosingStrategy: .constant(.automaticBolus)) } } diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index f30ee4d170..dc3f6ec3c8 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -165,7 +165,7 @@ extension SettingsView { private var dosingStrategySection: some View { Section(header: SectionHeader(label: NSLocalizedString("Dosing Strategy", comment: "The title of the Dosing Strategy section in settings"))) { - NavigationLink(destination: DosingStrategySelectionView(automaticDosingStrategy: $viewModel.automaticDosingStrategy, applyLinearRampToBolusApplicationFactor: viewModel.applyLinearRampToBolusApplicationFactorBinding)) + NavigationLink(destination: DosingStrategySelectionView(automaticDosingStrategy: $viewModel.automaticDosingStrategy)) { HStack { Text(viewModel.automaticDosingStrategy.title) From 8b3f61efad9255bb4ada54fa7b7a78cf1c376cbb Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sun, 4 Jun 2023 06:47:43 -0700 Subject: [PATCH 14/22] Updates per requested changes for Loop PR 1988 --- Loop.xcodeproj/project.pbxproj | 20 +++++++++ ...ingsView+algorithmExperimentsSection.swift | 26 ++++++++++++ Loop/Managers/ApplicationFactorStrategy.swift | 20 +++++++++ Loop/Managers/ConstantDosingStrategy.swift | 23 ++++++++++ .../GlucoseBasedApplicationFactor.swift | 42 +++++++++++++++++++ Loop/Managers/LoopDataManager.swift | 21 +++++++++- .../EnhancedAutoBolusSelectionView.swift | 42 +++++++++++++++++++ Loop/Views/SettingsView.swift | 5 +++ 8 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 Loop/Extensions/SettingsView+algorithmExperimentsSection.swift create mode 100644 Loop/Managers/ApplicationFactorStrategy.swift create mode 100644 Loop/Managers/ConstantDosingStrategy.swift create mode 100644 Loop/Managers/GlucoseBasedApplicationFactor.swift create mode 100644 Loop/Views/EnhancedAutoBolusSelectionView.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 360db94c06..490db40d64 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -516,6 +516,11 @@ C1FB428D21791D2500FAB378 /* PumpManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C3B6F620BBCAA30026CAFA /* PumpManager.swift */; }; C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; + DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */; }; + DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift */; }; + DDC389FA2A2B62470066E2E8 /* ConstantDosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */; }; + DDC389FC2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */; }; + DDC389FE2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FD2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift */; }; E90909D124E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */; }; E90909D224E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CD24E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json */; }; E90909D324E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CE24E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json */; }; @@ -1683,6 +1688,11 @@ C1FF3D4B29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1FF3D4C29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1FF3D4D29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoPlist.strings; sourceTree = ""; }; + DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationFactorStrategy.swift; sourceTree = ""; }; + DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactor.swift; sourceTree = ""; }; + DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantDosingStrategy.swift; sourceTree = ""; }; + DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+algorithmExperimentsSection.swift"; sourceTree = ""; }; + DDC389FD2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedAutoBolusSelectionView.swift; sourceTree = ""; }; E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_momentum_effect.json; sourceTree = ""; }; E90909CD24E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_insulin_effect.json; sourceTree = ""; }; E90909CE24E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_predicted_glucose.json; sourceTree = ""; }; @@ -2309,6 +2319,7 @@ isa = PBXGroup; children = ( A98556842493F901000FD662 /* AlertStore+SimulatedCoreData.swift */, + DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */, C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */, A9F703722489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift */, C17824991E1999FA00D9D25C /* CaseCountable.swift */, @@ -2395,6 +2406,7 @@ 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */, + DDC389FD2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift */, ); path = Views; sourceTree = ""; @@ -2436,6 +2448,9 @@ C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */, A96DAC2B2838F31200D94E38 /* SharedLogging.swift */, 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, + DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */, + DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift */, + DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */, ); path = Managers; sourceTree = ""; @@ -3866,6 +3881,7 @@ C1201E2C23ECDBD0002DA84A /* WatchContextRequestUserInfo.swift in Sources */, 1D49795824E7289700948F05 /* ServicesViewModel.swift in Sources */, 1D4A3E2D2478628500FD601B /* StoredAlert+CoreDataClass.swift in Sources */, + DDC389FA2A2B62470066E2E8 /* ConstantDosingStrategy.swift in Sources */, 892D7C5123B54A15008A9656 /* CarbEntryViewController.swift in Sources */, B4E202302661063E009421B5 /* AutomaticDosingStatus.swift in Sources */, C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, @@ -3897,6 +3913,7 @@ 1DB619AC270BAD3D006C9D07 /* VersionUpdateViewModel.swift in Sources */, A9C62D882331703100535612 /* Service.swift in Sources */, 89CAB36324C8FE96009EE3CE /* PredictedGlucoseChartView.swift in Sources */, + DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift in Sources */, 4F6663941E905FD2009E74FC /* ChartColorPalette+Loop.swift in Sources */, A9F703732489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift in Sources */, 4328E0351CFC0AE100E199AA /* WatchDataManager.swift in Sources */, @@ -3926,6 +3943,7 @@ E9B080B1253BDA6300BAD8F8 /* UserDefaults+LoopIntents.swift in Sources */, C1AF062329426300002C1B19 /* ManualGlucoseEntryRow.swift in Sources */, C148CEE724FD91BD00711B3B /* DeliveryUncertaintyAlertManager.swift in Sources */, + DDC389FC2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift in Sources */, 1D12D3B92548EFDD00B53E8B /* main.swift in Sources */, 435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */, A9DCF32A25B0FABF00C89088 /* LoopUIColorPalette+Default.swift in Sources */, @@ -3960,6 +3978,7 @@ A97F250825E056D500F0EE19 /* OnboardingManager.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */, + DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */, 4F70C2101DE8FAC5006380B7 /* ExtensionDataManager.swift in Sources */, 43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */, A9FB75F1252BE320004C7D3F /* BolusDosingDecision.swift in Sources */, @@ -3967,6 +3986,7 @@ 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */, 1D05219D2469F1F5000EBBDE /* AlertStore.swift in Sources */, 439897371CD2F80600223065 /* AnalyticsServicesManager.swift in Sources */, + DDC389FE2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift in Sources */, A9C62D842331700E00535612 /* DiagnosticLog+Subsystem.swift in Sources */, 895FE0952201234000FCF18A /* OverrideSelectionViewController.swift in Sources */, C1EF747228D6A44A00C8C083 /* CrashRecoveryManager.swift in Sources */, diff --git a/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift new file mode 100644 index 0000000000..a2ec6a3245 --- /dev/null +++ b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift @@ -0,0 +1,26 @@ +// +// SettingsView+algorithmExperimentsSection.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-03. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import SwiftUI +import LoopKitUI + +extension SettingsView { + internal var algorithmExperimentsSection: some View { + Section(header: SectionHeader(label: NSLocalizedString("Algorithm Experiments", comment: "The title of the Algorithm Experiments section in settings"))) { + + NavigationLink(destination: EnhancedAutoBolusSelectionView(isEnhancedAutoBolusEnabled: $isEnhancedAutoBolusEnabled)) { + HStack { + Text("Enable Enhanced AutoBolus") + Spacer() + Text(isEnhancedAutoBolusEnabled ? "On" : "Off") + } + } + } + } +} diff --git a/Loop/Managers/ApplicationFactorStrategy.swift b/Loop/Managers/ApplicationFactorStrategy.swift new file mode 100644 index 0000000000..bf67935c4e --- /dev/null +++ b/Loop/Managers/ApplicationFactorStrategy.swift @@ -0,0 +1,20 @@ +// +// ApplicationFactorStrategy.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-03. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore + +protocol ApplicationFactorStrategy { + func calculateDosingFactor( + for glucose: HKQuantity, + correctionRangeSchedule: GlucoseRangeSchedule, + settings: LoopSettings + ) -> Double +} diff --git a/Loop/Managers/ConstantDosingStrategy.swift b/Loop/Managers/ConstantDosingStrategy.swift new file mode 100644 index 0000000000..5084108e5f --- /dev/null +++ b/Loop/Managers/ConstantDosingStrategy.swift @@ -0,0 +1,23 @@ +// +// ConstantDosingStrategy.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-03. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore + +struct ConstantDosingStrategy: ApplicationFactorStrategy { + func calculateDosingFactor( + for glucose: HKQuantity, + correctionRangeSchedule: GlucoseRangeSchedule, + settings: LoopSettings + ) -> Double { + // The original strategy uses a constant dosing factor. + return LoopConstants.bolusPartialApplicationFactor + } +} diff --git a/Loop/Managers/GlucoseBasedApplicationFactor.swift b/Loop/Managers/GlucoseBasedApplicationFactor.swift new file mode 100644 index 0000000000..f6556978a7 --- /dev/null +++ b/Loop/Managers/GlucoseBasedApplicationFactor.swift @@ -0,0 +1,42 @@ +// +// GlucoseBasedApplicationFactor.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-03. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore + +struct GlucoseBasedApplicationFactor: ApplicationFactorStrategy { + static let minPartialApplicationFactor = 0.20 // min fraction of correction when glucose > minGlucoseSlidingScale + static let maxPartialApplicationFactor = 0.80 // max fraction of correction when glucose > maxGlucoseSlidingScale + // set minGlucoseSlidingScale based on user setting for correction range + // use mg/dL for calculations + static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL + static let maxGlucoseSlidingScale = 200.0 // mg/dL + + func calculateDosingFactor( + for glucose: HKQuantity, + correctionRangeSchedule: GlucoseRangeSchedule, + settings: LoopSettings + ) -> Double { + // Calculate current glucose and lower bound target + let currentGlucose = glucose.doubleValue(for: .milligramsPerDeciliter) + let correctionRange = correctionRangeSchedule.quantityRange(at: Date()) + let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: .milligramsPerDeciliter) + + // Calculate minimum glucose sliding scale and scaling fraction + let minGlucoseSlidingScale = GlucoseBasedApplicationFactor.minGlucoseDeltaSlidingScale + lowerBoundTarget + let scalingFraction = (GlucoseBasedApplicationFactor.maxPartialApplicationFactor - GlucoseBasedApplicationFactor.minPartialApplicationFactor) / (GlucoseBasedApplicationFactor.maxGlucoseSlidingScale - minGlucoseSlidingScale) + let scalingGlucose = max(currentGlucose - minGlucoseSlidingScale, 0.0) + + // Calculate effectiveBolusApplicationFactor + let effectiveBolusApplicationFactor = min(GlucoseBasedApplicationFactor.minPartialApplicationFactor + scalingGlucose * scalingFraction, GlucoseBasedApplicationFactor.maxPartialApplicationFactor) + + return effectiveBolusApplicationFactor + } +} diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index ffc66ee314..348309578f 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -1698,7 +1698,23 @@ extension LoopDataManager { return self.delegate?.roundBolusVolume(units: units) ?? units } - let maxAutomaticBolus = min(iobHeadroom, maxBolus! * LoopConstants.bolusPartialApplicationFactor) + // Create dosing strategy based on user setting + let applicationFactorStrategy: ApplicationFactorStrategy = UserDefaults.standard.bool(forKey: "applyExperimentalEnhancedAutoBolus") + ? GlucoseBasedApplicationFactor() + : ConstantDosingStrategy() + + let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() + + let effectiveBolusApplicationFactor = applicationFactorStrategy.calculateDosingFactor( + for: glucose.quantity, + correctionRangeSchedule: correctionRangeSchedule!, + settings: settings + ) + + print(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, Double(Int(100.0*effectiveBolusApplicationFactor))/100.0) + + // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus + let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(effectiveBolusApplicationFactor, 1.0)) dosingRecommendation = predictedGlucose.recommendedAutomaticDose( to: glucoseTargetRange!, @@ -1708,7 +1724,7 @@ extension LoopDataManager { model: doseStore.insulinModelProvider.model(for: pumpInsulinType), basalRates: basalRateSchedule!, maxAutomaticBolus: maxAutomaticBolus, - partialApplicationFactor: LoopConstants.bolusPartialApplicationFactor * self.timeBasedDoseApplicationFactor, + partialApplicationFactor: effectiveBolusApplicationFactor * self.timeBasedDoseApplicationFactor, lastTempBasal: lastTempBasal, volumeRounder: volumeRounder, rateRounder: rateRounder, @@ -2128,6 +2144,7 @@ extension LoopDataManager { "insulinOnBoard: \(String(describing: manager.insulinOnBoard))", "error: \(String(describing: state.error))", "overrideInUserDefaults: \(String(describing: UserDefaults.appGroup?.intentExtensionOverrideToSet))", + "applyExperimentalEnhancedAutoBolus: \(UserDefaults.standard.bool(forKey: "applyExperimentalEnhancedAutoBolus"))", "", String(reflecting: self.retrospectiveCorrection), "", diff --git a/Loop/Views/EnhancedAutoBolusSelectionView.swift b/Loop/Views/EnhancedAutoBolusSelectionView.swift new file mode 100644 index 0000000000..bcabc4ad25 --- /dev/null +++ b/Loop/Views/EnhancedAutoBolusSelectionView.swift @@ -0,0 +1,42 @@ +// +// EnhancedAutoBolusSelectionView.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-04. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import SwiftUI +import LoopKit +import LoopKitUI + +public struct EnhancedAutoBolusSelectionView: View { + @Binding var isEnhancedAutoBolusEnabled: Bool + + public init(isEnhancedAutoBolusEnabled: Binding) { + self._isEnhancedAutoBolusEnabled = isEnhancedAutoBolusEnabled + } + + public var body: some View { + List { + automaticBolusSection + } + .navigationTitle(NSLocalizedString("Enhanced AutoBolus", comment: "Title of Enhanced AutoBolus")) + } + + private var automaticBolusSection: some View { + Section(footer: DescriptiveText(label: NSLocalizedString("Active only when Automatic Bolus is selected under Dosing Strategy.\n\nWhen disabled, Automatic Bolus Dosing Strategy uses a constant percentage of 40% when Loop recommends a bolus.\n\nWhen enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Enhanced AutoBolus toggle."))) { + Toggle(NSLocalizedString("Enable Enhanced AutoBolus", comment: "Title for Enhanced AutoBolus toggle"), isOn: $isEnhancedAutoBolusEnabled) + .onChange(of: isEnhancedAutoBolusEnabled) { newValue in + UserDefaults.standard.set(newValue, forKey: "applyExperimentalEnhancedAutoBolus") + } + } + } +} + +struct EnhancedAutoBolusSelectionView_Previews: PreviewProvider { + static var previews: some View { + EnhancedAutoBolusSelectionView(isEnhancedAutoBolusEnabled: .constant(true)) + } +} diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index dc3f6ec3c8..b562070c3b 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -30,6 +30,8 @@ public struct SettingsView: View { @State private var therapySettingsIsPresented: Bool = false @State private var deletePumpDataAlertIsPresented = false @State private var deleteCGMDataAlertIsPresented = false + @State internal var enhancedAutoBolusIsPresented: Bool = false + @State internal var isEnhancedAutoBolusEnabled = UserDefaults.standard.bool(forKey: "applyExperimentalEnhancedAutoBolus") var localizedAppNameAndVersion: String @@ -50,6 +52,9 @@ public struct SettingsView: View { if FeatureFlags.automaticBolusEnabled { dosingStrategySection } + // Note: Experimental Section + algorithmExperimentsSection + // alertManagementSection if viewModel.pumpManagerSettingsViewModel.isSetUp() { configurationSection From ee9dee0c19234bf3f420c338bb8626b96786e775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sun, 4 Jun 2023 19:02:54 +0200 Subject: [PATCH 15/22] Renaming, adjusting views, logging --- Loop.xcodeproj/project.pbxproj | 16 +++--- ...ingsView+algorithmExperimentsSection.swift | 8 +-- ...ucoseBasedApplicationFactorStrategy.swift} | 10 ++-- Loop/Managers/LoopDataManager.swift | 8 +-- .../EnhancedAutoBolusSelectionView.swift | 42 -------------- ...eBasedApplicationFactorSelectionView.swift | 55 +++++++++++++++++++ Loop/Views/SettingsView.swift | 2 +- 7 files changed, 77 insertions(+), 64 deletions(-) rename Loop/Managers/{GlucoseBasedApplicationFactor.swift => GlucoseBasedApplicationFactorStrategy.swift} (72%) delete mode 100644 Loop/Views/EnhancedAutoBolusSelectionView.swift create mode 100644 Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 490db40d64..f403686e32 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -517,10 +517,10 @@ C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */; }; - DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift */; }; + DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */; }; DDC389FA2A2B62470066E2E8 /* ConstantDosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */; }; DDC389FC2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */; }; - DDC389FE2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FD2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift */; }; + DDC389FE2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */; }; E90909D124E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */; }; E90909D224E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CD24E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json */; }; E90909D324E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CE24E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json */; }; @@ -1689,10 +1689,10 @@ C1FF3D4C29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1FF3D4D29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoPlist.strings; sourceTree = ""; }; DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationFactorStrategy.swift; sourceTree = ""; }; - DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactor.swift; sourceTree = ""; }; + DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactorStrategy.swift; sourceTree = ""; }; DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantDosingStrategy.swift; sourceTree = ""; }; DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+algorithmExperimentsSection.swift"; sourceTree = ""; }; - DDC389FD2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnhancedAutoBolusSelectionView.swift; sourceTree = ""; }; + DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactorSelectionView.swift; sourceTree = ""; }; E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_momentum_effect.json; sourceTree = ""; }; E90909CD24E34AC500F963D2 /* high_and_rising_with_cob_insulin_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_insulin_effect.json; sourceTree = ""; }; E90909CE24E34AC500F963D2 /* high_and_rising_with_cob_predicted_glucose.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_predicted_glucose.json; sourceTree = ""; }; @@ -2406,7 +2406,7 @@ 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */, - DDC389FD2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift */, + DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */, ); path = Views; sourceTree = ""; @@ -2449,7 +2449,7 @@ A96DAC2B2838F31200D94E38 /* SharedLogging.swift */, 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */, - DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift */, + DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */, DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */, ); path = Managers; @@ -3913,7 +3913,7 @@ 1DB619AC270BAD3D006C9D07 /* VersionUpdateViewModel.swift in Sources */, A9C62D882331703100535612 /* Service.swift in Sources */, 89CAB36324C8FE96009EE3CE /* PredictedGlucoseChartView.swift in Sources */, - DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactor.swift in Sources */, + DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift in Sources */, 4F6663941E905FD2009E74FC /* ChartColorPalette+Loop.swift in Sources */, A9F703732489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift in Sources */, 4328E0351CFC0AE100E199AA /* WatchDataManager.swift in Sources */, @@ -3986,7 +3986,7 @@ 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */, 1D05219D2469F1F5000EBBDE /* AlertStore.swift in Sources */, 439897371CD2F80600223065 /* AnalyticsServicesManager.swift in Sources */, - DDC389FE2A2C4C830066E2E8 /* EnhancedAutoBolusSelectionView.swift in Sources */, + DDC389FE2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift in Sources */, A9C62D842331700E00535612 /* DiagnosticLog+Subsystem.swift in Sources */, 895FE0952201234000FCF18A /* OverrideSelectionViewController.swift in Sources */, C1EF747228D6A44A00C8C083 /* CrashRecoveryManager.swift in Sources */, diff --git a/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift index a2ec6a3245..811a2f339d 100644 --- a/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift +++ b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift @@ -12,13 +12,13 @@ import LoopKitUI extension SettingsView { internal var algorithmExperimentsSection: some View { - Section(header: SectionHeader(label: NSLocalizedString("Algorithm Experiments", comment: "The title of the Algorithm Experiments section in settings"))) { + Section(header: SectionHeader(label: NSLocalizedString("Algorithm Experiments ⚠️", comment: "The title of the Algorithm Experiments section in settings"))) { - NavigationLink(destination: EnhancedAutoBolusSelectionView(isEnhancedAutoBolusEnabled: $isEnhancedAutoBolusEnabled)) { + NavigationLink(destination: GlucoseBasedApplicationFactorSelectionView(isGlucoseBasedApplicationFactorEnabled: $isGlucoseBasedApplicationFactorEnabled)) { HStack { - Text("Enable Enhanced AutoBolus") + Text("Glucose Based Partial Application") Spacer() - Text(isEnhancedAutoBolusEnabled ? "On" : "Off") + Text(isGlucoseBasedApplicationFactorEnabled ? "On" : "Off") } } } diff --git a/Loop/Managers/GlucoseBasedApplicationFactor.swift b/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift similarity index 72% rename from Loop/Managers/GlucoseBasedApplicationFactor.swift rename to Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift index f6556978a7..41caa3d773 100644 --- a/Loop/Managers/GlucoseBasedApplicationFactor.swift +++ b/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift @@ -1,5 +1,5 @@ // -// GlucoseBasedApplicationFactor.swift +// GlucoseBasedApplicationFactorStrategy.swift // Loop // // Created by Jonas Björkert on 2023-06-03. @@ -11,7 +11,7 @@ import HealthKit import LoopKit import LoopCore -struct GlucoseBasedApplicationFactor: ApplicationFactorStrategy { +struct GlucoseBasedApplicationFactorStrategy: ApplicationFactorStrategy { static let minPartialApplicationFactor = 0.20 // min fraction of correction when glucose > minGlucoseSlidingScale static let maxPartialApplicationFactor = 0.80 // max fraction of correction when glucose > maxGlucoseSlidingScale // set minGlucoseSlidingScale based on user setting for correction range @@ -30,12 +30,12 @@ struct GlucoseBasedApplicationFactor: ApplicationFactorStrategy { let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: .milligramsPerDeciliter) // Calculate minimum glucose sliding scale and scaling fraction - let minGlucoseSlidingScale = GlucoseBasedApplicationFactor.minGlucoseDeltaSlidingScale + lowerBoundTarget - let scalingFraction = (GlucoseBasedApplicationFactor.maxPartialApplicationFactor - GlucoseBasedApplicationFactor.minPartialApplicationFactor) / (GlucoseBasedApplicationFactor.maxGlucoseSlidingScale - minGlucoseSlidingScale) + let minGlucoseSlidingScale = GlucoseBasedApplicationFactorStrategy.minGlucoseDeltaSlidingScale + lowerBoundTarget + let scalingFraction = (GlucoseBasedApplicationFactorStrategy.maxPartialApplicationFactor - GlucoseBasedApplicationFactorStrategy.minPartialApplicationFactor) / (GlucoseBasedApplicationFactorStrategy.maxGlucoseSlidingScale - minGlucoseSlidingScale) let scalingGlucose = max(currentGlucose - minGlucoseSlidingScale, 0.0) // Calculate effectiveBolusApplicationFactor - let effectiveBolusApplicationFactor = min(GlucoseBasedApplicationFactor.minPartialApplicationFactor + scalingGlucose * scalingFraction, GlucoseBasedApplicationFactor.maxPartialApplicationFactor) + let effectiveBolusApplicationFactor = min(GlucoseBasedApplicationFactorStrategy.minPartialApplicationFactor + scalingGlucose * scalingFraction, GlucoseBasedApplicationFactorStrategy.maxPartialApplicationFactor) return effectiveBolusApplicationFactor } diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 348309578f..d1be106e29 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -1699,8 +1699,8 @@ extension LoopDataManager { } // Create dosing strategy based on user setting - let applicationFactorStrategy: ApplicationFactorStrategy = UserDefaults.standard.bool(forKey: "applyExperimentalEnhancedAutoBolus") - ? GlucoseBasedApplicationFactor() + let applicationFactorStrategy: ApplicationFactorStrategy = UserDefaults.standard.bool(forKey: "applyExperimentalGlucoseBasedApplicationFactor") + ? GlucoseBasedApplicationFactorStrategy() : ConstantDosingStrategy() let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() @@ -1711,7 +1711,7 @@ extension LoopDataManager { settings: settings ) - print(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, Double(Int(100.0*effectiveBolusApplicationFactor))/100.0) + self.logger.debug(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, Double(Int(100.0*effectiveBolusApplicationFactor))/100.0) // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(effectiveBolusApplicationFactor, 1.0)) @@ -2144,7 +2144,7 @@ extension LoopDataManager { "insulinOnBoard: \(String(describing: manager.insulinOnBoard))", "error: \(String(describing: state.error))", "overrideInUserDefaults: \(String(describing: UserDefaults.appGroup?.intentExtensionOverrideToSet))", - "applyExperimentalEnhancedAutoBolus: \(UserDefaults.standard.bool(forKey: "applyExperimentalEnhancedAutoBolus"))", + "applyExperimentalGlucoseBasedApplicationFactor: \(UserDefaults.standard.bool(forKey: "applyExperimentalGlucoseBasedApplicationFactor"))", "", String(reflecting: self.retrospectiveCorrection), "", diff --git a/Loop/Views/EnhancedAutoBolusSelectionView.swift b/Loop/Views/EnhancedAutoBolusSelectionView.swift deleted file mode 100644 index bcabc4ad25..0000000000 --- a/Loop/Views/EnhancedAutoBolusSelectionView.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// EnhancedAutoBolusSelectionView.swift -// Loop -// -// Created by Jonas Björkert on 2023-06-04. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import Foundation -import SwiftUI -import LoopKit -import LoopKitUI - -public struct EnhancedAutoBolusSelectionView: View { - @Binding var isEnhancedAutoBolusEnabled: Bool - - public init(isEnhancedAutoBolusEnabled: Binding) { - self._isEnhancedAutoBolusEnabled = isEnhancedAutoBolusEnabled - } - - public var body: some View { - List { - automaticBolusSection - } - .navigationTitle(NSLocalizedString("Enhanced AutoBolus", comment: "Title of Enhanced AutoBolus")) - } - - private var automaticBolusSection: some View { - Section(footer: DescriptiveText(label: NSLocalizedString("Active only when Automatic Bolus is selected under Dosing Strategy.\n\nWhen disabled, Automatic Bolus Dosing Strategy uses a constant percentage of 40% when Loop recommends a bolus.\n\nWhen enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Enhanced AutoBolus toggle."))) { - Toggle(NSLocalizedString("Enable Enhanced AutoBolus", comment: "Title for Enhanced AutoBolus toggle"), isOn: $isEnhancedAutoBolusEnabled) - .onChange(of: isEnhancedAutoBolusEnabled) { newValue in - UserDefaults.standard.set(newValue, forKey: "applyExperimentalEnhancedAutoBolus") - } - } - } -} - -struct EnhancedAutoBolusSelectionView_Previews: PreviewProvider { - static var previews: some View { - EnhancedAutoBolusSelectionView(isEnhancedAutoBolusEnabled: .constant(true)) - } -} diff --git a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift new file mode 100644 index 0000000000..5e2e210db2 --- /dev/null +++ b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift @@ -0,0 +1,55 @@ +// +// GlucoseBasedApplicationFactorSelectionView.swift +// Loop +// +// Created by Jonas Björkert on 2023-06-04. +// Copyright © 2023 LoopKit Authors. All rights reserved. +// + +import Foundation +import SwiftUI +import LoopKit +import LoopKitUI + +public struct GlucoseBasedApplicationFactorSelectionView: View { + @Binding var isGlucoseBasedApplicationFactorEnabled: Bool + + public init(isGlucoseBasedApplicationFactorEnabled: Binding) { + self._isGlucoseBasedApplicationFactorEnabled = isGlucoseBasedApplicationFactorEnabled + } + + public var body: some View { + List { + automaticBolusSection + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + Text("Glucose Based") + .font(.headline) + Text("Partial Application") + .font(.subheadline) + } + } + } + } + + private var automaticBolusSection: some View { + VStack { + DescriptiveText(label: NSLocalizedString("Active only when Automatic Bolus is selected under Dosing Strategy.\n\nWhen disabled, Automatic Bolus Dosing Strategy uses a constant percentage of 40% when Loop recommends a bolus.\n\nWhen enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Glucose Based Partial Application toggle."), color: .black) + Section() { + Toggle(NSLocalizedString("Glucose Based Partial Application", comment: "Title for Glucose Based Partial Application toggle"), isOn: $isGlucoseBasedApplicationFactorEnabled) + .onChange(of: isGlucoseBasedApplicationFactorEnabled) { newValue in + UserDefaults.standard.set(newValue, forKey: "applyExperimentalGlucoseBasedApplicationFactor") + } + } + } + } +} + +struct EnhancedAutoBolusSelectionView_Previews: PreviewProvider { + static var previews: some View { + GlucoseBasedApplicationFactorSelectionView(isGlucoseBasedApplicationFactorEnabled: .constant(true)) + } +} diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index b562070c3b..1b5f4de328 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -31,7 +31,7 @@ public struct SettingsView: View { @State private var deletePumpDataAlertIsPresented = false @State private var deleteCGMDataAlertIsPresented = false @State internal var enhancedAutoBolusIsPresented: Bool = false - @State internal var isEnhancedAutoBolusEnabled = UserDefaults.standard.bool(forKey: "applyExperimentalEnhancedAutoBolus") + @State internal var isGlucoseBasedApplicationFactorEnabled = UserDefaults.standard.bool(forKey: "applyExperimentalGlucoseBasedApplicationFactor") var localizedAppNameAndVersion: String From 0cda46354eb899fe48a33d4f197d94d730bc99c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sun, 4 Jun 2023 19:42:14 +0200 Subject: [PATCH 16/22] Wording change --- Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift index 5e2e210db2..90d05d0c90 100644 --- a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift +++ b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift @@ -37,7 +37,7 @@ public struct GlucoseBasedApplicationFactorSelectionView: View { private var automaticBolusSection: some View { VStack { - DescriptiveText(label: NSLocalizedString("Active only when Automatic Bolus is selected under Dosing Strategy.\n\nWhen disabled, Automatic Bolus Dosing Strategy uses a constant percentage of 40% when Loop recommends a bolus.\n\nWhen enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Glucose Based Partial Application toggle."), color: .black) + DescriptiveText(label: NSLocalizedString("Active only when Automatic Bolus is selected under Dosing Strategy.\n\nWhen Glucose Based Partial Application is disabled, Automatic Bolus Dosing Strategy uses a constant percentage of 40% when Loop recommends a bolus.\n\nWhen enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Glucose Based Partial Application toggle."), color: .black) Section() { Toggle(NSLocalizedString("Glucose Based Partial Application", comment: "Title for Glucose Based Partial Application toggle"), isOn: $isGlucoseBasedApplicationFactorEnabled) .onChange(of: isGlucoseBasedApplicationFactorEnabled) { newValue in From 069cfc7d2bddbda7d0e3bb3ab47d3afda22050ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sun, 4 Jun 2023 21:38:53 +0200 Subject: [PATCH 17/22] Logging error fixed --- Loop/Managers/LoopDataManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index d1be106e29..1b1bab1f41 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -1711,8 +1711,8 @@ extension LoopDataManager { settings: settings ) - self.logger.debug(" *** Glucose, effectiveBolusApplicationFactor: ", glucose.quantity, Double(Int(100.0*effectiveBolusApplicationFactor))/100.0) - + self.logger.debug(" *** Glucose: %{public}@, effectiveBolusApplicationFactor: %.2f", glucose.quantity.description, effectiveBolusApplicationFactor) + // If a user customizes maxPartialApplicationFactor > 1; this respects maxBolus let maxAutomaticBolus = min(iobHeadroom, maxBolus! * min(effectiveBolusApplicationFactor, 1.0)) From e434474b8033a0fc8f835c6b483a825e61182293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 10 Jun 2023 16:12:12 +0200 Subject: [PATCH 18/22] dead code --- Loop/Views/SettingsView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 1b5f4de328..2403220301 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -30,7 +30,6 @@ public struct SettingsView: View { @State private var therapySettingsIsPresented: Bool = false @State private var deletePumpDataAlertIsPresented = false @State private var deleteCGMDataAlertIsPresented = false - @State internal var enhancedAutoBolusIsPresented: Bool = false @State internal var isGlucoseBasedApplicationFactorEnabled = UserDefaults.standard.bool(forKey: "applyExperimentalGlucoseBasedApplicationFactor") var localizedAppNameAndVersion: String From abb83b06d147fd08ed535b009baee09f87918197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Tue, 13 Jun 2023 20:16:08 +0200 Subject: [PATCH 19/22] missed rename --- Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift index 90d05d0c90..1e78c81e8b 100644 --- a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift +++ b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift @@ -48,7 +48,7 @@ public struct GlucoseBasedApplicationFactorSelectionView: View { } } -struct EnhancedAutoBolusSelectionView_Previews: PreviewProvider { +struct GlucoseBasedApplicationFactorSelectionView_Previews: PreviewProvider { static var previews: some View { GlucoseBasedApplicationFactorSelectionView(isGlucoseBasedApplicationFactorEnabled: .constant(true)) } From 2ac23f45be3f5cf4e845cfbcb2ffe7a8efcb6ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 17 Jun 2023 21:34:54 +0200 Subject: [PATCH 20/22] dark mode fix --- Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift index 1e78c81e8b..53ff05cdb4 100644 --- a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift +++ b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift @@ -37,7 +37,7 @@ public struct GlucoseBasedApplicationFactorSelectionView: View { private var automaticBolusSection: some View { VStack { - DescriptiveText(label: NSLocalizedString("Active only when Automatic Bolus is selected under Dosing Strategy.\n\nWhen Glucose Based Partial Application is disabled, Automatic Bolus Dosing Strategy uses a constant percentage of 40% when Loop recommends a bolus.\n\nWhen enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Glucose Based Partial Application toggle."), color: .black) + DescriptiveText(label: NSLocalizedString("Active only when Automatic Bolus is selected under Dosing Strategy.\n\nWhen Glucose Based Partial Application is disabled, Automatic Bolus Dosing Strategy uses a constant percentage of 40% when Loop recommends a bolus.\n\nWhen enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Glucose Based Partial Application toggle.")) Section() { Toggle(NSLocalizedString("Glucose Based Partial Application", comment: "Title for Glucose Based Partial Application toggle"), isOn: $isGlucoseBasedApplicationFactorEnabled) .onChange(of: isGlucoseBasedApplicationFactorEnabled) { newValue in From 8e1ef8cd4b65158dc3d5362f02777da1f11ffc10 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 25 Jun 2023 00:26:13 -0500 Subject: [PATCH 21/22] Updated algorithm experiments view and glucose based partial application view --- Loop.xcodeproj/project.pbxproj | 8 +-- ...ingsView+algorithmExperimentsSection.swift | 55 ++++++++++++++++--- ...> ConstantApplicationFactorStrategy.swift} | 2 +- ...lucoseBasedApplicationFactorStrategy.swift | 15 +++++ Loop/Managers/LoopDataManager.swift | 6 +- ...eBasedApplicationFactorSelectionView.swift | 53 ++++++++++-------- Loop/Views/SettingsView.swift | 11 ++-- 7 files changed, 108 insertions(+), 42 deletions(-) rename Loop/Managers/{ConstantDosingStrategy.swift => ConstantApplicationFactorStrategy.swift} (88%) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 0d2dda5e3c..c48bf0fcc4 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -520,7 +520,7 @@ C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */; }; DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */; }; - DDC389FA2A2B62470066E2E8 /* ConstantDosingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */; }; + DDC389FA2A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */; }; DDC389FC2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */; }; DDC389FE2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */; }; E90909D124E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json in Resources */ = {isa = PBXBuildFile; fileRef = E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */; }; @@ -1694,7 +1694,7 @@ C1FF3D4D29C786A900BDC1EC /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoPlist.strings; sourceTree = ""; }; DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationFactorStrategy.swift; sourceTree = ""; }; DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactorStrategy.swift; sourceTree = ""; }; - DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantDosingStrategy.swift; sourceTree = ""; }; + DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantApplicationFactorStrategy.swift; sourceTree = ""; }; DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+algorithmExperimentsSection.swift"; sourceTree = ""; }; DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactorSelectionView.swift; sourceTree = ""; }; E90909CC24E34AC500F963D2 /* high_and_rising_with_cob_momentum_effect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = high_and_rising_with_cob_momentum_effect.json; sourceTree = ""; }; @@ -2455,7 +2455,7 @@ 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */, DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */, - DDC389F92A2B62470066E2E8 /* ConstantDosingStrategy.swift */, + DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */, ); path = Managers; sourceTree = ""; @@ -3887,7 +3887,7 @@ C1201E2C23ECDBD0002DA84A /* WatchContextRequestUserInfo.swift in Sources */, 1D49795824E7289700948F05 /* ServicesViewModel.swift in Sources */, 1D4A3E2D2478628500FD601B /* StoredAlert+CoreDataClass.swift in Sources */, - DDC389FA2A2B62470066E2E8 /* ConstantDosingStrategy.swift in Sources */, + DDC389FA2A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift in Sources */, 892D7C5123B54A15008A9656 /* CarbEntryViewController.swift in Sources */, B4E202302661063E009421B5 /* AutomaticDosingStatus.swift in Sources */, C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, diff --git a/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift index 811a2f339d..c6b9f93970 100644 --- a/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift +++ b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift @@ -8,19 +8,60 @@ import Foundation import SwiftUI +import LoopKit import LoopKitUI extension SettingsView { internal var algorithmExperimentsSection: some View { - Section(header: SectionHeader(label: NSLocalizedString("Algorithm Experiments ⚠️", comment: "The title of the Algorithm Experiments section in settings"))) { + NavigationLink(NSLocalizedString("Algorithm Experiments", comment: "The title of the Algorithm Experiments section in settings")) { + ExperimentsSettingsView(automaticDosingStrategy: viewModel.automaticDosingStrategy) + } + } +} + +public struct ExperimentRow: View { + var name: String + var enabled: Bool + + public var body: some View { + HStack { + Text(name) + .foregroundColor(.primary) + Spacer() + Text(enabled ? "On" : "Off") + .foregroundColor(enabled ? .red : .secondary) + } + } +} + +public struct ExperimentsSettingsView: View { + @State private var isGlucoseBasedApplicationFactorEnabled = UserDefaults.standard.glucoseBasedApplicationFactorEnabled + var automaticDosingStrategy: AutomaticDosingStrategy + + public var body: some View { + VStack(alignment: .center, spacing: 12) { + VStack { + Text("⚠️").font(.largeTitle) + Text("Caution") + } + Divider() + VStack(alignment: .leading, spacing: 12) { + Text(NSLocalizedString("Algorithm Experiments are optional modifications to the Loop Algorithm. These modifications are less tested than the standard Loop algorithm, so please use carefully.", comment: "Algorithm Experiments description.")) + Text(NSLocalizedString("In future versions of Loop these experiments may change, end up as standard parts of the Loop Algorithm, or be removed from Loop entirely. Please follow along in the Loop Zulip chat to stay informed of possible changes to these features.", comment: "Algorithm Experiments description second paragraph.")) + } + .foregroundColor(.secondary) - NavigationLink(destination: GlucoseBasedApplicationFactorSelectionView(isGlucoseBasedApplicationFactorEnabled: $isGlucoseBasedApplicationFactorEnabled)) { - HStack { - Text("Glucose Based Partial Application") - Spacer() - Text(isGlucoseBasedApplicationFactorEnabled ? "On" : "Off") - } + Divider() + NavigationLink(destination: GlucoseBasedApplicationFactorSelectionView(isGlucoseBasedApplicationFactorEnabled: $isGlucoseBasedApplicationFactorEnabled, automaticDosingStrategy: automaticDosingStrategy)) { + ExperimentRow(name: "Glucose Based Partial Application", enabled: isGlucoseBasedApplicationFactorEnabled && automaticDosingStrategy == .automaticBolus) } + .padding() + .background(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .cornerRadius(10) + Spacer() } + .padding() + .navigationTitle(NSLocalizedString("Algorithm Experiments", comment: "Navigation title for algorithms experiments screen")) } } diff --git a/Loop/Managers/ConstantDosingStrategy.swift b/Loop/Managers/ConstantApplicationFactorStrategy.swift similarity index 88% rename from Loop/Managers/ConstantDosingStrategy.swift rename to Loop/Managers/ConstantApplicationFactorStrategy.swift index 5084108e5f..e13c40c42e 100644 --- a/Loop/Managers/ConstantDosingStrategy.swift +++ b/Loop/Managers/ConstantApplicationFactorStrategy.swift @@ -11,7 +11,7 @@ import HealthKit import LoopKit import LoopCore -struct ConstantDosingStrategy: ApplicationFactorStrategy { +struct ConstantApplicationFactorStrategy: ApplicationFactorStrategy { func calculateDosingFactor( for glucose: HKQuantity, correctionRangeSchedule: GlucoseRangeSchedule, diff --git a/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift b/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift index 41caa3d773..2573deba31 100644 --- a/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift +++ b/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift @@ -40,3 +40,18 @@ struct GlucoseBasedApplicationFactorStrategy: ApplicationFactorStrategy { return effectiveBolusApplicationFactor } } + +extension UserDefaults { + private enum Key: String { + case GlucoseBasedApplicationFactorEnabled = "com.loopkit.algorithmExperiments.glucoseBasedApplicationFactorEnabled" + } + + var glucoseBasedApplicationFactorEnabled: Bool { + get { + bool(forKey: Key.GlucoseBasedApplicationFactorEnabled.rawValue) as Bool + } + set { + set(newValue, forKey: Key.GlucoseBasedApplicationFactorEnabled.rawValue) + } + } +} diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 1b1bab1f41..9994b09b3a 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -1699,9 +1699,9 @@ extension LoopDataManager { } // Create dosing strategy based on user setting - let applicationFactorStrategy: ApplicationFactorStrategy = UserDefaults.standard.bool(forKey: "applyExperimentalGlucoseBasedApplicationFactor") + let applicationFactorStrategy: ApplicationFactorStrategy = UserDefaults.standard.glucoseBasedApplicationFactorEnabled ? GlucoseBasedApplicationFactorStrategy() - : ConstantDosingStrategy() + : ConstantApplicationFactorStrategy() let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() @@ -2144,7 +2144,7 @@ extension LoopDataManager { "insulinOnBoard: \(String(describing: manager.insulinOnBoard))", "error: \(String(describing: state.error))", "overrideInUserDefaults: \(String(describing: UserDefaults.appGroup?.intentExtensionOverrideToSet))", - "applyExperimentalGlucoseBasedApplicationFactor: \(UserDefaults.standard.bool(forKey: "applyExperimentalGlucoseBasedApplicationFactor"))", + "glucoseBasedApplicationFactorEnabled: \(UserDefaults.standard.glucoseBasedApplicationFactorEnabled)", "", String(reflecting: self.retrospectiveCorrection), "", diff --git a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift index 53ff05cdb4..2b5556355b 100644 --- a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift +++ b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift @@ -13,43 +13,52 @@ import LoopKitUI public struct GlucoseBasedApplicationFactorSelectionView: View { @Binding var isGlucoseBasedApplicationFactorEnabled: Bool + var automaticDosingStrategy: AutomaticDosingStrategy - public init(isGlucoseBasedApplicationFactorEnabled: Binding) { + public init(isGlucoseBasedApplicationFactorEnabled: Binding, automaticDosingStrategy: AutomaticDosingStrategy) { + self.automaticDosingStrategy = automaticDosingStrategy self._isGlucoseBasedApplicationFactorEnabled = isGlucoseBasedApplicationFactorEnabled } public var body: some View { - List { - automaticBolusSection - } - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .principal) { - VStack { - Text("Glucose Based") - .font(.headline) - Text("Partial Application") - .font(.subheadline) - } + automaticBolusSection + .onChange(of: isGlucoseBasedApplicationFactorEnabled) { newValue in + UserDefaults.standard.glucoseBasedApplicationFactorEnabled = newValue } - } } private var automaticBolusSection: some View { - VStack { - DescriptiveText(label: NSLocalizedString("Active only when Automatic Bolus is selected under Dosing Strategy.\n\nWhen Glucose Based Partial Application is disabled, Automatic Bolus Dosing Strategy uses a constant percentage of 40% when Loop recommends a bolus.\n\nWhen enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level. Near correction range, use 20% (similar to Temp Basal). Gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Glucose Based Partial Application toggle.")) - Section() { - Toggle(NSLocalizedString("Glucose Based Partial Application", comment: "Title for Glucose Based Partial Application toggle"), isOn: $isGlucoseBasedApplicationFactorEnabled) - .onChange(of: isGlucoseBasedApplicationFactorEnabled) { newValue in - UserDefaults.standard.set(newValue, forKey: "applyExperimentalGlucoseBasedApplicationFactor") - } + VStack(spacing: 10) { + Text(NSLocalizedString("Glucose Based Partial Application", comment: "Title for glucose based partial application experiment description")) + .font(.headline) + .padding(.bottom, 20) + + Divider() + + if automaticDosingStrategy == .automaticBolus { + Text(NSLocalizedString("Loop normally gives 40% of your predicted insulin needs each dosing cycle.\n\nWhen Glucose Based Partial Application is enabled, this experimental feature varies the percentage of recommended bolus delivered each cycle with glucose level.\n\nNear correction range, it will use 20% (similar to Temp Basal), and gradually increase to a maximum of 80% at high glucose (200 mg/dL, 11.1 mmol/L).\n\nPlease be aware that during fast rising glucose, such as after an unannounced meal, this feature, combined with Loop's velocity and retrospective correction effects, may result in a larger dose than your ISF would call for.", comment: "Description of Glucose Based Partial Application toggle.")) + .foregroundColor(.secondary) + Divider() + + HStack { + Toggle(NSLocalizedString("Enable Glucose Based Partial Application", comment: "Title for Glucose Based Partial Application toggle"), isOn: $isGlucoseBasedApplicationFactorEnabled) + Spacer() + } + .padding(.top, 20) + } else { + Text(NSLocalizedString("This option only applies when Loop's Dosing Strategy is set to Automatic Bolus.", comment: "String shown when glucose based partial application cannot be enabled because dosing strategy is not set to Automatic Bolus")) } + + Spacer() } + .padding() } } struct GlucoseBasedApplicationFactorSelectionView_Previews: PreviewProvider { static var previews: some View { - GlucoseBasedApplicationFactorSelectionView(isGlucoseBasedApplicationFactorEnabled: .constant(true)) + NavigationView { + GlucoseBasedApplicationFactorSelectionView(isGlucoseBasedApplicationFactorEnabled: .constant(true), automaticDosingStrategy: .automaticBolus) + } } } diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index c8320e5635..e036b87751 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -30,8 +30,7 @@ public struct SettingsView: View { @State private var therapySettingsIsPresented: Bool = false @State private var deletePumpDataAlertIsPresented = false @State private var deleteCGMDataAlertIsPresented = false - @State internal var isGlucoseBasedApplicationFactorEnabled = UserDefaults.standard.bool(forKey: "applyExperimentalGlucoseBasedApplicationFactor") - + var localizedAppNameAndVersion: String public init(viewModel: SettingsViewModel, localizedAppNameAndVersion: String) { @@ -51,9 +50,6 @@ public struct SettingsView: View { if FeatureFlags.automaticBolusEnabled { dosingStrategySection } - // Note: Experimental Section - algorithmExperimentsSection - // alertManagementSection if viewModel.pumpManagerSettingsViewModel.isSetUp() { configurationSection @@ -228,6 +224,11 @@ extension SettingsView { ForEach(pluginMenuItems.filter {$0.section == .configuration}) { item in item.view } + + // Note: Experimental Section + algorithmExperimentsSection + // + } } From 17fc1670b593e6473b9f167376a2c01dbaae4728 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 25 Jun 2023 10:51:05 -0500 Subject: [PATCH 22/22] Put algorithm experiments behind feature flag --- Common/FeatureFlags.swift | 9 ++++++++- Loop/Views/SettingsView.swift | 7 +++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Common/FeatureFlags.swift b/Common/FeatureFlags.swift index 3ed4b4a4eb..197eed6d49 100644 --- a/Common/FeatureFlags.swift +++ b/Common/FeatureFlags.swift @@ -39,6 +39,7 @@ struct FeatureFlagConfiguration: Decodable { let adultChildInsulinModelSelectionEnabled: Bool let profileExpirationSettingsViewEnabled: Bool let missedMealNotifications: Bool + let allowAlgorithmExperiments: Bool fileprivate init() { @@ -229,6 +230,11 @@ struct FeatureFlagConfiguration: Decodable { self.missedMealNotifications = true #endif + #if ALLOW_ALGORITHM_EXPERIMENTS + self.allowAlgorithmExperiments = true + #else + self.allowAlgorithmExperiments = false + #endif } } @@ -263,7 +269,8 @@ extension FeatureFlagConfiguration : CustomDebugStringConvertible { "* dynamicCarbAbsorptionEnabled: \(dynamicCarbAbsorptionEnabled)", "* adultChildInsulinModelSelectionEnabled: \(adultChildInsulinModelSelectionEnabled)", "* profileExpirationSettingsViewEnabled: \(profileExpirationSettingsViewEnabled)", - "* missedMealNotifications: \(missedMealNotifications)" + "* missedMealNotifications: \(missedMealNotifications)", + "* allowAlgorithmExperiments: \(allowAlgorithmExperiments)" ].joined(separator: "\n") } } diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index e036b87751..afdc404fcc 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -225,10 +225,9 @@ extension SettingsView { item.view } - // Note: Experimental Section - algorithmExperimentsSection - // - + if FeatureFlags.allowAlgorithmExperiments { + algorithmExperimentsSection + } } }