Pārlūkot izejas kodu

Add Determination WiP

polscm32 aka Marvout 1 gadu atpakaļ
vecāks
revīzija
2e635a62e6

+ 2 - 0
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -75,10 +75,12 @@ import Swinject
             async let importPumpHistory: () = importer.importPumpHistoryIfNeeded()
             async let importCarbHistory: () = importer.importCarbHistoryIfNeeded()
             async let importGlucoseHistory: () = importer.importGlucoseHistoryIfNeeded()
+            async let importDeterminationHistory: () = importer.importDeterminationHistoryIfNeeded()
 
             await importPumpHistory
             await importCarbHistory
             await importGlucoseHistory
+            await importDeterminationHistory
         }
     }
 

+ 163 - 1
FreeAPS/Sources/Models/Determination.swift

@@ -1,6 +1,7 @@
+import CoreData
 import Foundation
 
-struct Determination: JSON, Equatable {
+struct Determination: JSON, Equatable, Decodable {
     let id: UUID?
     var reason: String
     let units: Decimal?
@@ -33,6 +34,167 @@ struct Determination: JSON, Equatable {
     let received: Bool?
 }
 
+struct Determination2: Decodable, ImportableDTO {
+    var timestamp: String? // JSON outputs `timestamp` as String
+    var deliverAt: String?
+    var cob: Int
+    var temp: String?
+    var iob: Double? // JSON outputs IOB as Double
+    var minDelta: Double?
+    var expectedDelta: Double?
+    var rate: Double?
+    var reason: String?
+    var tdd: Double?
+    var reservoir: Int? // JSON outputs reservoir as Int
+    var duration: Int
+    var currentTarget: Double?
+    var insulinForManualBolus: Double?
+    var sensitivityRatio: Double?
+    var threshold: Double?
+    var eventualBG: Double?
+    var predictions: Predictions?
+    var received: Bool // typo
+    var minGuardBG: Double?
+    var insulin: Insulin?
+    var insulinReq: Double?
+    var isf: Double?
+    var manualBolusErrorString: Double?
+    var cr: Double?
+    var bg: Double?
+
+    enum CodingKeys: String, CodingKey {
+        case timestamp
+        case deliverAt
+        case cob = "COB"
+        case temp
+        case iob = "IOB"
+        case minDelta
+        case expectedDelta
+        case rate
+        case reason
+        case tdd = "TDD"
+        case reservoir
+        case duration
+        case currentTarget = "current_target"
+        case insulinForManualBolus
+        case sensitivityRatio
+        case threshold
+        case eventualBG
+        case predictions = "predBGs"
+        case received = "recieved" // typo corrected
+        case minGuardBG
+        case insulin
+        case insulinReq
+        case isf = "ISF"
+        case manualBolusErrorString
+        case cr = "CR"
+        case bg
+    }
+
+    struct Predictions: Decodable {
+        var iob: [Int]?
+        var zt: [Int]?
+        var uam: [Int]?
+
+        enum CodingKeys: String, CodingKey {
+            case iob = "IOB"
+            case zt = "ZT"
+            case uam = "UAM"
+        }
+    }
+
+    struct Insulin: Decodable {
+        var tempBasal: Decimal?
+        var bolus: Decimal?
+        var tdd: Decimal?
+        var scheduledBasal: Decimal?
+
+        enum CodingKeys: String, CodingKey {
+            case tempBasal = "temp_basal"
+            case bolus
+            case tdd = "TDD"
+            case scheduledBasal = "scheduled_basal"
+        }
+    }
+
+    typealias ManagedObject = OrefDetermination
+
+    func store(in context: NSManagedObjectContext) -> OrefDetermination {
+        let determinationEntity = OrefDetermination(context: context)
+
+        determinationEntity.timestamp = convertToDate(from: timestamp)
+        determinationEntity.deliverAt = convertToDate(from: deliverAt)
+        determinationEntity.cob = Int16(cob)
+        determinationEntity.temp = temp
+        determinationEntity.iob = convertToDecimalNumber(from: iob)
+        determinationEntity.minDelta = convertToDecimalNumber(from: minDelta)
+        determinationEntity.expectedDelta = convertToDecimalNumber(from: expectedDelta)
+        determinationEntity.rate = convertToDecimalNumber(from: rate)
+        determinationEntity.reason = reason
+        determinationEntity.totalDailyDose = convertToDecimalNumber(from: tdd)
+        determinationEntity.reservoir = convertToDecimalNumber(from: reservoir)
+        determinationEntity.duration = NSDecimalNumber(value: duration)
+        determinationEntity.currentTarget = convertToDecimalNumber(from: currentTarget)
+        determinationEntity.insulinForManualBolus = convertToDecimalNumber(from: insulinForManualBolus)
+        determinationEntity.sensitivityRatio = convertToDecimalNumber(from: sensitivityRatio)
+        determinationEntity.threshold = convertToDecimalNumber(from: threshold)
+        determinationEntity.eventualBG = convertToDecimalNumber(from: eventualBG)
+        determinationEntity.received = received
+        determinationEntity.insulinReq = convertToDecimalNumber(from: insulinReq)
+        determinationEntity.insulinSensitivity = convertToDecimalNumber(from: isf)
+        determinationEntity.manualBolusErrorString = convertToDecimalNumber(from: manualBolusErrorString)
+        determinationEntity.carbRatio = convertToDecimalNumber(from: cr)
+        determinationEntity.glucose = convertToDecimalNumber(from: bg)
+
+        if let predictionData = predictions {
+            var forecasts = Set<Forecast>()
+
+            if let iobPredictions = predictionData.iob {
+                forecasts.insert(createForecast(context: context, type: "IOB", values: iobPredictions))
+            }
+            if let ztPredictions = predictionData.zt {
+                forecasts.insert(createForecast(context: context, type: "ZT", values: ztPredictions))
+            }
+            if let uamPredictions = predictionData.uam {
+                forecasts.insert(createForecast(context: context, type: "UAM", values: uamPredictions))
+            }
+
+            determinationEntity.forecasts = forecasts
+        }
+
+        return determinationEntity
+    }
+
+    private func convertToDecimalNumber(from value: Double?) -> NSDecimalNumber? {
+        guard let value = value else { return nil }
+        return NSDecimalNumber(value: value)
+    }
+
+    private func convertToDecimalNumber(from value: Int?) -> NSDecimalNumber? {
+        guard let value = value else { return nil }
+        return NSDecimalNumber(value: value)
+    }
+
+    private func convertToDate(from string: String?) -> Date? {
+        guard let string = string else { return nil }
+        let formatter = ISO8601DateFormatter()
+        return formatter.date(from: string)
+    }
+
+    private func createForecast(context: NSManagedObjectContext, type: String, values: [Int]) -> Forecast {
+        let forecast = Forecast(context: context)
+        forecast.type = type
+        forecast.date = Date()
+        forecast.forecastValues = Set(values.enumerated().map { index, value in
+            let forecastValue = ForecastValue(context: context)
+            forecastValue.index = Int32(index)
+            forecastValue.value = Int32(value)
+            return forecastValue
+        })
+        return forecast
+    }
+}
+
 struct Predictions: JSON, Equatable {
     let iob: [Int]?
     let zt: [Int]?

+ 89 - 14
Model/MigrationScript.swift

@@ -29,6 +29,64 @@ class JSONImporter {
     ///   - filePathComponent: Path component of the JSON file.
     ///   - dtoType: The DTO type conforming to `ImportableDTO`.
     ///   - dateDecodingStrategy: The date decoding strategy for JSON decoding.
+//    func importDataIfNeeded<T: ImportableDTO>(
+//        userDefaultsKey: String,
+//        filePathComponent: String,
+//        dtoType _: T.Type,
+//        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .iso8601
+//    ) async {
+//        let hasImported = UserDefaults.standard.bool(forKey: userDefaultsKey)
+//
+//        //        guard !hasImported else {
+//        //            debugPrint("\(filePathComponent) already imported. Skipping import.")
+//        //            return
+//        //        }
+//
+//        do {
+//            // Get the file path for the JSON file
+//            guard let filePath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
+//                .first?
+//                .appendingPathComponent(filePathComponent),
+//                fileManager.fileExists(atPath: filePath.path)
+//            else {
+//                debugPrint("\(filePathComponent) file not found at path \(filePathComponent)")
+//                return
+//            }
+//
+//            // Read data from the JSON file
+//            let data = try Data(contentsOf: filePath)
+//            let decoder = JSONDecoder()
+//            decoder.dateDecodingStrategy = dateDecodingStrategy
+//
+//            // Decode the data into an array of DTOs
+//            let entries = try decoder.decode([T].self, from: data)
+//
+//            // Save the DTOs into Core Data
+//            await context.perform {
+//                for entry in entries {
+//                    _ = entry.store(in: self.context)
+//                }
+//
+//                do {
+//                    guard self.context.hasChanges else { return }
+//                    try self.context.save()
+//                    debugPrint("\(DebuggingIdentifiers.succeeded) \(filePathComponent) successfully imported into Core Data.")
+//                } catch {
+//                    debugPrint("\(DebuggingIdentifiers.failed) Failed to save \(filePathComponent) to Core Data: \(error)")
+//                }
+//            }
+//
+//            // Delete the JSON file after successful import
+//            try fileManager.removeItem(at: filePath)
+//            debugPrint("\(filePathComponent) deleted after successful import.")
+//
+//            // Update UserDefaults to indicate that the data has been imported
+//            UserDefaults.standard.set(true, forKey: userDefaultsKey)
+//        } catch {
+//            debugPrint("Error importing \(filePathComponent): \(error)")
+//        }
+//    }
+
     func importDataIfNeeded<T: ImportableDTO>(
         userDefaultsKey: String,
         filePathComponent: String,
@@ -37,38 +95,49 @@ class JSONImporter {
     ) async {
         let hasImported = UserDefaults.standard.bool(forKey: userDefaultsKey)
 
-        guard !hasImported else {
-            debugPrint("\(filePathComponent) already imported. Skipping import.")
-            return
-        }
+        //    if hasImported {
+        //        debugPrint("\(filePathComponent) already imported. Skipping import.")
+        //        return
+        //    }
 
         do {
-            // Get the file path for the JSON file
             guard let filePath = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
                 .first?
                 .appendingPathComponent(filePathComponent),
                 fileManager.fileExists(atPath: filePath.path)
             else {
-                debugPrint("\(filePathComponent) file not found at path \(filePathComponent)")
+                debugPrint("File not found: \(filePathComponent).")
                 return
             }
 
-            // Read data from the JSON file
             let data = try Data(contentsOf: filePath)
             let decoder = JSONDecoder()
             decoder.dateDecodingStrategy = dateDecodingStrategy
 
-            // Decode the data into an array of DTOs
-            let entries = try decoder.decode([T].self, from: data)
+            var entries: [T] = []
+
+            do {
+                if let array = try? decoder.decode([T].self, from: data) {
+                    debugPrint("Decoded \(array.count) entries as an array.")
+                    entries = array
+                } else if let singleObject = try? decoder.decode(T.self, from: data) {
+                    debugPrint("Decoded a single object.")
+                    entries = [singleObject]
+                } else {
+                    debugPrint("Failed to decode \(filePathComponent) as either an array or a single object.")
+                    return
+                }
+            }
 
-            // Save the DTOs into Core Data
             await context.perform {
                 for entry in entries {
                     _ = entry.store(in: self.context)
                 }
 
                 do {
-                    guard self.context.hasChanges else { return }
+                    guard self.context.hasChanges else {
+                        return
+                    }
                     try self.context.save()
                     debugPrint("\(DebuggingIdentifiers.succeeded) \(filePathComponent) successfully imported into Core Data.")
                 } catch {
@@ -76,11 +145,8 @@ class JSONImporter {
                 }
             }
 
-            // Delete the JSON file after successful import
             try fileManager.removeItem(at: filePath)
             debugPrint("\(filePathComponent) deleted after successful import.")
-
-            // Update UserDefaults to indicate that the data has been imported
             UserDefaults.standard.set(true, forKey: userDefaultsKey)
         } catch {
             debugPrint("Error importing \(filePathComponent): \(error)")
@@ -117,4 +183,13 @@ extension JSONImporter {
             dateDecodingStrategy: .iso8601
         )
     }
+
+    func importDeterminationHistoryIfNeeded() async {
+        await importDataIfNeeded(
+            userDefaultsKey: "enactedHistoryImported",
+            filePathComponent: OpenAPS.Enact.enacted,
+            dtoType: Determination2.self,
+            dateDecodingStrategy: .iso8601
+        )
+    }
 }