diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 05301b2c0b..4fa85e8f2e 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -597,9 +597,7 @@ CB1AF8155B1761A30ACA5591 /* Loop/Resources/Sounds/synth.caf in Resources */ = {isa = PBXBuildFile; fileRef = 83081FFB0497A4596F597830 /* Loop/Resources/Sounds/synth.caf */; }; D6E28676C2C7751C77C7F2FB /* Loop/Resources/Sounds/spring.caf in Resources */ = {isa = PBXBuildFile; fileRef = 9471E156FC10D6D0FF219C77 /* Loop/Resources/Sounds/spring.caf */; }; DD3DBD292A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */; }; - DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */; }; - DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */; }; - DDC389FA2A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */; }; + A1B2C3D42E8F000100000002 /* GuardAndRunwaySelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D42E8F000100000001 /* GuardAndRunwaySelectionView.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 */; }; DE965737FE2DAE9FE692769E /* Loop/Resources/Sounds/alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = 0FC34B523FE5A50C920506D5 /* Loop/Resources/Sounds/alarm.caf */; }; @@ -1408,9 +1406,7 @@ C9SCENED0000000000000002 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; CE102E23D8C1C1291EF66387 /* PluginPickerView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PluginPickerView.swift; sourceTree = ""; }; DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrectionSelectionView.swift; 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 /* ConstantApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantApplicationFactorStrategy.swift; sourceTree = ""; }; + A1B2C3D42E8F000100000001 /* GuardAndRunwaySelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuardAndRunwaySelectionView.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 = ""; }; E4854073AAE5C2CB97167F63 /* HealthAccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthAccessView.swift; sourceTree = ""; }; @@ -1707,14 +1703,11 @@ 43757D131C06F26C00910CB9 /* Models */ = { isa = PBXGroup; children = ( - DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */, FEEDF0000000000000000000 /* GlucoseStatistics.swift */, A9FB75F0252BE320004C7D3F /* BolusDosingDecision.swift */, 8378B7D1F532A5F47CEBB6AE /* CarbEntryChange.swift */, - DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */, C1EF747128D6A44A00C8C083 /* CrashRecoveryManager.swift */, 1455ACB12C667BEE004F44F2 /* Deeplink.swift */, - DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */, B40D07C6251A89D500C1C6D7 /* GlucoseDisplay.swift */, 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */, C1C660D0252E4DD5009B5C32 /* LoopConstants.swift */, @@ -2069,6 +2062,7 @@ C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */, DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */, DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */, + A1B2C3D42E8F000100000001 /* GuardAndRunwaySelectionView.swift */, 84DEB10C2C18FABA00170734 /* IOSFocusModesView.swift */, 84D1F1A62D09053A00CB271F /* StatusTableView.swift */, B429CAB22E97C7F300FA988E /* LoopStatusModalView.swift */, @@ -3495,7 +3489,6 @@ 1D49795824E7289700948F05 /* ServicesViewModel.swift in Sources */, 1D4A3E2D2478628500FD601B /* StoredAlert+CoreDataClass.swift in Sources */, C1B80D632AF97D7200AB7705 /* LoopDataManager+CarbAbsorption.swift in Sources */, - DDC389FA2A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift in Sources */, C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, A977A2F424ACFECF0059C207 /* CriticalEventLogExportManager.swift in Sources */, 89CA2B32226C18B8004D9350 /* TestingScenariosTableViewController.swift in Sources */, @@ -3532,6 +3525,7 @@ 1DDE273D24AEA4B000796622 /* SettingsViewModel.swift in Sources */, FEEDB0000000000000000001 /* StatisticsViewModel.swift in Sources */, DD3DBD292A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift in Sources */, + A1B2C3D42E8F000100000002 /* GuardAndRunwaySelectionView.swift in Sources */, 842E40A72F22F7E2000CCCE0 /* TintedContent.swift in Sources */, 842E40A92F22F7E2000CCCE0 /* EstimatedReadTime.swift in Sources */, 842E40AA2F22F7E2000CCCE0 /* PresetsTrainingView.swift in Sources */, @@ -3547,7 +3541,6 @@ 1DB619AC270BAD3D006C9D07 /* VersionUpdateViewModel.swift in Sources */, A9C62D882331703100535612 /* Service.swift in Sources */, 89CAB36324C8FE96009EE3CE /* PredictedGlucoseChartView.swift in Sources */, - DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift in Sources */, 847F23432E4543140035C864 /* ActivePresetBanner.swift in Sources */, 84BC5BDF2E1C4A7300432000 /* InsulinDeliveryEventDetailsView.swift in Sources */, 4F6663941E905FD2009E74FC /* ChartColorPalette+Loop.swift in Sources */, @@ -3632,7 +3625,6 @@ A97F250825E056D500F0EE19 /* OnboardingManager.swift in Sources */, 438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */, 892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */, - DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */, FEEDB0000000000000000000 /* GlucoseStatistics.swift in Sources */, 4F70C2101DE8FAC5006380B7 /* ExtensionDataManager.swift in Sources */, 1455ACB22C667BEE004F44F2 /* Deeplink.swift in Sources */, diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 472f4468b4..30ab8fb2a8 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -166,6 +166,8 @@ final class LoopDataManager: ObservableObject { private var lastManualBolusRecommendation: ManualBolusRecommendation? + private var volatilityBaseline: (value: Double?, date: Date)? + private(set) var dosingStrategySelectionEnabled: Bool var usePositiveMomentumAndRCForManualBoluses: Bool @@ -536,23 +538,8 @@ final class LoopDataManager: ObservableObject { } - // Create dosing strategy based on user setting - let applicationFactorStrategy: ApplicationFactorStrategy = UserDefaults.standard.glucoseBasedApplicationFactorEnabled - ? GlucoseBasedApplicationFactorStrategy() - : ConstantApplicationFactorStrategy() - - let correctionRange = target.closestPrior(to: baseTime)?.value - - let effectiveBolusApplicationFactor: Double? - - if let latestGlucose = glucose.last { - effectiveBolusApplicationFactor = applicationFactorStrategy.calculateDosingFactor( - for: latestGlucose.quantity, - correctionRange: correctionRange! - ) - } else { - effectiveBolusApplicationFactor = nil - } + let experiments = enabledAlgorithmExperiments + let volatilityBaseline = experiments.contains(.guardAndRunway) ? await currentVolatilityBaseline(at: baseTime) : nil return StoredDataAlgorithmInput( glucoseHistory: glucose, @@ -566,12 +553,28 @@ final class LoopDataManager: ObservableObject { suspendThreshold: suspendThreshold, maxBolus: maxBolus, maxBasalRate: maxBasalRate, - useIntegralRetrospectiveCorrection: UserDefaults.standard.integralRetrospectiveCorrectionEnabled, + useIntegralRetrospectiveCorrection: false, includePositiveVelocityAndRC: true, carbAbsorptionModel: carbAbsorptionModel, recommendationInsulinModel: recommendationInsulinModel, recommendationType: .manualBolus, - automaticBolusApplicationFactor: effectiveBolusApplicationFactor) + automaticBolusApplicationFactor: nil, + experiments: experiments, + volatilityBaseline: volatilityBaseline) + } + + private var enabledAlgorithmExperiments: Set { + var experiments: Set = [] + if UserDefaults.standard.integralRetrospectiveCorrectionEnabled { + experiments.insert(.integralRetrospectiveCorrection) + } + if UserDefaults.standard.glucoseBasedApplicationFactorEnabled { + experiments.insert(.glucoseBasedApplicationFactor) + } + if UserDefaults.standard.guardAndRunwayEnabled { + experiments.insert(.guardAndRunway) + } + return experiments } func loopingReEnabled() async { @@ -1053,14 +1056,31 @@ extension LoopDataManager { /// Glucose based partial application only affects automatic boluses, so it is in effect only with that strategy private func algorithmExperiments(for dosingStrategy: AutomaticDosingStrategy) -> [AlgorithmExperiment] { - var experiments: [AlgorithmExperiment] = [] - if UserDefaults.standard.integralRetrospectiveCorrectionEnabled { - experiments.append(AlgorithmExperiment(identifier: "integralRetrospectiveCorrection")) + enabledAlgorithmExperiments + .filter { $0 != .glucoseBasedApplicationFactor || dosingStrategy == .automaticBolus } + .sorted { $0.rawValue < $1.rawValue } + .map { experiment in + let parameters: JSONValue? = experiment == .guardAndRunway + ? volatilityBaseline?.value.map { .object(["volatilityBaseline": .number($0)]) } + : nil + return AlgorithmExperiment(identifier: experiment.rawValue, parameters: parameters) + } + } + + /// The median σ5 over the last four weeks, for guard + runway's volatility band. Nil until there is a day of glucose. + private func currentVolatilityBaseline(at date: Date) async -> Double? { + if let volatilityBaseline, abs(date.timeIntervalSince(volatilityBaseline.date)) < (volatilityBaseline.value == nil ? .hours(1) : .days(1)) { + return volatilityBaseline.value } - if UserDefaults.standard.glucoseBasedApplicationFactorEnabled && dosingStrategy == .automaticBolus { - experiments.append(AlgorithmExperiment(identifier: "glucoseBasedApplicationFactor")) + do { + let glucose = try await glucoseStore.getGlucoseSamples(start: date.addingTimeInterval(.days(-28)), end: date) + let baseline = GlucoseVolatility.baseline(glucose) + volatilityBaseline = (baseline, date) + return baseline + } catch { + logger.error("Error fetching glucose for volatility baseline: %{public}@", error.localizedDescription) + return volatilityBaseline?.value } - return experiments } func storeManualBolusDosingDecision(_ bolusDosingDecision: BolusDosingDecision, withDate date: Date) async { @@ -1304,7 +1324,8 @@ extension StoredDataAlgorithmInput { algorithmEffectsOptions: effectsOptions, useIntegralRetrospectiveCorrection: self.useIntegralRetrospectiveCorrection, useMidAbsorptionISF: true, - carbAbsorptionModel: self.carbAbsorptionModel.model + carbAbsorptionModel: self.carbAbsorptionModel.model, + experiments: experiments ) return prediction.glucose } @@ -1786,8 +1807,8 @@ extension LoopDataManager: DiagnosticReportGenerator { "insulinOnBoard: \(String(describing: algoOutput?.activeInsulin))", "error: \(String(describing: loopError))", "overrideInUserDefaults: \(String(describing: UserDefaults.appGroup?.intentExtensionOverrideToSet))", - "glucoseBasedApplicationFactorEnabled: \(UserDefaults.standard.glucoseBasedApplicationFactorEnabled)", - "integralRetrospectiveCorrectionEanbled: \(String(describing: algoInput?.useIntegralRetrospectiveCorrection))", + "algorithmExperiments: \(algoInput.map { $0.experiments.map(\.rawValue).sorted() } ?? [])", + "volatilityBaseline: \(String(describing: algoInput?.volatilityBaseline))", "" ] return entries.joined(separator: "\n") diff --git a/Loop/Models/ApplicationFactorStrategy.swift b/Loop/Models/ApplicationFactorStrategy.swift deleted file mode 100644 index 57038f5cb0..0000000000 --- a/Loop/Models/ApplicationFactorStrategy.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// ApplicationFactorStrategy.swift -// Loop -// -// Created by Jonas Björkert on 2023-06-03. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import Foundation -import LoopAlgorithm -import LoopKit -import LoopCore - -protocol ApplicationFactorStrategy { - func calculateDosingFactor( - for glucose: LoopQuantity, - correctionRange: ClosedRange - ) -> Double -} diff --git a/Loop/Models/ConstantApplicationFactorStrategy.swift b/Loop/Models/ConstantApplicationFactorStrategy.swift deleted file mode 100644 index 41b4b478e2..0000000000 --- a/Loop/Models/ConstantApplicationFactorStrategy.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// ConstantDosingStrategy.swift -// Loop -// -// Created by Jonas Björkert on 2023-06-03. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import Foundation -import LoopKit -import LoopCore -import LoopAlgorithm - -struct ConstantApplicationFactorStrategy: ApplicationFactorStrategy { - func calculateDosingFactor( - for glucose: LoopQuantity, - correctionRange: ClosedRange - ) -> Double { - // The original strategy uses a constant dosing factor. - return LoopAlgorithm.defaultBolusPartialApplicationFactor - } -} diff --git a/Loop/Models/GlucoseBasedApplicationFactorStrategy.swift b/Loop/Models/GlucoseBasedApplicationFactorStrategy.swift deleted file mode 100644 index e339d4a881..0000000000 --- a/Loop/Models/GlucoseBasedApplicationFactorStrategy.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// GlucoseBasedApplicationFactorStrategy.swift -// Loop -// -// Created by Jonas Björkert on 2023-06-03. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import Foundation -import LoopAlgorithm -import LoopKit -import LoopCore - -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 - // use mg/dL for calculations - static let minGlucoseDeltaSlidingScale = 10.0 // mg/dL - static let maxGlucoseSlidingScale = 200.0 // mg/dL - - func calculateDosingFactor( - for glucose: LoopQuantity, - correctionRange: ClosedRange - ) -> Double { - // Calculate current glucose and lower bound target - let currentGlucose = glucose.doubleValue(for: .milligramsPerDeciliter) - let lowerBoundTarget = correctionRange.lowerBound.doubleValue(for: .milligramsPerDeciliter) - - // Calculate minimum glucose sliding scale and scaling fraction - 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(GlucoseBasedApplicationFactorStrategy.minPartialApplicationFactor + scalingGlucose * scalingFraction, GlucoseBasedApplicationFactorStrategy.maxPartialApplicationFactor) - - return effectiveBolusApplicationFactor - } -} diff --git a/Loop/Models/StoredDataAlgorithmInput.swift b/Loop/Models/StoredDataAlgorithmInput.swift index ba3c169d88..8d8441c45a 100644 --- a/Loop/Models/StoredDataAlgorithmInput.swift +++ b/Loop/Models/StoredDataAlgorithmInput.swift @@ -56,4 +56,8 @@ struct StoredDataAlgorithmInput: AlgorithmInput { var maxActiveInsulinMultiplier: Double? = nil var gradualTransitionsThreshold: Double? = nil + + var experiments: Set = [] + + var volatilityBaseline: Double? = nil } diff --git a/Loop/Views/GuardAndRunwaySelectionView.swift b/Loop/Views/GuardAndRunwaySelectionView.swift new file mode 100644 index 0000000000..16d5696325 --- /dev/null +++ b/Loop/Views/GuardAndRunwaySelectionView.swift @@ -0,0 +1,39 @@ +// +// GuardAndRunwaySelectionView.swift +// Loop +// +// Copyright © 2026 LoopKit Authors. All rights reserved. +// + +import SwiftUI + +public struct GuardAndRunwaySelectionView: View { + @Binding var isGuardAndRunwayEnabled: Bool + + public var body: some View { + ScrollView { + VStack(spacing: 10) { + Text(NSLocalizedString("Guard + Runway", comment: "Title for guard and runway experiment description")) + .font(.headline) + .padding(.bottom, 20) + + Divider() + + Text(NSLocalizedString("Guard + Runway pairs a guard against lows with a little more automatic correction when there is room for it.\n\nAfter a low: for 12 hours after a reading below 70 mg/dL (3.9 mmol/L), while glucose is below 180 mg/dL (10 mmol/L), rises count less toward retrospective correction, so the rebound after treating a low is not taken as a lasting need for more insulin.\n\nVolatility: with no carbs on board, the first two hours of the forecast are lowered according to how much glucose has been varying recently, so Loop is more cautious when glucose is unsettled.\n\nRunway: at 150 mg/dL (8.3 mmol/L) or above with no carbs on board, Loop gives some extra automatic insulin when glucose is calm enough that suspending basal could still take it back before reaching your glucose safety limit.\n\nYour correction range, manual bolus recommendations and glucose safety limit are unchanged. This experiment has been evaluated in simulation and in replays of real data, not yet by people using it.", comment: "Description of Guard + Runway experiment.")) + .foregroundColor(.secondary) + Divider() + + Toggle(NSLocalizedString("Enable Guard + Runway", comment: "Title for Guard + Runway toggle"), isOn: $isGuardAndRunwayEnabled) + .padding(.top, 20) + } + .padding() + } + .navigationBarTitleDisplayMode(.inline) + } +} + +struct GuardAndRunwaySelectionView_Previews: PreviewProvider { + static var previews: some View { + GuardAndRunwaySelectionView(isGuardAndRunwayEnabled: .constant(true)) + } +} diff --git a/Loop/Views/SettingsView+algorithmExperimentsSection.swift b/Loop/Views/SettingsView+algorithmExperimentsSection.swift index 795fbdd860..f956c63586 100644 --- a/Loop/Views/SettingsView+algorithmExperimentsSection.swift +++ b/Loop/Views/SettingsView+algorithmExperimentsSection.swift @@ -41,6 +41,7 @@ public struct ExperimentRow: View { public struct ExperimentsSettingsView: View { @AppStorage(UserDefaults.Key.GlucoseBasedApplicationFactorEnabled.rawValue) private var isGlucoseBasedApplicationFactorEnabled = false @AppStorage(UserDefaults.Key.IntegralRetrospectiveCorrectionEnabled.rawValue) private var isIntegralRetrospectiveCorrectionEnabled = false + @AppStorage(UserDefaults.Key.GuardAndRunwayEnabled.rawValue) private var isGuardAndRunwayEnabled = false var automaticDosingStrategy: AutomaticDosingStrategy public var body: some View { @@ -70,6 +71,11 @@ public struct ExperimentsSettingsView: View { name: NSLocalizedString("Integral Retrospective Correction", comment: "Title of integral retrospective correction experiment"), enabled: isIntegralRetrospectiveCorrectionEnabled) } + NavigationLink(destination: GuardAndRunwaySelectionView(isGuardAndRunwayEnabled: $isGuardAndRunwayEnabled)) { + ExperimentRow( + name: NSLocalizedString("Guard + Runway", comment: "Title of guard and runway experiment"), + enabled: isGuardAndRunwayEnabled) + } Spacer() } .padding() @@ -83,6 +89,7 @@ extension UserDefaults { fileprivate enum Key: String { case GlucoseBasedApplicationFactorEnabled = "com.loopkit.algorithmExperiments.glucoseBasedApplicationFactorEnabled" case IntegralRetrospectiveCorrectionEnabled = "com.loopkit.algorithmExperiments.integralRetrospectiveCorrectionEnabled" + case GuardAndRunwayEnabled = "com.loopkit.algorithmExperiments.guardAndRunwayEnabled" } var glucoseBasedApplicationFactorEnabled: Bool { @@ -103,4 +110,13 @@ extension UserDefaults { } } + var guardAndRunwayEnabled: Bool { + get { + bool(forKey: Key.GuardAndRunwayEnabled.rawValue) as Bool + } + set { + set(newValue, forKey: Key.GuardAndRunwayEnabled.rawValue) + } + } + }