polscm32 преди 1 година
родител
ревизия
025f1eb202

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -308,6 +308,7 @@
 		BD7DA9AC2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9AB2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift */; };
 		BDB3C1192C03DD1000CEEAA1 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */; };
 		BDB899882C564509006F3298 /* ForeCastChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899872C564509006F3298 /* ForeCastChart.swift */; };
+		BDB8998A2C565D0C006F3298 /* CarbsGlucose+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */; };
 		BDBAACFA2C2D439700370AAE /* OverrideData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBAACF92C2D439700370AAE /* OverrideData.swift */; };
 		BDC2EA452C3043B000E5BBD0 /* OverrideStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */; };
 		BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA462C3045AD00E5BBD0 /* Override.swift */; };
@@ -909,6 +910,7 @@
 		BD7DA9AB2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigRootView.swift; sourceTree = "<group>"; };
 		BDB3C1182C03DD1000CEEAA1 /* UserDefaultsExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsExtension.swift; sourceTree = "<group>"; };
 		BDB899872C564509006F3298 /* ForeCastChart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForeCastChart.swift; sourceTree = "<group>"; };
+		BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CarbsGlucose+helper.swift"; sourceTree = "<group>"; };
 		BDBAACF92C2D439700370AAE /* OverrideData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideData.swift; sourceTree = "<group>"; };
 		BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStorage.swift; sourceTree = "<group>"; };
 		BDC2EA462C3045AD00E5BBD0 /* Override.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Override.swift; sourceTree = "<group>"; };
@@ -2111,6 +2113,7 @@
 				582FAE422C05102C00D1C13F /* CoreDataError.swift */,
 				BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */,
 				BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */,
+				BDB899892C565D0B006F3298 /* CarbsGlucose+helper.swift */,
 			);
 			path = Helper;
 			sourceTree = "<group>";
@@ -3080,6 +3083,7 @@
 				6B1A8D2E2B156EEF00E76752 /* LiveActivityBridge.swift in Sources */,
 				581516A92BCEEDF800BF67D7 /* NSPredicates.swift in Sources */,
 				38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */,
+				BDB8998A2C565D0C006F3298 /* CarbsGlucose+helper.swift in Sources */,
 				38F37828261260DC009DB701 /* Color+Extensions.swift in Sources */,
 				BDCD47AF2C1F3F1700F8BCD5 /* OverrideStored+helper.swift in Sources */,
 				3811DE3F25C9D4A100A708ED /* SettingsStateModel.swift in Sources */,

+ 13 - 0
FreeAPS/Sources/APS/APSManager.swift

@@ -24,6 +24,7 @@ protocol APSManager {
     func makeProfiles() async throws -> Bool
     func determineBasal() async -> Bool
     func determineBasalSync() async
+    func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination?
     func roundBolus(amount: Decimal) -> Decimal
     var lastError: CurrentValueSubject<Error?, Never> { get }
     func cancelBolus() async
@@ -398,6 +399,18 @@ final class BaseAPSManager: APSManager, Injectable {
         _ = await determineBasal()
     }
 
+    func simulateDetermineBasal(carbs: Decimal, iob: Decimal) async -> Determination? {
+        do {
+            let temp = await fetchCurrentTempBasal(date: Date.now)
+            return try await openAPS.simulateDetermineBasal(currentTemp: temp, clock: Date(), carbs: carbs, iob: iob)
+        } catch {
+            debugPrint(
+                "\(DebuggingIdentifiers.failed) \(#file) \(#function) Error occurred in invokeDummyDetermineBasalSync: \(error)"
+            )
+            return nil
+        }
+    }
+
     func makeProfiles() async throws -> Bool {
         let tunedProfile = await openAPS.makeProfiles(useAutotune: settings.useAutotune)
         if let basalProfile = tunedProfile?.basalProfile {

+ 161 - 2
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -171,7 +171,52 @@ final class OpenAPS {
         }
     }
 
-    private func parsePumpHistory(_ pumpHistoryObjectIDs: [NSManagedObjectID]) async -> String {
+//    private func parsePumpHistory(_ pumpHistoryObjectIDs: [NSManagedObjectID], iob: Decimal? = nil) async -> String {
+//        // Return an empty JSON object if the list of object IDs is empty
+//        guard !pumpHistoryObjectIDs.isEmpty else { return "{}" }
+//
+//        // Execute all operations on the background context
+//        return await context.perform {
+//            // Load the pump events from the object IDs
+//            let pumpHistory: [PumpEventStored] = pumpHistoryObjectIDs
+//                .compactMap { self.context.object(with: $0) as? PumpEventStored }
+//
+//            // Create the DTOs
+//            var dtos: [PumpEventDTO] = pumpHistory.flatMap { event -> [PumpEventDTO] in
+//                var eventDTOs: [PumpEventDTO] = []
+//                if let bolusDTO = event.toBolusDTOEnum() {
+//                    eventDTOs.append(bolusDTO)
+//                }
+//                if let tempBasalDTO = event.toTempBasalDTOEnum() {
+//                    eventDTOs.append(tempBasalDTO)
+//                }
+//                if let tempBasalDurationDTO = event.toTempBasalDurationDTOEnum() {
+//                    eventDTOs.append(tempBasalDurationDTO)
+//                }
+//                return eventDTOs
+//            }
+//
+//            // Optionally add the IOB as a DTO
+//            if let iob = iob {
+//                let dateFormatted = OpenAPS.dateFormatter.string(from: Date())
+//                let bolusDTO = BolusDTO(
+//                    id: UUID().uuidString,
+//                    timestamp: dateFormatted,
+//                    amount: Double(iob),
+//                    isExternal: false,
+//                    isSMB: true,
+//                    duration: 0,
+//                    _type: "Bolus"
+//                )
+//                dtos.append(.bolus(bolusDTO))
+//            }
+//
+//            // Convert the DTOs to JSON
+//            return self.jsonConverter.convertToJSON(dtos)
+//        }
+//    }
+
+    private func parsePumpHistory(_ pumpHistoryObjectIDs: [NSManagedObjectID], iob: Decimal? = nil) async -> String {
         // Return an empty JSON object if the list of object IDs is empty
         guard !pumpHistoryObjectIDs.isEmpty else { return "{}" }
 
@@ -182,11 +227,25 @@ final class OpenAPS {
                 .compactMap { self.context.object(with: $0) as? PumpEventStored }
 
             // Create the DTOs
-            let dtos: [PumpEventDTO] = pumpHistory.flatMap { event -> [PumpEventDTO] in
+            var dtos: [PumpEventDTO] = pumpHistory.flatMap { event -> [PumpEventDTO] in
                 var eventDTOs: [PumpEventDTO] = []
                 if let bolusDTO = event.toBolusDTOEnum() {
                     eventDTOs.append(bolusDTO)
                 }
+                // Optionally add the IOB as a DTO
+                if let iob = iob {
+                    let dateFormatted = OpenAPS.dateFormatter.string(from: Date())
+                    let bolusDTO = BolusDTO(
+                        id: UUID().uuidString,
+                        timestamp: dateFormatted,
+                        amount: Double(iob),
+                        isExternal: false,
+                        isSMB: true,
+                        duration: 0,
+                        _type: "Bolus"
+                    )
+                    eventDTOs.append(.bolus(bolusDTO))
+                }
                 if let tempBasalDTO = event.toTempBasalDTOEnum() {
                     eventDTOs.append(tempBasalDTO)
                 }
@@ -201,6 +260,106 @@ final class OpenAPS {
         }
     }
 
+    func simulateDetermineBasal(
+        currentTemp: TempBasal,
+        clock: Date = Date(),
+        carbs: Decimal,
+        iob: Decimal
+    ) async throws -> Determination? {
+        debug(.openAPS, "Start determineBasal")
+
+        // clock
+        let dateFormatted = OpenAPS.dateFormatter.string(from: clock)
+        let dateFormattedAsString = "\"\(dateFormatted)\""
+
+        // temp_basal
+        let tempBasal = currentTemp.rawJSON
+
+        // Perform asynchronous calls in parallel
+        async let pumpHistoryObjectIDs = fetchPumpHistoryObjectIDs() ?? []
+        async let carbs = fetchAndProcessCarbs()
+        async let glucose = fetchAndProcessGlucose()
+        async let oref2 = oref2()
+        async let profileAsync = loadFileFromStorageAsync(name: Settings.profile)
+        async let basalAsync = loadFileFromStorageAsync(name: Settings.basalProfile)
+        async let autosenseAsync = loadFileFromStorageAsync(name: Settings.autosense)
+        async let reservoirAsync = loadFileFromStorageAsync(name: Monitor.reservoir)
+        async let preferencesAsync = loadFileFromStorageAsync(name: Settings.preferences)
+
+        // Await the results of asynchronous tasks
+        let (
+            pumpHistoryJSON,
+            carbsAsJSON,
+            glucoseAsJSON,
+            oref2_variables,
+            profile,
+            basalProfile,
+            autosens,
+            reservoir,
+            preferences
+        ) = await (
+            parsePumpHistory(await pumpHistoryObjectIDs, iob: iob),
+            carbs,
+            glucose,
+            oref2,
+            profileAsync,
+            basalAsync,
+            autosenseAsync,
+            reservoirAsync,
+            preferencesAsync
+        )
+//        print("carbs: \(carbsAsJSON)")
+
+        // Meal
+        let meal = try await self.meal(
+            pumphistory: pumpHistoryJSON,
+            profile: profile,
+            basalProfile: basalProfile,
+            clock: dateFormattedAsString,
+            carbs: carbsAsJSON,
+            glucose: glucoseAsJSON
+        )
+
+        // IOB
+        let iob = try await self.iob(
+            pumphistory: pumpHistoryJSON,
+            profile: profile,
+            clock: dateFormattedAsString,
+            autosens: autosens.isEmpty ? .null : autosens
+        )
+        print("pumphistory : \(pumpHistoryJSON)")
+        print("iob: \(iob)")
+
+        // Determine basal
+        let orefDetermination = try await determineBasal(
+            glucose: glucoseAsJSON,
+            currentTemp: tempBasal,
+            iob: iob,
+            profile: profile,
+            autosens: autosens.isEmpty ? .null : autosens,
+            meal: meal,
+            microBolusAllowed: true,
+            reservoir: reservoir,
+            pumpHistory: pumpHistoryJSON,
+            preferences: preferences,
+            basalProfile: basalProfile,
+            oref2_variables: oref2_variables
+        )
+
+        debug(.openAPS, "oref 2 scheiß: \(oref2_variables)")
+        debug(.openAPS, "Determinated: \(orefDetermination)")
+
+        if var determination = Determination(from: orefDetermination), let deliverAt = determination.deliverAt {
+            // set both timestamp and deliverAt to the SAME date; this will be updated for timestamp once it is enacted
+            // AAPS does it the same way! we'll follow their example!
+            determination.timestamp = deliverAt
+
+            return determination
+        } else {
+            return nil
+        }
+    }
+
     func determineBasal(currentTemp: TempBasal, clock: Date = Date()) async throws -> Determination? {
         debug(.openAPS, "Start determineBasal")
 

+ 5 - 0
FreeAPS/Sources/Modules/Bolus/BolusStateModel.swift

@@ -692,4 +692,9 @@ extension Bolus.StateModel {
             }
         }
     }
+
+    func updateForecasts() async {
+        let dummyDetermination = await apsManager.simulateDetermineBasal(carbs: carbs, iob: amount)
+        print("determination: \(dummyDetermination)")
+    }
 }

+ 3 - 0
FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift

@@ -96,6 +96,9 @@ extension Bolus {
             debounce?.cancel()
             debounce = DispatchWorkItem { [self] in
                 state.insulinCalculated = state.calculateInsulin()
+                Task {
+                    await state.updateForecasts()
+                }
             }
             if let debounce = debounce {
                 DispatchQueue.main.asyncAfter(deadline: .now() + 0.35, execute: debounce)

+ 17 - 0
Model/Helper/CarbsGlucose+helper.swift

@@ -0,0 +1,17 @@
+import Foundation
+
+struct CarbAndGlucose: Encodable {
+    let carbs: Decimal
+    let glucose: Decimal
+
+    enum CodingKeys: String, CodingKey {
+        case carbs
+        case glucose
+    }
+
+    func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        try container.encode(NSDecimalNumber(decimal: carbs).stringValue, forKey: .carbs)
+        try container.encode(NSDecimalNumber(decimal: glucose).stringValue, forKey: .glucose)
+    }
+}