From 135c724ce1f6ebeedab1b8b70bdf115e48dd2fee Mon Sep 17 00:00:00 2001 From: ryanbr Date: Tue, 21 Jul 2026 23:31:59 -0700 Subject: [PATCH] Log why a day is skipped when HR samples are insufficient (#714) When a day has fewer than 200 HR samples, the analytics pass silently skips it. The sleep line still emits totalSleepMin=nil matched=0 but nothing distinguishes "stager ran, found nothing" from "no data to stage." A one-line diagnostic on each platform now makes every "no sleep" report self-diagnosing from the strap log alone. --- Strand/Data/IntelligenceEngine.swift | 5 ++++- .../src/main/java/com/noop/analytics/IntelligenceEngine.kt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Strand/Data/IntelligenceEngine.swift b/Strand/Data/IntelligenceEngine.swift index 4421460488..748adf9a0f 100644 --- a/Strand/Data/IntelligenceEngine.swift +++ b/Strand/Data/IntelligenceEngine.swift @@ -540,7 +540,10 @@ final class IntelligenceEngine: ObservableObject { registry: registry, fallbackDeviceId: ownerFallbackId) let hr = (try? await store.hrSamples(deviceId: owner, from: from, to: to, limit: 200_000)) ?? [] - guard hr.count >= 200 else { continue } // need real raw data, not a stray sample + guard hr.count >= 200 else { + diag("sleep day=\(day) SKIPPED hrSamples=\(hr.count) (need ≥200)") + continue + } let rr = (try? await store.rrIntervals(deviceId: owner, from: from, to: to, limit: 200_000)) ?? [] let resp = (try? await store.respSamples(deviceId: owner, from: from, to: to, limit: 200_000)) ?? [] let grav = (try? await store.gravitySamples(deviceId: owner, from: from, to: to, limit: 200_000)) ?? [] diff --git a/android/app/src/main/java/com/noop/analytics/IntelligenceEngine.kt b/android/app/src/main/java/com/noop/analytics/IntelligenceEngine.kt index dab0df9c7d..94362be2ca 100644 --- a/android/app/src/main/java/com/noop/analytics/IntelligenceEngine.kt +++ b/android/app/src/main/java/com/noop/analytics/IntelligenceEngine.kt @@ -463,7 +463,10 @@ object IntelligenceEngine { // scored-days loop, NOT here). Only when the universal sink is on. A day skipped below for too // few rows is never scored, so it emits no line, byte-identical to the iOS behaviour. if (universalSink != null) readOwnerByDay[day] = OwnerRead(owner, hr.size) - if (hr.size < MIN_HR_SAMPLES) continue // need real raw data, not a stray sample + if (hr.size < MIN_HR_SAMPLES) { + diag("sleep day=$day SKIPPED hrSamples=${hr.size} (need ≥$MIN_HR_SAMPLES)") + continue + } val rr = repo.rrIntervals(owner, from, to, STREAM_LIMIT) val resp = repo.respSamples(owner, from, to, STREAM_LIMIT) val grav = repo.gravitySamples(owner, from, to, STREAM_LIMIT)