Skip to content

HRV and Charge stop at an R-R boundary while HR, sleep and RHR continue past it #2370

Description

@ryanbr

Observation

On a 5/MG install, a full 21-night re-score produces HRV and Charge for only the most recent eleven nights, while sleep and resting HR reach the whole window. The census line is stable across consecutive passes:

re-score census: 21 night(s) 2026-09-01..2026-09-21 charge=11 effort=14 sleep=20 hrv=11 rhr=20

The eleven nights carrying HRV are contiguous and are the newest eleven. Every night older than that boundary has no HRV, and therefore no Charge.

What the evidence says

R-R is ABSENT before the boundary, not present-and-rejected. The clearest single line, from the same pass:

sleep-detect day=2026-09-07 NO-NIGHT hr=134089 rr=0 resp=0 grav=0 skin=0 steps=0

Abundant heart rate, zero R-R, on a day that sits before the boundary. On the eleven nights after it, R-R beat counts per night run from roughly 3,700 to 23,000, so where R-R exists there is plenty of it.

So HRV and Charge are following R-R availability. That part is not in question.

What is ruled out

Two plausible explanations were checked and do not hold:

  • Not a quality gate. HRVAnalyzer only gates on the two OVER-COUNT verdicts; underCovered and unmeasurable stay trusted (HRVAnalyzer.swift:422, :436). Most of the nights that DO produce HRV read underCovered, so that verdict plainly is not what removes a night.
  • Not age-based retention. The only R-R prune is pruneRrByTs, called from WhoopRepository with a plausibility window of MIN_PLAUSIBLE_UNIX through now + futureMargin. It removes implausible timestamps, not old rows.

What is NOT established

Why R-R stops at that boundary while heart rate continues through it. The install has two registered straps, a 5/MG active and a 4.0 last seen a fortnight ago, so a handover between sources is one candidate, and a capture path that only began storing R-R later is another. Nothing in the log distinguishes them, and this issue deliberately does not pick one.

What would discriminate

A single store query settles the first fork: do rr rows exist for the days before the boundary under EITHER device id?

  • No rows at all means R-R was never captured for those days, and the question becomes which source was writing heart rate for them and why it carried no R-R.
  • Rows present means they are being captured but not reaching the analyzer, and the question moves to the read path and its device-id scoping.

Worth running that before anyone changes a threshold or a window.

Why it matters

Charge depends on HRV, so a user with more history than the R-R boundary sees roughly half their recovery scores silently absent, with the census line reporting it as "the pass produced nothing for" rather than as a missing input. That reads like a scoring fault when the inputs are the thing that is missing.

Filed from a maintainer device log rather than a user report. No biometric values are quoted here, only row counts and dates.

Activity

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