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

Add logic for customizable HBT in makeProfiles()

polscm32 aka Marvout 1 год назад
Родитель
Сommit
e1f615b21f
2 измененных файлов с 104 добавлено и 164 удалено
  1. 104 152
      FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift
  2. 0 12
      FreeAPS/Sources/Models/Oref2_variables.swift

+ 104 - 152
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -357,161 +357,65 @@ final class OpenAPS {
 
     func oref2() async -> RawJSON {
         await context.perform {
-            // Retrieve preferences
-            let preferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
-            let defaultHalfBasalTarget = preferences?.halfBasalExerciseTarget ?? 160
-            var hbt_ = defaultHalfBasalTarget
-            let wp = preferences?.weightPercentage ?? 1.0
-            let smbMinutes = preferences?.maxSMBBasalMinutes ?? 30
-            let uamMinutes = preferences?.maxUAMSMBBasalMinutes ?? 30
+            // Retrieve user preferences
+            let userPreferences = self.storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self)
+            let weightPercentage = userPreferences?.weightPercentage ?? 1.0
+            let maxSMBBasalMinutes = userPreferences?.maxSMBBasalMinutes ?? 30
+            let maxUAMBasalMinutes = userPreferences?.maxUAMSMBBasalMinutes ?? 30
 
+            // Fetch historical events for Total Daily Dose (TDD) calculation
             let tenDaysAgo = Date().addingTimeInterval(-10.days.timeInterval)
             let twoHoursAgo = Date().addingTimeInterval(-2.hours.timeInterval)
-
-            // Fetch unique events for TDD calculation
-            var uniqueEvents = [[String: Any]]()
-            let requestTDD = OrefDetermination.fetchRequest() as NSFetchRequest<NSFetchRequestResult>
-            requestTDD.predicate = NSPredicate(format: "timestamp > %@ AND totalDailyDose > 0", tenDaysAgo as NSDate)
-            requestTDD.propertiesToFetch = ["timestamp", "totalDailyDose"]
-            let sortTDD = NSSortDescriptor(key: "timestamp", ascending: true)
-            requestTDD.sortDescriptors = [sortTDD]
-            requestTDD.resultType = .dictionaryResultType
-
-            do {
-                if let fetchedResults = try self.context.fetch(requestTDD) as? [[String: Any]] {
-                    uniqueEvents = fetchedResults
-                }
-            } catch {
-                debugPrint("Failed to fetch TDD Data")
-            }
-
-            // Get the last active Override
-            var overrideArray = [OverrideStored]()
-            let requestOverrides = OverrideStored.fetchRequest() as NSFetchRequest<OverrideStored>
-            let sortOverride = NSSortDescriptor(key: "date", ascending: false)
-            requestOverrides.sortDescriptors = [sortOverride]
-            requestOverrides.predicate = NSPredicate.lastActiveOverride
-            requestOverrides.fetchLimit = 1
-            try? overrideArray = self.context.fetch(requestOverrides)
-
-            // Get the last active Temp Target
-            var tempTargetsArray = [TempTargetStored]()
-            let requestTempTargets = TempTargetStored.fetchRequest() as NSFetchRequest<TempTargetStored>
-            let sortTT = NSSortDescriptor(key: "date", ascending: false)
-            requestTempTargets.sortDescriptors = [sortTT]
-            requestTempTargets.predicate = NSPredicate.lastActiveTempTarget
-            requestTempTargets.fetchLimit = 1
-            try? tempTargetsArray = self.context.fetch(requestTempTargets)
-
-            var isTemptargetActive = tempTargetsArray.first?.enabled ?? false
-
-            // Calculate averages for TDD
-            let total = uniqueEvents.compactMap { ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue }.reduce(0, +)
-            var indices = uniqueEvents.count
-
-            // Fetch data for the past two hours
-            let twoHoursArray = uniqueEvents.filter { ($0["timestamp"] as? Date ?? Date()) >= twoHoursAgo }
-            var nrOfIndices = twoHoursArray.count
-            let totalAmount = twoHoursArray.compactMap { ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue }
+            let historicalTDDData = self.fetchHistoricalTDDData(from: tenDaysAgo)
+
+            // Fetch the last active Override
+            let activeOverrides = self.fetchActiveOverrides()
+            let isOverrideActive = activeOverrides.first?.enabled ?? false
+            let overridePercentage = Decimal(activeOverrides.first?.percentage ?? 100)
+            let isOverrideIndefinite = activeOverrides.first?.indefinite ?? true
+            let disableSMBs = activeOverrides.first?.smbIsOff ?? false
+            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 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 recentDataCount = max(recentTDDData.count, 1)
+            let recentTotalTDD = recentTDDData.compactMap { ($0["totalDailyDose"] as? NSDecimalNumber)?.decimalValue }
                 .reduce(0, +)
 
-            var useOverride = overrideArray.first?.enabled ?? false
-            var overridePercentage = Decimal(overrideArray.first?.percentage ?? 100)
-            var unlimited = overrideArray.first?.indefinite ?? true
-            var disableSMBs = overrideArray.first?.smbIsOff ?? false
-
-            let currentTDD = uniqueEvents.last?["totalDailyDose"] as? Decimal ?? 0
-
-            if indices == 0 { indices = 1 }
-            if nrOfIndices == 0 { nrOfIndices = 1 }
-
-            let average2hours = totalAmount / Decimal(nrOfIndices)
-            let average14 = total / Decimal(indices)
-
-            let weightedAverage = wp * average2hours + (1 - wp) * average14
-
-            var duration: Decimal = 0
-            var overrideTarget: Decimal = 0
-
-            // Handle Overrides
-            if useOverride {
-                duration = overrideArray.first?.duration?.decimalValue ?? 0
-                overrideTarget = overrideArray.first?.target?.decimalValue ?? 0
-                let advancedSettings = overrideArray.first?.advancedSettings ?? false
-                let addedMinutes = Int(truncating: overrideArray.first?.duration ?? 0)
-                let date = overrideArray.first?.date ?? Date()
-                let overrideEndTime = date.addingTimeInterval(Double(addedMinutes) * 60)
-
-                if overrideEndTime < Date(), !unlimited {
-                    // Override has expired
-                    useOverride = false
-                    let saveToCoreData = OverrideStored(context: self.context)
-                    saveToCoreData.enabled = false
-                    saveToCoreData.date = Date()
-                    saveToCoreData.duration = 0
-                    saveToCoreData.indefinite = false
-                    saveToCoreData.percentage = 100
-                    do {
-                        guard self.context.hasChanges else { return "{}" }
-                        try self.context.save()
-                    } catch {
-                        print(error.localizedDescription)
-                    }
-                }
-            }
-
-            if !useOverride {
-                // Reset to default values if no override is active
-                unlimited = true
-                overridePercentage = 100
-                duration = 0
-                overrideTarget = 0
-                disableSMBs = false
-            }
-
-            // Temp Target Handling
-            if isTemptargetActive {
-                if let tempTarget = tempTargetsArray.first {
-                    let tempDuration = tempTarget.duration?.doubleValue ?? 0
-                    let halfBasalTarget = tempTarget.halfBasalTarget ?? NSDecimalNumber(decimal: hbt_)
-                    let startDate = tempTarget.date ?? Date()
-                    let tempTargetEndTime = startDate.addingTimeInterval(tempDuration * 60)
-                    let timeRemaining = tempTargetEndTime.timeIntervalSinceNow / 60 // Time remaining in minutes
-
-                    if timeRemaining > 0 {
-                        hbt_ = halfBasalTarget.decimalValue
-                    }
-                }
-            }
+            let currentTDD = historicalTDDData.last?["totalDailyDose"] as? Decimal ?? 0
+            let averageTDDLastTwoHours = recentTotalTDD / Decimal(recentDataCount)
+            let averageTDDLastTenDays = totalTDD / Decimal(totalDaysCount)
+            let weightedTDD = weightPercentage * averageTDDLastTwoHours + (1 - weightPercentage) * averageTDDLastTenDays
 
             // Prepare Oref2 variables
-            let averages = Oref2_variables(
-                average_total_data: currentTDD > 0 ? average14 : 0,
-                weightedAverage: currentTDD > 0 ? weightedAverage : 1,
-                past2hoursAverage: currentTDD > 0 ? average2hours : 0,
+            let oref2Data = Oref2_variables(
+                average_total_data: currentTDD > 0 ? averageTDDLastTenDays : 0,
+                weightedAverage: currentTDD > 0 ? weightedTDD : 1,
+                past2hoursAverage: currentTDD > 0 ? averageTDDLastTwoHours : 0,
                 date: Date(),
-                isEnabled: hbt_ != defaultHalfBasalTarget,
-                presetActive: isTemptargetActive,
                 overridePercentage: overridePercentage,
-                useOverride: useOverride,
-                duration: duration,
-                unlimited: unlimited,
-                hbt: hbt_,
-                overrideTarget: overrideTarget,
+                useOverride: isOverrideActive,
+                duration: activeOverrides.first?.duration?.decimalValue ?? 0,
+                unlimited: isOverrideIndefinite,
+                overrideTarget: overrideTargetBG,
                 smbIsOff: disableSMBs,
-                advancedSettings: overrideArray.first?.advancedSettings ?? false,
-                isfAndCr: overrideArray.first?.isfAndCr ?? false,
-                isf: overrideArray.first?.isf ?? false,
-                cr: overrideArray.first?.cr ?? false,
-                smbIsScheduledOff: overrideArray.first?.smbIsScheduledOff ?? false,
-                start: (overrideArray.first?.start ?? 0) as Decimal,
-                end: (overrideArray.first?.end ?? 0) as Decimal,
-                smbMinutes: overrideArray.first?.smbMinutes?.decimalValue ?? smbMinutes,
-                uamMinutes: overrideArray.first?.uamMinutes?.decimalValue ?? uamMinutes
+                advancedSettings: activeOverrides.first?.advancedSettings ?? false,
+                isfAndCr: activeOverrides.first?.isfAndCr ?? false,
+                isf: activeOverrides.first?.isf ?? false,
+                cr: activeOverrides.first?.cr ?? false,
+                smbIsScheduledOff: activeOverrides.first?.smbIsScheduledOff ?? false,
+                start: (activeOverrides.first?.start ?? 0) as Decimal,
+                end: (activeOverrides.first?.end ?? 0) as Decimal,
+                smbMinutes: activeOverrides.first?.smbMinutes?.decimalValue ?? maxSMBBasalMinutes,
+                uamMinutes: activeOverrides.first?.uamMinutes?.decimalValue ?? maxUAMBasalMinutes
             )
 
             // Save and return the Oref2 variables
-            self.storage.save(averages, as: OpenAPS.Monitor.oref2_variables)
+            self.storage.save(oref2Data, as: OpenAPS.Monitor.oref2_variables)
             return self.loadFileFromStorage(name: Monitor.oref2_variables)
         }
     }
@@ -619,7 +523,7 @@ final class OpenAPS {
     func makeProfiles(useAutotune _: Bool) async -> Autotune? {
         debug(.openAPS, "Start makeProfiles")
 
-        async let getPreferences = loadFileFromStorageAsync(name: Settings.preferences)
+        // Load required settings and profiles asynchronously
         async let getPumpSettings = loadFileFromStorageAsync(name: Settings.settings)
         async let getBGTargets = loadFileFromStorageAsync(name: Settings.bgTargets)
         async let getBasalProfile = loadFileFromStorageAsync(name: Settings.basalProfile)
@@ -630,8 +534,7 @@ final class OpenAPS {
         async let getAutotune = loadFileFromStorageAsync(name: Settings.autotune)
         async let getFreeAPS = loadFileFromStorageAsync(name: FreeAPS.settings)
 
-        let (preferences, pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, autotune, freeaps) = await (
-            getPreferences,
+        let (pumpSettings, bgTargets, basalProfile, isf, cr, tempTargets, model, autotune, freeaps) = await (
             getPumpSettings,
             getBGTargets,
             getBasalProfile,
@@ -643,13 +546,26 @@ final class OpenAPS {
             getFreeAPS
         )
 
+        // Retrieve user preferences, or set defaults if not available
+        let preferences = storage.retrieve(OpenAPS.Settings.preferences, as: Preferences.self) ?? Preferences()
+        let defaultHalfBasalTarget = preferences.halfBasalExerciseTarget
         var adjustedPreferences = preferences
-        if adjustedPreferences.isEmpty {
-            adjustedPreferences = Preferences().rawJSON
+
+        // Check for active Temp Targets and adjust HBT if necessary
+        await context.perform {
+            // Check if a Temp Target is active and if its HBT differs from user preferences
+            if let activeTempTarget = self.fetchActiveTempTargets().first,
+               activeTempTarget.enabled,
+               let activeHBT = activeTempTarget.halfBasalTarget?.decimalValue,
+               activeHBT != defaultHalfBasalTarget
+            {
+                // Overwrite the HBT in preferences
+                adjustedPreferences.halfBasalExerciseTarget = activeHBT
+                debug(.openAPS, "Updated halfBasalExerciseTarget to active Temp Target value: \(activeHBT)")
+            }
         }
 
         do {
-            // Pump Profile
             let pumpProfile = try await makeProfile(
                 preferences: adjustedPreferences,
                 pumpSettings: pumpSettings,
@@ -663,7 +579,6 @@ final class OpenAPS {
                 freeaps: freeaps
             )
 
-            // Profile
             let profile = try await makeProfile(
                 preferences: adjustedPreferences,
                 pumpSettings: pumpSettings,
@@ -677,25 +592,26 @@ final class OpenAPS {
                 freeaps: freeaps
             )
 
+            // Save the profiles
             await storage.saveAsync(pumpProfile, as: Settings.pumpProfile)
             await storage.saveAsync(profile, as: Settings.profile)
 
+            // Return the Autotune object, if available
             if let tunedProfile = Autotune(from: profile) {
                 return tunedProfile
             } else {
                 return nil
             }
         } catch {
+            // Handle errors and log failure
             debug(
                 .apsManager,
-                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to execute makeProfiles() to return Autoune results"
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to execute makeProfiles()"
             )
             return nil
         }
     }
 
-    // MARK: - Private
-
     private func iob(pumphistory: JSON, profile: JSON, clock: JSON, autosens: JSON) async throws -> RawJSON {
         await withCheckedContinuation { continuation in
             jsWorker.inCommonContext { worker in
@@ -988,3 +904,39 @@ final class OpenAPS {
         }
     }
 }
+
+// Non-Async fetch methods for oref2
+extension OpenAPS {
+    func fetchActiveTempTargets() -> [TempTargetStored] {
+        CoreDataStack.shared.fetchEntities(
+            ofType: TempTargetStored.self,
+            onContext: context,
+            predicate: NSPredicate.lastActiveTempTarget,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1
+        )
+    }
+
+    func fetchActiveOverrides() -> [OverrideStored] {
+        CoreDataStack.shared.fetchEntities(
+            ofType: OverrideStored.self,
+            onContext: context,
+            predicate: NSPredicate.lastActiveOverride,
+            key: "date",
+            ascending: false,
+            fetchLimit: 1
+        )
+    }
+
+    func fetchHistoricalTDDData(from date: Date) -> [[String: Any]] {
+        CoreDataStack.shared.fetchEntities(
+            ofType: OrefDetermination.self,
+            onContext: context,
+            predicate: NSPredicate(format: "timestamp > %@ AND totalDailyDose > 0", date as NSDate),
+            key: "timestamp",
+            ascending: true,
+            propertiesToFetch: ["timestamp", "totalDailyDose"]
+        ) as? [[String: Any]] ?? []
+    }
+}

+ 0 - 12
FreeAPS/Sources/Models/Oref2_variables.swift

@@ -5,13 +5,10 @@ struct Oref2_variables: JSON, Equatable {
     var weightedAverage: Decimal
     var past2hoursAverage: Decimal
     var date: Date
-    var isEnabled: Bool
-    var presetActive: Bool
     var overridePercentage: Decimal
     var useOverride: Bool
     var duration: Decimal
     var unlimited: Bool
-    var hbt: Decimal
     var overrideTarget: Decimal
     var smbIsOff: Bool
     var advancedSettings: Bool
@@ -29,13 +26,10 @@ struct Oref2_variables: JSON, Equatable {
         weightedAverage: Decimal,
         past2hoursAverage: Decimal,
         date: Date,
-        isEnabled: Bool,
-        presetActive: Bool,
         overridePercentage: Decimal,
         useOverride: Bool,
         duration: Decimal,
         unlimited: Bool,
-        hbt: Decimal,
         overrideTarget: Decimal,
         smbIsOff: Bool,
         advancedSettings: Bool,
@@ -52,13 +46,10 @@ struct Oref2_variables: JSON, Equatable {
         self.weightedAverage = weightedAverage
         self.past2hoursAverage = past2hoursAverage
         self.date = date
-        self.isEnabled = isEnabled
-        self.presetActive = presetActive
         self.overridePercentage = overridePercentage
         self.useOverride = useOverride
         self.duration = duration
         self.unlimited = unlimited
-        self.hbt = hbt
         self.overrideTarget = overrideTarget
         self.smbIsOff = smbIsOff
         self.advancedSettings = advancedSettings
@@ -79,13 +70,10 @@ extension Oref2_variables {
         case weightedAverage
         case past2hoursAverage
         case date
-        case isEnabled
-        case presetActive
         case overridePercentage
         case useOverride
         case duration
         case unlimited
-        case hbt
         case overrideTarget
         case smbIsOff
         case advancedSettings