From b7ebfe5db48ab66caf56eed650548bd65eb137f7 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Thu, 16 Sep 2021 18:43:01 -0600 Subject: [PATCH 01/31] sign --- Loop.xcodeproj/project.pbxproj | 8 ++++---- WatchApp/DerivedAssets.xcassets/Contents.json | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 360db94c06..06213dff9a 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -5645,7 +5645,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( @@ -5667,7 +5667,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( @@ -5729,7 +5729,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Intent Extension/Loop Intent Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Loop Intent Extension/Info.plist"; @@ -5752,7 +5752,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Intent Extension/Loop Intent Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Loop Intent Extension/Info.plist"; diff --git a/WatchApp/DerivedAssets.xcassets/Contents.json b/WatchApp/DerivedAssets.xcassets/Contents.json index da4a164c91..73c00596a7 100644 --- a/WatchApp/DerivedAssets.xcassets/Contents.json +++ b/WatchApp/DerivedAssets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} From 8521cdc6bd143f6290fcce37a1fad0ede0f85238 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Thu, 16 Sep 2021 20:43:49 -0600 Subject: [PATCH 02/31] add irc to settings --- LoopCore/LoopSettings.swift | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 63418ad4aa..77f2c8d52e 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -19,6 +19,22 @@ public extension AutomaticDosingStrategy { } } +public enum RetrospectiveCorrection: Int, CaseIterable { + case standardRetrospectiveCorrection + case integralRetrospectiveCorrection +} + +public extension RetrospectiveCorrection { + var title: String { + switch self { + case .standardRetrospectiveCorrection: + return NSLocalizedString("Standard Retrospective Correction", comment: "Title string for standard retrospective correction") + case .integralRetrospectiveCorrection: + return NSLocalizedString("Integral Retrospective Correction", comment: "Title string for integral retrospective correction") + } + } +} + public struct LoopSettings: Equatable { public var isScheduleOverrideInfiniteWorkout: Bool { guard let scheduleOverride = scheduleOverride else { return false } @@ -74,6 +90,8 @@ public struct LoopSettings: Equatable { public var automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly public var defaultRapidActingModel: ExponentialInsulinModelPreset? + + public var retrospectiveCorrection: RetrospectiveCorrection = .standardRetrospectiveCorrection public var glucoseUnit: HKUnit? { return glucoseTargetRangeSchedule?.unit @@ -277,6 +295,11 @@ extension LoopSettings: RawRepresentable { { self.automaticDosingStrategy = automaticDosingStrategy } + + if let rawRetrospectiveCorrection = rawValue["retrospectiveCorrection"] as? RetrospectiveCorrection.RawValue, + let retrospectiveCorrection = RetrospectiveCorrection(rawValue: rawRetrospectiveCorrection) { + self.retrospectiveCorrection = retrospectiveCorrection + } } public var rawValue: RawValue { @@ -295,6 +318,7 @@ extension LoopSettings: RawRepresentable { raw["maximumBolus"] = maximumBolus raw["minimumBGGuard"] = suspendThreshold?.rawValue raw["dosingStrategy"] = automaticDosingStrategy.rawValue + raw["retrospectiveCorrection"] = retrospectiveCorrection.rawValue return raw } From 05d0c45e1a3b659ed9c166c4598676fa7cf722e4 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sat, 18 Sep 2021 16:45:57 -0600 Subject: [PATCH 03/31] rc selection view --- Loop.xcodeproj/project.pbxproj | 4 ++ Loop/Models/LoopSettings+Loop.swift | 2 +- ...RetrospectiveCorrectionSelectionView.swift | 70 +++++++++++++++++++ LoopCore/LoopSettings.swift | 10 +-- 4 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 Loop/Views/RetrospectiveCorrectionSelectionView.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 06213dff9a..7911221f6c 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -331,6 +331,7 @@ 89F9119424358E4500ECCAF3 /* CarbAbsorptionTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9119324358E4500ECCAF3 /* CarbAbsorptionTime.swift */; }; 89F9119624358E6900ECCAF3 /* BolusPickerValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9119524358E6900ECCAF3 /* BolusPickerValues.swift */; }; 89FE21AD24AC57E30033F501 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21AC24AC57E30033F501 /* Collection.swift */; }; + 9E9C350626F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */; }; A90EF53C25DEF06200F32D61 /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* PluginManager.swift */; }; A90EF54425DEF0A000F32D61 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; A91D2A3F26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91D2A3E26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift */; }; @@ -1318,6 +1319,7 @@ A900531B28D608CA000BC15B /* Cancel Override.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cancel Override.shortcut"; sourceTree = ""; }; A900531C28D6090D000BC15B /* Loop Remote Overrides.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop Remote Overrides.shortcut"; sourceTree = ""; }; A91D2A3E26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconTitleSubtitleTableViewCell.swift; sourceTree = ""; }; + 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrospectiveCorrectionSelectionView.swift; sourceTree = ""; }; A91E4C2024F867A700BE9213 /* StoredAlertTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredAlertTests.swift; sourceTree = ""; }; A91E4C2224F86F1000BE9213 /* CriticalEventLogExportManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CriticalEventLogExportManagerTests.swift; sourceTree = ""; }; A9347F2E24E7508A00C99C34 /* WatchHistoricalCarbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchHistoricalCarbs.swift; sourceTree = ""; }; @@ -2395,6 +2397,7 @@ 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */, + 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */, ); path = Views; sourceTree = ""; @@ -3869,6 +3872,7 @@ 892D7C5123B54A15008A9656 /* CarbEntryViewController.swift in Sources */, B4E202302661063E009421B5 /* AutomaticDosingStatus.swift in Sources */, C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, + 9E9C350626F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift in Sources */, A977A2F424ACFECF0059C207 /* CriticalEventLogExportManager.swift in Sources */, 89CA2B32226C18B8004D9350 /* TestingScenariosTableViewController.swift in Sources */, 43E93FB71E469A5100EAB8DB /* HKUnit.swift in Sources */, diff --git a/Loop/Models/LoopSettings+Loop.swift b/Loop/Models/LoopSettings+Loop.swift index ce7433a945..4fdd31c350 100644 --- a/Loop/Models/LoopSettings+Loop.swift +++ b/Loop/Models/LoopSettings+Loop.swift @@ -19,7 +19,7 @@ extension LoopSettings { } static let retrospectiveCorrectionEffectDuration = TimeInterval(hours: 1) - + /// Creates an instance of the enabled retrospective correction implementation var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection { return StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) diff --git a/Loop/Views/RetrospectiveCorrectionSelectionView.swift b/Loop/Views/RetrospectiveCorrectionSelectionView.swift new file mode 100644 index 0000000000..1616c43c5f --- /dev/null +++ b/Loop/Views/RetrospectiveCorrectionSelectionView.swift @@ -0,0 +1,70 @@ +// +// RetrospectiveCorrectionSelectionView.swift +// Loop +// +// Created by Dragan Maksimovic on 9/18/21. +// Copyright © 2021 LoopKit Authors. All rights reserved. +// + +import SwiftUI +import LoopCore +import LoopKitUI + +public struct RetrospectiveCorrectionSelectionView: View { + + @Binding private var retrospectiveCorrection: RetrospectiveCorrectionOptions + + @State private var internalRetrospectiveCorrection: RetrospectiveCorrectionOptions + + public init(retrospectiveCorrection: Binding) { + self._retrospectiveCorrection = retrospectiveCorrection + self._internalRetrospectiveCorrection = State(initialValue: retrospectiveCorrection.wrappedValue) + } + + public var body: some View { + List { + Section { + options + } + .buttonStyle(PlainButtonStyle()) // Disable row highlighting on selection + } + .insetGroupedListStyle() + } + + public var options: some View { + ForEach(RetrospectiveCorrectionOptions.allCases, id: \.self) { rcOption in + CheckmarkListItem( + title: Text(rcOption.title), + description: Text(rcOption.informationalText), + isSelected: Binding( + get: { self.retrospectiveCorrection == rcOption }, + set: { isSelected in + if isSelected { + self.retrospectiveCorrection = rcOption + self.internalRetrospectiveCorrection = rcOption // Hack to force update. :( + } + } + ) + ) + .padding(.vertical, 4) + } + } +} + +extension RetrospectiveCorrectionOptions { + var informationalText: String { + switch self { + case .standardRetrospectiveCorrection: + return NSLocalizedString("Discrepancy between modeled and observed glucose over the past 30 min extended over the next 60 min", comment: "Description string for standard retrospective correction") + case .integralRetrospectiveCorrection: + return NSLocalizedString("Retrospective correction extended over extended time interval based on past oberved discrepancies between modeled and observed glucose", comment: "Description string for integral retrospective correction") + } + } + +} + +struct RetrospectiveCorrectionSelectionView_Previews: PreviewProvider { + static var previews: some View { + RetrospectiveCorrectionSelectionView(retrospectiveCorrection: .constant(.standardRetrospectiveCorrection)) + } +} diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 77f2c8d52e..0dc6761299 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -19,12 +19,12 @@ public extension AutomaticDosingStrategy { } } -public enum RetrospectiveCorrection: Int, CaseIterable { +public enum RetrospectiveCorrectionOptions: Int, CaseIterable { case standardRetrospectiveCorrection case integralRetrospectiveCorrection } -public extension RetrospectiveCorrection { +public extension RetrospectiveCorrectionOptions { var title: String { switch self { case .standardRetrospectiveCorrection: @@ -91,7 +91,7 @@ public struct LoopSettings: Equatable { public var defaultRapidActingModel: ExponentialInsulinModelPreset? - public var retrospectiveCorrection: RetrospectiveCorrection = .standardRetrospectiveCorrection + public var retrospectiveCorrection: RetrospectiveCorrectionOptions = .standardRetrospectiveCorrection public var glucoseUnit: HKUnit? { return glucoseTargetRangeSchedule?.unit @@ -296,8 +296,8 @@ extension LoopSettings: RawRepresentable { self.automaticDosingStrategy = automaticDosingStrategy } - if let rawRetrospectiveCorrection = rawValue["retrospectiveCorrection"] as? RetrospectiveCorrection.RawValue, - let retrospectiveCorrection = RetrospectiveCorrection(rawValue: rawRetrospectiveCorrection) { + if let rawRetrospectiveCorrection = rawValue["retrospectiveCorrection"] as? RetrospectiveCorrectionOptions.RawValue, + let retrospectiveCorrection = RetrospectiveCorrectionOptions(rawValue: rawRetrospectiveCorrection) { self.retrospectiveCorrection = retrospectiveCorrection } } From c5af2478f64f0b943c8469b601b387ce5446a12a Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sat, 18 Sep 2021 17:38:37 -0600 Subject: [PATCH 04/31] rc selection in settings --- .../StatusTableViewController.swift | 5 ++ Loop/View Models/SettingsViewModel.swift | 10 ++++ Loop/Views/SettingsView.swift | 49 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index da7072498b..7b477c6a65 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -1550,6 +1550,7 @@ final class StatusTableViewController: LoopChartsTableViewController { availableSupports: supportManager.availableSupports, isOnboardingComplete: onboardingManager.isComplete, therapySettingsViewModelDelegate: deviceManager, + retrospectiveCorrection: deviceManager.loopManager.settings.retrospectiveCorrection, delegate: self) let hostingController = DismissibleHostingController( rootView: SettingsView(viewModel: viewModel, localizedAppNameAndVersion: supportManager.localizedAppNameAndVersion) @@ -2132,6 +2133,10 @@ extension StatusTableViewController: SettingsViewModelDelegate { var closedLoopDescriptiveText: String? { return deviceManager.closedLoopDisallowedLocalizedDescription } + + func retrospectiveCorrectionChanged(_ retrospectiveCorrection: RetrospectiveCorrectionOptions) { + self.deviceManager.loopManager.settings.retrospectiveCorrection = retrospectiveCorrection + } func dosingEnabledChanged(_ value: Bool) { deviceManager.loopManager.mutateSettings { settings in diff --git a/Loop/View Models/SettingsViewModel.swift b/Loop/View Models/SettingsViewModel.swift index 3e3c20be24..490dba491b 100644 --- a/Loop/View Models/SettingsViewModel.swift +++ b/Loop/View Models/SettingsViewModel.swift @@ -53,6 +53,7 @@ public protocol SettingsViewModelDelegate: AnyObject { func dosingEnabledChanged(_: Bool) func dosingStrategyChanged(_: AutomaticDosingStrategy) func didTapIssueReport() + func retrospectiveCorrectionChanged(_: RetrospectiveCorrectionOptions) var closedLoopDescriptiveText: String? { get } } @@ -92,6 +93,12 @@ public class SettingsViewModel: ObservableObject { delegate?.dosingStrategyChanged(automaticDosingStrategy) } } + + @Published var retrospectiveCorrection: RetrospectiveCorrectionOptions { + didSet { + delegate?.retrospectiveCorrectionChanged(retrospectiveCorrection) + } + } var closedLoopPreference: Bool { didSet { @@ -113,6 +120,7 @@ public class SettingsViewModel: ObservableObject { initialDosingEnabled: Bool, isClosedLoopAllowed: Published.Publisher, automaticDosingStrategy: AutomaticDosingStrategy, + retrospectiveCorrection: RetrospectiveCorrectionOptions, availableSupports: [SupportUI], isOnboardingComplete: Bool, therapySettingsViewModelDelegate: TherapySettingsViewModelDelegate?, @@ -130,6 +138,8 @@ public class SettingsViewModel: ObservableObject { self.closedLoopPreference = initialDosingEnabled self.isClosedLoopAllowed = false self.automaticDosingStrategy = automaticDosingStrategy + self.retrospectiveCorrection = retrospectiveCorrection + self.supportInfoProvider = supportInfoProvider self.availableSupports = availableSupports self.isOnboardingComplete = isOnboardingComplete self.therapySettingsViewModelDelegate = therapySettingsViewModelDelegate diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index dc3f6ec3c8..7ab8c9a3d4 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -73,6 +73,24 @@ public struct SettingsView: View { if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled { profileExpirationSection(profileExpiration: profileExpiration) } + if FeatureFlags.automaticBolusEnabled { + dosingStrategySection + } + alertManagementSection + retrospectiveCorrectionSection + if viewModel.pumpManagerSettingsViewModel.isSetUp() { + configurationSection + } + deviceSettingsSection + if viewModel.pumpManagerSettingsViewModel.isTestingDevice || viewModel.cgmManagerSettingsViewModel.isTestingDevice { + deleteDataSection + } + if viewModel.servicesViewModel.showServices { + servicesSection + } + supportSection + if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled { + profileExpirationSection(profileExpiration: profileExpiration) } } .insetGroupedListStyle() @@ -174,6 +192,19 @@ extension SettingsView { } } + private var retrospectiveCorrectionSection: some View { + Section(header: SectionHeader(label: NSLocalizedString("Retrospective Correction", comment: "The title of the Retrospective Correction section in settings"))) { + + NavigationLink(destination: RetrospectiveCorrectionSelectionView(retrospectiveCorrection: $viewModel.retrospectiveCorrection)) + { + HStack { + Text(viewModel.retrospectiveCorrection.title) + } + } + } + } + + private var alertManagementSection: some View { Section { NavigationLink(destination: AlertManagementView(checker: viewModel.alertPermissionsChecker, alertMuter: viewModel.alertMuter)) @@ -491,6 +522,24 @@ public struct SettingsView_Previews: PreviewProvider { public static var previews: some View { let displayGlucoseUnitObservable = DisplayGlucoseUnitObservable(displayGlucoseUnit: .milligramsPerDeciliter) let viewModel = SettingsViewModel.preview + /* DM check + let viewModel = SettingsViewModel(notificationsCriticalAlertPermissionsViewModel: NotificationsCriticalAlertPermissionsViewModel(), + pumpManagerSettingsViewModel: DeviceViewModel(), + cgmManagerSettingsViewModel: DeviceViewModel(), + servicesViewModel: servicesViewModel, + criticalEventLogExportViewModel: CriticalEventLogExportViewModel(exporterFactory: MockCriticalEventLogExporterFactory()), + therapySettings: { TherapySettings() }, + pumpSupportedIncrements: nil, + syncPumpSchedule: nil, + sensitivityOverridesEnabled: false, + initialDosingEnabled: true, + isClosedLoopAllowed: fakeClosedLoopAllowedPublisher.$mockIsClosedLoopAllowed, + supportInfoProvider: MockSupportInfoProvider(), + dosingStrategy: .automaticBolus, + retrospectiveCorrection: .standardRetrospectiveCorrection, + availableSupports: [], + delegate: nil) +*/ return Group { SettingsView(viewModel: viewModel, localizedAppNameAndVersion: "Loop Demo V1") .colorScheme(.light) From 6f2baf630f001aed2aec466248773725a62eb063 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sat, 18 Sep 2021 22:42:42 -0600 Subject: [PATCH 05/31] rc selection in data manager --- Loop/Managers/LoopDataManager.swift | 11 +++++++++-- Loop/Models/LoopSettings+Loop.swift | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index ffc66ee314..c8d9ea5ced 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -72,7 +72,7 @@ final class LoopDataManager { NotificationCenter.default.removeObserver(observer) } } - + init( lastLoopCompleted: Date?, basalDeliveryState: PumpManagerStatus.BasalDeliveryState?, @@ -123,7 +123,14 @@ final class LoopDataManager { self.trustedTimeOffset = trustedTimeOffset - retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm + /// Creates an instance of the enabled retrospective correction implementation + // retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm + switch settings.retrospectiveCorrection { + case .standardRetrospectiveCorrection: + retrospectiveCorrection = StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) + case .integralRetrospectiveCorrection: + retrospectiveCorrection = StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) + } overrideIntentObserver = UserDefaults.appGroup?.observe(\.intentExtensionOverrideToSet, options: [.new], changeHandler: {[weak self] (defaults, change) in guard let name = change.newValue??.lowercased(), let appGroup = UserDefaults.appGroup else { diff --git a/Loop/Models/LoopSettings+Loop.swift b/Loop/Models/LoopSettings+Loop.swift index 4fdd31c350..6cde81d506 100644 --- a/Loop/Models/LoopSettings+Loop.swift +++ b/Loop/Models/LoopSettings+Loop.swift @@ -21,7 +21,7 @@ extension LoopSettings { static let retrospectiveCorrectionEffectDuration = TimeInterval(hours: 1) /// Creates an instance of the enabled retrospective correction implementation - var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection { - return StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) - } + // var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection { + // return StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) + //} } From f1f0220523f2ae04e3b6dbf783daa8ac4cbf081f Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sun, 19 Sep 2021 10:24:12 -0600 Subject: [PATCH 06/31] rc subtitle test --- .../PredictionTableViewController.swift | 10 +++++++++- Loop/en.lproj/Localizable.strings | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index 10ed414ade..4f02fda3df 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -272,13 +272,21 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable formatter.numberFormatter.positivePrefix = formatter.numberFormatter.plusSign values.append(formatter.string(from: lastDiscrepancy.quantity, for: glucoseChart.glucoseUnit) ?? "?") + let rcFlag: String + switch deviceManager.settings.retrospectiveCorrection { + case .standardRetrospectiveCorrection: + rcFlag = "** S" + case .integralRetrospectiveCorrection: + rcFlag = "++ I" + } + let retro = String( format: NSLocalizedString("Predicted: %1$@\nActual: %2$@ (%3$@)", comment: "Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference)"), values[0], values[1], values[2] ) // Standard retrospective correction - subtitleText = String(format: "%@\n%@", subtitleText, retro) + subtitleText = String(format: "%@\n%@\n%@", subtitleText, retro, rcFlag) } cell.subtitleLabel?.text = subtitleText diff --git a/Loop/en.lproj/Localizable.strings b/Loop/en.lproj/Localizable.strings index 78907ea3de..c5593fa0a7 100644 --- a/Loop/en.lproj/Localizable.strings +++ b/Loop/en.lproj/Localizable.strings @@ -246,6 +246,9 @@ /* The title text for the issue report cell */ "Issue Report" = "Issue Report"; +/* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ +"prediction-description-retrospective-correction" = "Predicted: %1$@\nActual: %2$@ (%3$@)"; + /* Glucose HUD accessibility hint */ "Launches CGM app" = "Launches CGM app"; From fb7822b8407fcf1b5f0294eb3ba6165a1bece0ed Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sun, 19 Sep 2021 14:23:26 -0600 Subject: [PATCH 07/31] add irc file --- Common/Extensions/GlucoseRangeSchedule.swift | 3 + Loop.xcodeproj/project.pbxproj | 4 + .../IntegralRetrospectiveCorrection.swift | 226 ++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 Loop/Models/RetrospectiveCorrection/IntegralRetrospectiveCorrection.swift diff --git a/Common/Extensions/GlucoseRangeSchedule.swift b/Common/Extensions/GlucoseRangeSchedule.swift index c8ef74fedb..ccc75b0b8c 100644 --- a/Common/Extensions/GlucoseRangeSchedule.swift +++ b/Common/Extensions/GlucoseRangeSchedule.swift @@ -13,6 +13,9 @@ extension GlucoseRangeSchedule { func minQuantity(at date: Date) -> HKQuantity { return HKQuantity(unit: unit, doubleValue: value(at: date).minValue) } + func maxQuantity(at date: Date) -> HKQuantity { + return HKQuantity(unit: unit, doubleValue: value(at: date).maxValue) + } } diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 7911221f6c..5e9ffc966f 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -331,6 +331,7 @@ 89F9119424358E4500ECCAF3 /* CarbAbsorptionTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9119324358E4500ECCAF3 /* CarbAbsorptionTime.swift */; }; 89F9119624358E6900ECCAF3 /* BolusPickerValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9119524358E6900ECCAF3 /* BolusPickerValues.swift */; }; 89FE21AD24AC57E30033F501 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21AC24AC57E30033F501 /* Collection.swift */; }; + 9E1B653A26F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1B653926F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift */; }; 9E9C350626F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */; }; A90EF53C25DEF06200F32D61 /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* PluginManager.swift */; }; A90EF54425DEF0A000F32D61 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; @@ -1319,6 +1320,7 @@ A900531B28D608CA000BC15B /* Cancel Override.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cancel Override.shortcut"; sourceTree = ""; }; A900531C28D6090D000BC15B /* Loop Remote Overrides.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop Remote Overrides.shortcut"; sourceTree = ""; }; A91D2A3E26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconTitleSubtitleTableViewCell.swift; sourceTree = ""; }; + 9E1B653926F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrection.swift; sourceTree = ""; }; 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrospectiveCorrectionSelectionView.swift; sourceTree = ""; }; A91E4C2024F867A700BE9213 /* StoredAlertTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredAlertTests.swift; sourceTree = ""; }; A91E4C2224F86F1000BE9213 /* CriticalEventLogExportManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CriticalEventLogExportManagerTests.swift; sourceTree = ""; }; @@ -2028,6 +2030,7 @@ children = ( 43511CDF21FD80E400566C63 /* RetrospectiveCorrection.swift */, 43511CE021FD80E400566C63 /* StandardRetrospectiveCorrection.swift */, + 9E1B653926F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift */, ); path = RetrospectiveCorrection; sourceTree = ""; @@ -3860,6 +3863,7 @@ A9CBE45A248ACBE1008E7BA2 /* DosingDecisionStore+SimulatedCoreData.swift in Sources */, A9C62D8A2331703100535612 /* ServicesManager.swift in Sources */, 43DBF0531C93EC8200B3C386 /* DeviceDataManager.swift in Sources */, + 9E1B653A26F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift in Sources */, A9347F2F24E7508A00C99C34 /* WatchHistoricalCarbs.swift in Sources */, A9B996F027235191002DC09C /* LoopWarning.swift in Sources */, C17824A01E19CF9800D9D25C /* GlucoseThresholdTableViewController.swift in Sources */, diff --git a/Loop/Models/RetrospectiveCorrection/IntegralRetrospectiveCorrection.swift b/Loop/Models/RetrospectiveCorrection/IntegralRetrospectiveCorrection.swift new file mode 100644 index 0000000000..e6ece1c7a5 --- /dev/null +++ b/Loop/Models/RetrospectiveCorrection/IntegralRetrospectiveCorrection.swift @@ -0,0 +1,226 @@ +// +// IntegralRetrospectiveCorrection.swift +// Loop +// +// Created by Dragan Maksimovic on 9/19/21. +// Copyright © 2021 LoopKit Authors. All rights reserved. +// + +import Foundation +import HealthKit +import LoopKit +import LoopCore + +/** + Integral Retrospective Correction (IRC) calculates a correction effect in glucose prediction based on a timeline of past discrepancies between observed glucose movement and movement expected based on insulin and carb models. Integral retrospective correction acts as a proportional-integral-differential (PID) controller aimed at reducing modeling errors in glucose prediction. + + In the above summary, "discrepancy" is a difference between the actual glucose and the model predicted glucose over retrospective correction grouping interval (set to 30 min in LoopSettings), whereas "past discrepancies" refers to a timeline of discrepancies computed over retrospective correction integration interval (set to 180 min in Loop Settings). + + */ +class IntegralRetrospectiveCorrection: RetrospectiveCorrection { + let retrospectionInterval = TimeInterval(minutes: 180) + + /// RetrospectiveCorrection protocol variables + /// Standard effect duration + let effectDuration: TimeInterval + /// Overall retrospective correction effect + var totalGlucoseCorrectionEffect: HKQuantity? + + /** + Integral retrospective correction parameters: + - currentDiscrepancyGain: Standard retrospective correction gain + - persistentDiscrepancyGain: Gain for persistent long-term modeling errors, must be greater than or equal to currentDiscrepancyGain + - correctionTimeConstant: How fast integral effect accumulates in response to persistent errors + - differentialGain: Differential effect gain + - delta: Glucose sampling time interval (5 min) + - maximumCorrectionEffectDuration: Maximum duration of the correction effect in glucose prediction + - retrospectiveCorrectionIntegrationInterval: Maximum duration over which to integrate retrospective correction changes + */ + static let currentDiscrepancyGain: Double = 1.0 + static let persistentDiscrepancyGain: Double = 2.0 // was 5.0 + static let correctionTimeConstant: TimeInterval = TimeInterval(minutes: 60.0) // was 90.0 + static let differentialGain: Double = 2.0 + static let delta: TimeInterval = TimeInterval(minutes: 5.0) + static let maximumCorrectionEffectDuration: TimeInterval = TimeInterval(minutes: 180.0) // was 240.0 + + /// Initialize computed integral retrospective correction parameters + static let integralForget: Double = exp( -delta.minutes / correctionTimeConstant.minutes ) + static let integralGain: Double = ((1 - integralForget) / integralForget) * + (persistentDiscrepancyGain - currentDiscrepancyGain) + static let proportionalGain: Double = currentDiscrepancyGain - integralGain + + /// All math is performed with glucose expressed in mg/dL + private let unit = HKUnit.milligramsPerDeciliter + + /// State variables reported in diagnostic issue report + var recentDiscrepancyValues: [Double] = [] + var integralCorrectionEffectDuration: TimeInterval? + var proportionalCorrection: Double = 0.0 + var integralCorrection: Double = 0.0 + var differentialCorrection: Double = 0.0 + var currentDate: Date = Date() + var ircStatus: String = "-" + + /** + Initialize integral retrospective correction settings based on current values of user settings + + - Parameters: + - settings: User settings + - insulinSensitivity: User insulin sensitivity schedule + - basalRates: User basal rate schedule + + - Returns: Integral Retrospective Correction customized with controller parameters and user settings + */ + init(effectDuration: TimeInterval) { + self.effectDuration = effectDuration + } + + /** + Calculates overall correction effect based on timeline of discrepancies, and updates glucoseCorrectionEffect + + - Parameters: + - glucose: Most recent glucose + - retrospectiveGlucoseDiscrepanciesSummed: Timeline of past discepancies + + - Returns: + - totalRetrospectiveCorrection: Overall glucose effect + */ + func computeEffect( + startingAt startingGlucose: GlucoseValue, + retrospectiveGlucoseDiscrepanciesSummed: [GlucoseChange]?, + recencyInterval: TimeInterval, + insulinSensitivitySchedule: InsulinSensitivitySchedule?, + basalRateSchedule: BasalRateSchedule?, + glucoseCorrectionRangeSchedule: GlucoseRangeSchedule?, + retrospectiveCorrectionGroupingInterval: TimeInterval + ) -> [GlucoseEffect] { + + // Loop settings relevant for calculation of effect limits + // let settings = UserDefaults.appGroup?.loopSettings ?? LoopSettings() + currentDate = Date() + + // Last discrepancy should be recent, otherwise clear the effect and return + let glucoseDate = startingGlucose.startDate + var glucoseCorrectionEffect: [GlucoseEffect] = [] + guard let currentDiscrepancy = retrospectiveGlucoseDiscrepanciesSummed?.last, + glucoseDate.timeIntervalSince(currentDiscrepancy.endDate) <= recencyInterval + else { + ircStatus = "discrepancy not available, effect not computed." + totalGlucoseCorrectionEffect = nil + return( [] ) + } + + // Default values if we are not able to calculate integral retrospective correction + ircStatus = "defaulted to standard RC, past discrepancies or user settings not available." + let currentDiscrepancyValue = currentDiscrepancy.quantity.doubleValue(for: unit) + var scaledCorrection = currentDiscrepancyValue + totalGlucoseCorrectionEffect = HKQuantity(unit: unit, doubleValue: currentDiscrepancyValue) + integralCorrectionEffectDuration = effectDuration + + // Calculate integral retrospective correction if past discrepancies over integration interval are available and if user settings are available + if let pastDiscrepancies = retrospectiveGlucoseDiscrepanciesSummed?.filterDateRange(glucoseDate.addingTimeInterval(-retrospectionInterval), glucoseDate), + let sensitivity = insulinSensitivitySchedule, + let basals = basalRateSchedule, + let glucoseCorrectionRangeSchedule = glucoseCorrectionRangeSchedule { + + ircStatus = "effect computed successfully." + + // To reduce response delay, integral retrospective correction is computed over an array of recent contiguous discrepancy values having the same sign as the latest discrepancy value + recentDiscrepancyValues = [] + var nextDiscrepancy = currentDiscrepancy + let currentDiscrepancySign = currentDiscrepancy.quantity.doubleValue(for: unit).sign + for pastDiscrepancy in pastDiscrepancies.reversed() { + let pastDiscrepancyValue = pastDiscrepancy.quantity.doubleValue(for: unit) + if (pastDiscrepancyValue.sign == currentDiscrepancySign && + nextDiscrepancy.endDate.timeIntervalSince(pastDiscrepancy.endDate) + <= recencyInterval && abs(pastDiscrepancyValue) >= 0.1) + { + recentDiscrepancyValues.append(pastDiscrepancyValue) + nextDiscrepancy = pastDiscrepancy + } else { + break + } + } + recentDiscrepancyValues = recentDiscrepancyValues.reversed() + + let currentSensitivity = sensitivity.quantity(at: glucoseDate).doubleValue(for: unit) + let currentBasalRate = basals.value(at: glucoseDate) + let correctionRangeMin = glucoseCorrectionRangeSchedule.minQuantity(at: glucoseDate).doubleValue(for: unit) + let correctionRangeMax = glucoseCorrectionRangeSchedule.maxQuantity(at: glucoseDate).doubleValue(for: unit) + let latestGlucoseValue = startingGlucose.quantity.doubleValue(for: unit) // most recent glucose + + // Safety limit for (+) integral effect. The limit is set to a larger value if the current blood glucose is further away from the correction range because we have more time available for corrections + let glucoseError = latestGlucoseValue - correctionRangeMax + let zeroTempEffect = abs(currentSensitivity * currentBasalRate) + let integralEffectPositiveLimit = min(max(glucoseError, 1.0 * zeroTempEffect), 4.0 * zeroTempEffect) + + // Limit for (-) integral effect: glucose prediction reduced by no more than 10 mg/dL below the correction range minimum + let integralEffectNegativeLimit = -max(10.0, latestGlucoseValue - correctionRangeMin) + + // Integral effect math + integralCorrection = 0.0 + var integralCorrectionEffectMinutes = effectDuration.minutes - 2.0 * IntegralRetrospectiveCorrection.delta.minutes + for discrepancy in recentDiscrepancyValues { + integralCorrection = + IntegralRetrospectiveCorrection.integralForget * integralCorrection + + IntegralRetrospectiveCorrection.integralGain * discrepancy + integralCorrectionEffectMinutes += 2.0 * IntegralRetrospectiveCorrection.delta.minutes + } + // Limits applied to integral correction effect and effect duration + integralCorrection = min(max(integralCorrection, integralEffectNegativeLimit), integralEffectPositiveLimit) + integralCorrectionEffectMinutes = min(integralCorrectionEffectMinutes, IntegralRetrospectiveCorrection.maximumCorrectionEffectDuration.minutes) + + // Differential effect math + var differentialDiscrepancy: Double = 0.0 + if recentDiscrepancyValues.count > 1 { + let previousDiscrepancyValue = recentDiscrepancyValues[recentDiscrepancyValues.count - 2] + differentialDiscrepancy = currentDiscrepancyValue - previousDiscrepancyValue + } + + // Overall glucose effect calculated as a sum of propotional, integral and differential effects + proportionalCorrection = IntegralRetrospectiveCorrection.proportionalGain * currentDiscrepancyValue + differentialCorrection = IntegralRetrospectiveCorrection.differentialGain * differentialDiscrepancy + let totalCorrection = proportionalCorrection + integralCorrection + differentialCorrection + totalGlucoseCorrectionEffect = HKQuantity(unit: unit, doubleValue: totalCorrection) + integralCorrectionEffectDuration = TimeInterval(minutes: integralCorrectionEffectMinutes) + + // correction value scaled to account for extended effect duration + scaledCorrection = totalCorrection * effectDuration.minutes / integralCorrectionEffectDuration!.minutes + } + + let retrospectionTimeInterval = currentDiscrepancy.endDate.timeIntervalSince(currentDiscrepancy.startDate) + let discrepancyTime = max(retrospectionTimeInterval, retrospectiveCorrectionGroupingInterval) + let velocity = HKQuantity(unit: unit.unitDivided(by: .second()), doubleValue: scaledCorrection / discrepancyTime) + + // Update array of glucose correction effects + glucoseCorrectionEffect = startingGlucose.decayEffect(atRate: velocity, for: integralCorrectionEffectDuration!) + + // Return glucose correction effects + return( glucoseCorrectionEffect ) + } + + var debugDescription: String { + let report: [String] = [ + "## IntegralRetrospectiveCorrection", + "", + "Last updated: \(currentDate)", + "Status: \(ircStatus)", + "currentDiscrepancyGain: \(IntegralRetrospectiveCorrection.currentDiscrepancyGain)", + "persistentDiscrepancyGain: \(IntegralRetrospectiveCorrection.persistentDiscrepancyGain)", + "correctionTimeConstant [min]: \(IntegralRetrospectiveCorrection.correctionTimeConstant.minutes)", + "proportionalGain: \(IntegralRetrospectiveCorrection.proportionalGain)", + "integralForget: \(IntegralRetrospectiveCorrection.integralForget)", + "integralGain: \(IntegralRetrospectiveCorrection.integralGain)", + "differentialGain: \(IntegralRetrospectiveCorrection.differentialGain)", + "Integration performed over \(recentDiscrepancyValues.count) most recent discrepancies having the same sign as the latest discrepancy value. Earliest-to-most-recent recentDiscrepancyValues [mg/dL]: \(recentDiscrepancyValues)", + "proportionalCorrection [mg/dL]: \(proportionalCorrection)", + "integralCorrection [mg/dL]: \(integralCorrection)", + "differentialCorrection [mg/dL]: \(differentialCorrection)", + "totalGlucoseCorrectionEffect: \(String(describing: totalGlucoseCorrectionEffect))", + "integralCorrectionEffectDuration [min]: \(String(describing: integralCorrectionEffectDuration?.minutes))" + ] + + return report.joined(separator: "\n") + } + +} From e171aeeb28983dea66043fb8a02c4cd50bd58354 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sun, 19 Sep 2021 16:16:04 -0600 Subject: [PATCH 08/31] enable irc --- Loop/Managers/AnalyticsServicesManager.swift | 4 +++ Loop/Managers/LoopDataManager.swift | 2 +- Loop/Models/LoopSettings+Loop.swift | 4 --- .../PredictionTableViewController.swift | 30 ++++++++++++------- ...RetrospectiveCorrectionSelectionView.swift | 4 +-- Loop/en.lproj/Localizable.strings | 3 ++ 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/Loop/Managers/AnalyticsServicesManager.swift b/Loop/Managers/AnalyticsServicesManager.swift index 682407ac43..ff0de5fa31 100644 --- a/Loop/Managers/AnalyticsServicesManager.swift +++ b/Loop/Managers/AnalyticsServicesManager.swift @@ -132,6 +132,10 @@ final class AnalyticsServicesManager { if newValue.dosingEnabled != oldValue.dosingEnabled { logEvent("Closed loop enabled change") } + + if newValue.retrospectiveCorrection != oldValue.retrospectiveCorrection { + logEvent("Retrospective correction change") + } if newValue.basalRateSchedule?.timeZone != oldValue.basalRateSchedule?.timeZone { logEvent("Therapy schedule time zone change") diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index c8d9ea5ced..d46e8e9f33 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -129,7 +129,7 @@ final class LoopDataManager { case .standardRetrospectiveCorrection: retrospectiveCorrection = StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) case .integralRetrospectiveCorrection: - retrospectiveCorrection = StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) + retrospectiveCorrection = IntegralRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) } overrideIntentObserver = UserDefaults.appGroup?.observe(\.intentExtensionOverrideToSet, options: [.new], changeHandler: {[weak self] (defaults, change) in diff --git a/Loop/Models/LoopSettings+Loop.swift b/Loop/Models/LoopSettings+Loop.swift index 6cde81d506..fd35b6416b 100644 --- a/Loop/Models/LoopSettings+Loop.swift +++ b/Loop/Models/LoopSettings+Loop.swift @@ -20,8 +20,4 @@ extension LoopSettings { static let retrospectiveCorrectionEffectDuration = TimeInterval(hours: 1) - /// Creates an instance of the enabled retrospective correction implementation - // var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection { - // return StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) - //} } diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index 4f02fda3df..616c0aa29b 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -272,21 +272,29 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable formatter.numberFormatter.positivePrefix = formatter.numberFormatter.plusSign values.append(formatter.string(from: lastDiscrepancy.quantity, for: glucoseChart.glucoseUnit) ?? "?") - let rcFlag: String - switch deviceManager.settings.retrospectiveCorrection { - case .standardRetrospectiveCorrection: - rcFlag = "** S" - case .integralRetrospectiveCorrection: - rcFlag = "++ I" - } - let retro = String( format: NSLocalizedString("Predicted: %1$@\nActual: %2$@ (%3$@)", comment: "Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference)"), values[0], values[1], values[2] ) - - // Standard retrospective correction - subtitleText = String(format: "%@\n%@\n%@", subtitleText, retro, rcFlag) + switch deviceManager.settings.retrospectiveCorrection { + case .standardRetrospectiveCorrection: + subtitleText = String(format: "%@\n%@", subtitleText, retro) + case .integralRetrospectiveCorrection: + var integralEffectDisplay = "?" + var totalEffectDisplay = "?" + if let totalEffect = self.totalRetrospectiveCorrection { + let integralEffectValue = totalEffect.doubleValue(for: glucoseChart.glucoseUnit) - lastDiscrepancy.quantity.doubleValue(for: glucoseChart.glucoseUnit) + let integralEffect = HKQuantity(unit: glucoseChart.glucoseUnit, doubleValue: integralEffectValue) + integralEffectDisplay = formatter.string(from: integralEffect, for: glucoseChart.glucoseUnit) ?? "?" + totalEffectDisplay = formatter.string(from: totalEffect, for: glucoseChart.glucoseUnit) ?? "?" + } + let integralRetro = String( + format: NSLocalizedString("prediction-description-integral-retrospective-correction", comment: "Format string describing integral retrospective correction. (1: Integral glucose effect)(2: Total glucose effect)"), + integralEffectDisplay, totalEffectDisplay + ) + subtitleText = String(format: "%@\n%@", retro, integralRetro) + } + } cell.subtitleLabel?.text = subtitleText diff --git a/Loop/Views/RetrospectiveCorrectionSelectionView.swift b/Loop/Views/RetrospectiveCorrectionSelectionView.swift index 1616c43c5f..89303a2ed7 100644 --- a/Loop/Views/RetrospectiveCorrectionSelectionView.swift +++ b/Loop/Views/RetrospectiveCorrectionSelectionView.swift @@ -55,9 +55,9 @@ extension RetrospectiveCorrectionOptions { var informationalText: String { switch self { case .standardRetrospectiveCorrection: - return NSLocalizedString("Discrepancy between modeled and observed glucose over the past 30 min extended over the next 60 min", comment: "Description string for standard retrospective correction") + return NSLocalizedString("Correcton to glucose forecast based on the most recent 30 min comparison of glucose prediction vs actual, continued with decay over 60 min.", comment: "Description string for standard retrospective correction") case .integralRetrospectiveCorrection: - return NSLocalizedString("Retrospective correction extended over extended time interval based on past oberved discrepancies between modeled and observed glucose", comment: "Description string for integral retrospective correction") + return NSLocalizedString("Correction to glucose forecast based on the most recent and past 30 min discrepancies between glucose prediction vs actual. Compared to standard retrospective correction, integral retrospective correction results in increased insulin corrections when glucose persistently drops slower than expected, and in reduced insulin delivery when glucose persistently drops faster than expected.", comment: "Description string for integral retrospective correction") } } diff --git a/Loop/en.lproj/Localizable.strings b/Loop/en.lproj/Localizable.strings index c5593fa0a7..54d0ee5724 100644 --- a/Loop/en.lproj/Localizable.strings +++ b/Loop/en.lproj/Localizable.strings @@ -249,6 +249,9 @@ /* Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference) */ "prediction-description-retrospective-correction" = "Predicted: %1$@\nActual: %2$@ (%3$@)"; +/* Format string describing integral retrospective correction. (1: Integral glucose effect)(2: Total glucose effect) */ +"prediction-description-integral-retrospective-correction" = "Integral effect: %1$@\nTotal glucose effect: %2$@"; + /* Glucose HUD accessibility hint */ "Launches CGM app" = "Launches CGM app"; From e1041504ccc2cb574bbfd45869ea00e8dbcc0f82 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sat, 2 Oct 2021 14:15:56 -0600 Subject: [PATCH 09/31] correct report of discrepancies --- Loop/Managers/LoopDataManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index d46e8e9f33..454fbc3617 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -2113,7 +2113,7 @@ extension LoopDataManager { "retrospectiveGlucoseDiscrepancies: [", "* GlucoseEffect(start, mg/dL)", - (state.retrospectiveGlucoseDiscrepancies ?? []).reduce(into: "", { (entries, entry) in + (manager.retrospectiveGlucoseDiscrepancies ?? []).reduce(into: "", { (entries, entry) in entries.append("* \(entry.startDate), \(entry.quantity.doubleValue(for: .milligramsPerDeciliter))\n") }), "]", From 6f7d7742cd48f766335fd80c2e045e92e9a7629a Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sun, 14 May 2023 21:42:05 -0600 Subject: [PATCH 10/31] watch updates --- .../Complication - WatchApp.xcscheme | 127 +++++++++++++++++ .../Notification - WatchApp.xcscheme | 129 ++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme create mode 100644 Loop.xcodeproj/xcshareddata/xcschemes/Notification - WatchApp.xcscheme diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme new file mode 100644 index 0000000000..0e7664a95f --- /dev/null +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Notification - WatchApp.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Notification - WatchApp.xcscheme new file mode 100644 index 0000000000..85323bccce --- /dev/null +++ b/Loop.xcodeproj/xcshareddata/xcschemes/Notification - WatchApp.xcscheme @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fc7441ead3c051f4d2558324962987c01f01c799 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Mon, 15 May 2023 18:34:21 -0600 Subject: [PATCH 11/31] sign --- Loop.xcodeproj/project.pbxproj | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 5e9ffc966f..d9806f2e21 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -1316,12 +1316,12 @@ 89F9119324358E4500ECCAF3 /* CarbAbsorptionTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarbAbsorptionTime.swift; sourceTree = ""; }; 89F9119524358E6900ECCAF3 /* BolusPickerValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusPickerValues.swift; sourceTree = ""; }; 89FE21AC24AC57E30033F501 /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; + 9E1B653926F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrection.swift; sourceTree = ""; }; + 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrospectiveCorrectionSelectionView.swift; sourceTree = ""; }; A900531A28D60862000BC15B /* Loop.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop.shortcut; sourceTree = ""; }; A900531B28D608CA000BC15B /* Cancel Override.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cancel Override.shortcut"; sourceTree = ""; }; A900531C28D6090D000BC15B /* Loop Remote Overrides.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop Remote Overrides.shortcut"; sourceTree = ""; }; A91D2A3E26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconTitleSubtitleTableViewCell.swift; sourceTree = ""; }; - 9E1B653926F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrection.swift; sourceTree = ""; }; - 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrospectiveCorrectionSelectionView.swift; sourceTree = ""; }; A91E4C2024F867A700BE9213 /* StoredAlertTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredAlertTests.swift; sourceTree = ""; }; A91E4C2224F86F1000BE9213 /* CriticalEventLogExportManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CriticalEventLogExportManagerTests.swift; sourceTree = ""; }; A9347F2E24E7508A00C99C34 /* WatchHistoricalCarbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchHistoricalCarbs.swift; sourceTree = ""; }; @@ -5037,7 +5037,7 @@ CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 101; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; GCC_DYNAMIC_NO_PIC = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GENERATE_INFOPLIST_FILE = NO; @@ -5083,7 +5083,7 @@ CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 101; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = StatusWidget/Info.plist; @@ -5340,7 +5340,7 @@ CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5368,7 +5368,7 @@ CODE_SIGN_ENTITLEMENTS = "$(LOOP_ENTITLEMENTS)"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5393,7 +5393,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; FRAMEWORK_SEARCH_PATHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( @@ -5420,7 +5420,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; FRAMEWORK_SEARCH_PATHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( @@ -5447,7 +5447,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; FRAMEWORK_SEARCH_PATHS = ""; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -5471,7 +5471,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; + DEVELOPMENT_TEAM = L6S34EF9DC; FRAMEWORK_SEARCH_PATHS = ""; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; From 434ebe264ded2d27b33b007435bd0616c16d0232 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Tue, 16 May 2023 06:49:03 -0600 Subject: [PATCH 12/31] merge fixes --- Loop/View Controllers/StatusTableViewController.swift | 6 ++++-- Loop/View Models/SettingsViewModel.swift | 1 + Loop/Views/SettingsView.swift | 8 +++++--- WatchApp/DerivedAssets.xcassets/Contents.json | 6 +++--- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 7b477c6a65..17a6a2c7bf 100644 --- a/Loop/View Controllers/StatusTableViewController.swift +++ b/Loop/View Controllers/StatusTableViewController.swift @@ -1547,10 +1547,10 @@ final class StatusTableViewController: LoopChartsTableViewController { initialDosingEnabled: deviceManager.loopManager.settings.dosingEnabled, isClosedLoopAllowed: automaticDosingStatus.$isAutomaticDosingAllowed, automaticDosingStrategy: deviceManager.loopManager.settings.automaticDosingStrategy, + retrospectiveCorrection: deviceManager.loopManager.settings.retrospectiveCorrection, availableSupports: supportManager.availableSupports, isOnboardingComplete: onboardingManager.isComplete, therapySettingsViewModelDelegate: deviceManager, - retrospectiveCorrection: deviceManager.loopManager.settings.retrospectiveCorrection, delegate: self) let hostingController = DismissibleHostingController( rootView: SettingsView(viewModel: viewModel, localizedAppNameAndVersion: supportManager.localizedAppNameAndVersion) @@ -2135,7 +2135,9 @@ extension StatusTableViewController: SettingsViewModelDelegate { } func retrospectiveCorrectionChanged(_ retrospectiveCorrection: RetrospectiveCorrectionOptions) { - self.deviceManager.loopManager.settings.retrospectiveCorrection = retrospectiveCorrection + self.deviceManager.loopManager.mutateSettings { settings in + settings.retrospectiveCorrection = retrospectiveCorrection + } } func dosingEnabledChanged(_ value: Bool) { diff --git a/Loop/View Models/SettingsViewModel.swift b/Loop/View Models/SettingsViewModel.swift index 490dba491b..e95d9f87ab 100644 --- a/Loop/View Models/SettingsViewModel.swift +++ b/Loop/View Models/SettingsViewModel.swift @@ -188,6 +188,7 @@ extension SettingsViewModel { initialDosingEnabled: true, isClosedLoopAllowed: FakeClosedLoopAllowedPublisher().$mockIsClosedLoopAllowed, automaticDosingStrategy: .automaticBolus, + retrospectiveCorrection: .standardRetrospectiveCorrection, availableSupports: [], isOnboardingComplete: false, therapySettingsViewModelDelegate: nil, diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 7ab8c9a3d4..5843575050 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -88,9 +88,11 @@ public struct SettingsView: View { if viewModel.servicesViewModel.showServices { servicesSection } - supportSection - if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled { - profileExpirationSection(profileExpiration: profileExpiration) + Group { + supportSection + if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled { + profileExpirationSection(profileExpiration: profileExpiration) + } } } .insetGroupedListStyle() diff --git a/WatchApp/DerivedAssets.xcassets/Contents.json b/WatchApp/DerivedAssets.xcassets/Contents.json index 73c00596a7..da4a164c91 100644 --- a/WatchApp/DerivedAssets.xcassets/Contents.json +++ b/WatchApp/DerivedAssets.xcassets/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "author" : "xcode", - "version" : 1 + "version" : 1, + "author" : "xcode" } -} +} \ No newline at end of file From 2344062d28e07d9afc0f8b0938ee49cca7cc87c1 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Tue, 16 May 2023 17:49:10 -0600 Subject: [PATCH 13/31] clean SettingsView --- Loop/Views/SettingsView.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 5843575050..4a2d88d2df 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -75,9 +75,12 @@ public struct SettingsView: View { } if FeatureFlags.automaticBolusEnabled { dosingStrategySection + if FeatureFlags.automaticBolusEnabled { + dosingStrategySection + } + alertManagementSection + retrospectiveCorrectionSection } - alertManagementSection - retrospectiveCorrectionSection if viewModel.pumpManagerSettingsViewModel.isSetUp() { configurationSection } @@ -88,11 +91,9 @@ public struct SettingsView: View { if viewModel.servicesViewModel.showServices { servicesSection } - Group { - supportSection - if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled { - profileExpirationSection(profileExpiration: profileExpiration) - } + supportSection + if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled { + profileExpirationSection(profileExpiration: profileExpiration) } } .insetGroupedListStyle() From 3d18c4c8380965b491e5cba3d2c9db42cb3ce9f2 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Wed, 17 May 2023 08:53:34 -0600 Subject: [PATCH 14/31] refactor enabled RC algo --- Loop/Managers/LoopDataManager.swift | 10 ++-------- Loop/Models/LoopSettings+Loop.swift | 12 ++++++++++++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 454fbc3617..0ac0357794 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -124,14 +124,8 @@ final class LoopDataManager { self.trustedTimeOffset = trustedTimeOffset /// Creates an instance of the enabled retrospective correction implementation - // retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm - switch settings.retrospectiveCorrection { - case .standardRetrospectiveCorrection: - retrospectiveCorrection = StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) - case .integralRetrospectiveCorrection: - retrospectiveCorrection = IntegralRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) - } - + retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm(retrospectiveCorrection: settings.retrospectiveCorrection) + overrideIntentObserver = UserDefaults.appGroup?.observe(\.intentExtensionOverrideToSet, options: [.new], changeHandler: {[weak self] (defaults, change) in guard let name = change.newValue??.lowercased(), let appGroup = UserDefaults.appGroup else { return diff --git a/Loop/Models/LoopSettings+Loop.swift b/Loop/Models/LoopSettings+Loop.swift index fd35b6416b..576aa89519 100644 --- a/Loop/Models/LoopSettings+Loop.swift +++ b/Loop/Models/LoopSettings+Loop.swift @@ -20,4 +20,16 @@ extension LoopSettings { static let retrospectiveCorrectionEffectDuration = TimeInterval(hours: 1) + /// Creates an instance of the enabled retrospective correction implementation + func enabledRetrospectiveCorrectionAlgorithm(retrospectiveCorrection: RetrospectiveCorrectionOptions) -> RetrospectiveCorrection { + var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection + switch retrospectiveCorrection { + case .standardRetrospectiveCorrection: + enabledRetrospectiveCorrectionAlgorithm = StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) + case .integralRetrospectiveCorrection: + enabledRetrospectiveCorrectionAlgorithm = IntegralRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) + } + return enabledRetrospectiveCorrectionAlgorithm + } + } From 2ab9cd8779c4425312a89dd40b086940ff77973d Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Wed, 17 May 2023 14:31:26 -0600 Subject: [PATCH 15/31] fix IRC description --- Loop/Views/RetrospectiveCorrectionSelectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Views/RetrospectiveCorrectionSelectionView.swift b/Loop/Views/RetrospectiveCorrectionSelectionView.swift index 89303a2ed7..d2cf6174fb 100644 --- a/Loop/Views/RetrospectiveCorrectionSelectionView.swift +++ b/Loop/Views/RetrospectiveCorrectionSelectionView.swift @@ -57,7 +57,7 @@ extension RetrospectiveCorrectionOptions { case .standardRetrospectiveCorrection: return NSLocalizedString("Correcton to glucose forecast based on the most recent 30 min comparison of glucose prediction vs actual, continued with decay over 60 min.", comment: "Description string for standard retrospective correction") case .integralRetrospectiveCorrection: - return NSLocalizedString("Correction to glucose forecast based on the most recent and past 30 min discrepancies between glucose prediction vs actual. Compared to standard retrospective correction, integral retrospective correction results in increased insulin corrections when glucose persistently drops slower than expected, and in reduced insulin delivery when glucose persistently drops faster than expected.", comment: "Description string for integral retrospective correction") + return NSLocalizedString("Correction to glucose forecast based on the history of discrepancies between glucose prediction based on carb and insuln data vs actual. Results in increased insulin corrections when glucose is persistently higher than expected, and in reduced insulin delivery when glucose is persistently lower than expected.", comment: "Description string for integral retrospective correction") } } From 603c1a0f1d0c3d2db0ab01876d3d78aae90ec1d6 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Wed, 17 May 2023 18:45:05 -0600 Subject: [PATCH 16/31] update RC algo upon settings change --- Loop/Managers/LoopDataManager.swift | 4 ++++ LoopCore/LoopSettings.swift | 2 ++ 2 files changed, 6 insertions(+) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index 0ac0357794..a8cb1d37ae 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -318,6 +318,10 @@ final class LoopDataManager { self.insulinEffect = nil } } + + if newValue.retrospectiveCorrection != oldValue.retrospectiveCorrection { + retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm(retrospectiveCorrection: settings.retrospectiveCorrection) + } notify(forChange: .preferences) analyticsServicesManager.didChangeLoopSettings(from: oldValue, to: newValue) diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 0dc6761299..3ff283d6cb 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -112,6 +112,7 @@ public struct LoopSettings: Equatable { maximumBolus: Double? = nil, suspendThreshold: GlucoseThreshold? = nil, automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly, + retrospectiveCorrection: RetrospectiveCorrectionOptions = .standardRetrospectiveCorrection, defaultRapidActingModel: ExponentialInsulinModelPreset? = nil ) { self.dosingEnabled = dosingEnabled @@ -128,6 +129,7 @@ public struct LoopSettings: Equatable { self.maximumBolus = maximumBolus self.suspendThreshold = suspendThreshold self.automaticDosingStrategy = automaticDosingStrategy + self.retrospectiveCorrection = retrospectiveCorrection self.defaultRapidActingModel = defaultRapidActingModel } } From f75ff7cd0c13f299eccebfee16977836df28ed2f Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Wed, 17 May 2023 19:13:30 -0600 Subject: [PATCH 17/31] default IRC --- LoopCore/LoopSettings.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 3ff283d6cb..1da7ab2d13 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -91,7 +91,7 @@ public struct LoopSettings: Equatable { public var defaultRapidActingModel: ExponentialInsulinModelPreset? - public var retrospectiveCorrection: RetrospectiveCorrectionOptions = .standardRetrospectiveCorrection + public var retrospectiveCorrection: RetrospectiveCorrectionOptions = .integralRetrospectiveCorrection public var glucoseUnit: HKUnit? { return glucoseTargetRangeSchedule?.unit @@ -112,7 +112,7 @@ public struct LoopSettings: Equatable { maximumBolus: Double? = nil, suspendThreshold: GlucoseThreshold? = nil, automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly, - retrospectiveCorrection: RetrospectiveCorrectionOptions = .standardRetrospectiveCorrection, + retrospectiveCorrection: RetrospectiveCorrectionOptions = .integralRetrospectiveCorrection, defaultRapidActingModel: ExponentialInsulinModelPreset? = nil ) { self.dosingEnabled = dosingEnabled From b94cdbef5696bf9eca7c411f2b7e7c86dccec186 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Sun, 21 May 2023 15:09:52 -0600 Subject: [PATCH 18/31] fix rebase to Loop dev --- Loop/View Models/SettingsViewModel.swift | 1 - Loop/Views/SettingsView.swift | 22 +--------------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/Loop/View Models/SettingsViewModel.swift b/Loop/View Models/SettingsViewModel.swift index e95d9f87ab..e339e78e61 100644 --- a/Loop/View Models/SettingsViewModel.swift +++ b/Loop/View Models/SettingsViewModel.swift @@ -139,7 +139,6 @@ public class SettingsViewModel: ObservableObject { self.isClosedLoopAllowed = false self.automaticDosingStrategy = automaticDosingStrategy self.retrospectiveCorrection = retrospectiveCorrection - self.supportInfoProvider = supportInfoProvider self.availableSupports = availableSupports self.isOnboardingComplete = isOnboardingComplete self.therapySettingsViewModelDelegate = therapySettingsViewModelDelegate diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 4a2d88d2df..03ed8e9964 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -51,6 +51,7 @@ public struct SettingsView: View { dosingStrategySection } alertManagementSection + retrospectiveCorrectionSection if viewModel.pumpManagerSettingsViewModel.isSetUp() { configurationSection } @@ -73,27 +74,6 @@ public struct SettingsView: View { if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled { profileExpirationSection(profileExpiration: profileExpiration) } - if FeatureFlags.automaticBolusEnabled { - dosingStrategySection - if FeatureFlags.automaticBolusEnabled { - dosingStrategySection - } - alertManagementSection - retrospectiveCorrectionSection - } - if viewModel.pumpManagerSettingsViewModel.isSetUp() { - configurationSection - } - deviceSettingsSection - if viewModel.pumpManagerSettingsViewModel.isTestingDevice || viewModel.cgmManagerSettingsViewModel.isTestingDevice { - deleteDataSection - } - if viewModel.servicesViewModel.showServices { - servicesSection - } - supportSection - if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled { - profileExpirationSection(profileExpiration: profileExpiration) } } .insetGroupedListStyle() From 3729d266d18651d6b0212ccf00b218ed1844833a Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 6 Jun 2023 17:27:10 -0700 Subject: [PATCH 19/31] Restore automatic signing --- Loop.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index d9806f2e21..7a4f3a4b8d 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -5037,7 +5037,7 @@ CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 101; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; GCC_DYNAMIC_NO_PIC = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GENERATE_INFOPLIST_FILE = NO; @@ -5083,7 +5083,7 @@ CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; CURRENT_PROJECT_VERSION = 101; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = StatusWidget/Info.plist; @@ -5340,7 +5340,7 @@ CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5368,7 +5368,7 @@ CODE_SIGN_ENTITLEMENTS = "$(LOOP_ENTITLEMENTS)"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Loop/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -5393,7 +5393,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; FRAMEWORK_SEARCH_PATHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( @@ -5420,7 +5420,7 @@ CODE_SIGN_ENTITLEMENTS = "WatchApp Extension/WatchApp Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; FRAMEWORK_SEARCH_PATHS = ""; INFOPLIST_FILE = "WatchApp Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( @@ -5447,7 +5447,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; FRAMEWORK_SEARCH_PATHS = ""; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -5471,7 +5471,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "$(APPICON_NAME)"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; FRAMEWORK_SEARCH_PATHS = ""; IBSC_MODULE = WatchApp_Extension; INFOPLIST_FILE = WatchApp/Info.plist; @@ -5653,7 +5653,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( @@ -5675,7 +5675,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Status Extension/Loop Status Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Loop Status Extension/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( @@ -5737,7 +5737,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Intent Extension/Loop Intent Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_DEBUG)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Loop Intent Extension/Info.plist"; @@ -5760,7 +5760,7 @@ CODE_SIGN_ENTITLEMENTS = "Loop Intent Extension/Loop Intent Extension.entitlements"; CODE_SIGN_IDENTITY = "$(LOOP_CODE_SIGN_IDENTITY_RELEASE)"; CODE_SIGN_STYLE = "$(LOOP_CODE_SIGN_STYLE)"; - DEVELOPMENT_TEAM = L6S34EF9DC; + DEVELOPMENT_TEAM = "$(LOOP_DEVELOPMENT_TEAM)"; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Loop Intent Extension/Info.plist"; From dadb4ef3c5c55034332ac6f4d0d4cac9aa05f48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 10 Jun 2023 13:55:24 +0200 Subject: [PATCH 20/31] Isolation of code and moved settings to Experimental Section --- Loop.xcodeproj/project.pbxproj | 12 ++-- ...ingsView+algorithmExperimentsSection.swift | 25 +++++++ Loop/Managers/AnalyticsServicesManager.swift | 4 -- Loop/Managers/LoopDataManager.swift | 13 ++-- Loop/Models/LoopSettings+Loop.swift | 13 ++-- .../PredictionTableViewController.swift | 9 +-- .../StatusTableViewController.swift | 7 -- Loop/View Models/SettingsViewModel.swift | 10 --- ...RetrospectiveCorrectionSelectionView.swift | 54 ++++++++++++++ ...RetrospectiveCorrectionSelectionView.swift | 70 ------------------- Loop/Views/SettingsView.swift | 38 ++-------- LoopCore/LoopSettings.swift | 26 ------- 12 files changed, 110 insertions(+), 171 deletions(-) create mode 100644 Loop/Extensions/SettingsView+algorithmExperimentsSection.swift create mode 100644 Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift delete mode 100644 Loop/Views/RetrospectiveCorrectionSelectionView.swift diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 7a4f3a4b8d..a27a450351 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -332,7 +332,6 @@ 89F9119624358E6900ECCAF3 /* BolusPickerValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9119524358E6900ECCAF3 /* BolusPickerValues.swift */; }; 89FE21AD24AC57E30033F501 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21AC24AC57E30033F501 /* Collection.swift */; }; 9E1B653A26F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E1B653926F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift */; }; - 9E9C350626F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */; }; A90EF53C25DEF06200F32D61 /* PluginManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C16DA84122E8E112008624C2 /* PluginManager.swift */; }; A90EF54425DEF0A000F32D61 /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4374B5EE209D84BE00D17AA8 /* OSLog.swift */; }; A91D2A3F26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A91D2A3E26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift */; }; @@ -518,6 +517,8 @@ 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 */; }; + DD3DBD272A33AEC8000F8B5B /* SettingsView+algorithmExperimentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3DBD262A33AEC8000F8B5B /* SettingsView+algorithmExperimentsSection.swift */; }; + DD3DBD292A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.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 */; }; @@ -1317,7 +1318,6 @@ 89F9119524358E6900ECCAF3 /* BolusPickerValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusPickerValues.swift; sourceTree = ""; }; 89FE21AC24AC57E30033F501 /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; 9E1B653926F7D14E00D5BD24 /* IntegralRetrospectiveCorrection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrection.swift; sourceTree = ""; }; - 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetrospectiveCorrectionSelectionView.swift; sourceTree = ""; }; A900531A28D60862000BC15B /* Loop.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = Loop.shortcut; sourceTree = ""; }; A900531B28D608CA000BC15B /* Cancel Override.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Cancel Override.shortcut"; sourceTree = ""; }; A900531C28D6090D000BC15B /* Loop Remote Overrides.shortcut */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Loop Remote Overrides.shortcut"; sourceTree = ""; }; @@ -1687,6 +1687,8 @@ 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 = ""; }; + DD3DBD262A33AEC8000F8B5B /* SettingsView+algorithmExperimentsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsView+algorithmExperimentsSection.swift"; sourceTree = ""; }; + DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrectionSelectionView.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 = ""; }; @@ -2351,6 +2353,7 @@ C13DA2AF24F6C7690098BB29 /* UIViewController.swift */, 430B29922041F5B200BA9F93 /* UserDefaults+Loop.swift */, A9B607AF247F000F00792BE4 /* UserNotifications+Loop.swift */, + DD3DBD262A33AEC8000F8B5B /* SettingsView+algorithmExperimentsSection.swift */, ); path = Extensions; sourceTree = ""; @@ -2400,7 +2403,7 @@ 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */, - 9E9C350526F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift */, + DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */, ); path = Views; sourceTree = ""; @@ -3876,9 +3879,9 @@ 892D7C5123B54A15008A9656 /* CarbEntryViewController.swift in Sources */, B4E202302661063E009421B5 /* AutomaticDosingStatus.swift in Sources */, C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, - 9E9C350626F69B2A0077F908 /* RetrospectiveCorrectionSelectionView.swift in Sources */, A977A2F424ACFECF0059C207 /* CriticalEventLogExportManager.swift in Sources */, 89CA2B32226C18B8004D9350 /* TestingScenariosTableViewController.swift in Sources */, + DD3DBD272A33AEC8000F8B5B /* SettingsView+algorithmExperimentsSection.swift in Sources */, 43E93FB71E469A5100EAB8DB /* HKUnit.swift in Sources */, 43C05CAF21EB2C24006FB252 /* NSBundle.swift in Sources */, A91D2A3F26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift in Sources */, @@ -3898,6 +3901,7 @@ C178249A1E1999FA00D9D25C /* CaseCountable.swift in Sources */, B4F3D25124AF890C0095CE44 /* BluetoothStateManager.swift in Sources */, 1DDE273D24AEA4B000796622 /* SettingsViewModel.swift in Sources */, + DD3DBD292A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift in Sources */, A9347F3124E7521800C99C34 /* CarbBackfillRequestUserInfo.swift in Sources */, A9CBE458248AB564008E7BA2 /* DoseStore+SimulatedCoreData.swift in Sources */, 897A5A9924C22DE800C4E71D /* BolusEntryViewModel.swift in Sources */, diff --git a/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift new file mode 100644 index 0000000000..b6f07ba186 --- /dev/null +++ b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift @@ -0,0 +1,25 @@ +// +// 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: IntegralRetrospectiveCorrectionSelectionView(isIntegralRetrospectiveCorrectionEnabled: $isIntegralRetrospectiveCorrectionEnabled)) { + HStack { + Text("Integral Retrospective Correction") + Spacer() + Text(isIntegralRetrospectiveCorrectionEnabled ? "On" : "Off") + } + } + } + } +} diff --git a/Loop/Managers/AnalyticsServicesManager.swift b/Loop/Managers/AnalyticsServicesManager.swift index ff0de5fa31..682407ac43 100644 --- a/Loop/Managers/AnalyticsServicesManager.swift +++ b/Loop/Managers/AnalyticsServicesManager.swift @@ -132,10 +132,6 @@ final class AnalyticsServicesManager { if newValue.dosingEnabled != oldValue.dosingEnabled { logEvent("Closed loop enabled change") } - - if newValue.retrospectiveCorrection != oldValue.retrospectiveCorrection { - logEvent("Retrospective correction change") - } if newValue.basalRateSchedule?.timeZone != oldValue.basalRateSchedule?.timeZone { logEvent("Therapy schedule time zone change") diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index a8cb1d37ae..15c593e77f 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -72,7 +72,7 @@ final class LoopDataManager { NotificationCenter.default.removeObserver(observer) } } - + init( lastLoopCompleted: Date?, basalDeliveryState: PumpManagerStatus.BasalDeliveryState?, @@ -123,9 +123,8 @@ final class LoopDataManager { self.trustedTimeOffset = trustedTimeOffset - /// Creates an instance of the enabled retrospective correction implementation - retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm(retrospectiveCorrection: settings.retrospectiveCorrection) - + retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm() + overrideIntentObserver = UserDefaults.appGroup?.observe(\.intentExtensionOverrideToSet, options: [.new], changeHandler: {[weak self] (defaults, change) in guard let name = change.newValue??.lowercased(), let appGroup = UserDefaults.appGroup else { return @@ -318,10 +317,6 @@ final class LoopDataManager { self.insulinEffect = nil } } - - if newValue.retrospectiveCorrection != oldValue.retrospectiveCorrection { - retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm(retrospectiveCorrection: settings.retrospectiveCorrection) - } notify(forChange: .preferences) analyticsServicesManager.didChangeLoopSettings(from: oldValue, to: newValue) @@ -2109,6 +2104,8 @@ extension LoopDataManager { }), "]", + "isExperimentalIntegralRetrospectiveCorrectionEnabled: \(UserDefaults.standard.bool(forKey: "isExperimentalIntegralRetrospectiveCorrectionEnabled"))", + "retrospectiveGlucoseDiscrepancies: [", "* GlucoseEffect(start, mg/dL)", (manager.retrospectiveGlucoseDiscrepancies ?? []).reduce(into: "", { (entries, entry) in diff --git a/Loop/Models/LoopSettings+Loop.swift b/Loop/Models/LoopSettings+Loop.swift index 576aa89519..f66cfc04d1 100644 --- a/Loop/Models/LoopSettings+Loop.swift +++ b/Loop/Models/LoopSettings+Loop.swift @@ -21,14 +21,17 @@ extension LoopSettings { static let retrospectiveCorrectionEffectDuration = TimeInterval(hours: 1) /// Creates an instance of the enabled retrospective correction implementation - func enabledRetrospectiveCorrectionAlgorithm(retrospectiveCorrection: RetrospectiveCorrectionOptions) -> RetrospectiveCorrection { + func enabledRetrospectiveCorrectionAlgorithm() -> RetrospectiveCorrection { var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection - switch retrospectiveCorrection { - case .standardRetrospectiveCorrection: - enabledRetrospectiveCorrectionAlgorithm = StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) - case .integralRetrospectiveCorrection: + + let isIntegralRetrospectiveCorrectionEnabled = UserDefaults.standard.bool(forKey: "isExperimentalIntegralRetrospectiveCorrectionEnabled") + + if isIntegralRetrospectiveCorrectionEnabled { enabledRetrospectiveCorrectionAlgorithm = IntegralRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) + } else { + enabledRetrospectiveCorrectionAlgorithm = StandardRetrospectiveCorrection(effectDuration: LoopSettings.retrospectiveCorrectionEffectDuration) } + return enabledRetrospectiveCorrectionAlgorithm } diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index 616c0aa29b..a0eea7618a 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -276,10 +276,9 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable format: NSLocalizedString("Predicted: %1$@\nActual: %2$@ (%3$@)", comment: "Format string describing retrospective glucose prediction comparison. (1: Predicted glucose)(2: Actual glucose)(3: difference)"), values[0], values[1], values[2] ) - switch deviceManager.settings.retrospectiveCorrection { - case .standardRetrospectiveCorrection: - subtitleText = String(format: "%@\n%@", subtitleText, retro) - case .integralRetrospectiveCorrection: + let isIntegralRetrospectiveCorrectionEnabled = UserDefaults.standard.bool(forKey: "isExperimentalIntegralRetrospectiveCorrectionEnabled") + + if isIntegralRetrospectiveCorrectionEnabled { var integralEffectDisplay = "?" var totalEffectDisplay = "?" if let totalEffect = self.totalRetrospectiveCorrection { @@ -293,6 +292,8 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable integralEffectDisplay, totalEffectDisplay ) subtitleText = String(format: "%@\n%@", retro, integralRetro) + } else { + subtitleText = String(format: "%@\n%@", subtitleText, retro) } } diff --git a/Loop/View Controllers/StatusTableViewController.swift b/Loop/View Controllers/StatusTableViewController.swift index 17a6a2c7bf..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, - retrospectiveCorrection: deviceManager.loopManager.settings.retrospectiveCorrection, availableSupports: supportManager.availableSupports, isOnboardingComplete: onboardingManager.isComplete, therapySettingsViewModelDelegate: deviceManager, @@ -2133,12 +2132,6 @@ extension StatusTableViewController: SettingsViewModelDelegate { var closedLoopDescriptiveText: String? { return deviceManager.closedLoopDisallowedLocalizedDescription } - - func retrospectiveCorrectionChanged(_ retrospectiveCorrection: RetrospectiveCorrectionOptions) { - self.deviceManager.loopManager.mutateSettings { settings in - settings.retrospectiveCorrection = retrospectiveCorrection - } - } func dosingEnabledChanged(_ value: Bool) { deviceManager.loopManager.mutateSettings { settings in diff --git a/Loop/View Models/SettingsViewModel.swift b/Loop/View Models/SettingsViewModel.swift index e339e78e61..3e3c20be24 100644 --- a/Loop/View Models/SettingsViewModel.swift +++ b/Loop/View Models/SettingsViewModel.swift @@ -53,7 +53,6 @@ public protocol SettingsViewModelDelegate: AnyObject { func dosingEnabledChanged(_: Bool) func dosingStrategyChanged(_: AutomaticDosingStrategy) func didTapIssueReport() - func retrospectiveCorrectionChanged(_: RetrospectiveCorrectionOptions) var closedLoopDescriptiveText: String? { get } } @@ -93,12 +92,6 @@ public class SettingsViewModel: ObservableObject { delegate?.dosingStrategyChanged(automaticDosingStrategy) } } - - @Published var retrospectiveCorrection: RetrospectiveCorrectionOptions { - didSet { - delegate?.retrospectiveCorrectionChanged(retrospectiveCorrection) - } - } var closedLoopPreference: Bool { didSet { @@ -120,7 +113,6 @@ public class SettingsViewModel: ObservableObject { initialDosingEnabled: Bool, isClosedLoopAllowed: Published.Publisher, automaticDosingStrategy: AutomaticDosingStrategy, - retrospectiveCorrection: RetrospectiveCorrectionOptions, availableSupports: [SupportUI], isOnboardingComplete: Bool, therapySettingsViewModelDelegate: TherapySettingsViewModelDelegate?, @@ -138,7 +130,6 @@ public class SettingsViewModel: ObservableObject { self.closedLoopPreference = initialDosingEnabled self.isClosedLoopAllowed = false self.automaticDosingStrategy = automaticDosingStrategy - self.retrospectiveCorrection = retrospectiveCorrection self.availableSupports = availableSupports self.isOnboardingComplete = isOnboardingComplete self.therapySettingsViewModelDelegate = therapySettingsViewModelDelegate @@ -187,7 +178,6 @@ extension SettingsViewModel { initialDosingEnabled: true, isClosedLoopAllowed: FakeClosedLoopAllowedPublisher().$mockIsClosedLoopAllowed, automaticDosingStrategy: .automaticBolus, - retrospectiveCorrection: .standardRetrospectiveCorrection, availableSupports: [], isOnboardingComplete: false, therapySettingsViewModelDelegate: nil, diff --git a/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift b/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift new file mode 100644 index 0000000000..b38cba6bf3 --- /dev/null +++ b/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift @@ -0,0 +1,54 @@ +// +// IntegralRetrospectiveCorrectionSelectionView.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 IntegralRetrospectiveCorrectionSelectionView: View { + @Binding var isIntegralRetrospectiveCorrectionEnabled: Bool + + public init(isIntegralRetrospectiveCorrectionEnabled: Binding) { + self._isIntegralRetrospectiveCorrectionEnabled = isIntegralRetrospectiveCorrectionEnabled + } + + public var body: some View { + List { + retrospectiveCorrectionSection + } + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + VStack { + Text("Integral") + .font(.headline) + Text("Retrospective Correction") + .font(.subheadline) + } + } + } + } + + private var retrospectiveCorrectionSection: some View { + VStack { + DescriptiveText(label: NSLocalizedString("Integral Retrospective Correction (IRC) is an advanced control technique applied to glucose forecasting based on the history of discrepancies between predicted and actual glucose levels. The predictions are made using carbohydrate and insulin data. When enabled, IRC adjusts insulin delivery in response to consistent patterns: it increases insulin delivery when glucose levels consistently measure higher than expected, and decreases it when glucose levels are consistently lower than expected. IRC uses a proportional-integral-differential (PID) controller that adjusts insulin recommendations based on immediate, accumulated, and rate of change discrepancies. This provides a more adaptive and responsive control compared to standard retrospective correction. However, it's important to know that the effectiveness of IRC will heavily depend on the accuracy of your insulin sensitivity, carbohydrate ratios, and basal rates settings. While IRC can improve glucose management in cases of consistent discrepancies, please note that it might potentially lead to more aggressive corrections.", comment: "Description of Integral Retrospective Correction toggle."), color: .black) + Section() { + Toggle(NSLocalizedString("Integral Retrospective Correction", comment: "Title for Integral Retrospective Correction toggle"), isOn: $isIntegralRetrospectiveCorrectionEnabled) + .onChange(of: isIntegralRetrospectiveCorrectionEnabled) { newValue in + UserDefaults.standard.set(newValue, forKey: "isExperimentalIntegralRetrospectiveCorrectionEnabled") + } + } + } + } +} + +struct EnhancedAutoBolusSelectionView_Previews: PreviewProvider { + static var previews: some View { + IntegralRetrospectiveCorrectionSelectionView(isIntegralRetrospectiveCorrectionEnabled: .constant(true)) + } +} diff --git a/Loop/Views/RetrospectiveCorrectionSelectionView.swift b/Loop/Views/RetrospectiveCorrectionSelectionView.swift deleted file mode 100644 index d2cf6174fb..0000000000 --- a/Loop/Views/RetrospectiveCorrectionSelectionView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// RetrospectiveCorrectionSelectionView.swift -// Loop -// -// Created by Dragan Maksimovic on 9/18/21. -// Copyright © 2021 LoopKit Authors. All rights reserved. -// - -import SwiftUI -import LoopCore -import LoopKitUI - -public struct RetrospectiveCorrectionSelectionView: View { - - @Binding private var retrospectiveCorrection: RetrospectiveCorrectionOptions - - @State private var internalRetrospectiveCorrection: RetrospectiveCorrectionOptions - - public init(retrospectiveCorrection: Binding) { - self._retrospectiveCorrection = retrospectiveCorrection - self._internalRetrospectiveCorrection = State(initialValue: retrospectiveCorrection.wrappedValue) - } - - public var body: some View { - List { - Section { - options - } - .buttonStyle(PlainButtonStyle()) // Disable row highlighting on selection - } - .insetGroupedListStyle() - } - - public var options: some View { - ForEach(RetrospectiveCorrectionOptions.allCases, id: \.self) { rcOption in - CheckmarkListItem( - title: Text(rcOption.title), - description: Text(rcOption.informationalText), - isSelected: Binding( - get: { self.retrospectiveCorrection == rcOption }, - set: { isSelected in - if isSelected { - self.retrospectiveCorrection = rcOption - self.internalRetrospectiveCorrection = rcOption // Hack to force update. :( - } - } - ) - ) - .padding(.vertical, 4) - } - } -} - -extension RetrospectiveCorrectionOptions { - var informationalText: String { - switch self { - case .standardRetrospectiveCorrection: - return NSLocalizedString("Correcton to glucose forecast based on the most recent 30 min comparison of glucose prediction vs actual, continued with decay over 60 min.", comment: "Description string for standard retrospective correction") - case .integralRetrospectiveCorrection: - return NSLocalizedString("Correction to glucose forecast based on the history of discrepancies between glucose prediction based on carb and insuln data vs actual. Results in increased insulin corrections when glucose is persistently higher than expected, and in reduced insulin delivery when glucose is persistently lower than expected.", comment: "Description string for integral retrospective correction") - } - } - -} - -struct RetrospectiveCorrectionSelectionView_Previews: PreviewProvider { - static var previews: some View { - RetrospectiveCorrectionSelectionView(retrospectiveCorrection: .constant(.standardRetrospectiveCorrection)) - } -} diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 03ed8e9964..7f9dd33fdf 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -30,7 +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 isIntegralRetrospectiveCorrectionEnabled = UserDefaults.standard.bool(forKey: "isExperimentalIntegralRetrospectiveCorrectionEnabled") + var localizedAppNameAndVersion: String public init(viewModel: SettingsViewModel, localizedAppNameAndVersion: String) { @@ -51,7 +52,9 @@ public struct SettingsView: View { dosingStrategySection } alertManagementSection - retrospectiveCorrectionSection + // Note: Experimental Section + algorithmExperimentsSection + // if viewModel.pumpManagerSettingsViewModel.isSetUp() { configurationSection } @@ -175,19 +178,6 @@ extension SettingsView { } } - private var retrospectiveCorrectionSection: some View { - Section(header: SectionHeader(label: NSLocalizedString("Retrospective Correction", comment: "The title of the Retrospective Correction section in settings"))) { - - NavigationLink(destination: RetrospectiveCorrectionSelectionView(retrospectiveCorrection: $viewModel.retrospectiveCorrection)) - { - HStack { - Text(viewModel.retrospectiveCorrection.title) - } - } - } - } - - private var alertManagementSection: some View { Section { NavigationLink(destination: AlertManagementView(checker: viewModel.alertPermissionsChecker, alertMuter: viewModel.alertMuter)) @@ -505,24 +495,6 @@ public struct SettingsView_Previews: PreviewProvider { public static var previews: some View { let displayGlucoseUnitObservable = DisplayGlucoseUnitObservable(displayGlucoseUnit: .milligramsPerDeciliter) let viewModel = SettingsViewModel.preview - /* DM check - let viewModel = SettingsViewModel(notificationsCriticalAlertPermissionsViewModel: NotificationsCriticalAlertPermissionsViewModel(), - pumpManagerSettingsViewModel: DeviceViewModel(), - cgmManagerSettingsViewModel: DeviceViewModel(), - servicesViewModel: servicesViewModel, - criticalEventLogExportViewModel: CriticalEventLogExportViewModel(exporterFactory: MockCriticalEventLogExporterFactory()), - therapySettings: { TherapySettings() }, - pumpSupportedIncrements: nil, - syncPumpSchedule: nil, - sensitivityOverridesEnabled: false, - initialDosingEnabled: true, - isClosedLoopAllowed: fakeClosedLoopAllowedPublisher.$mockIsClosedLoopAllowed, - supportInfoProvider: MockSupportInfoProvider(), - dosingStrategy: .automaticBolus, - retrospectiveCorrection: .standardRetrospectiveCorrection, - availableSupports: [], - delegate: nil) -*/ return Group { SettingsView(viewModel: viewModel, localizedAppNameAndVersion: "Loop Demo V1") .colorScheme(.light) diff --git a/LoopCore/LoopSettings.swift b/LoopCore/LoopSettings.swift index 1da7ab2d13..63418ad4aa 100644 --- a/LoopCore/LoopSettings.swift +++ b/LoopCore/LoopSettings.swift @@ -19,22 +19,6 @@ public extension AutomaticDosingStrategy { } } -public enum RetrospectiveCorrectionOptions: Int, CaseIterable { - case standardRetrospectiveCorrection - case integralRetrospectiveCorrection -} - -public extension RetrospectiveCorrectionOptions { - var title: String { - switch self { - case .standardRetrospectiveCorrection: - return NSLocalizedString("Standard Retrospective Correction", comment: "Title string for standard retrospective correction") - case .integralRetrospectiveCorrection: - return NSLocalizedString("Integral Retrospective Correction", comment: "Title string for integral retrospective correction") - } - } -} - public struct LoopSettings: Equatable { public var isScheduleOverrideInfiniteWorkout: Bool { guard let scheduleOverride = scheduleOverride else { return false } @@ -90,8 +74,6 @@ public struct LoopSettings: Equatable { public var automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly public var defaultRapidActingModel: ExponentialInsulinModelPreset? - - public var retrospectiveCorrection: RetrospectiveCorrectionOptions = .integralRetrospectiveCorrection public var glucoseUnit: HKUnit? { return glucoseTargetRangeSchedule?.unit @@ -112,7 +94,6 @@ public struct LoopSettings: Equatable { maximumBolus: Double? = nil, suspendThreshold: GlucoseThreshold? = nil, automaticDosingStrategy: AutomaticDosingStrategy = .tempBasalOnly, - retrospectiveCorrection: RetrospectiveCorrectionOptions = .integralRetrospectiveCorrection, defaultRapidActingModel: ExponentialInsulinModelPreset? = nil ) { self.dosingEnabled = dosingEnabled @@ -129,7 +110,6 @@ public struct LoopSettings: Equatable { self.maximumBolus = maximumBolus self.suspendThreshold = suspendThreshold self.automaticDosingStrategy = automaticDosingStrategy - self.retrospectiveCorrection = retrospectiveCorrection self.defaultRapidActingModel = defaultRapidActingModel } } @@ -297,11 +277,6 @@ extension LoopSettings: RawRepresentable { { self.automaticDosingStrategy = automaticDosingStrategy } - - if let rawRetrospectiveCorrection = rawValue["retrospectiveCorrection"] as? RetrospectiveCorrectionOptions.RawValue, - let retrospectiveCorrection = RetrospectiveCorrectionOptions(rawValue: rawRetrospectiveCorrection) { - self.retrospectiveCorrection = retrospectiveCorrection - } } public var rawValue: RawValue { @@ -320,7 +295,6 @@ extension LoopSettings: RawRepresentable { raw["maximumBolus"] = maximumBolus raw["minimumBGGuard"] = suspendThreshold?.rawValue raw["dosingStrategy"] = automaticDosingStrategy.rawValue - raw["retrospectiveCorrection"] = retrospectiveCorrection.rawValue return raw } From 7fe37e44f8d733b340f1009ca44e60f9a7622125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 10 Jun 2023 18:42:20 +0200 Subject: [PATCH 21/31] Removed unnecessary xcscheme files --- .../Complication - WatchApp.xcscheme | 127 ----------------- .../Notification - WatchApp.xcscheme | 129 ------------------ 2 files changed, 256 deletions(-) delete mode 100644 Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme delete mode 100644 Loop.xcodeproj/xcshareddata/xcschemes/Notification - WatchApp.xcscheme diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme deleted file mode 100644 index 0e7664a95f..0000000000 --- a/Loop.xcodeproj/xcshareddata/xcschemes/Complication - WatchApp.xcscheme +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Loop.xcodeproj/xcshareddata/xcschemes/Notification - WatchApp.xcscheme b/Loop.xcodeproj/xcshareddata/xcschemes/Notification - WatchApp.xcscheme deleted file mode 100644 index 85323bccce..0000000000 --- a/Loop.xcodeproj/xcshareddata/xcschemes/Notification - WatchApp.xcscheme +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 2fcf9c2b862e0eb2e1c0e7fb3a20ad30988fbb94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 10 Jun 2023 22:13:28 +0200 Subject: [PATCH 22/31] moved up algorithmExperimentsSection --- Loop/Views/SettingsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index 7f9dd33fdf..2615596c4f 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -51,10 +51,10 @@ public struct SettingsView: View { if FeatureFlags.automaticBolusEnabled { dosingStrategySection } - alertManagementSection // Note: Experimental Section algorithmExperimentsSection // + alertManagementSection if viewModel.pumpManagerSettingsViewModel.isSetUp() { configurationSection } From 3c951df725e4c264bef2c5165f401808fa4019c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Tue, 13 Jun 2023 19:39:12 +0200 Subject: [PATCH 23/31] copy paste error --- Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift b/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift index b38cba6bf3..d7940e28d2 100644 --- a/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift +++ b/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift @@ -47,7 +47,7 @@ public struct IntegralRetrospectiveCorrectionSelectionView: View { } } -struct EnhancedAutoBolusSelectionView_Previews: PreviewProvider { +struct IntegralRetrospectiveCorrectionSelectionView_Previews: PreviewProvider { static var previews: some View { IntegralRetrospectiveCorrectionSelectionView(isIntegralRetrospectiveCorrectionEnabled: .constant(true)) } From d236c069c81aeab6ece598baa939f540856157e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 17 Jun 2023 21:20:31 +0200 Subject: [PATCH 24/31] dark mode fix --- Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift b/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift index d7940e28d2..e522a6ae97 100644 --- a/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift +++ b/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift @@ -36,7 +36,9 @@ public struct IntegralRetrospectiveCorrectionSelectionView: View { private var retrospectiveCorrectionSection: some View { VStack { - DescriptiveText(label: NSLocalizedString("Integral Retrospective Correction (IRC) is an advanced control technique applied to glucose forecasting based on the history of discrepancies between predicted and actual glucose levels. The predictions are made using carbohydrate and insulin data. When enabled, IRC adjusts insulin delivery in response to consistent patterns: it increases insulin delivery when glucose levels consistently measure higher than expected, and decreases it when glucose levels are consistently lower than expected. IRC uses a proportional-integral-differential (PID) controller that adjusts insulin recommendations based on immediate, accumulated, and rate of change discrepancies. This provides a more adaptive and responsive control compared to standard retrospective correction. However, it's important to know that the effectiveness of IRC will heavily depend on the accuracy of your insulin sensitivity, carbohydrate ratios, and basal rates settings. While IRC can improve glucose management in cases of consistent discrepancies, please note that it might potentially lead to more aggressive corrections.", comment: "Description of Integral Retrospective Correction toggle."), color: .black) + Text(NSLocalizedString("Integral Retrospective Correction (IRC) is an advanced control technique applied to glucose forecasting based on the history of discrepancies between predicted and actual glucose levels. The predictions are made using carbohydrate and insulin data. When enabled, IRC adjusts insulin delivery in response to consistent patterns: it increases insulin delivery when glucose levels consistently measure higher than expected, and decreases it when glucose levels are consistently lower than expected. IRC uses a proportional-integral-differential (PID) controller that adjusts insulin recommendations based on immediate, accumulated, and rate of change discrepancies. This provides a more adaptive and responsive control compared to standard retrospective correction. However, it's important to know that the effectiveness of IRC will heavily depend on the accuracy of your insulin sensitivity, carbohydrate ratios, and basal rates settings. While IRC can improve glucose management in cases of consistent discrepancies, please note that it might potentially lead to more aggressive corrections.", comment: "Description of Integral Retrospective Correction toggle.")) + .font(.footnote) + .foregroundColor(.secondary) Section() { Toggle(NSLocalizedString("Integral Retrospective Correction", comment: "Title for Integral Retrospective Correction toggle"), isOn: $isIntegralRetrospectiveCorrectionEnabled) .onChange(of: isIntegralRetrospectiveCorrectionEnabled) { newValue in From 512c64f7ede40387b510e16b8ea7516e79cf941f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B6rkert?= Date: Sat, 17 Jun 2023 21:26:15 +0200 Subject: [PATCH 25/31] dark mode fix --- Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift b/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift index e522a6ae97..9ee87ed6df 100644 --- a/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift +++ b/Loop/Views/IntegralRetrospectiveCorrectionSelectionView.swift @@ -36,9 +36,7 @@ public struct IntegralRetrospectiveCorrectionSelectionView: View { private var retrospectiveCorrectionSection: some View { VStack { - Text(NSLocalizedString("Integral Retrospective Correction (IRC) is an advanced control technique applied to glucose forecasting based on the history of discrepancies between predicted and actual glucose levels. The predictions are made using carbohydrate and insulin data. When enabled, IRC adjusts insulin delivery in response to consistent patterns: it increases insulin delivery when glucose levels consistently measure higher than expected, and decreases it when glucose levels are consistently lower than expected. IRC uses a proportional-integral-differential (PID) controller that adjusts insulin recommendations based on immediate, accumulated, and rate of change discrepancies. This provides a more adaptive and responsive control compared to standard retrospective correction. However, it's important to know that the effectiveness of IRC will heavily depend on the accuracy of your insulin sensitivity, carbohydrate ratios, and basal rates settings. While IRC can improve glucose management in cases of consistent discrepancies, please note that it might potentially lead to more aggressive corrections.", comment: "Description of Integral Retrospective Correction toggle.")) - .font(.footnote) - .foregroundColor(.secondary) + DescriptiveText(label: NSLocalizedString("Integral Retrospective Correction (IRC) is an advanced control technique applied to glucose forecasting based on the history of discrepancies between predicted and actual glucose levels. The predictions are made using carbohydrate and insulin data. When enabled, IRC adjusts insulin delivery in response to consistent patterns: it increases insulin delivery when glucose levels consistently measure higher than expected, and decreases it when glucose levels are consistently lower than expected. IRC uses a proportional-integral-differential (PID) controller that adjusts insulin recommendations based on immediate, accumulated, and rate of change discrepancies. This provides a more adaptive and responsive control compared to standard retrospective correction. However, it's important to know that the effectiveness of IRC will heavily depend on the accuracy of your insulin sensitivity, carbohydrate ratios, and basal rates settings. While IRC can improve glucose management in cases of consistent discrepancies, please note that it might potentially lead to more aggressive corrections.", comment: "Description of Integral Retrospective Correction toggle.")) Section() { Toggle(NSLocalizedString("Integral Retrospective Correction", comment: "Title for Integral Retrospective Correction toggle"), isOn: $isIntegralRetrospectiveCorrectionEnabled) .onChange(of: isIntegralRetrospectiveCorrectionEnabled) { newValue in From a9a7891c652386f25e7bdfdd3e99c0daa6605786 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sun, 18 Jun 2023 11:48:22 -0700 Subject: [PATCH 26/31] make consistent with dev updates --- Loop/View Controllers/PredictionTableViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Loop/View Controllers/PredictionTableViewController.swift b/Loop/View Controllers/PredictionTableViewController.swift index 192f1ba961..5d6165d477 100644 --- a/Loop/View Controllers/PredictionTableViewController.swift +++ b/Loop/View Controllers/PredictionTableViewController.swift @@ -283,8 +283,8 @@ class PredictionTableViewController: LoopChartsTableViewController, Identifiable if let totalEffect = self.totalRetrospectiveCorrection { let integralEffectValue = totalEffect.doubleValue(for: glucoseChart.glucoseUnit) - lastDiscrepancy.quantity.doubleValue(for: glucoseChart.glucoseUnit) let integralEffect = HKQuantity(unit: glucoseChart.glucoseUnit, doubleValue: integralEffectValue) - integralEffectDisplay = formatter.string(from: integralEffect, for: glucoseChart.glucoseUnit) ?? "?" - totalEffectDisplay = formatter.string(from: totalEffect, for: glucoseChart.glucoseUnit) ?? "?" + integralEffectDisplay = formatter.string(from: integralEffect) ?? "?" + totalEffectDisplay = formatter.string(from: totalEffect) ?? "?" } let integralRetro = String( format: NSLocalizedString("prediction-description-integral-retrospective-correction", comment: "Format string describing integral retrospective correction. (1: Integral glucose effect)(2: Total glucose effect)"), From a92700149fc44c236b47e5c12970b5acaaabbef4 Mon Sep 17 00:00:00 2001 From: Marion Barker Date: Sun, 25 Jun 2023 09:15:42 -0700 Subject: [PATCH 27/31] Glucose Based Partial Application Factor (#1988) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Dosing Strategy: automaticBolusSlidingScale * Reverse order of two new constants * revert to dev for dosing strategy selection * revert to dev for dosing strategy selection * Rename to effectiveBolusApplicationFactor, change to flag inside existing automaticBolus case * Add applyLinearRamp slider to Dosing Strategy * Update constants * Modify explanation seen by user * missed one phrase * Update explanation seen by user, modify font * Enable swapping of original and replacment protocols, minimize LoopKit changes * Restore to dev branch version prior to pr1988_update merge * Restore to dev branch version prior to update for PR 1988 * Updates per requested changes for Loop PR 1988 * Renaming, adjusting views, logging * Wording change * Logging error fixed * dead code * missed rename * dark mode fix * Updated algorithm experiments view and glucose based partial application view * Put algorithm experiments behind feature flag --------- Co-authored-by: Jonas Björkert Co-authored-by: Pete Schwamb --- Common/FeatureFlags.swift | 9 ++- Loop.xcodeproj/project.pbxproj | 20 ++++++ ...ingsView+algorithmExperimentsSection.swift | 67 +++++++++++++++++++ Loop/Managers/ApplicationFactorStrategy.swift | 20 ++++++ .../ConstantApplicationFactorStrategy.swift | 23 +++++++ ...lucoseBasedApplicationFactorStrategy.swift | 57 ++++++++++++++++ Loop/Managers/LoopDataManager.swift | 21 +++++- ...eBasedApplicationFactorSelectionView.swift | 64 ++++++++++++++++++ Loop/Views/SettingsView.swift | 6 +- 9 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 Loop/Extensions/SettingsView+algorithmExperimentsSection.swift create mode 100644 Loop/Managers/ApplicationFactorStrategy.swift create mode 100644 Loop/Managers/ConstantApplicationFactorStrategy.swift create mode 100644 Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift create mode 100644 Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift 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.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index d9e4871882..c48bf0fcc4 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -518,6 +518,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 /* GlucoseBasedApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.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 */; }; 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,6 +1692,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 /* GlucoseBasedApplicationFactorStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseBasedApplicationFactorStrategy.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 = ""; }; 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 = ""; }; @@ -2314,6 +2324,7 @@ isa = PBXGroup; children = ( A98556842493F901000FD662 /* AlertStore+SimulatedCoreData.swift */, + DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */, C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */, A9F703722489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift */, C17824991E1999FA00D9D25C /* CaseCountable.swift */, @@ -2400,6 +2411,7 @@ 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */, + DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */, ); path = Views; sourceTree = ""; @@ -2441,6 +2453,9 @@ C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */, A96DAC2B2838F31200D94E38 /* SharedLogging.swift */, 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, + DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */, + DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */, + DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */, ); path = Managers; sourceTree = ""; @@ -3872,6 +3887,7 @@ C1201E2C23ECDBD0002DA84A /* WatchContextRequestUserInfo.swift in Sources */, 1D49795824E7289700948F05 /* ServicesViewModel.swift in Sources */, 1D4A3E2D2478628500FD601B /* StoredAlert+CoreDataClass.swift in Sources */, + DDC389FA2A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift in Sources */, 892D7C5123B54A15008A9656 /* CarbEntryViewController.swift in Sources */, B4E202302661063E009421B5 /* AutomaticDosingStatus.swift in Sources */, C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, @@ -3903,6 +3919,7 @@ 1DB619AC270BAD3D006C9D07 /* VersionUpdateViewModel.swift in Sources */, A9C62D882331703100535612 /* Service.swift in Sources */, 89CAB36324C8FE96009EE3CE /* PredictedGlucoseChartView.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 */, @@ -3933,6 +3950,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 */, @@ -3967,6 +3985,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 */, @@ -3974,6 +3993,7 @@ 431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */, 1D05219D2469F1F5000EBBDE /* AlertStore.swift in Sources */, 439897371CD2F80600223065 /* AnalyticsServicesManager.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 new file mode 100644 index 0000000000..c6b9f93970 --- /dev/null +++ b/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift @@ -0,0 +1,67 @@ +// +// 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 LoopKit +import LoopKitUI + +extension SettingsView { + internal var algorithmExperimentsSection: some View { + 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) + + 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/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/ConstantApplicationFactorStrategy.swift b/Loop/Managers/ConstantApplicationFactorStrategy.swift new file mode 100644 index 0000000000..e13c40c42e --- /dev/null +++ b/Loop/Managers/ConstantApplicationFactorStrategy.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 ConstantApplicationFactorStrategy: 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/GlucoseBasedApplicationFactorStrategy.swift b/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift new file mode 100644 index 0000000000..2573deba31 --- /dev/null +++ b/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift @@ -0,0 +1,57 @@ +// +// GlucoseBasedApplicationFactorStrategy.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 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: 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 = 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 + } +} + +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 ffc66ee314..9994b09b3a 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.glucoseBasedApplicationFactorEnabled + ? GlucoseBasedApplicationFactorStrategy() + : ConstantApplicationFactorStrategy() + + let correctionRangeSchedule = settings.effectiveGlucoseTargetRangeSchedule() + + let effectiveBolusApplicationFactor = applicationFactorStrategy.calculateDosingFactor( + for: glucose.quantity, + correctionRangeSchedule: correctionRangeSchedule!, + settings: settings + ) + + 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)) 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))", + "glucoseBasedApplicationFactorEnabled: \(UserDefaults.standard.glucoseBasedApplicationFactorEnabled)", "", String(reflecting: self.retrospectiveCorrection), "", diff --git a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift new file mode 100644 index 0000000000..2b5556355b --- /dev/null +++ b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift @@ -0,0 +1,64 @@ +// +// 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 + var automaticDosingStrategy: AutomaticDosingStrategy + + public init(isGlucoseBasedApplicationFactorEnabled: Binding, automaticDosingStrategy: AutomaticDosingStrategy) { + self.automaticDosingStrategy = automaticDosingStrategy + self._isGlucoseBasedApplicationFactorEnabled = isGlucoseBasedApplicationFactorEnabled + } + + public var body: some View { + automaticBolusSection + .onChange(of: isGlucoseBasedApplicationFactorEnabled) { newValue in + UserDefaults.standard.glucoseBasedApplicationFactorEnabled = newValue + } + } + + private var automaticBolusSection: some View { + 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 { + NavigationView { + GlucoseBasedApplicationFactorSelectionView(isGlucoseBasedApplicationFactorEnabled: .constant(true), automaticDosingStrategy: .automaticBolus) + } + } +} diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift index bd4e86e8de..afdc404fcc 100644 --- a/Loop/Views/SettingsView.swift +++ b/Loop/Views/SettingsView.swift @@ -30,7 +30,7 @@ public struct SettingsView: View { @State private var therapySettingsIsPresented: Bool = false @State private var deletePumpDataAlertIsPresented = false @State private var deleteCGMDataAlertIsPresented = false - + var localizedAppNameAndVersion: String public init(viewModel: SettingsViewModel, localizedAppNameAndVersion: String) { @@ -224,6 +224,10 @@ extension SettingsView { ForEach(pluginMenuItems.filter {$0.section == .configuration}) { item in item.view } + + if FeatureFlags.allowAlgorithmExperiments { + algorithmExperimentsSection + } } } From 3b3c6823246420fc3adfc748a13f26195ed26e08 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 25 Jun 2023 11:49:20 -0500 Subject: [PATCH 28/31] Wording tweak --- 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 2b5556355b..f3e768930f 100644 --- a/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift +++ b/Loop/Views/GlucoseBasedApplicationFactorSelectionView.swift @@ -36,7 +36,7 @@ public struct GlucoseBasedApplicationFactorSelectionView: View { 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.")) + Text(NSLocalizedString("Loop normally gives 40% of your predicted insulin needs each dosing cycle.\n\nWhen the Glucose Based Partial Application experiment is enabled, Loop will vary 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 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() From 21cec6bd80717a5017b7462b7f39a79a92383dbb Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 25 Jun 2023 12:37:06 -0500 Subject: [PATCH 29/31] Font adjustments --- Loop.xcodeproj/project.pbxproj | 8 +++----- .../SettingsView+algorithmExperimentsSection.swift | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) rename Loop/{Extensions => Views}/SettingsView+algorithmExperimentsSection.swift (95%) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index dacd28dedd..3f368f1037 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -519,12 +519,12 @@ 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 */; }; + 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 */; }; DDC389FC2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */; }; DDC389FE2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */; }; - DD3DBD292A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.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 */; }; @@ -1695,12 +1695,12 @@ 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 = ""; }; + 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 = ""; }; 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 = ""; }; - DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegralRetrospectiveCorrectionSelectionView.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 = ""; }; @@ -2329,7 +2329,6 @@ isa = PBXGroup; children = ( A98556842493F901000FD662 /* AlertStore+SimulatedCoreData.swift */, - DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */, C1D289B422F90A52003FFBD9 /* BasalDeliveryState.swift */, A9F703722489BC8500C98AD8 /* CarbStore+SimulatedCoreData.swift */, C17824991E1999FA00D9D25C /* CaseCountable.swift */, @@ -2367,7 +2366,6 @@ C13DA2AF24F6C7690098BB29 /* UIViewController.swift */, 430B29922041F5B200BA9F93 /* UserDefaults+Loop.swift */, A9B607AF247F000F00792BE4 /* UserNotifications+Loop.swift */, - DD3DBD262A33AEC8000F8B5B /* SettingsView+algorithmExperimentsSection.swift */, ); path = Extensions; sourceTree = ""; @@ -2413,6 +2411,7 @@ 438D42FA1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift */, 439706E522D2E84900C81566 /* PredictionSettingTableViewCell.swift */, 1DE09BA824A3E23F009EE9F9 /* SettingsView.swift */, + DDC389FB2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift */, C1DE5D22251BFC4D00439E49 /* SimpleBolusView.swift */, 43F64DD81D9C92C900D24DC6 /* TitleSubtitleTableViewCell.swift */, 4311FB9A1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift */, @@ -3901,7 +3900,6 @@ C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */, A977A2F424ACFECF0059C207 /* CriticalEventLogExportManager.swift in Sources */, 89CA2B32226C18B8004D9350 /* TestingScenariosTableViewController.swift in Sources */, - DD3DBD272A33AEC8000F8B5B /* SettingsView+algorithmExperimentsSection.swift in Sources */, 43E93FB71E469A5100EAB8DB /* HKUnit.swift in Sources */, 43C05CAF21EB2C24006FB252 /* NSBundle.swift in Sources */, A91D2A3F26CF0FF80023B075 /* IconTitleSubtitleTableViewCell.swift in Sources */, diff --git a/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift b/Loop/Views/SettingsView+algorithmExperimentsSection.swift similarity index 95% rename from Loop/Extensions/SettingsView+algorithmExperimentsSection.swift rename to Loop/Views/SettingsView+algorithmExperimentsSection.swift index 32b9fb75d7..a5b787e1d1 100644 --- a/Loop/Extensions/SettingsView+algorithmExperimentsSection.swift +++ b/Loop/Views/SettingsView+algorithmExperimentsSection.swift @@ -45,6 +45,8 @@ public struct ExperimentsSettingsView: View { public var body: some View { VStack(alignment: .center, spacing: 12) { + Text(NSLocalizedString("Algorithm Experiments", comment: "Navigation title for algorithms experiments screen")) + .font(.headline) VStack { Text("⚠️").font(.largeTitle) Text("Caution") @@ -70,7 +72,7 @@ public struct ExperimentsSettingsView: View { Spacer() } .padding() - .navigationTitle(NSLocalizedString("Algorithm Experiments", comment: "Navigation title for algorithms experiments screen")) + .navigationBarTitleDisplayMode(.inline) } } From 7518c70ad1d2be2d37fc9e0faa20e0acbd0c2156 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 25 Jun 2023 13:01:10 -0500 Subject: [PATCH 30/31] Change settings.enabledRetrospectiveCorrectionAlgorithm back to var --- Loop/Managers/LoopDataManager.swift | 2 +- Loop/Models/LoopSettings+Loop.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift index fec95f10a1..e4d6dadeb5 100644 --- a/Loop/Managers/LoopDataManager.swift +++ b/Loop/Managers/LoopDataManager.swift @@ -123,7 +123,7 @@ final class LoopDataManager { self.trustedTimeOffset = trustedTimeOffset - retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm() + retrospectiveCorrection = settings.enabledRetrospectiveCorrectionAlgorithm overrideIntentObserver = UserDefaults.appGroup?.observe(\.intentExtensionOverrideToSet, options: [.new], changeHandler: {[weak self] (defaults, change) in guard let name = change.newValue??.lowercased(), let appGroup = UserDefaults.appGroup else { diff --git a/Loop/Models/LoopSettings+Loop.swift b/Loop/Models/LoopSettings+Loop.swift index 4d4a822ec4..ff6f4d2f74 100644 --- a/Loop/Models/LoopSettings+Loop.swift +++ b/Loop/Models/LoopSettings+Loop.swift @@ -21,7 +21,7 @@ extension LoopSettings { static let retrospectiveCorrectionEffectDuration = TimeInterval(hours: 1) /// Creates an instance of the enabled retrospective correction implementation - func enabledRetrospectiveCorrectionAlgorithm() -> RetrospectiveCorrection { + var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection { var enabledRetrospectiveCorrectionAlgorithm: RetrospectiveCorrection let isIntegralRetrospectiveCorrectionEnabled = UserDefaults.standard.integralRetrospectiveCorrectionEnabled From 5e5ba6586ea4692ae09ef921a7ee93f59f437317 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Sun, 25 Jun 2023 13:07:53 -0500 Subject: [PATCH 31/31] Reorg some files --- Loop.xcodeproj/project.pbxproj | 24 +++++++++---------- .../ApplicationFactorStrategy.swift | 0 .../ConstantApplicationFactorStrategy.swift | 0 ...lucoseBasedApplicationFactorStrategy.swift | 0 4 files changed, 12 insertions(+), 12 deletions(-) rename Loop/{Managers => Models}/ApplicationFactorStrategy.swift (100%) rename Loop/{Managers => Models}/ConstantApplicationFactorStrategy.swift (100%) rename Loop/{Managers => Models}/GlucoseBasedApplicationFactorStrategy.swift (100%) diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj index 3f368f1037..84547bfac4 100644 --- a/Loop.xcodeproj/project.pbxproj +++ b/Loop.xcodeproj/project.pbxproj @@ -2052,24 +2052,27 @@ 43757D131C06F26C00910CB9 /* Models */ = { isa = PBXGroup; children = ( - A99A114029A581D6007919CE /* Remote */, - 43511CDD21FD80AD00566C63 /* RetrospectiveCorrection */, - A9FB75F0252BE320004C7D3F /* BolusDosingDecision.swift */, - C17824A41E1AD4D100D9D25C /* ManualBolusRecommendation.swift */, + DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */, B4E2022F2661063E009421B5 /* AutomaticDosingStatus.swift */, + A9FB75F0252BE320004C7D3F /* BolusDosingDecision.swift */, + DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */, + C1EF747128D6A44A00C8C083 /* CrashRecoveryManager.swift */, + DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */, B40D07C6251A89D500C1C6D7 /* GlucoseDisplay.swift */, 43C2FAE01EB656A500364AFF /* GlucoseEffectVelocity.swift */, + C1C660D0252E4DD5009B5C32 /* LoopConstants.swift */, 436A0DA41D236A2A00104B24 /* LoopError.swift */, + E9C00EF424C623EF00628F35 /* LoopSettings+Loop.swift */, A9B996EF27235191002DC09C /* LoopWarning.swift */, + C17824A41E1AD4D100D9D25C /* ManualBolusRecommendation.swift */, 4F526D601DF8D9A900A04910 /* NetBasal.swift */, 438D42F81D7C88BC003244B0 /* PredictionInputEffect.swift */, - 4328E0311CFC068900E199AA /* WatchContext+LoopKit.swift */, - E9C00EF424C623EF00628F35 /* LoopSettings+Loop.swift */, - A987CD4824A58A0100439ADC /* ZipArchive.swift */, + A99A114029A581D6007919CE /* Remote */, + 43511CDD21FD80AD00566C63 /* RetrospectiveCorrection */, C19008FD25225D3900721625 /* SimpleBolusCalculator.swift */, C1E3862428247B7100F561A4 /* StoredLoopNotRunningNotification.swift */, - C1C660D0252E4DD5009B5C32 /* LoopConstants.swift */, - C1EF747128D6A44A00C8C083 /* CrashRecoveryManager.swift */, + 4328E0311CFC068900E199AA /* WatchContext+LoopKit.swift */, + A987CD4824A58A0100439ADC /* ZipArchive.swift */, ); path = Models; sourceTree = ""; @@ -2459,9 +2462,6 @@ C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */, A96DAC2B2838F31200D94E38 /* SharedLogging.swift */, 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, - DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */, - DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */, - DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */, ); path = Managers; sourceTree = ""; diff --git a/Loop/Managers/ApplicationFactorStrategy.swift b/Loop/Models/ApplicationFactorStrategy.swift similarity index 100% rename from Loop/Managers/ApplicationFactorStrategy.swift rename to Loop/Models/ApplicationFactorStrategy.swift diff --git a/Loop/Managers/ConstantApplicationFactorStrategy.swift b/Loop/Models/ConstantApplicationFactorStrategy.swift similarity index 100% rename from Loop/Managers/ConstantApplicationFactorStrategy.swift rename to Loop/Models/ConstantApplicationFactorStrategy.swift diff --git a/Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift b/Loop/Models/GlucoseBasedApplicationFactorStrategy.swift similarity index 100% rename from Loop/Managers/GlucoseBasedApplicationFactorStrategy.swift rename to Loop/Models/GlucoseBasedApplicationFactorStrategy.swift