Marc G. Fournier 3 лет назад
Родитель
Сommit
623884a9ae

+ 64 - 62
FreeAPS/Sources/APS/APSManager.swift

@@ -180,8 +180,13 @@ final class BaseAPSManager: APSManager, Injectable {
             return
         }
 
-        loopStats(starting: true)
         debug(.apsManager, "Starting loop")
+
+        var loopStatRecord = LoopStats(
+            createdAt: Date(),
+            loopStatus: "Starting"
+        )
+
         isLooping.send(true)
         determineBasal()
             .replaceEmpty(with: false)
@@ -202,30 +207,37 @@ final class BaseAPSManager: APSManager, Injectable {
             }
             .sink { [weak self] completion in
                 guard let self = self else { return }
+                loopStatRecord.loopEnd = Date()
+                loopStatRecord.loopDuration = self.roundDouble(
+                    (loopStatRecord.loopEnd! - loopStatRecord.createdAt).timeInterval / 60,
+                    1
+                )
                 if case let .failure(error) = completion {
-                    self.loopCompleted(error: error)
+                    loopStatRecord.loopStatus = error.localizedDescription
+                    self.loopCompleted(error: error, loopStatRecord: loopStatRecord)
                 } else {
-                    self.loopCompleted()
+                    loopStatRecord.loopStatus = "Success"
+                    self.loopCompleted(loopStatRecord: loopStatRecord)
                 }
             } receiveValue: {}
             .store(in: &lifetime)
     }
 
     // Loop exit point
-    private func loopCompleted(error: Error? = nil) {
+    private func loopCompleted(error: Error? = nil, loopStatRecord: LoopStats) {
         isLooping.send(false)
 
         if let error = error {
-            loopStats(error: error, starting: false)
             warning(.apsManager, "Loop failed with error: \(error.localizedDescription)")
             processError(error)
         } else {
-            loopStats(starting: false)
             debug(.apsManager, "Loop succeeded")
             lastLoopDate = Date()
             lastError.send(nil)
         }
 
+        loopStats(loopStatRecord: loopStatRecord)
+
         if settings.closedLoop {
             reportEnacted(received: error == nil)
         }
@@ -804,7 +816,6 @@ final class BaseAPSManager: APSManager, Injectable {
             .sorted { $0.createdAt > $1.createdAt } ?? []
 
         var successRate: Double?
-        var roundedMinutesBetweenLoops: Double?
         var successNR = 0.0
         var errorNR = 0.0
         var minimumInt = 999.0
@@ -820,30 +831,36 @@ final class BaseAPSManager: APSManager, Injectable {
         var medianLoopTime = 0.0
         var timeIntervalLoopArray: [Double] = []
         var medianInterval = 0.0
+        var averageIntervalLoops = 0.0
         var successIs = false
 
         if !lsData.isEmpty {
             var i = 0.0
             var j = 0.0
 
-            if lsData[0].loopStatus.contains("Success") {
-                previousTimeLoop = lsData[0].createdAt
+            if let loopEnd = lsData[0].loopEnd {
+                previousTimeLoop = loopEnd
             }
 
             for each in lsData {
-                if each.loopStatus.contains("Success") {
+                if let loopEnd = each.loopEnd, let loopDuration = each.loopDuration {
+                    if each.loopStatus.contains("Success") {
+                        successNR += 1
+                    } else {
+                        errorNR += 1
+                    }
                     i += 1
-                    successNR += 1
 
-                    timeIntervalLoops = (previousTimeLoop - each.createdAt).timeInterval / 60
+                    if previousTimeLoop != loopEnd {
+                        timeIntervalLoops = (previousTimeLoop - each.createdAt).timeInterval / 60
+                    } else {
+                        timeIntervalLoops = 0.0
+                    }
 
                     if timeIntervalLoops > 0.0 {
                         timeIntervalLoopArray.append(timeIntervalLoops)
                     }
 
-                    endTimeForOneLoop = each.createdAt
-                    successIs = true
-
                     if timeIntervalLoops > maximumInt {
                         maximumInt = timeIntervalLoops
                     }
@@ -851,51 +868,37 @@ final class BaseAPSManager: APSManager, Injectable {
                         minimumInt = timeIntervalLoops
                     }
 
-                    previousTimeLoop = each.createdAt
-
-                } else if each.loopStatus.contains("Starting") {
-                    j += 1
-                    if successIs {
-                        let test = (endTimeForOneLoop - each.createdAt).timeInterval / 60
-
-                        if test > 0 {
-                            timeForOneLoop = test
+                    timeForOneLoop = loopDuration
 
-                            timeForOneLoopArray.append(timeForOneLoop)
-                            averageLoopTime += timeForOneLoop
-                            timeForOneLoop = roundDouble(timeForOneLoop, 1)
-                        }
-
-                        if timeForOneLoop >= maximumLoopTime, timeForOneLoop != 0.0 {
-                            maximumLoopTime = timeForOneLoop
-                        }
-                        if timeForOneLoop <= minimumLoopTime, timeForOneLoop != 0.0 {
-                            minimumLoopTime = timeForOneLoop
-                        }
+                    timeForOneLoopArray.append(timeForOneLoop)
+                    averageLoopTime += timeForOneLoop
+                    timeForOneLoop = roundDouble(timeForOneLoop, 1)
 
-                        successIs = false
+                    if timeForOneLoop >= maximumLoopTime, timeForOneLoop != 0.0 {
+                        maximumLoopTime = timeForOneLoop
                     }
-                } else {
-                    errorNR += 1
-                    i += 1
+                    if timeForOneLoop <= minimumLoopTime, timeForOneLoop != 0.0 {
+                        minimumLoopTime = timeForOneLoop
+                    }
+
+                    previousTimeLoop = loopEnd
                 }
             }
 
             successRate = (successNR / Double(i)) * 100
 
-            let endI = lsData.count - 1
-            let loopDataTime = lsData[0].createdAt - lsData[endI].createdAt
-            let minutesBetweenLoops = (loopDataTime.timeInterval / successNR) / 60
-            averageLoopTime /= Double(j)
+            averageIntervalLoops = timeIntervalLoopArray.reduce(0,+) / Double(timeIntervalLoopArray.count)
+
+            averageLoopTime /= Double(i)
 
             // Median values
             medianLoopTime = medianCalculation(array: timeForOneLoopArray)
             medianInterval = medianCalculation(array: timeIntervalLoopArray)
 
+            averageIntervalLoops = roundDouble(averageIntervalLoops, 1)
             medianInterval = roundDouble(medianInterval, 1)
             medianLoopTime = roundDouble(medianLoopTime, 1)
             averageLoopTime = roundDouble(averageLoopTime, 1)
-            roundedMinutesBetweenLoops = roundDouble(minutesBetweenLoops, 1)
             minimumInt = roundDouble(minimumInt, 1)
             maximumInt = roundDouble(maximumInt, 1)
         }
@@ -1187,6 +1190,20 @@ final class BaseAPSManager: APSManager, Injectable {
             loopString += "Longest Loop: \(maximumLoopTime) min."
         }
 
+        let loopstat = LoopCycles(
+            success_perc: Int(round(successRate ?? 0)),
+            loops: Int(successNR),
+            errors: Int(errorNR),
+            median_interval: String(medianInterval),
+            avg_interval: String(averageIntervalLoops),
+            max_interval: String(maximumInt),
+            min_interval: String(minimumInt),
+            median_loop: String(medianLoopTime),
+            avg_loop: String(averageLoopTime),
+            max_loop: String(maximumLoopTime),
+            min_loop: String(minimumLoopTime)
+        )
+
         let dailystat = DailyStats(
             createdAt: Date(),
             iPhone: UIDevice.current.getDeviceId,
@@ -1206,9 +1223,7 @@ final class BaseAPSManager: APSManager, Injectable {
             TIR: tirString,
             BG_Average: bgAverageString,
             HbA1c: HbA1c_string,
-            Loop_Cycles: "Success Rate : \(round(successRate ?? 0)) %. Loops/Errors: \(Int(successNR))/\(Int(errorNR)). Median Time Between Loop Cycles: \(medianInterval) min. Average Time Between Loop Cycles: \(roundedMinutesBetweenLoops ?? 0) min.  " +
-                minString + maxString + loopString +
-                " Median Loop Duration: \(medianLoopTime) min. Average Loop Duration: \(averageLoopTime) min. "
+            LoopStats: [loopstat]
         )
 
         var uniqeEvents: [DailyStats] = []
@@ -1223,26 +1238,13 @@ final class BaseAPSManager: APSManager, Injectable {
         }
     }
 
-    private func loopStats(error: Error? = nil, starting: Bool) {
+    private func loopStats(loopStatRecord: LoopStats) {
         let file = OpenAPS.Monitor.loopStats
-        var errString = "Success"
 
-        if let error = error {
-            errString = error.localizedDescription
-        }
-
-        if starting {
-            errString = "Starting"
-        }
-
-        let loopstat = LoopStats(
-            createdAt: Date(),
-            loopStatus: errString
-        )
         var uniqEvents: [LoopStats] = []
 
         storage.transaction { storage in
-            storage.append(loopstat, to: file, uniqBy: \.createdAt)
+            storage.append(loopStatRecord, to: file, uniqBy: \.createdAt)
             uniqEvents = storage.retrieve(file, as: [LoopStats].self)?
                 .filter { $0.createdAt.addingTimeInterval(24.hours.timeInterval) > Date() }
                 .sorted { $0.createdAt > $1.createdAt } ?? []

+ 35 - 1
FreeAPS/Sources/Helpers/UIDevice+Extensions.swift

@@ -10,7 +10,41 @@ extension UIDevice {
             return identifier + String(UnicodeScalar(UInt8(value)))
         }
 
-        return identifier
+        func mapToDevice(identifier: String) -> String {
+            switch identifier {
+            case "iPhone11,8":
+                return "iPhone XR (A12)"
+
+            case "iPhone12,1":
+                return "iPhone 11 (A13)"
+            case "iPhone12,8":
+                return "iPhone SE (2nd Gen) (A13)"
+
+            case "iPhone13,2":
+                return "iPhone 12 (A14)"
+            case "iPhone13,3":
+                return "iPhone 12 Pro (A14)"
+            case "iPhone13,4":
+                return "iPhone 12 Pro Max (A14)"
+
+            case "iPhone14,4":
+                return "iPhone 13 mini (A15)"
+            case "iPhone14,5":
+                return "iPhone 13 (A15)"
+            case "iPhone14,6":
+                return "iPhone SE (3rd Gen) (A15)"
+            case "iPhone14,7":
+                return "iPhone 14 (A15)"
+
+            case "iPhone15,2":
+                return "iPhone 14 Pro (A16)"
+
+            default:
+                return identifier
+            }
+        }
+
+        return mapToDevice(identifier: identifier)
     }
 
     var getOSInfo: String {

+ 34 - 4
FreeAPS/Sources/Models/DailyStats.swift

@@ -19,7 +19,7 @@ struct DailyStats: JSON, Equatable {
     var TIR: String
     var BG_Average: String
     var HbA1c: String
-    var Loop_Cycles: String
+    var LoopStats: [LoopCycles]
 
     init(
         createdAt: Date,
@@ -40,7 +40,7 @@ struct DailyStats: JSON, Equatable {
         TIR: String,
         BG_Average: String,
         HbA1c: String,
-        Loop_Cycles: String
+        LoopStats: [LoopCycles]
     ) {
         self.createdAt = createdAt
         self.iPhone = iPhone
@@ -60,7 +60,7 @@ struct DailyStats: JSON, Equatable {
         self.TIR = TIR
         self.BG_Average = BG_Average
         self.HbA1c = HbA1c
-        self.Loop_Cycles = Loop_Cycles
+        self.LoopStats = LoopStats
     }
 
     static func == (lhs: DailyStats, rhs: DailyStats) -> Bool {
@@ -92,6 +92,36 @@ extension DailyStats {
         case TIR
         case BG_Average
         case HbA1c
-        case Loop_Cycles
+        case LoopStats
+    }
+}
+
+struct LoopCycles: JSON, Equatable {
+    var success_perc: Int
+    var loops: Int
+    var errors: Int
+    var median_interval: String
+    var avg_interval: String
+    var max_interval: String
+    var min_interval: String
+    var median_loop: String
+    var avg_loop: String
+    var max_loop: String
+    var min_loop: String
+}
+
+extension LoopCycles {
+    private enum CodingKeys: String, CodingKey {
+        case success_perc
+        case loops
+        case errors
+        case median_interval
+        case avg_interval
+        case max_interval
+        case min_interval
+        case median_loop
+        case avg_loop
+        case max_loop
+        case min_loop
     }
 }

+ 4 - 0
FreeAPS/Sources/Models/LoopStats.swift

@@ -2,6 +2,8 @@ import Foundation
 
 struct LoopStats: JSON, Equatable {
     var createdAt: Date
+    var loopEnd: Date?
+    var loopDuration: Double?
     var loopStatus: String
 
     init(
@@ -16,6 +18,8 @@ struct LoopStats: JSON, Equatable {
 extension LoopStats {
     private enum CodingKeys: String, CodingKey {
         case createdAt
+        case loopEnd
+        case loopDuration
         case loopStatus
     }
 }