Repository navigation
log(hrv): describe the main night the gate judged in the hrv diag line - #2427
Merged
Merged
Conversation
The always-on `hrv diag` line pooled every sleep session of the day and divided their beat-time by first-to-last beat, so the hours between a night and a nap entered the denominator. On a real day whose main night the ryanbr#1118 gate refused at 1.31 coverage, the line printed `coverage=0.48 rrIntegrity=underCovered`: it called a night refused for an over-count "not an over-count". `hrvOverCounted` (the HRV card's unverified flag) and the RSA resp gate read the same rows, so they sided with the log against the gate. `analyzeDay` now exposes the main-night group it already scores from (`DayResult.mainNightBlocks`, the same `mainGroup`), and a pure `AnalyticsEngine.hrvDiagnosticRows` selects that group's beats over the gate's own inclusive [start, end] window. On a one-block night that is exactly the set `SleepStager.sessionHrvOverCounted` judged, so the over-count half of the verdict cannot disagree with the gate. A day with no main night keeps the old pooled, half-open set. The line's format is unchanged; its fields now describe the main night. Stacked on the refused-main-night fix, which introduces `sessionHrvOverCounted`. Tests: HrvDiagnosticRowsTests (Swift) and its Kotlin twin, same fixtures and expected values: the DayResult carries the scoring main group; on a refused night the selected rows are the night's only and classify as an over-count, agreeing with the gate, while the old pooled selection reads `plausible`; no main night keeps the half-open fallback. StrandAnalytics 2056/0; Kotlin twin 3/0; full Android suite shows the same 8 pre-existing failures as upstream/main. macOS `Strand` and iOS `NOOPiOS` both build. doc_comment_lint OK. Refs ryanbr#2425, ryanbr#1118 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AvMtCweTWqqM9iyDBz88XX
ryanbr
force-pushed
the
fix/hrv-diag-main-night-verdict
branch
from
September 24, 2026 01:30
ea58148 to
4dee916
Compare
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
This is the second half of #2425. The always-on
hrv diagline pooled every sleep session of the day and divided their beat-time by first-to-last beat, so the hours between a night and a nap entered the denominator. On a real day whose main night the #1118 gate refused at 1.31 coverage, the line printed:The log called a night refused for an over-count "not an over-count", and it sent triage of that report the wrong way first. Two other readers use the same rows:
hrvOverCounted(the HRV card's "unverified" flag) and the RSA respiration gate. Both sided with the log against the gate.Change
analyzeDayexposes the main-night group it already scores from, asDayResult.mainNightBlocks(the samemainGroup, not re-derived). It's trailing and defaulted on both platforms, and each has a single construction site.AnalyticsEngine.hrvDiagnosticRows, selects that group's beats over the gate's own inclusive[start, end]window. On a one-block night that's exactly the set of beatsSleepStager.sessionHrvOverCountedjudged, so the over-count half of the verdict cannot disagree with the gate.IntelligenceEngineread the rows through the helper. The line's format is unchanged; its fields now describe the main night.Verification
HrvDiagnosticRowsTests(Swift) and its Kotlin twinHrvDiagnosticRowsTest, same fixtures and expected values:DayResultcarries the scoring main group.plausible, which is pinned so the test fails if the fixture stops reproducing the disagreement.swift testStrandAnalytics: 2056/0. Kotlin twin: 3/0.upstream/main(RecoveryDrivers, StandardHrSensorFormat, StepsDetailIntegration, TodayExplainability), none in touched code.IntelligenceEngine.swift), so both app targets were built locally: macOSStrandand iOSNOOPiOS(generic Simulator) both build.doc_comment_lint: OK. No UI strings.Refs #2425, #1118