Просмотр исходного кода

Merge branch 'dev' of github.com:nightscout/Trio-dev into stats-wip

Deniz Cengiz 1 год назад
Родитель
Сommit
9edf4188dc
31 измененных файлов с 46804 добавлено и 31789 удалено
  1. 0 1
      Model/Classes+Properties/OrefDetermination+CoreDataProperties.swift
  2. 0 1
      Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents
  3. 12 0
      Trio.xcodeproj/project.pbxproj
  4. 1 1
      Trio/Resources/javascript/bundle/determine-basal.js
  5. 9 44
      Trio/Sources/APS/APSManager.swift
  6. 12 17
      Trio/Sources/APS/FetchGlucoseManager.swift
  7. 9 12
      Trio/Sources/APS/OpenAPS/OpenAPS.swift
  8. 0 2
      Trio/Sources/APS/Storage/DeterminationStorage.swift
  9. 1 1
      Trio/Sources/APS/Storage/TDDStorage.swift
  10. 29 0
      Trio/Sources/Helpers/BackgroundTask+Helper.swift
  11. 46303 31101
      Trio/Sources/Localizations/Main/Localizable.xcstrings
  12. 0 20
      Trio/Sources/Models/Determination.swift
  13. 4 0
      Trio/Sources/Models/Oref2_variables.swift
  14. 6 0
      Trio/Sources/Models/TDD.swift
  15. 52 0
      Trio/Sources/Modules/Home/HomeStateModel+Setup/CurrentTDDSetup.swift
  16. 10 0
      Trio/Sources/Modules/Home/HomeStateModel.swift
  17. 1 1
      Trio/Sources/Modules/Home/View/HomeRootView.swift
  18. 35 2
      Trio/Sources/Modules/Main/MainStateModel.swift
  19. 10 18
      Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift
  20. 25 10
      Trio/Sources/Services/LiveActivity/Data/DataManager.swift
  21. 3 16
      Trio/Sources/Shortcuts/LiveActivity/RestartLiveActivityIntentRequest.swift
  22. 15 2
      Trio/Sources/Shortcuts/Override/ApplyOverridePresetIntent.swift
  23. 11 8
      Trio/Sources/Shortcuts/Override/CancelOverrideIntent.swift
  24. 17 0
      Trio/Sources/Shortcuts/Override/OverridePresetEntity.swift
  25. 88 65
      Trio/Sources/Shortcuts/Override/OverridePresetsIntentRequest.swift
  26. 22 4
      Trio/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift
  27. 7 2
      Trio/Sources/Shortcuts/TempPresets/CancelTempPresetIntent.swift
  28. 23 0
      Trio/Sources/Shortcuts/TempPresets/TempPresetIntent.swift
  29. 94 92
      Trio/Sources/Shortcuts/TempPresets/TempPresetsIntentRequest.swift
  30. 3 1
      oref0_source_version.txt
  31. 2 368
      trio-oref/lib/determine-basal/determine-basal.js

+ 0 - 1
Model/Classes+Properties/OrefDetermination+CoreDataProperties.swift

@@ -37,7 +37,6 @@ public extension OrefDetermination {
     @NSManaged var threshold: NSDecimalNumber?
     @NSManaged var timestamp: Date?
     @NSManaged var timestampEnacted: Date?
-    @NSManaged var totalDailyDose: NSDecimalNumber?
     @NSManaged var forecasts: Set<Forecast>?
 }
 

+ 0 - 1
Model/TrioCoreDataPersistentContainer.xcdatamodeld/TrioCoreDataPersistentContainer.xcdatamodel/contents

@@ -138,7 +138,6 @@
         <attribute name="threshold" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <attribute name="timestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
         <attribute name="timestampEnacted" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
-        <attribute name="totalDailyDose" optional="YES" attributeType="Decimal" defaultValueString="0.0"/>
         <relationship name="forecasts" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Forecast" inverseName="orefDetermination" inverseEntity="Forecast"/>
         <fetchIndex name="byDate">
             <fetchIndexElement property="deliverAt" type="Binary" order="descending"/>

+ 12 - 0
Trio.xcodeproj/project.pbxproj

@@ -204,6 +204,8 @@
 		38FEF413273B317A00574A46 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF412273B317A00574A46 /* HKUnit.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
 		45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */; };
+		49249B1C2D46E45E000F4866 /* CurrentTDDSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49249B1B2D46E45E000F4866 /* CurrentTDDSetup.swift */; };
+		49249B382D46E76A000F4866 /* TDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49249B372D46E76A000F4866 /* TDD.swift */; };
 		491D6FBD2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBC2D56741C00C49F67 /* TempTargetStored+CoreDataProperties.swift */; };
 		491D6FBE2D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FB92D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift */; };
 		491D6FBF2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */; };
@@ -517,6 +519,7 @@
 		DD6B7CBB2C7FBBFA00B75029 /* ReviewInsulinActionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6B7CBA2C7FBBFA00B75029 /* ReviewInsulinActionView.swift */; };
 		DD6D67E42C9C253500660C9B /* ColorSchemeOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */; };
 		DD6F63CC2D27F615007D94CF /* TreatmentMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6F63CB2D27F606007D94CF /* TreatmentMenuView.swift */; };
+		DD73FA0F2D74F58E00D19D1E /* BackgroundTask+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD73FA0E2D74F57300D19D1E /* BackgroundTask+Helper.swift */; };
 		DD8262CB2D289297009F6F62 /* BolusConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD8262CA2D289297009F6F62 /* BolusConfirmationView.swift */; };
 		DD88C8E22C50420800F2D558 /* DefinitionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD88C8E12C50420800F2D558 /* DefinitionRow.swift */; };
 		DD940BAA2CA7585D000830A5 /* GlucoseColorScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */; };
@@ -925,6 +928,8 @@
 		3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorDataFlow.swift; sourceTree = "<group>"; };
 		42369F66CF91F30624C0B3A6 /* BasalProfileEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = BasalProfileEditorProvider.swift; sourceTree = "<group>"; };
 		44080E4709E3AE4B73054563 /* ConfigEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ConfigEditorProvider.swift; sourceTree = "<group>"; };
+		49249B1B2D46E45E000F4866 /* CurrentTDDSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentTDDSetup.swift; sourceTree = "<group>"; };
+		49249B372D46E76A000F4866 /* TDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD.swift; sourceTree = "<group>"; };
 		491D6FB92D56741C00C49F67 /* TempTargetRunStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetRunStored+CoreDataClass.swift"; sourceTree = "<group>"; };
 		491D6FBA2D56741C00C49F67 /* TempTargetRunStored+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetRunStored+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		491D6FBB2D56741C00C49F67 /* TempTargetStored+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TempTargetStored+CoreDataClass.swift"; sourceTree = "<group>"; };
@@ -1239,6 +1244,7 @@
 		DD6B7CBA2C7FBBFA00B75029 /* ReviewInsulinActionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewInsulinActionView.swift; sourceTree = "<group>"; };
 		DD6D67E32C9C253500660C9B /* ColorSchemeOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSchemeOption.swift; sourceTree = "<group>"; };
 		DD6F63CB2D27F606007D94CF /* TreatmentMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreatmentMenuView.swift; sourceTree = "<group>"; };
+		DD73FA0E2D74F57300D19D1E /* BackgroundTask+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BackgroundTask+Helper.swift"; sourceTree = "<group>"; };
 		DD8262CA2D289297009F6F62 /* BolusConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusConfirmationView.swift; sourceTree = "<group>"; };
 		DD88C8E12C50420800F2D558 /* DefinitionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefinitionRow.swift; sourceTree = "<group>"; };
 		DD940BA92CA7585D000830A5 /* GlucoseColorScheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseColorScheme.swift; sourceTree = "<group>"; };
@@ -2088,6 +2094,7 @@
 		388E5A5925B6F0250019842D /* Models */ = {
 			isa = PBXGroup;
 			children = (
+				49249B372D46E76A000F4866 /* TDD.swift */,
 				DD4FFF322D458EE600B6CFF9 /* GarminWatchState.swift */,
 				DD3078692D42F94000DE0490 /* GarminDevice.swift */,
 				DD3078672D42F5CE00DE0490 /* WatchGlucoseObject.swift */,
@@ -2152,6 +2159,7 @@
 			children = (
 				BD249DA62D42FE3800412DEB /* Calendar+GlucoseStatsChart.swift */,
 				BD249DA42D42FD9500412DEB /* CustomDatePicker.swift */,
+				DD73FA0E2D74F57300D19D1E /* BackgroundTask+Helper.swift */,
 				CEF1ED6A2D58FB4600FAF41E /* CGMOptions.swift */,
 				C2A0A42E2CE0312C003B98E8 /* ConstantValues.swift */,
 				DD940BAB2CA75889000830A5 /* DynamicGlucoseColor.swift */,
@@ -2416,6 +2424,7 @@
 				58645B982CA2D1A4008AFCE7 /* GlucoseSetup.swift */,
 				58645B9A2CA2D24F008AFCE7 /* CarbSetup.swift */,
 				58645B9C2CA2D275008AFCE7 /* DeterminationSetup.swift */,
+				49249B1B2D46E45E000F4866 /* CurrentTDDSetup.swift */,
 				58645B9E2CA2D2BE008AFCE7 /* PumpHistorySetup.swift */,
 				58645BA02CA2D2F8008AFCE7 /* OverrideSetup.swift */,
 				58645BA22CA2D325008AFCE7 /* BatterySetup.swift */,
@@ -3934,6 +3943,7 @@
 				1935364028496F7D001E0B16 /* Oref2_variables.swift in Sources */,
 				CE2FAD3A297D93F0001A872C /* BloodGlucoseExtensions.swift in Sources */,
 				38E4453A274E411700EC9A94 /* Disk+[UIImage].swift in Sources */,
+				DD73FA0F2D74F58E00D19D1E /* BackgroundTask+Helper.swift in Sources */,
 				72F1BD388F42FCA6C52E4500 /* ConfigEditorProvider.swift in Sources */,
 				E39E418C56A5A46B61D960EE /* ConfigEditorStateModel.swift in Sources */,
 				45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */,
@@ -4039,6 +4049,7 @@
 				38E44538274E411700EC9A94 /* Disk+[Data].swift in Sources */,
 				98641AF4F92123DA668AB931 /* CarbRatioEditorRootView.swift in Sources */,
 				BDF34F902C10CF8C00D51995 /* CoreDataStack.swift in Sources */,
+				49249B1C2D46E45E000F4866 /* CurrentTDDSetup.swift in Sources */,
 				CEE9A65C2BBB41C800EB5194 /* CalibrationService.swift in Sources */,
 				110AEDED2C51A0AE00615CC9 /* ShortcutsConfigProvider.swift in Sources */,
 				38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */,
@@ -4056,6 +4067,7 @@
 				9702FF92A09C53942F20D7EA /* TargetsEditorRootView.swift in Sources */,
 				1967DFBE29D052C200759F30 /* Icons.swift in Sources */,
 				DDD163182C4C694000CD525A /* AdjustmentsRootView.swift in Sources */,
+				49249B382D46E76A000F4866 /* TDD.swift in Sources */,
 				389ECE052601144100D86C4F /* ConcurrentMap.swift in Sources */,
 				110AEDEC2C51A0AE00615CC9 /* ShortcutsConfigDataFlow.swift in Sources */,
 				CE7CA3562A064973004BE681 /* StateIntentRequest.swift in Sources */,

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
Trio/Resources/javascript/bundle/determine-basal.js


+ 9 - 44
Trio/Sources/APS/APSManager.swift

@@ -81,7 +81,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
     private var lifetime = Lifetime()
 
-    private var backGroundTaskID: UIBackgroundTaskIdentifier?
+    private var backgroundTaskID: UIBackgroundTaskIdentifier?
 
     var pumpManager: PumpManagerUI? {
         get { deviceDataManager.pumpManager }
@@ -224,7 +224,7 @@ final class BaseAPSManager: APSManager, Injectable {
             // Cleanup background task
             if let backgroundTask = backgroundTask {
                 await UIApplication.shared.endBackgroundTask(backgroundTask)
-                self.backGroundTaskID = .invalid
+                self.backgroundTaskID = .invalid
             }
         }
     }
@@ -250,13 +250,13 @@ final class BaseAPSManager: APSManager, Injectable {
     private func setupLoop() async -> (LoopStats, UIBackgroundTaskIdentifier?) {
         // Start background task
         let backgroundTask = await UIApplication.shared.beginBackgroundTask(withName: "Loop starting") { [weak self] in
-            guard let self, let backgroundTask = self.backGroundTaskID else { return }
+            guard let self, let backgroundTask = self.backgroundTaskID else { return }
             Task {
                 UIApplication.shared.endBackgroundTask(backgroundTask)
             }
-            self.backGroundTaskID = .invalid
+            self.backgroundTaskID = .invalid
         }
-        backGroundTaskID = backgroundTask
+        backgroundTaskID = backgroundTask
 
         // Set loop start time
         lastLoopStartDate = Date()
@@ -325,9 +325,9 @@ final class BaseAPSManager: APSManager, Injectable {
 
         if let error = error {
             warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
-            if let backgroundTask = backGroundTaskID {
+            if let backgroundTask = backgroundTaskID {
                 await UIApplication.shared.endBackgroundTask(backgroundTask)
-                backGroundTaskID = .invalid
+                backgroundTaskID = .invalid
             }
             processError(error)
         } else {
@@ -343,9 +343,9 @@ final class BaseAPSManager: APSManager, Injectable {
         }
 
         // End of the BG tasks
-        if let backgroundTask = backGroundTaskID {
+        if let backgroundTask = backgroundTaskID {
             await UIApplication.shared.endBackgroundTask(backgroundTask)
-            backGroundTaskID = .invalid
+            backgroundTaskID = .invalid
         }
     }
 
@@ -1117,41 +1117,6 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
-    private func tddForStats() async -> (currentTDD: Decimal, tddTotalAverage: Decimal) {
-        let requestTDD = OrefDetermination.fetchRequest() as NSFetchRequest<NSFetchRequestResult>
-        let sort = NSSortDescriptor(key: "timestamp", ascending: false)
-        let daysOf14Ago = Date().addingTimeInterval(-14.days.timeInterval)
-        requestTDD.predicate = NSPredicate(format: "timestamp > %@", daysOf14Ago as NSDate)
-        requestTDD.sortDescriptors = [sort]
-        requestTDD.propertiesToFetch = ["timestamp", "totalDailyDose"]
-        requestTDD.resultType = .dictionaryResultType
-
-        var currentTDD: Decimal = 0
-        var tddTotalAverage: Decimal = 0
-
-        let results = await privateContext.perform {
-            do {
-                let fetchedResults = try self.privateContext.fetch(requestTDD) as? [[String: Any]]
-                return fetchedResults ?? []
-            } catch {
-                debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to get TDD Data for Statistics Upload")
-                return []
-            }
-        }
-
-        if !results.isEmpty {
-            if let latestTDD = results.first?["totalDailyDose"] as? NSDecimalNumber {
-                currentTDD = latestTDD.decimalValue
-            }
-            let tddArray = results.compactMap { ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue }
-            if !tddArray.isEmpty {
-                tddTotalAverage = tddArray.reduce(0, +) / Decimal(tddArray.count)
-            }
-        }
-
-        return (currentTDD, tddTotalAverage)
-    }
-
     private func glucoseForStats() async -> (
         oneDayGlucose: (ifcc: Double, ngsp: Double, average: Double, median: Double, sd: Double, cv: Double, readings: Double),
         eA1cDisplayUnit: EstimatedA1cDisplayUnit,

+ 12 - 17
Trio/Sources/APS/FetchGlucoseManager.swift

@@ -248,29 +248,22 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
         var filteredByDate: [BloodGlucose] = []
         var filtered: [BloodGlucose] = []
 
-        // start background time extension
-        var backGroundFetchBGTaskID: UIBackgroundTaskIdentifier?
-        backGroundFetchBGTaskID = await UIApplication.shared.beginBackgroundTask(withName: "save BG starting") {
-            guard let bg = backGroundFetchBGTaskID else { return }
-            UIApplication.shared.endBackgroundTask(bg)
-            backGroundFetchBGTaskID = .invalid
-        }
+        // Start background task
+        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+        backgroundTaskID = startBackgroundTask(withName: "Glucose Store and Heartbeat Decision")
 
-        defer {
-            if let backgroundTask = backGroundFetchBGTaskID {
-                Task {
-                    await UIApplication.shared.endBackgroundTask(backgroundTask)
-                }
-                backGroundFetchBGTaskID = .invalid
-            }
+        guard newGlucose.isNotEmpty else {
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "Glucose Store and Heartbeat Decision")
+            return
         }
 
-        guard newGlucose.isNotEmpty else { return }
-
         filteredByDate = newGlucose.filter { $0.dateString > syncDate }
         filtered = glucoseStorage.filterTooFrequentGlucose(filteredByDate, at: syncDate)
 
-        guard filtered.isNotEmpty else { return }
+        guard filtered.isNotEmpty else {
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "Glucose Store and Heartbeat Decision")
+            return
+        }
         debug(.deviceManager, "New glucose found")
 
         // filter the data if it is the case
@@ -289,6 +282,8 @@ final class BaseFetchGlucoseManager: FetchGlucoseManager, Injectable {
 
         try await glucoseStorage.storeGlucose(filtered)
         deviceDataManager.heartbeat(date: Date())
+
+        endBackgroundTaskSafely(&backgroundTaskID, taskName: "Glucose Store and Heartbeat Decision")
     }
 
     func sourceInfo() -> [String: Any]? {

+ 9 - 12
Trio/Sources/APS/OpenAPS/OpenAPS.swift

@@ -34,7 +34,6 @@ final class OpenAPS {
         await context.perform {
             let newOrefDetermination = OrefDetermination(context: self.context)
             newOrefDetermination.id = UUID()
-            newOrefDetermination.totalDailyDose = self.decimalToNSDecimalNumber(determination.tdd)
             newOrefDetermination.insulinSensitivity = self.decimalToNSDecimalNumber(determination.isf)
             newOrefDetermination.currentTarget = self.decimalToNSDecimalNumber(determination.current_target)
             newOrefDetermination.eventualBG = determination.eventualBG.map(NSDecimalNumber.init)
@@ -55,9 +54,6 @@ final class OpenAPS {
             newOrefDetermination.expectedDelta = self.decimalToNSDecimalNumber(determination.expectedDelta)
             newOrefDetermination.cob = Int16(Int(determination.cob ?? 0))
             newOrefDetermination.manualBolusErrorString = self.decimalToNSDecimalNumber(determination.manualBolusErrorString)
-            newOrefDetermination.tempBasal = determination.insulin?.temp_basal.map { NSDecimalNumber(decimal: $0) }
-            newOrefDetermination.scheduledBasal = determination.insulin?.scheduled_basal.map { NSDecimalNumber(decimal: $0) }
-            newOrefDetermination.bolus = determination.insulin?.bolus.map { NSDecimalNumber(decimal: $0) }
             newOrefDetermination.smbToDeliver = determination.units.map { NSDecimalNumber(decimal: $0) }
             newOrefDetermination.carbsRequired = Int16(Int(determination.carbsReq ?? 0))
             newOrefDetermination.isUploadedToNS = false
@@ -392,16 +388,16 @@ final class OpenAPS {
             let overrideTargetBG = activeOverrides.first?.target?.decimalValue ?? 0
 
             // Calculate averages for Total Daily Dose (TDD)
-            let totalTDD = historicalTDDData.compactMap { ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue }.reduce(0, +)
+            let totalTDD = historicalTDDData.compactMap { ($0["total"] as? NSDecimalNumber)?.decimalValue }.reduce(0, +)
             let totalDaysCount = max(historicalTDDData.count, 1)
 
             // Fetch recent TDD data for the past two hours
-            let recentTDDData = historicalTDDData.filter { ($0["timestamp"] as? Date ?? Date()) >= twoHoursAgo }
+            let recentTDDData = historicalTDDData.filter { ($0["date"] as? Date ?? Date()) >= twoHoursAgo }
             let recentDataCount = max(recentTDDData.count, 1)
-            let recentTotalTDD = recentTDDData.compactMap { ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue }
+            let recentTotalTDD = recentTDDData.compactMap { ($0["total"] as? NSDecimalNumber)?.decimalValue }
                 .reduce(0, +)
 
-            let currentTDD = historicalTDDData.last?["totalDailyDose"] as? Decimal ?? 0
+            let currentTDD = historicalTDDData.last?["total"] as? Decimal ?? 0
             let averageTDDLastTwoHours = recentTotalTDD / Decimal(recentDataCount)
             let averageTDDLastTenDays = totalTDD / Decimal(totalDaysCount)
             let weightedTDD = weightPercentage * averageTDDLastTwoHours + (1 - weightPercentage) * averageTDDLastTenDays
@@ -410,6 +406,7 @@ final class OpenAPS {
             let oref2Data = Oref2_variables(
                 average_total_data: currentTDD > 0 ? averageTDDLastTenDays : 0,
                 weightedAverage: currentTDD > 0 ? weightedTDD : 1,
+                currentTDD: currentTDD,
                 past2hoursAverage: currentTDD > 0 ? averageTDDLastTwoHours : 0,
                 date: Date(),
                 overridePercentage: overridePercentage,
@@ -831,12 +828,12 @@ extension OpenAPS {
 
     func fetchHistoricalTDDData(from date: Date) throws -> [[String: Any]] {
         try CoreDataStack.shared.fetchEntities(
-            ofType: OrefDetermination.self,
+            ofType: TDDStored.self,
             onContext: context,
-            predicate: NSPredicate(format: "timestamp > %@ AND totalDailyDose > 0", date as NSDate),
-            key: "timestamp",
+            predicate: NSPredicate(format: "date > %@ AND total > 0", date as NSDate),
+            key: "date",
             ascending: true,
-            propertiesToFetch: ["timestamp", "totalDailyDose"]
+            propertiesToFetch: ["date", "total"]
         ) as? [[String: Any]] ?? []
     }
 }

+ 0 - 2
Trio/Sources/APS/Storage/DeterminationStorage.swift

@@ -182,8 +182,6 @@ final class BaseDeterminationStorage: DeterminationStorage, Injectable {
                         reservoir: self.decimal(from: orefDetermination.reservoir),
                         isf: self.decimal(from: orefDetermination.insulinSensitivity),
                         timestamp: orefDetermination.timestamp,
-                        tdd: self.decimal(from: orefDetermination.totalDailyDose),
-                        insulin: nil,
                         current_target: self.decimal(from: orefDetermination.currentTarget),
                         insulinForManualBolus: self.decimal(from: orefDetermination.insulinForManualBolus),
                         manualBolusErrorString: self.decimal(from: orefDetermination.manualBolusErrorString),

+ 1 - 1
Trio/Sources/APS/Storage/TDDStorage.swift

@@ -109,7 +109,7 @@ final class BaseTDDStorage: TDDStorage, Injectable {
         let bolusString = String(format: "%.2f", NSDecimalNumber(decimal: bolus.rounded(toPlaces: 2)).doubleValue)
         let tempBasalString = String(format: "%.2f", NSDecimalNumber(decimal: temp.rounded(toPlaces: 2)).doubleValue)
         let scheduledBasalString = String(format: "%.2f", NSDecimalNumber(decimal: scheduled.rounded(toPlaces: 2)).doubleValue)
-        let weightedAvgString = String(format: "%.2f", NSDecimalNumber(decimal: weighted?.rounded(toPlaces: 2) ?? 0))
+        let weightedAvgString = String(format: "%.2f", NSDecimalNumber(decimal: weighted?.rounded(toPlaces: 2) ?? 0).doubleValue)
         let hoursString = String(format: "%.5f", NSDecimalNumber(decimal: Decimal(hours).truncated(toPlaces: 5)).doubleValue)
 
         debug(.apsManager, """

+ 29 - 0
Trio/Sources/Helpers/BackgroundTask+Helper.swift

@@ -0,0 +1,29 @@
+import UIKit
+
+/// Ends a background task safely and ensures it is not called multiple times.
+///
+/// - Parameter taskID: The background task identifier to be ended.
+func endBackgroundTaskSafely(_ taskID: inout UIBackgroundTaskIdentifier, taskName: String = "Unnamed Task") {
+    if taskID != .invalid {
+        UIApplication.shared.endBackgroundTask(taskID)
+        debug(.default, "Background task '\(taskName)' ended successfully.")
+        taskID = .invalid
+    } else {
+        debug(.default, "Background task '\(taskName)' was already invalid or ended.")
+    }
+}
+
+/// Starts a background task and handles its expiration safely.
+///
+/// - Parameter name: The background task name.
+func startBackgroundTask(withName name: String) -> UIBackgroundTaskIdentifier {
+    var taskID = UIBackgroundTaskIdentifier.invalid
+
+    taskID = UIApplication.shared.beginBackgroundTask(withName: name) {
+        Task { @MainActor in
+            endBackgroundTaskSafely(&taskID, taskName: name)
+        }
+    }
+
+    return taskID
+}

Разница между файлами не показана из-за своего большого размера
+ 46303 - 31101
Trio/Sources/Localizations/Main/Localizable.xcstrings


+ 0 - 20
Trio/Sources/Models/Determination.swift

@@ -19,8 +19,6 @@ struct Determination: JSON, Equatable {
     let reservoir: Decimal?
     var isf: Decimal?
     var timestamp: Date?
-    let tdd: Decimal?
-    let insulin: Insulin?
     var current_target: Decimal?
     let insulinForManualBolus: Decimal?
     let manualBolusErrorString: Decimal?
@@ -40,13 +38,6 @@ struct Predictions: JSON, Equatable {
     let uam: [Int]?
 }
 
-struct Insulin: JSON, Equatable {
-    let TDD: Decimal?
-    let bolus: Decimal?
-    let temp_basal: Decimal?
-    let scheduled_basal: Decimal?
-}
-
 extension Determination {
     private enum CodingKeys: String, CodingKey {
         case id
@@ -67,8 +58,6 @@ extension Determination {
         case reservoir
         case timestamp
         case isf = "ISF"
-        case tdd = "TDD"
-        case insulin
         case current_target
         case insulinForManualBolus
         case manualBolusErrorString
@@ -91,15 +80,6 @@ extension Predictions {
     }
 }
 
-extension Insulin {
-    private enum CodingKeys: String, CodingKey {
-        case TDD
-        case bolus
-        case temp_basal
-        case scheduled_basal
-    }
-}
-
 protocol DeterminationObserver {
     func determinationDidUpdate(_ determination: Determination)
 }

+ 4 - 0
Trio/Sources/Models/Oref2_variables.swift

@@ -2,6 +2,7 @@ import Foundation
 
 struct Oref2_variables: JSON, Equatable {
     var average_total_data: Decimal
+    var currentTDD: Decimal
     var weightedAverage: Decimal
     var past2hoursAverage: Decimal
     var date: Date
@@ -24,6 +25,7 @@ struct Oref2_variables: JSON, Equatable {
     init(
         average_total_data: Decimal,
         weightedAverage: Decimal,
+        currentTDD: Decimal,
         past2hoursAverage: Decimal,
         date: Date,
         overridePercentage: Decimal,
@@ -44,6 +46,7 @@ struct Oref2_variables: JSON, Equatable {
     ) {
         self.average_total_data = average_total_data
         self.weightedAverage = weightedAverage
+        self.currentTDD = currentTDD
         self.past2hoursAverage = past2hoursAverage
         self.date = date
         self.overridePercentage = overridePercentage
@@ -68,6 +71,7 @@ extension Oref2_variables {
     private enum CodingKeys: String, CodingKey {
         case average_total_data
         case weightedAverage
+        case currentTDD
         case past2hoursAverage
         case date
         case overridePercentage

+ 6 - 0
Trio/Sources/Models/TDD.swift

@@ -0,0 +1,6 @@
+import Foundation
+
+struct TDD: Codable, Equatable, Sendable {
+    let totalDailyDose: Decimal?
+    let timestamp: Date?
+}

+ 52 - 0
Trio/Sources/Modules/Home/HomeStateModel+Setup/CurrentTDDSetup.swift

@@ -0,0 +1,52 @@
+import CoreData
+import Foundation
+
+extension Home.StateModel {
+    func setupTDDArray() {
+        Task {
+            do {
+                // Get the NSManagedObjectIDs
+                let tddObjectIds = try await fetchTDDIDs()
+
+                // Get the NSManagedObjects and map them to TDD on the Main Thread
+                try await updateTDDArray(with: tddObjectIds, keyPath: \.fetchedTDDs)
+            } catch {
+                debug(.default, "\(DebuggingIdentifiers.failed) failed to fetch TDDs: \(error.localizedDescription)")
+            }
+        }
+    }
+
+    @MainActor private func updateTDDArray(
+        with IDs: [NSManagedObjectID],
+        keyPath: ReferenceWritableKeyPath<Home.StateModel, [TDD]>
+    ) async throws {
+        let tddObjects: [TDD] = try await CoreDataStack.shared
+            .getNSManagedObject(with: IDs, context: viewContext)
+            .compactMap { managedObject in
+                // Safely extract date and total as optional
+                let timestamp = managedObject.value(forKey: "date") as? Date
+                let totalDailyDose = (managedObject.value(forKey: "total") as? NSNumber)?.decimalValue
+                return TDD(totalDailyDose: totalDailyDose, timestamp: timestamp)
+            }
+        self[keyPath: keyPath] = tddObjects
+    }
+
+    private func fetchTDDIDs() async throws -> [NSManagedObjectID] {
+        let results = try await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TDDStored.self,
+            onContext: tddFetchContext,
+            predicate: NSPredicate.predicateForOneDayAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1,
+            propertiesToFetch: ["total", "date", "objectID"]
+        )
+
+        return await tddFetchContext.perform {
+            guard let fetchedResults = results as? [[String: Any]] else {
+                return []
+            }
+            return fetchedResults.compactMap { $0["objectID"] as? NSManagedObjectID }
+        }
+    }
+}

+ 10 - 0
Trio/Sources/Modules/Home/HomeStateModel.swift

@@ -86,6 +86,7 @@ extension Home {
         var fpusFromPersistence: [CarbEntryStored] = []
         var determinationsFromPersistence: [OrefDetermination] = []
         var enactedAndNonEnactedDeterminations: [OrefDetermination] = []
+        var fetchedTDDs: [TDD] = []
         var insulinFromPersistence: [PumpEventStored] = []
         var tempBasals: [PumpEventStored] = []
         var suspensions: [PumpEventStored] = []
@@ -125,6 +126,7 @@ extension Home {
         let carbsFetchContext = CoreDataStack.shared.newTaskContext()
         let fpuFetchContext = CoreDataStack.shared.newTaskContext()
         let determinationFetchContext = CoreDataStack.shared.newTaskContext()
+        let tddFetchContext = CoreDataStack.shared.newTaskContext()
         let pumpHistoryFetchContext = CoreDataStack.shared.newTaskContext()
         let overrideFetchContext = CoreDataStack.shared.newTaskContext()
         let tempTargetFetchContext = CoreDataStack.shared.newTaskContext()
@@ -179,6 +181,9 @@ extension Home {
                         self.setupDeterminationsArray()
                     }
                     group.addTask {
+                        self.setupTDDArray()
+                    }
+                    group.addTask {
                         self.setupInsulinArray()
                     }
                     group.addTask {
@@ -237,6 +242,11 @@ extension Home {
                 self.setupDeterminationsArray()
             }.store(in: &subscriptions)
 
+            coreDataPublisher?.filteredByEntityName("TDDStored").sink { [weak self] _ in
+                guard let self = self else { return }
+                self.setupTDDArray()
+            }.store(in: &subscriptions)
+
             coreDataPublisher?.filteredByEntityName("GlucoseStored").sink { [weak self] _ in
                 guard let self = self else { return }
                 self.setupGlucoseArray()

+ 1 - 1
Trio/Sources/Modules/Home/View/HomeRootView.swift

@@ -494,7 +494,7 @@ extension Home {
                         "TDD: " +
                             (
                                 Formatter.decimalFormatterWithTwoFractionDigits
-                                    .string(from: (state.determinationsFromPersistence.first?.totalDailyDose ?? 0) as NSNumber) ??
+                                    .string(from: (state.fetchedTDDs.first?.totalDailyDose ?? 0) as NSNumber) ??
                                     "0"
                             ) +
                             String(localized: " U", comment: "Insulin unit")

+ 35 - 2
Trio/Sources/Modules/Main/MainStateModel.swift

@@ -201,6 +201,37 @@ extension Main {
             SwiftMessages.show(config: config, view: view)
         }
 
+        /*
+          Reclassification is needed for Medtronic pumps for 'Pump error:' RileyLink related messages.
+          For details, see https://discord.com/channels/1020905149037813862/1338245444186279946/1343469793013141525.
+          Reclassification of Info type messages is based on APSManager.APSError enum values.
+          Currently, we only re-classify APSError.pumpError 'Pump error:' type to MessageType.error.
+          MessageType.error messagges are always displayed to the user and the user cannot disable them.
+          Other APSManager.APSError remain as MessageType.info which allows users to disable them
+          using the 'Trio Notification' -> 'Always Notify Algorithm' setting.
+         */
+        func reclassifyInfoNotification(_ message: inout MessageContent) {
+            if message.title == "" {
+                switch message.type {
+                case .info:
+                    if let errorIndex = message.content.range(of: "error", options: .caseInsensitive) {
+                        message.title = String(localized: "Error", comment: "Error title")
+                        if let errorPumpIndex = message.content.range(of: "Pump error:", options: .caseInsensitive) {
+                            message.type = .error
+                        }
+                    } else {
+                        message.title = String(localized: "Info", comment: "Info title")
+                    }
+                case .warning:
+                    message.title = String(localized: "Warning", comment: "Warning title")
+                case .error:
+                    message.title = String(localized: "Error", comment: "Error title")
+                case .other:
+                    message.title = String(localized: "Info", comment: "Info title")
+                }
+            }
+        }
+
         override func subscribe() {
             router.mainModalScreen
                 .map { $0?.modal(resolver: self.resolver!) }
@@ -223,8 +254,10 @@ extension Main {
                 .receive(on: DispatchQueue.main)
                 .sink { message in
                     guard !self.isApnPumpConfigAction(message) else { return }
-                    guard self.router.allowNotify(message, self.settingsManager.settings) else { return }
-                    self.showAlertMessage(message)
+                    var reclassifyMessage = message
+                    self.reclassifyInfoNotification(&reclassifyMessage)
+                    guard self.router.allowNotify(reclassifyMessage, self.settingsManager.settings) else { return }
+                    self.showAlertMessage(reclassifyMessage)
                 }
                 .store(in: &lifetime)
 

+ 10 - 18
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -727,14 +727,11 @@ extension Treatments.StateModel {
             let determinationObjects: [OrefDetermination] = try await CoreDataStack.shared
                 .getNSManagedObject(with: determinationObjectIDs, context: determinationFetchContext)
 
-            return await determinationFetchContext.perform {
-                guard let determinationObject = determinationObjects.first else {
-                    return nil
-                }
-
-                let eventualBG = determinationObject.eventualBG?.intValue
+            let determination = await determinationFetchContext.perform {
+                let determinationObject = determinationObjects.first
+                let eventualBG = determinationObject?.eventualBG?.intValue
 
-                let forecastsSet = determinationObject.forecasts ?? []
+                let forecastsSet = determinationObject?.forecasts ?? []
                 let predictions = Predictions(
                     iob: forecastsSet.extractValues(for: "iob"),
                     zt: forecastsSet.extractValues(for: "zt"),
@@ -747,7 +744,6 @@ extension Treatments.StateModel {
                     reason: "",
                     units: 0,
                     insulinReq: 0,
-                    eventualBG: eventualBG,
                     sensitivityRatio: 0,
                     rate: 0,
                     duration: 0,
@@ -756,23 +752,19 @@ extension Treatments.StateModel {
                     predictions: predictions.isEmpty ? nil : predictions,
                     carbsReq: 0,
                     temp: nil,
-                    bg: 0,
                     reservoir: 0,
-                    isf: 0,
-                    tdd: 0,
-                    insulin: nil,
-                    current_target: 0,
                     insulinForManualBolus: 0,
                     manualBolusErrorString: 0,
-                    minDelta: 0,
-                    expectedDelta: 0,
-                    minGuardBG: 0,
-                    minPredBG: 0,
-                    threshold: 0,
                     carbRatio: 0,
                     received: false
                 )
             }
+
+            guard !determinationObjects.isEmpty else {
+                return nil
+            }
+
+            return determination
         } catch {
             debug(
                 .default,

+ 25 - 10
Trio/Sources/Services/LiveActivity/Data/DataManager.swift

@@ -25,6 +25,7 @@ extension LiveActivityManager {
         }
     }
 
+    // TODO: extract logic or at least rename function appropiately
     func fetchAndMapDetermination() async throws -> DeterminationData? {
         let results = try await CoreDataStack.shared.fetchEntitiesAsync(
             ofType: OrefDetermination.self,
@@ -33,23 +34,37 @@ extension LiveActivityManager {
             key: "deliverAt",
             ascending: false,
             fetchLimit: 1,
-            propertiesToFetch: ["iob", "cob", "totalDailyDose", "currentTarget", "deliverAt"]
+            propertiesToFetch: ["iob", "cob", "currentTarget", "deliverAt"]
+        )
+
+        let tddResults = try await CoreDataStack.shared.fetchEntitiesAsync(
+            ofType: TDDStored.self,
+            onContext: context,
+            predicate: NSPredicate.predicateFor30MinAgo,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1,
+            propertiesToFetch: ["total"]
         )
 
         return try await context.perform {
-            guard let determinationResults = results as? [[String: Any]] else {
+            guard let determinationResults = results as? [[String: Any]], let tddResults = tddResults as? [[String: Any]] else {
                 throw CoreDataError.fetchError(function: #function, file: #file)
             }
 
-            return determinationResults.first.map {
-                DeterminationData(
-                    cob: ($0["cob"] as? Int) ?? 0,
-                    iob: ($0["iob"] as? NSDecimalNumber)?.decimalValue ?? 0,
-                    tdd: ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue ?? 0,
-                    target: ($0["currentTarget"] as? NSDecimalNumber)?.decimalValue ?? 0,
-                    date: $0["deliverAt"] as? Date ?? nil
-                )
+            guard let determination = determinationResults.first else {
+                return nil
             }
+
+            let tddValue = (tddResults.first?["total"] as? NSDecimalNumber)?.decimalValue ?? 0
+
+            return DeterminationData(
+                cob: (determination["cob"] as? Int) ?? 0,
+                iob: (determination["iob"] as? NSDecimalNumber)?.decimalValue ?? 0,
+                tdd: tddValue,
+                target: (determination["currentTarget"] as? NSDecimalNumber)?.decimalValue ?? 0,
+                date: determination["deliverAt"] as? Date ?? nil
+            )
         }
     }
 

+ 3 - 16
Trio/Sources/Shortcuts/LiveActivity/RestartLiveActivityIntentRequest.swift

@@ -11,18 +11,9 @@ import UIKit
     /// - Throws: An error if the restart process fails.
     /// - Returns: Void upon successful restart.
     @MainActor func performRestart() async throws {
-        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
-
         // Start background task
-        backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Restart Live Activity") {
-            Task { @MainActor in
-                if backgroundTaskID != .invalid {
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                    backgroundTaskID = .invalid
-                    debug(.default, "Background task expired and ended.")
-                }
-            }
-        }
+        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+        backgroundTaskID = startBackgroundTask(withName: "Restart Live Activity")
 
         guard backgroundTaskID != .invalid else {
             debug(.default, "Failed to start background task.")
@@ -34,10 +25,6 @@ import UIKit
         await liveActivityManager.restartActivityFromLiveActivityIntent()
 
         // Ensure background task ends properly
-        if backgroundTaskID != .invalid {
-            UIApplication.shared.endBackgroundTask(backgroundTaskID)
-            debug(.default, "Background task ended successfully.")
-            backgroundTaskID = .invalid
-        }
+        endBackgroundTaskSafely(&backgroundTaskID, taskName: "Restart Live Activity")
     }
 }

+ 15 - 2
Trio/Sources/Shortcuts/Override/ApplyOverridePresetIntent.swift

@@ -1,24 +1,28 @@
 import AppIntents
 import Foundation
 
+/// An App Intent that allows users to activate an override preset through the Shortcuts app.
 struct ApplyOverridePresetIntent: AppIntent {
-    // Title of the action in the Shortcuts app
+    /// The title displayed for this action in the Shortcuts app.
     static var title = LocalizedStringResource("Activate an override", table: "ShortcutsDetail")
 
-    // Description of the action in the Shortcuts app
+    /// The description displayed for this action in the Shortcuts app.
     static var description = IntentDescription(.init("Activate an override", table: "ShortcutsDetail"))
 
+    /// The override preset to be applied.
     @Parameter(
         title: LocalizedStringResource("Override", table: "ShortcutsDetail"),
         description: LocalizedStringResource("Override choice", table: "ShortcutsDetail")
     ) var preset: OverridePreset?
 
+    /// A boolean parameter that determines whether confirmation is required before applying the override.
     @Parameter(
         title: LocalizedStringResource("Confirm Before applying", table: "ShortcutsDetail"),
         description: LocalizedStringResource("If toggled, you will need to confirm before applying", table: "ShortcutsDetail"),
         default: true
     ) var confirmBeforeApplying: Bool
 
+    /// Defines the summary format shown in the Shortcuts app when configuring this intent.
     static var parameterSummary: some ParameterSummary {
         When(\ApplyOverridePresetIntent.$confirmBeforeApplying, .equalTo, true, {
             Summary("Applying \(\.$preset) override", table: "ShortcutsDetail") {
@@ -31,12 +35,18 @@ struct ApplyOverridePresetIntent: AppIntent {
         })
     }
 
+    /// Executes the intent to apply the selected override preset.
+    ///
+    /// - Returns: A dialog indicating whether the override was successfully applied or failed.
+    /// - Throws: An error if an issue occurs during execution.
     @MainActor func perform() async throws -> some ProvidesDialog {
         do {
+            // Determine which preset to apply
             let presetToApply: OverridePreset
             if let preset = preset {
                 presetToApply = preset
             } else {
+                // Request user selection if no preset is provided
                 presetToApply = try await $preset.requestDisambiguation(
                     among: await OverridePresetsIntentRequest().fetchAndProcessOverrides(),
                     dialog: IntentDialog(LocalizedStringResource("Select override", table: "ShortcutsDetail"))
@@ -44,6 +54,8 @@ struct ApplyOverridePresetIntent: AppIntent {
             }
 
             let displayName: String = presetToApply.name
+
+            // Request confirmation before applying if required
             if confirmBeforeApplying {
                 try await requestConfirmation(
                     result: .result(
@@ -55,6 +67,7 @@ struct ApplyOverridePresetIntent: AppIntent {
                 )
             }
 
+            // Apply the override and return the appropriate dialog message
             if await OverridePresetsIntentRequest().enactOverride(presetToApply) {
                 return .result(
                     dialog: IntentDialog(

+ 11 - 8
Trio/Sources/Shortcuts/Override/CancelOverrideIntent.swift

@@ -1,19 +1,22 @@
 import AppIntents
 import Foundation
 
+/// An App Intent that allows users to cancel an active override through the Shortcuts app.
 struct CancelOverrideIntent: AppIntent {
-    // Title of the action in the Shortcuts app
+    /// The title displayed for this action in the Shortcuts app.
     static var title = LocalizedStringResource("Cancel override", table: "ShortcutsDetail")
 
-    // Description of the action in the Shortcuts app
+    /// The description displayed for this action in the Shortcuts app.
     static var description = IntentDescription(.init("Cancel an active override", table: "ShortcutsDetail"))
 
+    /// Performs the intent action to cancel an active override.
+    ///
+    /// - Returns: A confirmation dialog indicating the override has been canceled.
+    /// - Throws: An error if the cancellation process fails.
     @MainActor func perform() async throws -> some ProvidesDialog {
-        do {
-            await OverridePresetsIntentRequest().cancelOverride()
-            return .result(
-                dialog: IntentDialog(LocalizedStringResource("Override canceled", table: "ShortcutsDetail"))
-            )
-        }
+        await OverridePresetsIntentRequest().cancelOverride()
+        return .result(
+            dialog: IntentDialog(LocalizedStringResource("Override canceled", table: "ShortcutsDetail"))
+        )
     }
 }

+ 17 - 0
Trio/Sources/Shortcuts/Override/OverridePresetEntity.swift

@@ -3,24 +3,41 @@ import Foundation
 import Intents
 import Swinject
 
+/// Represents an override preset that can be used in the app.
 struct OverridePreset: AppEntity, Identifiable {
+    /// Default query instance for fetching override presets.
     static var defaultQuery = OverridePresetsQuery()
 
+    /// Unique identifier for the override preset.
     var id: String
+
+    /// Name of the override preset.
     var name: String
 
+    /// Provides a display representation for the override preset.
     var displayRepresentation: DisplayRepresentation {
         DisplayRepresentation(title: "\(name)")
     }
 
+    /// Representation for the entity type when displayed in UI.
     static var typeDisplayRepresentation: TypeDisplayRepresentation = "Override"
 }
 
+/// Query structure for fetching override presets in an App Intent.
 struct OverridePresetsQuery: EntityQuery {
+    /// Fetches a list of override presets matching the given identifiers.
+    ///
+    /// - Parameter identifiers: A list of override preset IDs to fetch.
+    /// - Returns: An array of `OverridePreset` objects matching the given IDs.
+    /// - Throws: An error if the fetch operation fails.
     func entities(for identifiers: [OverridePreset.ID]) async throws -> [OverridePreset] {
         try await OverridePresetsIntentRequest().fetchIDs(identifiers)
     }
 
+    /// Fetches a list of suggested override presets.
+    ///
+    /// - Returns: An array of available `OverridePreset` objects.
+    /// - Throws: An error if fetching fails.
     func suggestedEntities() async throws -> [OverridePreset] {
         try await OverridePresetsIntentRequest().fetchAndProcessOverrides()
     }

+ 88 - 65
Trio/Sources/Shortcuts/Override/OverridePresetsIntentRequest.swift

@@ -9,6 +9,14 @@ import UIKit
         case noActiveOverride
     }
 
+    private var intentSuccess: Bool = false
+
+    /**
+     Fetches and processes override presets from Core Data.
+
+     - Returns: An array of `OverridePreset` objects.
+     - Throws: An error if fetching fails or Core Data operations fail.
+     */
     func fetchAndProcessOverrides() async throws -> [OverridePreset] {
         do {
             // Fetch all Override Presets via OverrideStorage
@@ -35,6 +43,13 @@ import UIKit
         }
     }
 
+    /**
+     Fetches override presets by their IDs.
+
+     - Parameter uuid: An array of `OverridePreset.ID` values to fetch.
+     - Returns: An array of `OverridePreset` objects matching the provided IDs.
+     - Throws: `overridePresetsError.noTempOverrideFound` if no presets are found.
+     */
     func fetchIDs(_ uuid: [OverridePreset.ID]) async throws -> [OverridePreset] {
         try await coredataContext.perform {
             let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
@@ -64,6 +79,13 @@ import UIKit
         }
     }
 
+    /**
+     Fetches the Core Data `NSManagedObjectID` for a given `OverridePreset`.
+
+     - Parameter preset: The `OverridePreset` for which to fetch the object ID.
+     - Returns: The corresponding `NSManagedObjectID`.
+     - Throws: `overridePresetsError.noTempOverrideFound` if the preset is not found.
+     */
     private func fetchOverrideID(_ preset: OverridePreset) async throws -> NSManagedObjectID {
         let fetchRequest: NSFetchRequest<OverrideStored> = OverrideStored.fetchRequest()
         fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id)
@@ -81,103 +103,96 @@ import UIKit
         }
     }
 
+    /**
+     Enacts an override preset by enabling it in Core Data and notifying the system.
+
+     - Parameter preset: The `OverridePreset` to enact.
+     - Returns: A boolean indicating whether the override was successfully enacted.
+     */
     @MainActor func enactOverride(_ preset: OverridePreset) async -> Bool {
-        // Start background task
+        debug(.default, "Enacting override: \(preset.name)")
+        intentSuccess = false
+
         var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
-        backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Upload") {
-            guard backgroundTaskID != .invalid else { return }
-            Task {
-                UIApplication.shared.endBackgroundTask(backgroundTaskID)
-            }
-            backgroundTaskID = .invalid
-        }
+        backgroundTaskID = startBackgroundTask(withName: "Override Enact")
 
-        defer {
-            if backgroundTaskID != .invalid {
-                Task {
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
+        await disableAllActiveOverrides(shouldStartBackgroundTask: false)
 
         do {
-            // Get NSManagedObjectID of Preset
             let overrideID = try await fetchOverrideID(preset)
             guard let overrideObject = try viewContext.existingObject(with: overrideID) as? OverrideStored else {
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Enact")
                 throw overridePresetsError.noTempOverrideFound
             }
 
-            // Enable Override
             overrideObject.enabled = true
             overrideObject.date = Date()
             overrideObject.isUploadedToNS = false
 
-            // Disable previous overrides if necessary
-            await disableAllActiveOverrides(except: overrideID, createOverrideRunEntry: true, shouldStartBackgroundTask: false)
-
             if viewContext.hasChanges {
+                debug(.default, "Saving changes...")
                 try viewContext.save()
+                debug(.default, "Waiting for notification...")
                 Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
                 await awaitNotification(.didUpdateOverrideConfiguration)
-                return true
+                debug(.default, "Notification received, continuing...")
+                intentSuccess = true
             }
-            return false
+
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Enact")
+            debug(.default, "Finished. Override enacted via Shortcut.")
+            return intentSuccess
         } catch {
             debug(
                 .default,
                 "\(DebuggingIdentifiers.failed) Failed to enact override: \(error.localizedDescription)"
             )
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Enact")
             return false
         }
     }
 
+    /**
+     Cancels all active overrides asynchronously.
+     */
     func cancelOverride() async {
-        await disableAllActiveOverrides(createOverrideRunEntry: true, shouldStartBackgroundTask: true)
+        await disableAllActiveOverrides(shouldStartBackgroundTask: true)
     }
 
-    @MainActor func disableAllActiveOverrides(
-        except overrideID: NSManagedObjectID? = nil,
-        createOverrideRunEntry: Bool,
-        shouldStartBackgroundTask: Bool = true
-    ) async {
-        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+    /**
+     Disables all active overrides and optionally starts a background task.
 
-        if shouldStartBackgroundTask {
-            // Start background task
-            backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "Override Cancel") {
-                guard backgroundTaskID != .invalid else { return }
-                Task {
-                    // End background task when the time is about to expire
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
+     - Parameter shouldStartBackgroundTask: A boolean indicating whether to start a background task.
+     */
+    @MainActor func disableAllActiveOverrides(shouldStartBackgroundTask: Bool) async {
+        debug(.default, "Disabling all active overrides")
+        var backgroundTaskID: UIBackgroundTaskIdentifier?
 
-        // Defer block to end background task when function exits, only if it was started
-        defer {
-            if shouldStartBackgroundTask, backgroundTaskID != .invalid {
-                Task {
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
+        if shouldStartBackgroundTask {
+            debug(.default, "Starting background task for override cancel")
+            backgroundTaskID = .invalid
+            backgroundTaskID = startBackgroundTask(withName: "Override Cancel")
         }
 
         do {
             // Get NSManagedObjectID of all active overrides
-            let ids = try await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0) // 0 = no fetch limit
-            // Fetch existing OverrideStored objects
+            let ids = try await overrideStorage.loadLatestOverrideConfigurations(fetchLimit: 0)
             let results = try ids.compactMap { id in
                 try self.viewContext.existingObject(with: id) as? OverrideStored
             }
 
             // Return early if no results
-            guard !results.isEmpty else { return }
+            guard !results.isEmpty else {
+                debug(.default, "No active overrides to cancel… returning early")
+                if var backgroundTaskID = backgroundTaskID {
+                    debug(.default, "Ending background task for override cancel")
+                    endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Cancel")
+                }
+                return
+            }
 
             // Create OverrideRunStored entry if needed
-            if createOverrideRunEntry, let canceledOverride = results.first {
+            if let canceledOverride = results.first {
                 let newOverrideRunStored = OverrideRunStored(context: viewContext)
                 newOverrideRunStored.id = UUID()
                 newOverrideRunStored.name = canceledOverride.name
@@ -190,30 +205,38 @@ import UIKit
                 newOverrideRunStored.isUploadedToNS = false
             }
 
-            // Disable all overrides except the one specified
+            // Disable all active overrides
             for overrideToCancel in results {
-                if overrideToCancel.objectID != overrideID {
-                    overrideToCancel.enabled = false
-                    overrideToCancel.isUploadedToNS = false
-                }
+                let endTime = overrideToCancel.date?
+                    .addingTimeInterval(TimeInterval(truncating: overrideToCancel.duration ?? 0))
+
+                debugPrint(
+                    "Disabling override: \(overrideToCancel.name ?? "Unnamed") with end time: \(endTime?.description ?? "Unknown")"
+                )
+                overrideToCancel.enabled = false
+                overrideToCancel.isUploadedToNS = false
             }
 
             if viewContext.hasChanges {
                 try viewContext.save()
-
-                // Update State variables in OverrideView
+                debug(.default, "Waiting for notification...")
                 Foundation.NotificationCenter.default.post(name: .willUpdateOverrideConfiguration, object: nil)
+                await awaitNotification(.didUpdateOverrideConfiguration)
+                debug(.default, "Notification received, continuing...")
             }
 
-            // Await the notification
-            print("Waiting for notification...")
-            await awaitNotification(.didUpdateOverrideConfiguration)
-            print("Notification received, continuing...")
-
+            if var backgroundTaskID = backgroundTaskID {
+                debug(.default, "Ending background task for override cancel")
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Cancel")
+            }
         } catch {
             debugPrint(
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Overrides with error: \(error.localizedDescription)"
             )
+            if var backgroundTaskID = backgroundTaskID {
+                debug(.default, "Ending background task for override cancel")
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "Override Cancel")
+            }
         }
     }
 }

+ 22 - 4
Trio/Sources/Shortcuts/TempPresets/ApplyTempPresetIntent.swift

@@ -1,21 +1,25 @@
 import AppIntents
 import Foundation
 
+/// An App Intent that allows users to apply a temporary target preset through the Shortcuts app.
 struct ApplyTempPresetIntent: AppIntent {
-    // Title of the action in the Shortcuts app
+    /// The title displayed for this action in the Shortcuts app.
     static var title: LocalizedStringResource = "Apply a Temporary Target"
 
-    // Description of the action in the Shortcuts app
+    /// The description displayed for this action in the Shortcuts app.
     static var description = IntentDescription("Enable a Temporary Target")
 
+    /// The temporary target preset to be applied.
     @Parameter(title: "Preset") var preset: TempPreset?
 
+    /// A boolean parameter that determines whether confirmation is required before applying the temporary target.
     @Parameter(
         title: "Confirm Before applying",
         description: "If toggled, you will need to confirm before applying",
         default: true
     ) var confirmBeforeApplying: Bool
 
+    /// Defines the summary format shown in the Shortcuts app when configuring this intent.
     static var parameterSummary: some ParameterSummary {
         When(\ApplyTempPresetIntent.$confirmBeforeApplying, .equalTo, true, {
             Summary("Applying \(\.$preset)") {
@@ -28,23 +32,34 @@ struct ApplyTempPresetIntent: AppIntent {
         })
     }
 
+    /// Converts a decimal duration value into a formatted time string.
+    ///
+    /// - Parameter decimal: The duration value in decimal format.
+    /// - Returns: A string representing the formatted time in hours and minutes.
     private func decimalToTimeFormattedString(decimal: Decimal) -> String {
-        let timeInterval = TimeInterval(decimal * 60) // seconds
+        let timeInterval = TimeInterval(decimal * 60) // Convert minutes to seconds
 
         let formatter = DateComponentsFormatter()
         formatter.allowedUnits = [.hour, .minute]
-        formatter.unitsStyle = .brief // example: 1h 10 min
+        formatter.unitsStyle = .brief // Example: "1h 10m"
 
         return formatter.string(from: timeInterval) ?? ""
     }
 
+    /// Executes the intent to apply the selected temporary target preset.
+    ///
+    /// - Returns: A dialog indicating whether the temporary target was successfully applied or failed.
+    /// - Throws: An error if an issue occurs during execution.
     @MainActor func perform() async throws -> some ProvidesDialog {
         do {
             let intentRequest = TempPresetsIntentRequest()
             let presetToApply: TempPreset
+
+            // Determine which preset to apply
             if let preset = preset {
                 presetToApply = preset
             } else {
+                // Request user selection if no preset is provided
                 presetToApply = try await $preset.requestDisambiguation(
                     among: intentRequest.fetchAndProcessTempTargets(),
                     dialog: "Select Temporary Target"
@@ -52,12 +67,15 @@ struct ApplyTempPresetIntent: AppIntent {
             }
 
             let displayName: String = presetToApply.name
+
+            // Request confirmation before applying if required
             if confirmBeforeApplying {
                 try await requestConfirmation(
                     result: .result(dialog: "Confirm to apply Temporary Target '\(displayName)'")
                 )
             }
 
+            // Apply the temporary target and return the appropriate dialog message
             if await intentRequest.enactTempTarget(presetToApply) {
                 return .result(
                     dialog: IntentDialog(

+ 7 - 2
Trio/Sources/Shortcuts/TempPresets/CancelTempPresetIntent.swift

@@ -1,13 +1,18 @@
 import AppIntents
 import Foundation
 
+/// An App Intent that allows users to cancel an active temporary target through the Shortcuts app.
 struct CancelTempPresetIntent: AppIntent {
-    // Title of the action in the Shortcuts app
+    /// The title displayed for this action in the Shortcuts app.
     static var title: LocalizedStringResource = "Cancel a Temporary Target"
 
-    // Description of the action in the Shortcuts app
+    /// The description displayed for this action in the Shortcuts app.
     static var description = IntentDescription("Cancel Temporary Target.")
 
+    /// Performs the intent action to cancel an active temporary target.
+    ///
+    /// - Returns: A confirmation dialog indicating that the temporary target has been canceled.
+    /// - Throws: An error if the cancellation process fails.
     @MainActor func perform() async throws -> some ProvidesDialog {
         await TempPresetsIntentRequest().cancelTempTarget()
         return .result(

+ 23 - 0
Trio/Sources/Shortcuts/TempPresets/TempPresetIntent.swift

@@ -3,27 +3,50 @@ import Foundation
 import Intents
 import Swinject
 
+/// Represents a temporary target preset that can be used in the app.
 struct TempPreset: AppEntity, Identifiable {
+    /// Default query instance for fetching temporary presets.
     static var defaultQuery = TempPresetsQuery()
 
+    /// Unique identifier for the temporary preset.
     var id: UUID
+
+    /// Name of the temporary preset.
     var name: String
+
+    /// The upper target value for the preset, if applicable.
     var targetTop: Decimal?
+
+    /// The lower target value for the preset, if applicable.
     var targetBottom: Decimal?
+
+    /// The duration of the temporary preset in minutes.
     var duration: Decimal
 
+    /// Provides a display representation for the temporary preset.
     var displayRepresentation: DisplayRepresentation {
         DisplayRepresentation(title: "\(name)")
     }
 
+    /// Representation for the entity type when displayed in UI.
     static var typeDisplayRepresentation: TypeDisplayRepresentation = "Presets"
 }
 
+/// Query structure for fetching temporary target presets in an App Intent.
 struct TempPresetsQuery: EntityQuery {
+    /// Fetches a list of temporary target presets matching the given identifiers.
+    ///
+    /// - Parameter identifiers: A list of preset IDs to fetch.
+    /// - Returns: An array of `TempPreset` objects matching the given IDs.
+    /// - Throws: An error if the fetch operation fails.
     func entities(for identifiers: [TempPreset.ID]) async throws -> [TempPreset] {
         await TempPresetsIntentRequest().fetchIDs(identifiers)
     }
 
+    /// Fetches a list of suggested temporary target presets.
+    ///
+    /// - Returns: An array of available `TempPreset` objects.
+    /// - Throws: An error if fetching fails.
     func suggestedEntities() async throws -> [TempPreset] {
         try await TempPresetsIntentRequest().fetchAndProcessTempTargets()
     }

+ 94 - 92
Trio/Sources/Shortcuts/TempPresets/TempPresetsIntentRequest.swift

@@ -2,12 +2,21 @@ import CoreData
 import Foundation
 import UIKit
 
+/// Handles intent requests related to temporary presets, such as fetching, enacting, and canceling temp targets.
 final class TempPresetsIntentRequest: BaseIntentsRequest {
+    /// Enum representing possible errors related to temporary presets.
     enum TempPresetsError: Error {
         case noTempTargetFound
         case noDurationDefined
     }
 
+    /// Tracks whether the intent execution was successful.
+    private var intentSuccess: Bool = false
+
+    /// Fetches and processes all available temporary target presets.
+    ///
+    /// - Returns: An array of `TempPreset` objects.
+    /// - Throws: An error if fetching or processing fails.
     func fetchAndProcessTempTargets() async throws -> [TempPreset] {
         // Fetch all Temp Target Presets via TempTargetStorage
         let allTempTargetPresetsIDs = try await tempTargetsStorage.fetchForTempTargetPresets()
@@ -38,6 +47,10 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
         }
     }
 
+    /// Fetches temporary target presets based on the given identifiers.
+    ///
+    /// - Parameter uuid: An array of preset IDs to fetch.
+    /// - Returns: An array of `TempPreset` objects.
     func fetchIDs(_ uuid: [TempPreset.ID]) async -> [TempPreset] {
         await coredataContext.perform {
             let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
@@ -67,6 +80,10 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
         }
     }
 
+    /// Fetches the `NSManagedObjectID` for a given `TempPreset`.
+    ///
+    /// - Parameter preset: The `TempPreset` to find.
+    /// - Returns: The `NSManagedObjectID` of the temp target if found, otherwise `nil`.
     private func fetchTempTargetID(_ preset: TempPreset) async -> NSManagedObjectID? {
         let fetchRequest: NSFetchRequest<TempTargetStored> = TempTargetStored.fetchRequest()
         fetchRequest.predicate = NSPredicate(format: "id == %@", preset.id.uuidString)
@@ -84,55 +101,43 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
         }
     }
 
+    /// Enacts a temporary target preset by updating Core Data and notifying relevant components.
+    ///
+    /// - Parameter preset: The `TempPreset` to apply.
+    /// - Returns: `true` if successfully enacted, otherwise `false`.
     @MainActor func enactTempTarget(_ preset: TempPreset) async -> Bool {
+        debug(.default, "Enacting Temp Target: \(preset.name)")
+        intentSuccess = false
+
         // Start background task
         var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
-        backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "TempTarget Upload") {
-            guard backgroundTaskID != .invalid else { return }
-            Task {
-                // End background task when the time is about to expire
-                UIApplication.shared.endBackgroundTask(backgroundTaskID)
-            }
-            backgroundTaskID = .invalid
-        }
+        backgroundTaskID = startBackgroundTask(withName: "TempTarget Enact")
 
-        // Defer block to end background task when function exits
-        defer {
-            if backgroundTaskID != .invalid {
-                Task {
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
+        // Disable previous temp targets if necessary, without starting a background task
+        await disableAllActiveTempTargets(shouldStartBackgroundTask: false)
 
         do {
             // Get NSManagedObjectID of Preset
             guard let tempTargetID = await fetchTempTargetID(preset),
                   let tempTargetObject = try viewContext.existingObject(with: tempTargetID) as? TempTargetStored
-            else { return false }
+            else {
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Enact")
+                throw TempPresetsError.noTempTargetFound
+            }
 
             // Enable TempTarget
             tempTargetObject.enabled = true
             tempTargetObject.date = Date()
             tempTargetObject.isUploadedToNS = false
 
-            // Disable previous overrides if necessary, without starting a background task
-            await disableAllActiveTempTargets(
-                except: tempTargetID,
-                createTempTargetRunEntry: true,
-                shouldStartBackgroundTask: false
-            )
-
             if viewContext.hasChanges {
+                debug(.default, "Saving changes...")
                 try viewContext.save()
-
+                debug(.default, "Waiting for notification...")
                 // Update State variables in TempTargetView
                 Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
 
-                // Await the notification
-                print("Waiting for notification...")
-
+                // Prepare JSON for oref
                 guard let tempTargetDate = tempTargetObject.date, let tempTarget = tempTargetObject.target,
                       let tempTargetDuration = tempTargetObject.duration else { return false }
 
@@ -148,108 +153,105 @@ final class TempPresetsIntentRequest: BaseIntentsRequest {
                     enabled: tempTargetObject.enabled,
                     halfBasalTarget: tempTargetObject.halfBasalTarget as Decimal?
                 )
-
                 // Save the temp targets to JSON so that they get used by oref
                 tempTargetsStorage.saveTempTargetsToStorage([tempTargetToStoreAsJSON])
 
                 await awaitNotification(.didUpdateTempTargetConfiguration)
-                print("Notification received, continuing...")
 
-                return true
+                debug(.default, "Notification received, continuing...")
+                intentSuccess = true
             }
+
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Enact")
+
+            debug(.default, "Finished. Temp Target enacted via Shortcut.")
+
+            return intentSuccess
         } catch {
-            // Handle error and ensure background task is ended
-            debugPrint("Failed to enact TempTarget: \(error.localizedDescription)")
-        }
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Temp Target with error: \(error.localizedDescription)"
+            )
 
-        return false
+            endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Enact")
+
+            intentSuccess = false
+            return intentSuccess
+        }
     }
 
+    /// Cancels an active temporary target.
     func cancelTempTarget() async {
-        await disableAllActiveTempTargets(createTempTargetRunEntry: true, shouldStartBackgroundTask: true)
+        await disableAllActiveTempTargets(shouldStartBackgroundTask: true)
         tempTargetsStorage.saveTempTargetsToStorage([TempTarget.cancel(at: Date().addingTimeInterval(-1))])
     }
 
-    @MainActor func disableAllActiveTempTargets(
-        except tempTargetID: NSManagedObjectID? = nil,
-        createTempTargetRunEntry: Bool,
-        shouldStartBackgroundTask: Bool = true
-    ) async {
-        var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
+    /// Disables all active temporary targets.
+    ///
+    /// - Parameter shouldStartBackgroundTask: A flag indicating whether a background task should be started.
+    @MainActor func disableAllActiveTempTargets(shouldStartBackgroundTask: Bool) async {
+        var backgroundTaskID: UIBackgroundTaskIdentifier?
 
         if shouldStartBackgroundTask {
-            // Start background task
-            backgroundTaskID = UIApplication.shared.beginBackgroundTask(withName: "TempTarget Cancel") {
-                guard backgroundTaskID != .invalid else { return }
-                Task {
-                    // End background task when the time is about to expire
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
-        }
-
-        // Defer block to end background task when function exits, only if it was started
-        defer {
-            if shouldStartBackgroundTask, backgroundTaskID != .invalid {
-                Task {
-                    UIApplication.shared.endBackgroundTask(backgroundTaskID)
-                }
-                backgroundTaskID = .invalid
-            }
+            debug(.default, "Starting background task for temp target cancel")
+            backgroundTaskID = .invalid
+            backgroundTaskID = startBackgroundTask(withName: "TempTarget Cancel")
         }
 
         do {
-            // Get NSManagedObjectID of all active temp Targets
+            // Fetch active temp targets
             let ids = try await tempTargetsStorage.loadLatestTempTargetConfigurations(fetchLimit: 0)
-            // Fetch existing OverrideStored objects
             let results = try ids.compactMap { id in
                 try self.viewContext.existingObject(with: id) as? TempTargetStored
             }
 
-            // Return early if no results
-            guard !results.isEmpty else { return }
-
-            // Create TempTargetRunStored entry if needed
-            if createTempTargetRunEntry {
-                // Use the first temp target to create a new TempTargetRunStored entry
-                if let canceledTempTarget = results.first {
-                    let newTempTargetRunStored = TempTargetRunStored(context: viewContext)
-                    newTempTargetRunStored.id = UUID()
-                    newTempTargetRunStored.name = canceledTempTarget.name
-                    newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
-                    newTempTargetRunStored.endDate = Date()
-                    newTempTargetRunStored
-                        .target = canceledTempTarget.target ?? 0
-                    newTempTargetRunStored.tempTarget = canceledTempTarget
-                    newTempTargetRunStored.isUploadedToNS = false
+            guard !results.isEmpty else {
+                debug(.default, "No active temp targets to cancel... returning early")
+
+                if var backgroundTaskID = backgroundTaskID {
+                    debug(.default, "Ending background task for temp target cancel")
+                    endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Cancel")
                 }
+                return
+            }
+
+            // Create a new `TempTargetRunStored` entry
+            if let canceledTempTarget = results.first {
+                let newTempTargetRunStored = TempTargetRunStored(context: viewContext)
+                newTempTargetRunStored.id = UUID()
+                newTempTargetRunStored.name = canceledTempTarget.name
+                newTempTargetRunStored.startDate = canceledTempTarget.date ?? .distantPast
+                newTempTargetRunStored.endDate = Date()
+                newTempTargetRunStored.target = canceledTempTarget.target ?? 0
+                newTempTargetRunStored.tempTarget = canceledTempTarget
+                newTempTargetRunStored.isUploadedToNS = false
             }
 
-            // Disable all override except the one with overrideID
+            // Disable all temp targets
             for tempTargetToCancel in results {
-                if tempTargetToCancel.objectID != tempTargetID {
-                    tempTargetToCancel.enabled = false
-                    tempTargetToCancel.isUploadedToNS = false
-                }
+                tempTargetToCancel.enabled = false
+                tempTargetToCancel.isUploadedToNS = false
             }
 
             if viewContext.hasChanges {
                 try viewContext.save()
-
-                // Update State variables in OverrideView
+                debug(.default, "Waiting for notification...")
                 Foundation.NotificationCenter.default.post(name: .willUpdateTempTargetConfiguration, object: nil)
+                await awaitNotification(.didUpdateTempTargetConfiguration)
+                debug(.default, "Notification received, continuing...")
             }
 
-            // Await the notification
-            print("Waiting for notification...")
-            await awaitNotification(.didUpdateTempTargetConfiguration)
-            print("Notification received, continuing...")
-
+            if var backgroundTaskID = backgroundTaskID {
+                debug(.default, "Ending background task for temp target cancel")
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Cancel")
+            }
         } catch {
             debugPrint(
                 "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Temp Targets with error: \(error.localizedDescription)"
             )
+            if var backgroundTaskID = backgroundTaskID {
+                debug(.default, "Ending background task for temp target cancel")
+                endBackgroundTaskSafely(&backgroundTaskID, taskName: "TempTarget Cancel")
+            }
         }
     }
 }

+ 3 - 1
oref0_source_version.txt

@@ -1,6 +1,8 @@
-oref0 branch: maxAbsorptionTime - git version: a542ed3
+oref0 branch: removeTDDCalc - git version: 73d6d6c
 
 Last commits:
+73d6d6c webpack for Trio-dev
+5c6ce4b remove TDD calculation
 a542ed3 use guarded maxAbsorptionTime
 4c77757 Revert "reduce dynISF logging"
 1567c76 use variable name maxMealAbsorptionTime

+ 2 - 368
trio-oref/lib/determine-basal/determine-basal.js

@@ -154,9 +154,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
     const isfAndCr = oref2_variables.isfAndCr;
     const isf = oref2_variables.isf;
     const cr_ = oref2_variables.cr;
-    const smbIsScheduledOff = oref2_variables.smbIsScheduledOff;
-    const start = oref2_variables.start;
-    var end = oref2_variables.end;
     const smbMinutes = oref2_variables.smbMinutes;
     const uamMinutes = oref2_variables.uamMinutes;
 
@@ -172,21 +169,10 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
     }
 
     // tdd past 24 hours
-    var pumpData = 0;
-    var logtdd = "";
-    var logBasal = "";
-    var logBolus = "";
-    var logTempBasal = "";
-    var dataLog = "";
     var logOutPut = "";
     var tddReason = "";
-    var current = 0;
-    var tdd = 0;
-    var insulin = 0;
-    var tempInsulin = 0;
-    var bolusInsulin = 0;
-    var scheduledBasalInsulin = 0;
-    var quota = 0;
+    var tdd = oref2_variables.currentTDD;
+
     const weightedAverage = oref2_variables.weightedAverage;
     var overrideFactor = 1;
     var sensitivity = profile.sens;
@@ -204,55 +190,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
     const weightPercentage = profile.weightPercentage;
     const average_total_data = oref2_variables.average_total_data;
 
-    function addTimeToDate(objDate, _hours) {
-        var ms = objDate.getTime();
-        var add_ms = _hours * 36e5;
-        var newDateObj = new Date(ms + add_ms);
-        return newDateObj;
-    }
-
-    function subtractTimeFromDate(date, hours_) {
-        var ms_ = date.getTime();
-        var add_ms_ = hours_ * 36e5;
-        var new_date = new Date(ms_ - add_ms_);
-        return new_date;
-    }
-
-    function accountForIncrements(insulin) {
-        // If you have not set this to.0.1 in Trio settings, this will be set to 0.05 (Omnipods) in code.
-        var minimalDose = profile.bolus_increment;
-        if (minimalDose != 0.1) {
-            minimalDose = 0.05;
-        }
-        var incrementsRaw = insulin / minimalDose;
-        if (incrementsRaw >= 1) {
-            var incrementsRounded = Math.floor(incrementsRaw);
-            return round(incrementsRounded * minimalDose, 5);
-        } else { return 0; }
-    }
-
-    function makeBaseString(base_timeStamp) {
-        function addZero(i) {
-            if (i < 10) { i = "0" + i }
-            return i;
-        }
-        let hour = addZero(base_timeStamp.getHours());
-        let minutes = addZero(base_timeStamp.getMinutes());
-        let seconds = "00";
-        let string = hour + ":" + minutes + ":" + seconds;
-        return string;
-    }
-
-    function timeDifferenceOfString(string1, string2) {
-        //Base time strings are in "00:00:00" format
-        var time1 = new Date("1/1/1999 " + string1);
-        var time2 = new Date("1/1/1999 " + string2);
-        var ms1 = time1.getTime();
-        var ms2 = time2.getTime();
-        var difference = (ms1 - ms2) / 36e5;
-        return difference;
-    }
-
     // In case the autosens.min/max limits are reversed:
     const minLimitChris = Math.min(profile.autosens_min, profile.autosens_max);
     const maxLimitChris = Math.max(profile.autosens_min, profile.autosens_max);
@@ -263,306 +200,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
         console.log("Dynamic ISF disabled due to current autosens settings");
     }
 
-    function calcScheduledBasalInsulin(lastRealTempTime, addedLastTempTime) {
-        var totalInsulin = 0;
-        var old = addedLastTempTime;
-        var totalDuration = (lastRealTempTime - addedLastTempTime) / 36e5;
-        var basDuration = 0;
-        var totalDurationCheck = totalDuration;
-        var durationCurrentSchedule = 0;
-
-        do {
-
-            if (totalDuration > 0) {
-
-                var baseTime_ = makeBaseString(old);
-
-                //Default basalrate in case none is found...
-                var basalScheduledRate_ = basalprofile[0].rate;
-                for (let m = 0; m < basalprofile.length; m++) {
-
-                    var timeToTest = basalprofile[m].start;
-
-                    if (baseTime_ == timeToTest) {
-
-                        if (m + 1 < basalprofile.length) {
-                            let end = basalprofile[m+1].start;
-                            let start = basalprofile[m].start;
-
-                            durationCurrentSchedule = timeDifferenceOfString(end, start);
-
-                            if (totalDuration >= durationCurrentSchedule) {
-                                basDuration = durationCurrentSchedule;
-                            } else if (totalDuration < durationCurrentSchedule) {
-                                basDuration = totalDuration;
-                            }
-
-                        }
-                        else if (m + 1 == basalprofile.length) {
-                            let end = basalprofile[0].start;
-                            let start = basalprofile[m].start;
-                            // First schedule is 00:00:00. Changed places of start and end here.
-                            durationCurrentSchedule = 24 - (timeDifferenceOfString(start, end));
-
-                            if (totalDuration >= durationCurrentSchedule) {
-                                basDuration = durationCurrentSchedule;
-                            } else if (totalDuration < durationCurrentSchedule) {
-                                basDuration = totalDuration;
-                            }
-
-                        }
-                        basalScheduledRate_ = basalprofile[m].rate;
-                        totalInsulin += accountForIncrements(basalScheduledRate_ * basDuration);
-                        totalDuration -= basDuration;
-                        console.log("Dynamic ratios log: scheduled insulin added: " + accountForIncrements(basalScheduledRate_ * basDuration) + " U. Bas duration: " + basDuration.toPrecision(3) + " h. Base Rate: " + basalScheduledRate_ + " U/h" + ". Time :" + baseTime_);
-                        // Move clock to new date
-                        old = addTimeToDate(old, basDuration);
-                    }
-
-                    else if (baseTime_ > timeToTest) {
-
-                        if (m + 1 < basalprofile.length) {
-                            var timeToTest2 = basalprofile[m+1].start
-
-                            if (baseTime_ < timeToTest2) {
-
-                               //  durationCurrentSchedule = timeDifferenceOfString(end, start);
-                               durationCurrentSchedule = timeDifferenceOfString(timeToTest2, baseTime_);
-
-                                if (totalDuration >= durationCurrentSchedule) {
-                                    basDuration = durationCurrentSchedule;
-                                } else if (totalDuration < durationCurrentSchedule) {
-                                    basDuration = totalDuration;
-                                }
-
-                                basalScheduledRate_ = basalprofile[m].rate;
-                                totalInsulin += accountForIncrements(basalScheduledRate_ * basDuration);
-                                totalDuration -= basDuration;
-                                console.log("Dynamic ratios log: scheduled insulin added: " + accountForIncrements(basalScheduledRate_ * basDuration) + " U. Bas duration: " + basDuration.toPrecision(3) + " h. Base Rate: " + basalScheduledRate_ + " U/h" + ". Time :" + baseTime_);
-                                // Move clock to new date
-                                old = addTimeToDate(old, basDuration);
-                            }
-                        }
-
-                        else if (m == basalprofile.length - 1) {
-                            // let start = basalprofile[m].start;
-                            let start = baseTime_;
-                            // First schedule is 00:00:00. Changed places of start and end here.
-                            durationCurrentSchedule = timeDifferenceOfString("23:59:59", start);
-
-                            if (totalDuration >= durationCurrentSchedule) {
-                                basDuration = durationCurrentSchedule;
-                            } else if (totalDuration < durationCurrentSchedule) {
-                                basDuration = totalDuration;
-                            }
-
-                            basalScheduledRate_ = basalprofile[m].rate;
-                            totalInsulin += accountForIncrements(basalScheduledRate_ * basDuration);
-                            totalDuration -= basDuration;
-                            console.log("Dynamic ratios log: scheduled insulin added: " + accountForIncrements(basalScheduledRate_ * basDuration) + " U. Bas duration: " + basDuration.toPrecision(3) + " h. Base Rate: " + basalScheduledRate_ + " U/h" + ". Time :" + baseTime_);
-                            // Move clock to new date
-                            old = addTimeToDate(old, basDuration);
-                        }
-                    }
-                }
-            }
-            //totalDurationCheck to avoid infinite loop
-        } while (totalDuration > 0 && totalDuration < totalDurationCheck);
-
-        // amount of insulin according to pump basal rate schedules
-        return totalInsulin;
-    }
-
-    // Check that there is enough pump history data (>21 hours) for tdd calculation. Estimate the missing hours (24-pumpData) using hours with scheduled basal rates. Not perfect, but sometimes the
-    // pump history in FAX is only 22-23.5 hours, even when you've been looping with FAX for many days. This is to reduce the error from just using pump history as data source as much as possible.
-    // AT basal rates are not used for this estimation, instead the basal rates in pump settings.
-
-    // Check for empty pump history (new FAX loopers). If empty: don't use dynamic settings!
-
-    if (!pumphistory.length) {
-        console.log("Pumphistory is empty!");
-        dynISFenabled = false;
-        enableDynamicCR = false;
-    } else {
-        let phLastEntry = pumphistory.length - 1;
-        var endDate = new Date(pumphistory[phLastEntry].timestamp);
-        var startDate = new Date(pumphistory[0].timestamp);
-
-        // If latest pump event is a temp basal
-        if (pumphistory[0]._type == "TempBasalDuration") {
-            startDate = new Date();
-        }
-        pumpData = (startDate - endDate) / 36e5;
-
-        if (pumpData < 23.9 && pumpData > 21) {
-            var missingHours = 24 - pumpData;
-            // Makes new end date for a total time duration of exakt 24 hour.
-            var endDate_ = subtractTimeFromDate(endDate, missingHours);
-            // endDate - endDate_ = missingHours
-            scheduledBasalInsulin = calcScheduledBasalInsulin(endDate, endDate_);
-            dataLog = "24 hours of data is required for an accurate tdd calculation. Currently only " + pumpData.toPrecision(3) + " hours of pump history data are available. Using your pump scheduled basals to fill in the missing hours. Scheduled basals added: " + scheduledBasalInsulin.toPrecision(5) + " U. ";
-        } else if (pumpData < 21) {
-            dynISFenabled = false;
-            enableDynamicCR = false;
-        } else {  dataLog = ""; }
-    }
-
-    // Calculate tdd ----------------------------------------------------------------------
-
-    //Bolus:
-    for (let i = 0; i < pumphistory.length; i++) {
-        if (pumphistory[i]._type == "Bolus") {
-            bolusInsulin += pumphistory[i].amount;
-        }
-    }
-
-    // Temp basals:
-    for (let j = 1; j < pumphistory.length; j++) {
-        if (pumphistory[j]._type == "TempBasal" && pumphistory[j].rate > 0) {
-            current = j;
-            quota = pumphistory[j].rate;
-            var duration = pumphistory[j-1]['duration (min)'] / 60;
-            var origDur = duration;
-            var pastTime = new Date(pumphistory[j-1].timestamp);
-            var morePresentTime = new Date(pastTime);
-            var substractTimeOfRewind = 0;
-            // If temp basal hasn't yet ended, use now as end date for calculation
-            do {
-                j--;
-                if (j == 0) {
-                    morePresentTime =  new Date();
-                    break;
-                }
-                else if (pumphistory[j]._type == "TempBasal" || pumphistory[j]._type == "PumpSuspend")  {
-                    morePresentTime = new Date(pumphistory[j].timestamp);
-                    break;
-                }
-                // During the time the Medtronic pumps are rewinded and primed, this duration of suspened insulin delivery needs to be accounted for.
-                var pp = j-2;
-                if (pp >= 0) {
-                    if (pumphistory[pp]._type == "Rewind") {
-                        let rewindTimestamp = pumphistory[pp].timestamp;
-                        // There can be several Prime events
-                        while (pp - 1 >= 0) {
-                            pp -= 1;
-                            if (pumphistory[pp]._type == "Prime") {
-                                substractTimeOfRewind = (pumphistory[pp].timestamp - rewindTimestamp) / 36e5;
-                            } else { break }
-                        }
-
-                        // If Medtronic user forgets to insert infusion set
-                        if (substractTimeOfRewind >= duration) {
-                            morePresentTime = new Date(rewindTimestamp);
-                            substractTimeOfRewind = 0;
-                        }
-                    }
-                }
-            } while (j > 0);
-
-            var diff = (morePresentTime - pastTime) / 36e5;
-            if (diff < origDur) {
-                duration = diff;
-            }
-
-            insulin = quota * (duration - substractTimeOfRewind);
-            tempInsulin += accountForIncrements(insulin);
-            j = current;
-        }
-    }
-    //  Check and count for when basals are delivered with a scheduled basal rate.
-    //  1. Check for 0 temp basals with 0 min duration. This is for when ending a manual temp basal and (perhaps) continuing in open loop for a while.
-    //  2. Check for temp basals that completes. This is for when disconnected from link/iphone, or when in open loop.
-    //  3. Account for a punp suspension. This is for when pod screams or when MDT or pod is manually suspended.
-    //  4. Account for a pump resume (in case pump/cgm is disconnected before next loop).
-    //  To do: are there more circumstances when scheduled basal rates are used? Do we need to care about "Prime" and "Rewind" with MDT pumps?
-    //
-    for (let k = 0; k < pumphistory.length; k++) {
-        // Check for 0 temp basals with 0 min duration.
-        insulin = 0;
-        if (pumphistory[k]['duration (min)'] == 0 || pumphistory[k]._type == "PumpResume") {
-            let time1 = new Date(pumphistory[k].timestamp);
-            let time2 = new Date(time1);
-            let l = k;
-            do {
-                if (l > 0) {
-                    --l;
-                    if (pumphistory[l]._type == "TempBasal") {
-                        time2 = new Date(pumphistory[l].timestamp);
-                        break;
-                    }
-                }
-            } while (l > 0);
-            // duration of current scheduled basal in h
-            let basDuration = (time2 - time1) / 36e5;
-
-            if (basDuration > 0) {
-                scheduledBasalInsulin += calcScheduledBasalInsulin(time2, time1);
-            }
-        }
-    }
-
-    // Check for temp basals that completes
-    for (let n = pumphistory.length -1; n > 0; n--) {
-        if (pumphistory[n]._type == "TempBasalDuration") {
-            // duration in hours
-            let oldBasalDuration = pumphistory[n]['duration (min)'] / 60;
-            // time of old temp basal
-            let oldTime = new Date(pumphistory[n].timestamp);
-            var newTime = new Date(oldTime);
-            let o = n;
-            do {
-                --o;
-                if (o >= 0) {
-                    if (pumphistory[o]._type == "TempBasal" || pumphistory[o]._type == "PumpSuspend") {
-                        // time of next (new) temp basal or a pump suspension
-                        newTime = new Date(pumphistory[o].timestamp);
-                        break;
-                    }
-                }
-            } while (o > 0);
-
-            // When latest temp basal is index 0 in pump history
-            if (n == 0 && pumphistory[0]._type == "TempBasalDuration") {
-                newTime = new Date();
-                oldBasalDuration = pumphistory[n]['duration (min)'] / 60;
-            }
-
-            let tempBasalTimeDifference = (newTime - oldTime) / 36e5;
-            let timeOfbasal = tempBasalTimeDifference - oldBasalDuration;
-            // if duration of scheduled basal is more than 0
-            if (timeOfbasal > 0) {
-                // Timestamp after completed temp basal
-                let timeOfScheduledBasal =  addTimeToDate(oldTime, oldBasalDuration);
-                scheduledBasalInsulin += calcScheduledBasalInsulin(newTime, timeOfScheduledBasal);
-            }
-        }
-    }
-
-    tdd = bolusInsulin + tempInsulin + scheduledBasalInsulin;
-
-
-    var insulin_ = {
-        TDD: round(tdd, 5),
-        bolus: round(bolusInsulin, 5),
-        temp_basal: round(tempInsulin, 5),
-        scheduled_basal: round(scheduledBasalInsulin, 5)
-    }
-
-    if (pumpData > 21) {
-        logBolus = ". Bolus insulin: " + bolusInsulin.toPrecision(5) + " U";
-        logTempBasal = ". Temporary basal insulin: " + tempInsulin.toPrecision(5) + " U";
-        logBasal = ". Insulin with scheduled basal rate: " + scheduledBasalInsulin.toPrecision(5) + " U";
-        logtdd = " TDD past 24h is: " + tdd.toPrecision(5) + " U";
-        logOutPut = dataLog + logtdd + logBolus + logTempBasal + logBasal;
-
-        tddReason = ", TDD: " + round(tdd,2) + " U, " + round(bolusInsulin/tdd*100,0) + "% Bolus " + round((tempInsulin+scheduledBasalInsulin)/tdd*100,0) +  "% Basal";
-
-    } else { tddReason = ", TDD: Not enough pumpData (< 21h)"; }
-
-    var tdd_before = tdd;
-
-    // -------------------- END OF TDD ----------------------------------------------------
-
     // Dynamic ratios
     const BG = glucose_status.glucose;
     const useDynamicCR = preferences.enableDynamicCR;
@@ -1136,8 +773,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
         , 'deliverAt' : deliverAt // The time at which the microbolus should be delivered
         , 'sensitivityRatio' : sensitivityRatio
         , 'CR' : round(carbRatio, 1)
-        , 'TDD': tdd_before
-        , 'insulin': insulin_
         , 'current_target': target_bg
         , 'insulinForManualBolus': insulinForManualBolus
         , 'manualBolusErrorString': manualBolusErrorString
@@ -1553,7 +1188,6 @@ var determine_basal = function determine_basal(glucose_status, currenttemp, iob_
     rT.ISF=convert_bg(sens, profile);
     rT.CR=round(carbRatio, 1);
     rT.target_bg=convert_bg(target_bg, profile);
-    rT.TDD=round(tdd_before, 2);
     rT.current_target=round(target_bg, 0);
 
     var cr_log = rT.CR;