Преглед изворни кода

Refactoring and Fixes for import of 'enacted' and 'pumpHistory'

polscm32 aka Marvout пре 1 година
родитељ
комит
213b63ebc3

+ 0 - 161
FreeAPS/Sources/Models/Determination.swift

@@ -34,167 +34,6 @@ struct Determination: JSON, Equatable, Decodable {
     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]?

+ 120 - 0
Model/Helper/Determination+helper.swift

@@ -51,3 +51,123 @@ extension NSPredicate {
         )
     }
 }
+
+// MARK: - DeterminationDTO and Conformance to ImportableDTO
+
+/// Data Transfer Object for the enacted.json.
+struct DeterminationDTO: Decodable, ImportableDTO {
+    let tdd: Decimal?
+    let threshold: Decimal?
+    let timestamp: String?
+    let insulinForManualBolus: Decimal?
+    let sensitivityRatio: Decimal?
+    let predictions: Predictions?
+    let received: Bool?
+    let currentTarget: Decimal?
+    let expectedDelta: Decimal?
+    let cob: Int?
+    let minDelta: Decimal?
+    let bg: Decimal?
+    let manualBolusErrorString: Decimal?
+    let eventualBG: Decimal?
+    let isf: Decimal?
+    let rate: Decimal?
+    let duration: Decimal?
+    let temp: String?
+    let insulinReq: Decimal?
+    let insulin: Insulin?
+    let deliverAt: String?
+    let reason: String?
+    let iob: Decimal?
+    let reservoir: Decimal?
+
+    enum CodingKeys: String, CodingKey {
+        case tdd = "TDD"
+        case threshold
+        case timestamp
+        case insulinForManualBolus
+        case sensitivityRatio
+        case predictions = "predBGs"
+        case received = "recieved"
+        case currentTarget = "current_target"
+        case expectedDelta
+        case cob = "COB"
+        case minDelta
+        case bg
+        case manualBolusErrorString
+        case eventualBG
+        case isf = "ISF"
+        case rate
+        case duration
+        case temp
+        case insulinReq
+        case insulin
+        case deliverAt
+        case reason
+        case iob = "IOB"
+        case reservoir
+    }
+
+    // Conformance to ImportableDTO
+    typealias ManagedObject = OrefDetermination
+
+    /// Stores the DTO in Core Data by mapping it to the corresponding managed object.
+    func store(in context: NSManagedObjectContext) -> OrefDetermination {
+        let determinationEntity = OrefDetermination(context: context)
+        let dateFormatter = ISO8601DateFormatter()
+
+        determinationEntity.timestamp = timestamp.flatMap { dateFormatter.date(from: $0) }
+        determinationEntity.deliverAt = deliverAt.flatMap { dateFormatter.date(from: $0) }
+        determinationEntity.cob = cob.map { Int16($0) } ?? 0
+        determinationEntity.temp = temp
+        determinationEntity.iob = iob.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.minDelta = minDelta.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.expectedDelta = expectedDelta.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.rate = rate.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.reason = reason
+        determinationEntity.totalDailyDose = tdd.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.reservoir = reservoir.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.duration = duration.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.currentTarget = currentTarget.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.insulinForManualBolus = insulinForManualBolus.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.sensitivityRatio = sensitivityRatio.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.threshold = threshold.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.eventualBG = eventualBG.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.received = received ?? false
+        determinationEntity.insulinReq = insulinReq.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.insulinSensitivity = isf.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.manualBolusErrorString = manualBolusErrorString.map { NSDecimalNumber(decimal: $0) }
+        determinationEntity.glucose = bg.map { NSDecimalNumber(decimal: $0) }
+
+        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 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
+    }
+}

+ 31 - 15
Model/Helper/PumpEvent+helper.swift

@@ -101,6 +101,7 @@ enum PumpEventDTO: Encodable, Decodable, ImportableDTO {
     case tempBasal(TempBasalDTO)
     case tempBasalDuration(TempBasalDurationDTO)
     case pumpSuspend(PumpSuspendDTO)
+    case unknown(String) // Catch-all for unknown types
 
     func encode(to encoder: Encoder) throws {
         switch self {
@@ -112,22 +113,23 @@ enum PumpEventDTO: Encodable, Decodable, ImportableDTO {
             try tempBasalDuration.encode(to: encoder)
         case let .pumpSuspend(pumpSuspend):
             try pumpSuspend.encode(to: encoder)
+        case let .unknown(type):
+            debugPrint("⚠️ Skipping unknown type during encoding: \(type)")
         }
     }
 
-    // Coding keys to identify the event type in JSON.
     private enum CodingKeys: String, CodingKey {
         case _type
     }
 
-    // Custom initializer to decode the JSON based on the `_type` field.
     init(from decoder: Decoder) throws {
         let container = try decoder.container(keyedBy: CodingKeys.self)
+
+        // Attempt to decode `_type` key
         guard let type = try? container.decode(String.self, forKey: ._type) else {
-            throw DecodingError.keyNotFound(
-                CodingKeys._type,
-                DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "_type key not found")
-            )
+            debugPrint("⚠️ Missing _type in JSON entry.")
+            self = .unknown("missing_type")
+            return
         }
 
         let singleValueContainer = try decoder.singleValueContainer()
@@ -146,18 +148,14 @@ enum PumpEventDTO: Encodable, Decodable, ImportableDTO {
             let pumpSuspendDTO = try singleValueContainer.decode(PumpSuspendDTO.self)
             self = .pumpSuspend(pumpSuspendDTO)
         default:
-            throw DecodingError.dataCorruptedError(
-                forKey: ._type,
-                in: container,
-                debugDescription: "Unknown _type value: \(type)"
-            )
+            debugPrint("⚠️ Unknown _type value: \(type)")
+            self = .unknown(type)
         }
     }
 
     // Conformance to ImportableDTO
     typealias ManagedObject = PumpEventStored
 
-    /// Stores the DTO in Core Data by mapping it to the corresponding managed object.
     func store(in context: NSManagedObjectContext) -> PumpEventStored {
         switch self {
         case let .bolus(bolusDTO):
@@ -199,11 +197,19 @@ enum PumpEventDTO: Encodable, Decodable, ImportableDTO {
 
             return pumpEvent
 
-        case .pumpSuspend:
-            // Handle pump suspend event if needed
+        case let .pumpSuspend(pumpSuspendDTO):
             let pumpEvent = PumpEventStored(context: context)
-            // Set properties for pump suspend if applicable
+            pumpEvent.id = pumpSuspendDTO.id
+            pumpEvent.timestamp = ISO8601DateFormatter().date(from: pumpSuspendDTO.timestamp)
+            pumpEvent.type = pumpSuspendDTO._type
+
+            // You can map additional pump suspend-specific fields here
             return pumpEvent
+
+        case .unknown:
+            debugPrint("⚠️ Skipping unknown event type.")
+            // Return an empty PumpEventStored object or handle appropriately
+            return PumpEventStored(context: context)
         }
     }
 }
@@ -217,6 +223,16 @@ struct BolusDTO: Codable {
     var isSMB: Bool?
     var duration: Int?
     var _type: String = "Bolus"
+
+    private enum CodingKeys: String, CodingKey {
+        case id
+        case timestamp
+        case amount
+        case isExternal = "isExternalInsulin"
+        case isSMB
+        case duration
+        case _type
+    }
 }
 
 struct TempBasalDTO: Codable {

+ 19 - 69
Model/MigrationScript.swift

@@ -29,64 +29,6 @@ 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,
@@ -95,18 +37,19 @@ class JSONImporter {
     ) async {
         let hasImported = UserDefaults.standard.bool(forKey: userDefaultsKey)
 
-        //    if hasImported {
-        //        debugPrint("\(filePathComponent) already imported. Skipping import.")
-        //        return
-        //    }
+//            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("File not found: \(filePathComponent).")
+                debugPrint("\(DebuggingIdentifiers.failed) File not found: \(filePathComponent).")
                 return
             }
 
@@ -117,18 +60,22 @@ class JSONImporter {
             var entries: [T] = []
 
             do {
+                // Decode as either an array or as a single object
                 if let array = try? decoder.decode([T].self, from: data) {
-                    debugPrint("Decoded \(array.count) entries as an array.")
+                    debugPrint("\(DebuggingIdentifiers.succeeded) 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.")
+                    debugPrint("\(DebuggingIdentifiers.succeeded) Decoded a single object.")
                     entries = [singleObject]
                 } else {
-                    debugPrint("Failed to decode \(filePathComponent) as either an array or a single object.")
+                    debugPrint(
+                        "\(DebuggingIdentifiers.failed) 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)
@@ -145,11 +92,14 @@ class JSONImporter {
                 }
             }
 
+            // Delete the JSON file after successful import
             try fileManager.removeItem(at: filePath)
-            debugPrint("\(filePathComponent) deleted after successful import.")
+            debugPrint("\(DebuggingIdentifiers.succeeded) \(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)")
+            debugPrint("\(DebuggingIdentifiers.failed) Error importing \(filePathComponent): \(error)")
         }
     }
 }
@@ -188,7 +138,7 @@ extension JSONImporter {
         await importDataIfNeeded(
             userDefaultsKey: "enactedHistoryImported",
             filePathComponent: OpenAPS.Enact.enacted,
-            dtoType: Determination2.self,
+            dtoType: DeterminationDTO.self,
             dateDecodingStrategy: .iso8601
         )
     }