Repository navigation
fix(hrv): make the windowed rMSSD gap-aware, like the nightly one already is (#1448) - #1449
Merged
Merged
Conversation
…eady is (#1448) The Deep Timeline's windowed rMSSD took successive differences across beats the artifact filter had removed. Dropping a beat joins two intervals that were never adjacent, and their difference is a splice rather than a physiological delta - the spurious large delta this analyzer's gap-aware pair (#204/#195) exists to exclude. The nightly analyze() already used cleanRRGapAware + rmssdGapAware. rollingRmssd used cleanRR + rmssdRaw, three lines away in the same file, on BOTH platforms - so the twins agreed with each other while both were wrong, which is why no parity check surfaced it. That matters most on the messiest data, which on a WHOOP 4.0 is most of it (#1008), and the trace exists precisely to be an honest windowed rMSSD (#803) rather than the raw-interval chart it replaced. Two things keep this narrow. CleanSeries.nn is byte-identical to cleanRR over the same input, so the minBeatsPerWindow survivor gate is unchanged; and rmssdGapAware equals rmssdRaw when a window has no gaps, so only windows that actually lost a beat move. The one visible consequence: a window whose survivors share no adjacent pair now emits nothing rather than a number built entirely from splices - honest absence, consistent with the existing sparse-window gate. Mirrored fixtures per platform, and the control is the important half: a gapless window still reads exactly 50.0 ms, proving this is not a numbers-move for clean data, while the same two runs separated by an out-of-range beat drop from a filter-invented 50.0 to 0.0. Android 4,070 tests / 0 failures; i18n and doc-comment gates clean. Both files sit under Packages / Android, so swift-packages executes the Swift twin - no app-target Swift touched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1448.
The defect
The Deep Timeline's windowed rMSSD took successive differences across beats the artifact filter had
removed. Dropping a beat joins two intervals that were never adjacent, and their difference is a
splice, not a physiological delta — the spurious large delta this analyzer's gap-aware pair (#204/#195)
exists to exclude.
The nightly
analyze()already usedcleanRRGapAware+rmssdGapAware.rollingRmssdusedcleanRR+rmssdRawthree lines away in the same file — on both platforms.analyzerollingRmssdThe twins agreed with each other while both were wrong, which is exactly why no parity check ever
surfaced it. It bites hardest on the messiest data — on a WHOOP 4.0 that is most of it (#1008) — and the
trace exists specifically to be an honest windowed rMSSD (#803) rather than the raw-interval chart it
replaced.
Why this stays narrow
CleanSeries.nnis documented as byte-identical tocleanRRover the same input, so theminBeatsPerWindowsurvivor gate does not move.rmssdGapAwareis identical tormssdRawwhen there are no gaps, so a clean window isunchanged. Only windows that actually lost a beat move.
The one visible consequence: a window whose survivors share no adjacent pair now emits nothing
rather than a number assembled entirely from splices. That is honest absence, and consistent with the
existing sparse-window gate that already prefers no point over a noisy one.
Tests
Mirrored fixtures per platform, built on the range filter so the drop is deterministic rather than
depending on Malik's neighbour median:
and the emitted value goes from a filter-invented 50.0 → 0.0
50.0, proving this is not a numbers-move for clean data
Verification
--rerun-tasks, both new vectors confirmed by name)Packages/and Android, soswift-packagesexecutes the Swift twin— no app-target Swift touched, so no app-build gate needed