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

Add dailyStats.json to FAX.

HbA1c (up to 120 days),
BG Average (up to120 days),
build version, build number,
branch name, build date,
algorithm used, Adjustment Factor,
, pump, cgm, insulin type, peak activity time, TDD, Carbs (24 hours), TIR and days with glucose data.

Extend storage in glucose.json to 120 days for HbA1c and BG Averages in stats.

When glucose.json in growing more data in dailyStats.json will be displayed (when 7 days of data HbA1c/BG Average for 7 days will be displayed, when  30 days of data HbA1c/BG Average for 30 days will be displayed. Total (up to 120) days for HbA1c and BG Average will be displayed).
Jon B.M 3 лет назад
Родитель
Сommit
d2cd34b8fc

+ 2 - 1
Config.xcconfig

@@ -1,6 +1,7 @@
 APP_DISPLAY_NAME = FreeAPS X
-APP_VERSION = 0.2.8
+APP_VERSION = 0.2.9.1
 APP_BUILD_NUMBER = 1
+BRANCH = DailyStats test branch
 DEVELOPER_TEAM = ##TEAM_ID##
 BUNDLE_IDENTIFIER = ru.artpancreas.$(DEVELOPMENT_TEAM).FreeAPS
 APP_GROUP_ID = group.com.$(DEVELOPMENT_TEAM).loopkit.LoopGroup

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -18,6 +18,7 @@
 		19795118275953E50044850D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; };
 		198377D2266BFFF6004DE65E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 198377D4266BFFF6004DE65E /* Localizable.strings */; };
 		199561C1275E61A50077B976 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 199561C0275E61A50077B976 /* HealthKit.framework */; };
+		19B0EF2128F6D66200069496 /* DailyStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B0EF2028F6D66200069496 /* DailyStats.swift */; };
 		19F79FA9283AE7E000646323 /* TDD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19F79FA8283AE7E000646323 /* TDD.swift */; };
 		1BBB001DAD60F3B8CEA4B1C7 /* ISFEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505E09DC17A0C3D0AF4B66FE /* ISFEditorStateModel.swift */; };
 		1D845DF2E3324130E1D95E67 /* DataTableProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60744C3E9BB3652895C908CC /* DataTableProvider.swift */; };
@@ -456,6 +457,7 @@
 		199561C0275E61A50077B976 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS8.0.sdk/System/Library/Frameworks/HealthKit.framework; sourceTree = DEVELOPER_DIR; };
 		199732B4271B72DD00129A3F /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
 		199732B5271B9EE900129A3F /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
+		19B0EF2028F6D66200069496 /* DailyStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyStats.swift; sourceTree = "<group>"; };
 		19C166682756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		19C166692756EFBD00ED12E3 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
 		19F79FA8283AE7E000646323 /* TDD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TDD.swift; sourceTree = "<group>"; };
@@ -1327,6 +1329,7 @@
 				19F79FA8283AE7E000646323 /* TDD.swift */,
 				1935363F28496F7D001E0B16 /* TDD_averages.swift */,
 				CE82E02628E869DF00473A9C /* AlertEntry.swift */,
+				19B0EF2028F6D66200069496 /* DailyStats.swift */,
 			);
 			path = Models;
 			sourceTree = "<group>";
@@ -2168,6 +2171,7 @@
 				3811DE3025C9D49500A708ED /* HomeStateModel.swift in Sources */,
 				38BF021725E7CBBC00579895 /* PumpManagerExtensions.swift in Sources */,
 				38F3B2EF25ED8E2A005C48AA /* TempTargetsStorage.swift in Sources */,
+				19B0EF2128F6D66200069496 /* DailyStats.swift in Sources */,
 				3811DF1025CAAAE200A708ED /* APSManager.swift in Sources */,
 				3870FF4725EC187A0088248F /* BloodGlucose.swift in Sources */,
 				38A0364225ED069400FCBB52 /* TempBasal.swift in Sources */,

+ 2 - 0
FreeAPS/Resources/Info.plist

@@ -67,6 +67,8 @@
 	<string>Health App is used to store blood glucose data</string>
 	<key>NSHealthUpdateUsageDescription</key>
 	<string>Health App is used to store blood glucose data</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>$(BRANCH)</string>
 	<key>UIApplicationSceneManifest</key>
 	<dict>
 		<key>UIApplicationSupportsMultipleScenes</key>

+ 312 - 58
FreeAPS/Sources/APS/APSManager.swift

@@ -641,79 +641,333 @@ final class BaseAPSManager: APSManager, Injectable {
 
             storage.save(enacted, as: OpenAPS.Enact.enacted)
 
-            // Add to tdd.json:
-            //
-            let preferences = settingsManager.preferences
-            let currentTDD = enacted.tdd ?? 0
-            let file = OpenAPS.Monitor.tdd
-            let tdd = TDD(
-                TDD: currentTDD,
-                timestamp: Date(),
-                id: UUID().uuidString
-            )
-            var uniqEvents: [TDD] = []
-            storage.transaction { storage in
-                storage.append(tdd, to: file, uniqBy: \.id)
-                uniqEvents = storage.retrieve(file, as: [TDD].self)?
-                    .filter { $0.timestamp.addingTimeInterval(14.days.timeInterval) > Date() }
-                    .sorted { $0.timestamp > $1.timestamp } ?? []
-
-                var total: Decimal = 0
-                var indeces: Decimal = 0
-
-                for uniqEvent in uniqEvents {
-                    if uniqEvent.TDD > 0 {
-                        total += uniqEvent.TDD
-                        indeces += 1
-                    }
+            // Create a tdd.json
+            tdd(enacted_: enacted)
+
+            // Create a dailyStats.json
+            dailyStats()
+
+            debug(.apsManager, "Suggestion enacted. Received: \(received)")
+            DispatchQueue.main.async {
+                self.broadcaster.notify(EnactedSuggestionObserver.self, on: .main) {
+                    $0.enactedSuggestionDidUpdate(enacted)
                 }
+            }
+            nightscout.uploadStatus()
+        }
+    }
 
-                let entriesPast2hours = storage.retrieve(file, as: [TDD].self)?
-                    .filter { $0.timestamp.addingTimeInterval(2.hours.timeInterval) > Date() }
-                    .sorted { $0.timestamp > $1.timestamp } ?? []
+    func tdd(enacted_: Suggestion) {
+        // Add to tdd.json:
+        let preferences = settingsManager.preferences
+        let currentTDD = enacted_.tdd ?? 0
+        let file = OpenAPS.Monitor.tdd
+        let tdd = TDD(
+            TDD: currentTDD,
+            timestamp: Date(),
+            id: UUID().uuidString
+        )
+        var uniqEvents: [TDD] = []
+        storage.transaction { storage in
+            storage.append(tdd, to: file, uniqBy: \.id)
+            uniqEvents = storage.retrieve(file, as: [TDD].self)?
+                .filter { $0.timestamp.addingTimeInterval(14.days.timeInterval) > Date() }
+                .sorted { $0.timestamp > $1.timestamp } ?? []
+            var total: Decimal = 0
+            var indeces: Decimal = 0
+            for uniqEvent in uniqEvents {
+                if uniqEvent.TDD > 0 {
+                    total += uniqEvent.TDD
+                    indeces += 1
+                }
+            }
+            let entriesPast2hours = storage.retrieve(file, as: [TDD].self)?
+                .filter { $0.timestamp.addingTimeInterval(2.hours.timeInterval) > Date() }
+                .sorted { $0.timestamp > $1.timestamp } ?? []
+            var totalAmount: Decimal = 0
+            var nrOfIndeces: Decimal = 0
+            for entry in entriesPast2hours {
+                if entry.TDD > 0 {
+                    totalAmount += entry.TDD
+                    nrOfIndeces += 1
+                }
+            }
+            if indeces == 0 {
+                indeces = 1
+            }
+            if nrOfIndeces == 0 {
+                nrOfIndeces = 1
+            }
+            let average14 = total / indeces
+            let average2hours = totalAmount / nrOfIndeces
+            let weight = preferences.weightPercentage
+            let weighted_average = weight * average2hours + (1 - weight) * average14
+            let averages = TDD_averages(
+                average_total_data: roundDecimal(average14, 1),
+                weightedAverage: roundDecimal(weighted_average, 1),
+                past2hoursAverage: roundDecimal(average2hours, 1),
+                date: Date()
+            )
+            storage.save(averages, as: OpenAPS.Monitor.tdd_averages)
+            storage.save(Array(uniqEvents), as: file)
+        }
+    }
 
-                var totalAmount: Decimal = 0
-                var nrOfIndeces: Decimal = 0
+    func roundDecimal(_ decimal: Decimal, _ digits: Double) -> Decimal {
+        let rounded = round(Double(decimal) * pow(10, digits)) / pow(10, digits)
+        return Decimal(rounded)
+    }
 
-                for entry in entriesPast2hours {
-                    if entry.TDD > 0 {
-                        totalAmount += entry.TDD
-                        nrOfIndeces += 1
-                    }
+    func dailyStats() {
+        // Add to dailyStats.JSON
+        let preferences = settingsManager.preferences
+        let carbs = storage.retrieve(OpenAPS.Monitor.carbHistory, as: [CarbsEntry].self)
+        let tdds = storage.retrieve(OpenAPS.Monitor.tdd, as: [TDD].self)
+        let currentTDD = tdds?[0].TDD
+        let carbs_length = carbs?.count ?? 0
+        var carbTotal: Decimal = 0
+
+        if carbs_length != 0 {
+            for each in carbs! {
+                if each.carbs != 0 {
+                    carbTotal += each.carbs
                 }
+            }
+        }
 
-                if indeces == 0 {
-                    indeces = 1
+        var algo_ = "oref0"
+        if preferences.enableChris, preferences.useNewFormula {
+            algo_ = "Dynamic ISF, Logarithmic Formula"
+        } else if !preferences.useNewFormula, preferences.enableChris {
+            algo_ = "Dynamic ISF, Original Formula"
+        }
+        let af = preferences.adjustmentFactor
+        let insulin_type = preferences.curve
+        var buildDate: Date {
+            if let infoPath = Bundle.main.path(forResource: "Info", ofType: "plist"),
+               let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath),
+               let infoDate = infoAttr[.modificationDate] as? Date
+            {
+                return infoDate
+            }
+            return Date()
+        }
+        let nsObject: AnyObject? = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as AnyObject
+        let version = nsObject as! String
+        let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
+        let branch = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String
+        let pump_ = pumpManager?.localizedTitle ?? ""
+        let cgm = settingsManager.settings.cgm
+        var file = OpenAPS.Monitor.dailyStats
+        var iPa: Decimal = 75
+        if preferences.useCustomPeakTime {
+            iPa = preferences.insulinPeakTime
+        } else if preferences.curve.rawValue == "rapid-acting" {
+            iPa = 65
+        } else if preferences.curve.rawValue == "ultra-rapid" {
+            iPa = 50
+        }
+
+        // HbA1c estimation (%, mmol/mol)
+        let NGSPa1CStatisticValue = (46.7 + tir().averageGlucose_1) / 28.7 // NGSP (%)
+        let IFCCa1CStatisticValue = 10.929 *
+            (NGSPa1CStatisticValue - 2.152) // IFCC (mmol/mol)  A1C(mmol/mol) = 10.929 * (A1C(%) - 2.15)
+        // 7 days
+        let NGSPa1CStatisticValue_7 = (46.7 + tir().averageGlucose_7) / 28.7
+        let IFCCa1CStatisticValue_7 = 10.929 * (NGSPa1CStatisticValue_7 - 2.152)
+        // 14 days
+        let NGSPa1CStatisticValue_30 = (46.7 + tir().averageGlucose_30) / 28.7
+        let IFCCa1CStatisticValue_30 = 10.929 * (NGSPa1CStatisticValue_30 - 2.152)
+        // All days
+        let NGSPa1CStatisticValue_total = (46.7 + tir().averageGlucose) / 28.7
+        let IFCCa1CStatisticValue_total = 10.929 * (NGSPa1CStatisticValue_total - 2.152)
+
+        // HbA1c string and BG string:
+
+        var HbA1c_string_1 = ""
+        var string7Days = ""
+        var string30Days = ""
+        var stringTotal = ""
+
+        var bgString1day = ""
+        var bgString7Days = ""
+        var bgString30Days = ""
+        var bgAverageTotalString = ""
+
+        let daysBG = tir().daysWithBG
+        print("Days with BG: \(daysBG)")
+
+        let avg1 = tir().averageGlucose_1
+        let avg7 = tir().averageGlucose_7
+        let avg30 = tir().averageGlucose_30
+        let avgTot = tir().averageGlucose
+
+        if avg1 != 0 {
+            bgString1day = " Average BG (mmol/l, 1 day): \(roundDecimal(avg1 * 0.0555, 2)). Average BG (mmg/dl, 1 day): \(avg1)."
+            HbA1c_string_1 =
+                "Estimated HbA1c (%, 1 day): \(roundDecimal(NGSPa1CStatisticValue, 1)). Estimated HbA1c (mmol/mol, 1 day): \(roundDecimal(IFCCa1CStatisticValue, 1))."
+        }
+        if avg7 != 0 {
+            string7Days =
+                " HbA1c 7 days (mmol/mol): \(roundDecimal(IFCCa1CStatisticValue_7, 1)). HbA1c 7 days (%): \(roundDecimal(NGSPa1CStatisticValue_7, 1))."
+            bgString7Days = " Average BG (mmol/l) 7 days: \(roundDecimal(avg7 * 0.0555, 2)). Average BG (mg/dl) 7 days: \(avg7)."
+        }
+        if avg30 != 0 {
+            string30Days =
+                " HbA1c 30 days (mmol/mol): \(roundDecimal(IFCCa1CStatisticValue_30, 1)).  HbA1c 30 days (%): \(roundDecimal(NGSPa1CStatisticValue_30, 1))."
+            bgString30Days =
+                " Average BG 30 days (mmol/l): \(roundDecimal(avg30 * 0.0555, 2)). Average BG 30 days (mg/dl): \(avg30). "
+        }
+        if avgTot != 0, daysBG >= 2 {
+            stringTotal =
+                " HbA1c Total (\(daysBG)) Days (mmol/mol): \(roundDecimal(IFCCa1CStatisticValue_total, 1)). HbA1c Total (\(daysBG)) Days (mg/dl): \(roundDecimal(NGSPa1CStatisticValue_total, 1)) %."
+            bgAverageTotalString =
+                "BG Average Total (\(daysBG)) Days (mmol/l): \(roundDecimal(avgTot * 0.0555, 2)). BG Average Total (\(daysBG)) Days (mmg/dl): \(avgTot)."
+        }
+
+        let HbA1c_string = HbA1c_string_1 + string7Days + string30Days + stringTotal
+
+        let tirString =
+            "\(tir().TIR) %. Time with Hypoglucemia: \(tir().hypos) % (< 4 mmol/l or 72 mg/dl). Time with Hyperglucemia: \(tir().hypers) % (> 10 mmol/l or 180 mg/dl)."
+
+        let bgAverageString = bgString1day + bgString7Days + bgString30Days + bgAverageTotalString
+
+        let dailystat = DailyStats(
+            createdAt: Date(),
+            FAX_Build_Version: version,
+            FAX_Build_Number: build ?? "1",
+            FAX_Branch: branch ?? "N/A",
+            FAX_Build_Date: buildDate,
+            Algorithm: algo_,
+            AdjustmentFactor: af,
+            Pump: pump_,
+            CGM: cgm.rawValue,
+            insulinType: insulin_type.rawValue,
+            peakActivityTime: iPa,
+            TDD: currentTDD ?? 0,
+            Carbs_24h: carbTotal,
+            TIR: tirString,
+            BG_Average: bgAverageString,
+            HbA1c: HbA1c_string,
+            id: UUID().uuidString
+        )
+
+        file = OpenAPS.Monitor.dailyStats
+
+        storage.save(dailystat, as: file)
+    }
+
+    // Time In Range (%) and Average Glucose (24 hours)
+    func tir()
+        -> (
+            averageGlucose: Decimal,
+            averageGlucose_1: Decimal,
+            averageGlucose_7: Decimal,
+            averageGlucose_30: Decimal,
+            hypos: Decimal,
+            hypers: Decimal,
+            TIR: Decimal,
+            daysWithBG: Decimal
+        )
+    {
+        let glucose = storage.retrieve(OpenAPS.Monitor.glucose, as: [BloodGlucose].self)
+
+        let length_ = glucose?.count ?? 0
+        let endIndex = length_ - 1
+
+        guard length_ != 0 else {
+            return (0, 0, 0, 0, 0, 0, 0, 0)
+        }
+
+        var bg: Decimal = 0
+        var nr_bgs: Decimal = 0
+        let startDate = glucose![0].date
+        var end1 = false
+        var end7 = false
+        var end30 = false
+        var bg_1: Decimal = 0
+        var bg_7: Decimal = 0
+        var bg_30: Decimal = 0
+
+        for entry in glucose! {
+            if entry.glucose! > 0 {
+                bg += Decimal(entry.glucose!)
+                nr_bgs += 1
+
+                if startDate - entry.date > 8.64E7, !end1 {
+                    end1 = true
+                    bg_1 = bg / nr_bgs
                 }
 
-                if nrOfIndeces == 0 {
-                    nrOfIndeces = 1
+                if startDate - entry.date > 6.045E8, !end7 {
+                    end7 = true
+                    bg_7 = bg / nr_bgs
                 }
+                if startDate - entry.date > 2.59E9, !end30 {
+                    end30 = true
+                    bg_30 = bg / nr_bgs
+                }
+            }
+        }
+
+        let bg_120 = bg / nr_bgs
+        let fullTime = glucose![0].date - glucose![endIndex].date
+
+        let daysBG = fullTime / 8.64E7
+
+        var timeInHypo: Decimal = 0
+        var timeInHyper: Decimal = 0
+        var hypos: Decimal = 0
+        var hypers: Decimal = 0
+        var i = -1
 
-                let average14 = total / indeces
-                let average2hours = totalAmount / nrOfIndeces
-                let weight = preferences.weightPercentage
-                let weighted_average = weight * average2hours + (1 - weight) * average14
+        var lastIndex = false
 
-                let averages = TDD_averages(
-                    average_total_data: average14,
-                    weightedAverage: weighted_average,
-                    past2hoursAverage: average2hours,
-                    date: Date()
-                )
-                storage.save(averages, as: OpenAPS.Monitor.tdd_averages)
-                storage.save(Array(uniqEvents), as: file)
+        while i < endIndex {
+            i += 1
+
+            let currentTime = glucose![i].date
+            var previousTime = currentTime
+
+            if i + 1 <= endIndex {
+                previousTime = glucose![i + 1].date
+            } else {
+                lastIndex = true
             }
-            // End of tdd.json
 
-            debug(.apsManager, "Suggestion enacted. Received: \(received)")
-            DispatchQueue.main.async {
-                self.broadcaster.notify(EnactedSuggestionObserver.self, on: .main) {
-                    $0.enactedSuggestionDidUpdate(enacted)
-                }
+            if glucose![i].glucose! < 72, !lastIndex {
+                timeInHypo += currentTime - previousTime
+            } else if glucose![i].glucose! > 180, !lastIndex {
+                timeInHyper += currentTime - previousTime
             }
-            nightscout.uploadStatus()
         }
+
+        if timeInHypo == 0 {
+            hypos = 0
+        } else { hypos = (timeInHypo / fullTime) * 100
+        }
+
+        if timeInHyper == 0 {
+            hypers = 0
+        } else { hypers = (timeInHyper / fullTime) * 100
+        }
+
+        let TIR = 100 - (hypos + hypers)
+
+        return (
+            roundDecimal(bg_120, 0),
+            roundDecimal(bg_1, 0),
+            roundDecimal(bg_7, 0),
+            roundDecimal(bg_30, 0),
+            roundDecimal(hypos, 1),
+            roundDecimal(hypers, 1),
+            roundDecimal(TIR, 1),
+            roundDecimal(daysBG, 1)
+        )
+    }
+
+    private func loadFileFromStorage(name: String) -> RawJSON {
+        storage.retrieveRaw(name) ?? OpenAPS.defaults(for: name)
     }
 
     private func processError(_ error: Error) {

+ 1 - 0
FreeAPS/Sources/APS/OpenAPS/Constants.swift

@@ -57,6 +57,7 @@ extension OpenAPS {
         static let tdd = "monitor/tdd.json"
         static let tdd_averages = "monitor/tdd_averages.json"
         static let alertHistory = "monitor/alerthistory.json"
+        static let dailyStats = "monitor/dailyStats.json"
     }
 
     enum Enact {

+ 1 - 1
FreeAPS/Sources/APS/Storage/GlucoseStorage.swift

@@ -38,7 +38,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
             self.storage.transaction { storage in
                 storage.append(glucose, to: file, uniqBy: \.dateString)
                 let uniqEvents = storage.retrieve(file, as: [BloodGlucose].self)?
-                    .filter { $0.dateString.addingTimeInterval(1.days.timeInterval) > Date() }
+                    .filter { $0.dateString.addingTimeInterval(120.days.timeInterval) > Date() }
                     .sorted { $0.dateString > $1.dateString } ?? []
                 let glucose = Array(uniqEvents)
                 storage.save(glucose, as: file)

+ 89 - 0
FreeAPS/Sources/Models/DailyStats.swift

@@ -0,0 +1,89 @@
+import Foundation
+
+struct DailyStats: JSON, Equatable {
+    var createdAt: Date
+    var FAX_Build_Version: String
+    var FAX_Build_Number: String
+    var FAX_Branch: String
+    var FAX_Build_Date: Date
+    var Algorithm: String
+    var AdjustmentFactor: Decimal
+    var Pump: String
+    var CGM: String
+    var insulinType: String
+    var peakActivityTime: Decimal
+    var TDD: Decimal
+    var Carbs_24h: Decimal
+    var TIR: String
+    var BG_Average: String
+    var HbA1c: String
+    var id: String
+
+    init(
+        createdAt: Date,
+        FAX_Build_Version: String,
+        FAX_Build_Number: String,
+        FAX_Branch: String,
+        FAX_Build_Date: Date,
+        Algorithm: String,
+        AdjustmentFactor: Decimal,
+        Pump: String,
+        CGM: String,
+        insulinType: String,
+        peakActivityTime: Decimal,
+        TDD: Decimal,
+        Carbs_24h: Decimal,
+        TIR: String,
+        BG_Average: String,
+        HbA1c: String,
+        id: String
+    ) {
+        self.createdAt = createdAt
+        self.FAX_Build_Version = FAX_Build_Version
+        self.FAX_Build_Number = FAX_Build_Number
+        self.FAX_Branch = FAX_Branch
+        self.FAX_Build_Date = FAX_Build_Date
+        self.Algorithm = Algorithm
+        self.AdjustmentFactor = AdjustmentFactor
+        self.Pump = Pump
+        self.CGM = CGM
+        self.insulinType = insulinType
+        self.peakActivityTime = peakActivityTime
+        self.TDD = TDD
+        self.Carbs_24h = Carbs_24h
+        self.TIR = TIR
+        self.BG_Average = BG_Average
+        self.HbA1c = HbA1c
+        self.id = id
+    }
+
+    static func == (lhs: DailyStats, rhs: DailyStats) -> Bool {
+        lhs.createdAt == rhs.createdAt
+    }
+
+    func hash(into hasher: inout Hasher) {
+        hasher.combine(createdAt)
+    }
+}
+
+extension DailyStats {
+    private enum CodingKeys: String, CodingKey {
+        case createdAt
+        case FAX_Build_Version
+        case FAX_Build_Number
+        case FAX_Branch
+        case FAX_Build_Date
+        case Algorithm
+        case AdjustmentFactor
+        case Pump
+        case CGM
+        case insulinType
+        case peakActivityTime
+        case TDD
+        case Carbs_24h
+        case TIR
+        case BG_Average
+        case HbA1c
+        case id
+    }
+}

+ 2 - 0
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -114,6 +114,8 @@ extension Settings {
                                 .navigationLink(to: .configEditor(file: OpenAPS.Monitor.tdd), from: self)
                             Text("TDD Averages")
                                 .navigationLink(to: .configEditor(file: OpenAPS.Monitor.tdd_averages), from: self)
+                            Text("Daily Statistics")
+                                .navigationLink(to: .configEditor(file: OpenAPS.Monitor.dailyStats), from: self)
                             Text("Edit settings json")
                                 .navigationLink(to: .configEditor(file: OpenAPS.FreeAPS.settings), from: self)
                         }