Skip to content

Re-score folds all ~21 days every pass even when only today changed (per-day scoping vs row-volume) #1147

Closed

Description

@ryanbr

Source: real WHOOP 4.0 capture noop-capture-260808-1527.txt + strap log (9.3.2-staging, 2026-08-08). Companion to #1144.

The post-backfill re-score fires on offload completion — including empty ones. In the noon auto-continue storm (#1144), empty offloads triggered re-scores over ~180k HR rows/day for no data change. Gate the post-backfill re-score on sessionRowsPersisted > 0 (the exact signal #1145 now trusts in shouldAutoContinue). Small change, cuts a big share of the 77 re-score passes. Both platforms.

Activity

  1. ryanbr commented on Aug 8, 2026

    @ryanbr
    OwnerAuthor

    Verification before implementing — this is near-no-op; repointing.

    Checked against the capture: during the 168-offload noon storm (#1144) there was exactly 1 re-score (12:15:59), not one per empty offload. onBackfillChunkCommitted (WhoopBleClient.kt:2076) is already "invoked once per non-empty decoded chunk" and schedules the pass once per session via analyzeAfterBackfillScheduled compare-and-set — so empty offloads don't trigger re-scores. An explicit rowsPersisted>0 gate here would be belt-and-braces only.

    The real re-score cost is elsewhere: 77 passes in 19.5h ≈ one every 15 min — i.e. the AppViewModel idle loop, not offloads. Each pass re-scores ~21 days over ~180k HR rows/day. It's meant to be a cheap no-op when HR is unchanged (fingerprint/watermark gate), but during a live connection the per-second live-HR stream advances the HR fingerprint every tick, so fp != watermark always holds and the 15-min loop does a full 21-day re-score every time (the #836 / #1124 area; iOS moved the cadence 15→30 min, Android is still 15 min here).

    Retargeting this issue to: the periodic idle re-score does a full multi-day pass every 15 min while a live HR stream is connected, because live HR always advances the fingerprint. Safe fixes to weigh: per-day fingerprints (only re-score days whose fingerprint changed — only today during a live session), or bump the Android idle cadence toward the iOS 30-min. NOT the partial-day-window skip (the #789/#999 window-wide-delete data-loss trap, per reference_noop_analyzerecent_perf).

  2. changed the title [-]Don't re-score after an empty offload (chunkRows=0)[/-] [+]Idle re-score does a full 21-day pass every 15 min while live HR is connected (fingerprint always advances)[/+] on Aug 8, 2026
  3. ryanbr commented on Aug 8, 2026

    @ryanbr
    OwnerAuthor

    Correction to my previous comment — Android is NOT 15 min. AppViewModel.ANALYZE_INTERVAL_MS = 30 * 60 * 1_000L (line 2532) = 30 min, already at parity with iOS's 30-min backstop (AppModel.swift:452). The // 15-min comments scattered in AppViewModel are stale (pre-#1124) — worth a doc-only cleanup.

    So the 77 re-scores in 19.5h are 30-min idle ticks + legitimate event-triggered passes (real non-empty offloads via onBackfillChunkCommitted, and #386 app-resume kicks that wake the loop early) — not a runaway 15-min loop.

    The genuine cost that remains: each pass re-scores all ~21 days over ~180k HR rows/day, even when only today's data changed. Two honest levers, neither a trivial quick-win:

    1. Per-day scoping — only re-score days whose HR fingerprint changed (during a live session that's just today). This is the right fix but must avoid the diag: say WHY the sparse sleep bridge did nothing (#737) #789/SourceBadge: centre the label in the pill on both axes #999 window-wide-delete data-loss trap (reference_noop_analyzerecent_perf) — needs care/design.
    2. Reduce the per-pass row volume — Raw-row retention: the on-device DB holds 17.2M raw rows and every re-score folds over all of it #1146 (raw-row retention) and WHOOP 4.0 R-R over-count contaminates HRV (not reproducible since 2026-08-25, cause unidentified) #1118 (crossSecondOverCount ~2×) both shrink what every pass folds over.

    Net: there is no safe one-liner here. Closing this as "investigated → folds into #1146/#1118 + a future per-day-scoping design", unless you want to pursue per-day fingerprints directly.

  4. changed the title [-]Idle re-score does a full 21-day pass every 15 min while live HR is connected (fingerprint always advances)[/-] [+]Re-score folds all ~21 days every pass even when only today changed (per-day scoping vs row-volume)[/+] on Aug 8, 2026
  5. ryanbr commented on Aug 8, 2026

    @ryanbr
    OwnerAuthor

    Closing: the original premise (re-score fires on empty offloads) was wrong — the re-score is already gated on non-empty chunks, and the repointed finding (each pass recomputes all ~21 days) is covered by #1146 (raw-row retention) + #1118 (over-count). The one unique lever — per-day fingerprint scoping — is now noted on #1146 as an alternative to retention. Reopen if per-day scoping warrants its own tracker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions