|
|
@@ -721,7 +721,6 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
}
|
|
|
}
|
|
|
nightscout.uploadStatus()
|
|
|
- statistics()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -879,327 +878,6 @@ final class BaseAPSManager: APSManager, Injectable {
|
|
|
return output
|
|
|
}
|
|
|
|
|
|
- // Add to statistics.JSON for upload to NS.
|
|
|
- private func statistics() {
|
|
|
- let now = Date()
|
|
|
- if settingsManager.settings.uploadStats {
|
|
|
- let hour = Calendar.current.component(.hour, from: now)
|
|
|
- guard hour > 20 else {
|
|
|
- return
|
|
|
- }
|
|
|
- coredataContext.performAndWait { [self] in
|
|
|
- var stats = [StatsData]()
|
|
|
- let requestStats = StatsData.fetchRequest() as NSFetchRequest<StatsData>
|
|
|
- let sortStats = NSSortDescriptor(key: "lastrun", ascending: false)
|
|
|
- requestStats.sortDescriptors = [sortStats]
|
|
|
- requestStats.fetchLimit = 1
|
|
|
- try? stats = coredataContext.fetch(requestStats)
|
|
|
- // Only save and upload once per day
|
|
|
- guard (-1 * (stats.first?.lastrun ?? .distantPast).timeIntervalSinceNow.hours) > 22 else { return }
|
|
|
-
|
|
|
- let units = self.settingsManager.settings.units
|
|
|
- let preferences = settingsManager.preferences
|
|
|
-
|
|
|
- // Carbs
|
|
|
- var carbs = [Carbohydrates]()
|
|
|
- var carbTotal: Decimal = 0
|
|
|
- let requestCarbs = Carbohydrates.fetchRequest() as NSFetchRequest<Carbohydrates>
|
|
|
- let daysAgo = Date().addingTimeInterval(-1.days.timeInterval)
|
|
|
- requestCarbs.predicate = NSPredicate(format: "carbs > 0 AND date > %@", daysAgo as NSDate)
|
|
|
- let sortCarbs = NSSortDescriptor(key: "date", ascending: true)
|
|
|
- requestCarbs.sortDescriptors = [sortCarbs]
|
|
|
- try? carbs = coredataContext.fetch(requestCarbs)
|
|
|
- carbTotal = carbs.map({ carbs in carbs.carbs as? Decimal ?? 0 }).reduce(0, +)
|
|
|
-
|
|
|
- // TDD
|
|
|
- var tdds = [TDD]()
|
|
|
- var currentTDD: Decimal = 0
|
|
|
- var tddTotalAverage: Decimal = 0
|
|
|
- let requestTDD = TDD.fetchRequest() as NSFetchRequest<TDD>
|
|
|
- let sort = NSSortDescriptor(key: "timestamp", ascending: false)
|
|
|
- let daysOf14Ago = Date().addingTimeInterval(-14.days.timeInterval)
|
|
|
- requestTDD.predicate = NSPredicate(format: "timestamp > %@", daysOf14Ago as NSDate)
|
|
|
- requestTDD.sortDescriptors = [sort]
|
|
|
- try? tdds = coredataContext.fetch(requestTDD)
|
|
|
-
|
|
|
- if !tdds.isEmpty {
|
|
|
- currentTDD = tdds[0].tdd?.decimalValue ?? 0
|
|
|
- let tddArray = tdds.compactMap({ insulin in insulin.tdd as? Decimal ?? 0 })
|
|
|
- tddTotalAverage = tddArray.reduce(0, +) / Decimal(tddArray.count)
|
|
|
- }
|
|
|
-
|
|
|
- var algo_ = "Oref0"
|
|
|
-
|
|
|
- if preferences.sigmoid, preferences.enableDynamicCR {
|
|
|
- algo_ = "Dynamic ISF + CR: Sigmoid"
|
|
|
- } else if preferences.sigmoid, !preferences.enableDynamicCR {
|
|
|
- algo_ = "Dynamic ISF: Sigmoid"
|
|
|
- } else if preferences.useNewFormula, preferences.enableDynamicCR {
|
|
|
- algo_ = "Dynamic ISF + CR: Logarithmic"
|
|
|
- } else if preferences.useNewFormula, !preferences.sigmoid,!preferences.enableDynamicCR {
|
|
|
- algo_ = "Dynamic ISF: Logarithmic"
|
|
|
- }
|
|
|
- let af = preferences.adjustmentFactor
|
|
|
- let insulin_type = preferences.curve
|
|
|
- let buildDate = Bundle.main.buildDate
|
|
|
- let version = Bundle.main.releaseVersionNumber
|
|
|
- let build = Bundle.main.buildVersionNumber
|
|
|
-
|
|
|
- // Read branch information from branch.txt instead of infoDictionary
|
|
|
- var branch = "Unknown"
|
|
|
- if let branchFileURL = Bundle.main.url(forResource: "branch", withExtension: "txt"),
|
|
|
- let branchFileContent = try? String(contentsOf: branchFileURL)
|
|
|
- {
|
|
|
- let lines = branchFileContent.components(separatedBy: .newlines)
|
|
|
- for line in lines {
|
|
|
- let components = line.components(separatedBy: "=")
|
|
|
- if components.count == 2 {
|
|
|
- let key = components[0].trimmingCharacters(in: .whitespaces)
|
|
|
- let value = components[1].trimmingCharacters(in: .whitespaces)
|
|
|
-
|
|
|
- if key == "BRANCH" {
|
|
|
- branch = value
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- branch = "Unknown"
|
|
|
- }
|
|
|
-
|
|
|
- let copyrightNotice_ = Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String ?? ""
|
|
|
- let pump_ = pumpManager?.localizedTitle ?? ""
|
|
|
- let cgm = settingsManager.settings.cgm
|
|
|
- let file = OpenAPS.Monitor.statistics
|
|
|
- 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
|
|
|
- }
|
|
|
- // CGM Readings
|
|
|
- var glucose_24 = [Readings]() // Day
|
|
|
- var glucose_7 = [Readings]() // Week
|
|
|
- var glucose_30 = [Readings]() // Month
|
|
|
- var glucose = [Readings]() // Total
|
|
|
- let filter = DateFilter()
|
|
|
- // 24h
|
|
|
- let requestGFS_24 = Readings.fetchRequest() as NSFetchRequest<Readings>
|
|
|
- let sortGlucose_24 = NSSortDescriptor(key: "date", ascending: false)
|
|
|
- requestGFS_24.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.day)
|
|
|
- requestGFS_24.sortDescriptors = [sortGlucose_24]
|
|
|
- try? glucose_24 = coredataContext.fetch(requestGFS_24)
|
|
|
- // Week
|
|
|
- let requestGFS_7 = Readings.fetchRequest() as NSFetchRequest<Readings>
|
|
|
- let sortGlucose_7 = NSSortDescriptor(key: "date", ascending: false)
|
|
|
- requestGFS_7.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.week)
|
|
|
- requestGFS_7.sortDescriptors = [sortGlucose_7]
|
|
|
- try? glucose_7 = coredataContext.fetch(requestGFS_7)
|
|
|
- // Month
|
|
|
- let requestGFS_30 = Readings.fetchRequest() as NSFetchRequest<Readings>
|
|
|
- let sortGlucose_30 = NSSortDescriptor(key: "date", ascending: false)
|
|
|
- requestGFS_30.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.month)
|
|
|
- requestGFS_30.sortDescriptors = [sortGlucose_30]
|
|
|
- try? glucose_30 = coredataContext.fetch(requestGFS_30)
|
|
|
- // Total
|
|
|
- let requestGFS = Readings.fetchRequest() as NSFetchRequest<Readings>
|
|
|
- let sortGlucose = NSSortDescriptor(key: "date", ascending: false)
|
|
|
- requestGFS.predicate = NSPredicate(format: "glucose > 0 AND date > %@", filter.total)
|
|
|
- requestGFS.sortDescriptors = [sortGlucose]
|
|
|
- try? glucose = coredataContext.fetch(requestGFS)
|
|
|
-
|
|
|
- // First date
|
|
|
- let previous = glucose.last?.date ?? Date()
|
|
|
- // Last date (recent)
|
|
|
- let current = glucose.first?.date ?? Date()
|
|
|
- // Total time in days
|
|
|
- let numberOfDays = (current - previous).timeInterval / 8.64E4
|
|
|
-
|
|
|
- // Get glucose computations for every case
|
|
|
- let oneDayGlucose = glucoseStats(glucose_24)
|
|
|
- let sevenDaysGlucose = glucoseStats(glucose_7)
|
|
|
- let thirtyDaysGlucose = glucoseStats(glucose_30)
|
|
|
- let totalDaysGlucose = glucoseStats(glucose)
|
|
|
-
|
|
|
- let median = Durations(
|
|
|
- day: roundDecimal(Decimal(oneDayGlucose.median), 1),
|
|
|
- week: roundDecimal(Decimal(sevenDaysGlucose.median), 1),
|
|
|
- month: roundDecimal(Decimal(thirtyDaysGlucose.median), 1),
|
|
|
- total: roundDecimal(Decimal(totalDaysGlucose.median), 1)
|
|
|
- )
|
|
|
-
|
|
|
- let overrideHbA1cUnit = settingsManager.settings.overrideHbA1cUnit
|
|
|
-
|
|
|
- let hbs = Durations(
|
|
|
- day: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
|
|
|
- roundDecimal(Decimal(oneDayGlucose.ifcc), 1) : roundDecimal(Decimal(oneDayGlucose.ngsp), 1),
|
|
|
- week: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
|
|
|
- roundDecimal(Decimal(sevenDaysGlucose.ifcc), 1) : roundDecimal(Decimal(sevenDaysGlucose.ngsp), 1),
|
|
|
- month: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
|
|
|
- roundDecimal(Decimal(thirtyDaysGlucose.ifcc), 1) : roundDecimal(Decimal(thirtyDaysGlucose.ngsp), 1),
|
|
|
- total: ((units == .mmolL && !overrideHbA1cUnit) || (units == .mgdL && overrideHbA1cUnit)) ?
|
|
|
- roundDecimal(Decimal(totalDaysGlucose.ifcc), 1) : roundDecimal(Decimal(totalDaysGlucose.ngsp), 1)
|
|
|
- )
|
|
|
-
|
|
|
- var oneDay_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
|
|
|
- var sevenDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
|
|
|
- var thirtyDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
|
|
|
- var totalDays_: (TIR: Double, hypos: Double, hypers: Double, normal_: Double) = (0.0, 0.0, 0.0, 0.0)
|
|
|
- // Get TIR computations for every case
|
|
|
- oneDay_ = tir(glucose_24)
|
|
|
- sevenDays_ = tir(glucose_7)
|
|
|
- thirtyDays_ = tir(glucose_30)
|
|
|
- totalDays_ = tir(glucose)
|
|
|
-
|
|
|
- let tir = Durations(
|
|
|
- day: roundDecimal(Decimal(oneDay_.TIR), 1),
|
|
|
- week: roundDecimal(Decimal(sevenDays_.TIR), 1),
|
|
|
- month: roundDecimal(Decimal(thirtyDays_.TIR), 1),
|
|
|
- total: roundDecimal(Decimal(totalDays_.TIR), 1)
|
|
|
- )
|
|
|
- let hypo = Durations(
|
|
|
- day: Decimal(oneDay_.hypos),
|
|
|
- week: Decimal(sevenDays_.hypos),
|
|
|
- month: Decimal(thirtyDays_.hypos),
|
|
|
- total: Decimal(totalDays_.hypos)
|
|
|
- )
|
|
|
- let hyper = Durations(
|
|
|
- day: Decimal(oneDay_.hypers),
|
|
|
- week: Decimal(sevenDays_.hypers),
|
|
|
- month: Decimal(thirtyDays_.hypers),
|
|
|
- total: Decimal(totalDays_.hypers)
|
|
|
- )
|
|
|
- let normal = Durations(
|
|
|
- day: Decimal(oneDay_.normal_),
|
|
|
- week: Decimal(sevenDays_.normal_),
|
|
|
- month: Decimal(thirtyDays_.normal_),
|
|
|
- total: Decimal(totalDays_.normal_)
|
|
|
- )
|
|
|
- let range = Threshold(
|
|
|
- low: units == .mmolL ? roundDecimal(settingsManager.settings.low.asMmolL, 1) :
|
|
|
- roundDecimal(settingsManager.settings.low, 0),
|
|
|
- high: units == .mmolL ? roundDecimal(settingsManager.settings.high.asMmolL, 1) :
|
|
|
- roundDecimal(settingsManager.settings.high, 0)
|
|
|
- )
|
|
|
- let TimeInRange = TIRs(
|
|
|
- TIR: tir,
|
|
|
- Hypos: hypo,
|
|
|
- Hypers: hyper,
|
|
|
- Threshold: range,
|
|
|
- Euglycemic: normal
|
|
|
- )
|
|
|
- let avgs = Durations(
|
|
|
- day: roundDecimal(Decimal(oneDayGlucose.average), 1),
|
|
|
- week: roundDecimal(Decimal(sevenDaysGlucose.average), 1),
|
|
|
- month: roundDecimal(Decimal(thirtyDaysGlucose.average), 1),
|
|
|
- total: roundDecimal(Decimal(totalDaysGlucose.average), 1)
|
|
|
- )
|
|
|
- let avg = Averages(Average: avgs, Median: median)
|
|
|
- // Standard Deviations
|
|
|
- let standardDeviations = Durations(
|
|
|
- day: roundDecimal(Decimal(oneDayGlucose.sd), 1),
|
|
|
- week: roundDecimal(Decimal(sevenDaysGlucose.sd), 1),
|
|
|
- month: roundDecimal(Decimal(thirtyDaysGlucose.sd), 1),
|
|
|
- total: roundDecimal(Decimal(totalDaysGlucose.sd), 1)
|
|
|
- )
|
|
|
- // CV = standard deviation / sample mean x 100
|
|
|
- let cvs = Durations(
|
|
|
- day: roundDecimal(Decimal(oneDayGlucose.cv), 1),
|
|
|
- week: roundDecimal(Decimal(sevenDaysGlucose.cv), 1),
|
|
|
- month: roundDecimal(Decimal(thirtyDaysGlucose.cv), 1),
|
|
|
- total: roundDecimal(Decimal(totalDaysGlucose.cv), 1)
|
|
|
- )
|
|
|
- let variance = Variance(SD: standardDeviations, CV: cvs)
|
|
|
-
|
|
|
- // Loops
|
|
|
- var lsr = [LoopStatRecord]()
|
|
|
- let requestLSR = LoopStatRecord.fetchRequest() as NSFetchRequest<LoopStatRecord>
|
|
|
- requestLSR.predicate = NSPredicate(
|
|
|
- format: "interval > 0 AND start > %@",
|
|
|
- Date().addingTimeInterval(-24.hours.timeInterval) as NSDate
|
|
|
- )
|
|
|
- let sortLSR = NSSortDescriptor(key: "start", ascending: false)
|
|
|
- requestLSR.sortDescriptors = [sortLSR]
|
|
|
- try? lsr = coredataContext.fetch(requestLSR)
|
|
|
- // Compute LoopStats for 24 hours
|
|
|
- let oneDayLoops = loops(lsr)
|
|
|
- let loopstat = LoopCycles(
|
|
|
- loops: oneDayLoops.loops,
|
|
|
- errors: oneDayLoops.errors,
|
|
|
- readings: Int(oneDayGlucose.readings),
|
|
|
- success_rate: oneDayLoops.success_rate,
|
|
|
- avg_interval: oneDayLoops.avg_interval,
|
|
|
- median_interval: oneDayLoops.median_interval,
|
|
|
- min_interval: oneDayLoops.min_interval,
|
|
|
- max_interval: oneDayLoops.max_interval,
|
|
|
- avg_duration: oneDayLoops.avg_duration,
|
|
|
- median_duration: oneDayLoops.median_duration,
|
|
|
- min_duration: oneDayLoops.max_duration,
|
|
|
- max_duration: oneDayLoops.max_duration
|
|
|
- )
|
|
|
-
|
|
|
- // Insulin
|
|
|
- var insulinDistribution = [InsulinDistribution]()
|
|
|
- var insulin = Ins(
|
|
|
- TDD: 0,
|
|
|
- bolus: 0,
|
|
|
- temp_basal: 0,
|
|
|
- scheduled_basal: 0,
|
|
|
- total_average: 0
|
|
|
- )
|
|
|
- let requestInsulinDistribution = InsulinDistribution.fetchRequest() as NSFetchRequest<InsulinDistribution>
|
|
|
- let sortInsulin = NSSortDescriptor(key: "date", ascending: false)
|
|
|
- requestInsulinDistribution.sortDescriptors = [sortInsulin]
|
|
|
- try? insulinDistribution = coredataContext.fetch(requestInsulinDistribution)
|
|
|
- insulin = Ins(
|
|
|
- TDD: roundDecimal(currentTDD, 2),
|
|
|
- bolus: insulinDistribution.first != nil ? ((insulinDistribution.first?.bolus ?? 0) as Decimal) : 0,
|
|
|
- temp_basal: insulinDistribution.first != nil ? ((insulinDistribution.first?.tempBasal ?? 0) as Decimal) : 0,
|
|
|
- scheduled_basal: insulinDistribution
|
|
|
- .first != nil ? ((insulinDistribution.first?.scheduledBasal ?? 0) as Decimal) : 0,
|
|
|
- total_average: roundDecimal(tddTotalAverage, 1)
|
|
|
- )
|
|
|
-
|
|
|
- let hbA1cUnit = !overrideHbA1cUnit ? (units == .mmolL ? "mmol/mol" : "%") : (units == .mmolL ? "%" : "mmol/mol")
|
|
|
-
|
|
|
- let dailystat = Statistics(
|
|
|
- created_at: Date(),
|
|
|
- iPhone: UIDevice.current.getDeviceId,
|
|
|
- iOS: UIDevice.current.getOSInfo,
|
|
|
- Build_Version: version ?? "",
|
|
|
- Build_Number: build ?? "1",
|
|
|
- Branch: branch,
|
|
|
- CopyRightNotice: String(copyrightNotice_.prefix(32)),
|
|
|
- Build_Date: buildDate,
|
|
|
- Algorithm: algo_,
|
|
|
- AdjustmentFactor: af,
|
|
|
- Pump: pump_,
|
|
|
- CGM: cgm.rawValue,
|
|
|
- insulinType: insulin_type.rawValue,
|
|
|
- peakActivityTime: iPa,
|
|
|
- Carbs_24h: carbTotal,
|
|
|
- GlucoseStorage_Days: Decimal(roundDouble(numberOfDays, 1)),
|
|
|
- Statistics: Stats(
|
|
|
- Distribution: TimeInRange,
|
|
|
- Glucose: avg,
|
|
|
- HbA1c: hbs, Units: Units(Glucose: units.rawValue, HbA1c: hbA1cUnit),
|
|
|
- LoopCycles: loopstat,
|
|
|
- Insulin: insulin,
|
|
|
- Variance: variance
|
|
|
- )
|
|
|
- )
|
|
|
- storage.save(dailystat, as: file)
|
|
|
- nightscout.uploadStatistics(dailystat: dailystat)
|
|
|
-
|
|
|
- let saveStatsCoreData = StatsData(context: self.coredataContext)
|
|
|
- saveStatsCoreData.lastrun = Date()
|
|
|
- try? self.coredataContext.save()
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
private func loopStats(loopStatRecord: LoopStats) {
|
|
|
coredataContext.perform {
|
|
|
let nLS = LoopStatRecord(context: self.coredataContext)
|