|
|
@@ -41,6 +41,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
|
|
|
|
|
|
private enum Config {
|
|
|
static let filterTime: TimeInterval = 3.5 * 60
|
|
|
+ static let minimumGlucose: Int = 39
|
|
|
}
|
|
|
|
|
|
private let context: NSManagedObjectContext
|
|
|
@@ -75,10 +76,11 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
|
|
|
/// it isn't within 3.5 minutes of an existing glucose reading, which is simple but not perfect.
|
|
|
/// But since this is a corner case that really shouldn't happen often, it's good enough.
|
|
|
func backfillGlucose(_ glucose: [BloodGlucose]) async throws {
|
|
|
+ let clamped = clampToMinimum(glucose)
|
|
|
try await context.perform {
|
|
|
// remove already deleted glucose values
|
|
|
let withoutDeletedGlucose = self.filterGlucoseValues(
|
|
|
- glucose,
|
|
|
+ clamped,
|
|
|
fetchRequest: DeletedGlucoseStored.fetchRequest(),
|
|
|
timeBuffer: 1
|
|
|
)
|
|
|
@@ -105,9 +107,10 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
|
|
|
}
|
|
|
|
|
|
func storeGlucose(_ glucose: [BloodGlucose]) async throws {
|
|
|
+ let clamped = clampToMinimum(glucose)
|
|
|
try await context.perform {
|
|
|
// Get new glucose values that don't exist yet
|
|
|
- let newGlucose = self.filterGlucoseValues(glucose, fetchRequest: GlucoseStored.fetchRequest(), timeBuffer: 1)
|
|
|
+ let newGlucose = self.filterGlucoseValues(clamped, fetchRequest: GlucoseStored.fetchRequest(), timeBuffer: 1)
|
|
|
guard !newGlucose.isEmpty else { return }
|
|
|
|
|
|
do {
|
|
|
@@ -121,7 +124,31 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
|
|
|
}
|
|
|
|
|
|
// Store CGM state if needed
|
|
|
- self.storeCGMState(glucose)
|
|
|
+ self.storeCGMState(clamped)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Clamps CGM-sourced glucose readings to a minimum of `Config.minimumGlucose`
|
|
|
+ /// (39 mg/dL — the official Libre/Dexcom algorithmic floor). Some CGM plugins
|
|
|
+ /// (notably LibreTransmitter) deliberately bypass the vendor floor and forward
|
|
|
+ /// values down to 1 mg/dL; the JS oref `glucose-get-last` filter then drops them
|
|
|
+ /// (`> 38`) and the loop has no fresh BG during the most dangerous range. We
|
|
|
+ /// clamp here so determination always has a usable value and emit a debug log
|
|
|
+ /// line so the raw reading survives for diagnostics.
|
|
|
+ private func clampToMinimum(_ glucose: [BloodGlucose]) -> [BloodGlucose] {
|
|
|
+ glucose.map { entry in
|
|
|
+ var clamped = entry
|
|
|
+ if let raw = entry.glucose, raw < Config.minimumGlucose {
|
|
|
+ debug(
|
|
|
+ .deviceManager,
|
|
|
+ "Clamping sub-\(Config.minimumGlucose) glucose: raw=\(raw) at \(entry.dateString) -> \(Config.minimumGlucose)"
|
|
|
+ )
|
|
|
+ clamped.glucose = Config.minimumGlucose
|
|
|
+ }
|
|
|
+ if let raw = entry.sgv, raw < Config.minimumGlucose {
|
|
|
+ clamped.sgv = Config.minimumGlucose
|
|
|
+ }
|
|
|
+ return clamped
|
|
|
}
|
|
|
}
|
|
|
|