Repository navigation
fix(hrv): do not replace a refused main night's HRV with the day's naps - #2426
Merged
ryanbr merged 1 commit intoSep 24, 2026
Merged
Conversation
The day's HRV is the in-bed-weighted mean of every session's HRV, on the premise that the main overnight dominates. When the ryanbr#1118 over-count gate refuses the main night, its HRV is nil and carries zero weight, so the naps became 100 % of the figure. Field case: a ring night at 1.31 coverage was refused, and the day's HRV became the mean of two 26-minute naps, (69.51 + 27.90) / 2 = 48.7 ms, which the baseline then folded as a full night. Now, when a main-night-group session's HRV is nil BECAUSE the gate refused it, only the main group is pooled, so the day holds (nil) unless another fragment of that night measured cleanly. A main night that simply banked no R-R is not refused, so ryanbr#1884's fill-in from the day's other sessions is unchanged. The deep-window mode is untouched. The refusal test is one pure helper on both platforms, SleepStager.sessionHrvOverCounted, which sessionAvgHRV now gates on too, so "refused" has a single definition that the value and the day-level rule cannot disagree about. Tests: new AnalyticsEngineRefusedMainNightHrvTests (Swift) and its Kotlin twin, same fixtures and expected values: refused main night + clean nap -> nil (60.0 without the fix, on both platforms); no-R-R main night + nap -> 60; clean night + nap -> 41.25 as before; helper agrees with the value gate. StrandAnalytics 2053/0. Kotlin twin 4/0, HrvOverCountGateTest 7/0, AnalyticsEngineProvidedSleepTest 5/0; full Android suite shows the same 8 pre-existing failures as upstream/main, none in touched code. doc_comment_lint OK. Refs ryanbr#1118, ryanbr#1884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AvMtCweTWqqM9iyDBz88XX
5 tasks done
5 tasks done
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.
Problem
The day's HRV (
avgHRVDaily) is the in-bed-weighted mean of every session's HRV. That rests on "the main overnight dominates". When the #1118 over-count gate refuses the main night, its HRV is nil and carries zero weight, so the naps become 100 % of the figure.Field case, one ring: the main night on 2026-09-20 read 1.31 coverage and was refused. The day's HRV became the mean of two 26-minute naps, (69.51 + 27.90) / 2 = 48.7 ms, which the HRV baseline then folded as a full night (mean 32.2 → 35.6 on the second of the four nights it needs to seed).
Change
SleepStager.sessionHrvOverCounted.sessionAvgHRVnow gates on it too, so "refused" has one definition that the value and the day-level rule cannot disagree about.Verification
AnalyticsEngineRefusedMainNightHrvTests(Swift) and its Kotlin twinAnalyticsEngineRefusedMainNightHrvTest, same fixtures and expected values:swift testStrandAnalytics: 2053/0.HrvOverCountGateTest7/0,AnalyticsEngineProvidedSleepTest5/0. Full Android unit suite: the same 8 failures as untouchedupstream/main(RecoveryDrivers, StandardHrSensorFormat, StepsDetailIntegration, TodayExplainability), none in touched code.doc_comment_lint: OK. No UI strings, no app-target Swift.Independent of #2423. With #2423 in, far fewer ring nights are refused in the first place; this covers the ones that still are, on any strap.
Refs #2425, #1118, #1884