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

Fix BG history truncation caused by duplicate readings from multiple uploaders (#662)

* Increase Nightscout BG entry count to handle multiple uploaders

Some users have multiple sources uploading BG entries to Nightscout for
the same sensor (e.g. a closed-loop system plus two Dexcom platform apps),
resulting in up to 3 entries per 5-minute slot. The previous count cap of
days × 2 × 288 was too low in those cases, silently truncating the oldest
portion of the requested time window before deduplication.

Raise the multiplier from 2 to 4 in both BGData.swift and
StatsDataFetcher.swift, giving headroom for up to 4 uploaders. The
date[$gte] filter still bounds the window, so no extra data is returned
when fewer entries exist.

* Supplement Dex Share with NS data when Dex doesn't cover the full window

When Dexcom credentials are present, LoopFollow fetches from Dex Share
first. If the Dex data doesn't reach back to the start of the requested
window (e.g. Dex Share's 24h cap when more days are configured, or any
other gap), fall through to webLoadNSBGData so Nightscout fills in the
older portion. The existing merge logic already handles stitching the two
sources together correctly.

* Deduplicate Dex Share readings before use

Multiple Dexcom uploaders (e.g. G7 iPhone app + Apple Watch) each write
readings to Dexcom's cloud, causing the Share API to return 2+ entries
per 5-minute slot. With the API's hard cap of 288 readings, duplicates
consume the budget and the returned data covers only ~15h instead of 24h.

Dedup Dex Share data with the same 30-second window used for Nightscout,
so both the NS-supplement path and the direct ProcessDexBGData path
receive clean, deduplicated readings.

* Extract BG entry-count multiplier into named globalVariables.maxExpectedUploaders

* Reuse deduplicateBGReadings helper for Dexcom Share dedup
Jonas Björkert пре 1 месец
родитељ
комит
99cd60e158

+ 11 - 5
LoopFollow/Controllers/Nightscout/BGData.swift

@@ -33,11 +33,17 @@ extension MainViewController {
                 return
             }
 
-            // Dexcom only returns 24 hrs of data. If we need more, call NS.
-            if graphHours > 24, IsNightscoutEnabled() {
-                self.webLoadNSBGData(dexData: data)
+            // Dexcom Share can return duplicate readings when multiple uploaders
+            // write to the same Dexcom account. Dedup before any further use.
+            let dedupedData = self.deduplicateBGReadings(data)
+
+            // Supplement with NS if Dex data doesn't cover the full requested window.
+            let dexCutoff = dateTimeUtils.getNowTimeIntervalUTC() - Double(graphHours) * 3600
+            let dexCoversFull = dedupedData.last.map { $0.date <= dexCutoff } ?? false
+            if !dexCoversFull, IsNightscoutEnabled() {
+                self.webLoadNSBGData(dexData: dedupedData)
             } else {
-                self.ProcessDexBGData(data: self.deduplicateBGReadings(data), sourceName: "Dexcom")
+                self.ProcessDexBGData(data: dedupedData, sourceName: "Dexcom")
             }
         }
     }
@@ -52,7 +58,7 @@ extension MainViewController {
 
         var parameters: [String: String] = [:]
         let date = Calendar.current.date(byAdding: .day, value: -1 * Storage.shared.downloadDays.value, to: Date())!
-        parameters["count"] = "\(Storage.shared.downloadDays.value * 2 * 24 * 60 / 5)"
+        parameters["count"] = "\(Storage.shared.downloadDays.value * globalVariables.maxExpectedUploaders * 24 * 60 / 5)"
         parameters["find[date][$gte]"] = "\(Int(date.timeIntervalSince1970 * 1000))"
 
         // Exclude 'cal' entries

+ 6 - 0
LoopFollow/Helpers/Globals.swift

@@ -18,4 +18,10 @@ enum globalVariables {
     // BG readings and prediction values on the graph.
     static let minDisplayGlucose: Int = 39
     static let maxDisplayGlucose: Int = 400
+
+    // Number of apps that may upload BG to the same account (a looping system,
+    // the Dexcom app, Apple Watch, ...). Each one writes a duplicate reading per
+    // slot, so the Nightscout entry-count request is multiplied by this to avoid
+    // truncating history before the date filter bounds the window.
+    static let maxExpectedUploaders = 4
 }

+ 1 - 1
LoopFollow/Stats/StatsDataFetcher.swift

@@ -26,7 +26,7 @@ class StatsDataFetcher {
         var parameters: [String: String] = [:]
         let utcISODateFormatter = ISO8601DateFormatter()
         let startDate = dataService?.startDate ?? dateTimeUtils.displayCalendar().date(byAdding: .day, value: -1 * days, to: Date())!
-        parameters["count"] = "\(days * 2 * 24 * 60 / 5)"
+        parameters["count"] = "\(days * globalVariables.maxExpectedUploaders * 24 * 60 / 5)"
         parameters["find[dateString][$gte]"] = utcISODateFormatter.string(from: startDate)
         parameters["find[type][$ne]"] = "cal"