Repository navigation
fix(oura): choose the R-R beat channel per hour, so a day-wide read keeps the daytime beats - #2625
Conversation
…eeps the daytime beats ryanbr#2423 made the scoring read keep ONE Oura beat channel for the whole requested window: green (0x80) when it holds more beats than the amplitude family (0x60 + 0x44), the amplitude family otherwise. That is right over a night, where the two channels overlap. It is wrong over a window that crosses wake, where they are disjoint in time: 0x60 is banked only overnight and 0x80 carries every daytime beat. On a real ring, 00:00-17:29 held 38,667 amplitude beats (night only) and 11,377 green (all of the day), so the whole window kept amplitude and dropped every daytime beat. The Deep Timeline's Windowed rMSSD for "Today" then drew the night and nothing after ~07:00. The choice is now made per UTC hour, counted inside the requested window with the same time/suspect predicates as before, the shape the WHOOP 4 per-hour source choice already uses. An hour where both channels fire still keeps the fuller one; an hour where only one fires keeps it. A window inside one hour reads exactly as the whole-window rule did, so every ryanbr#2423 test is unchanged. Swift WhoopStore.rrIntervals and Kotlin RR_INTERVALS_SQL change together; a new twin test with one shared fixture (night hour, green-only hour, mixed hour) fails on the old read and passes on the new one. Refs ryanbr#2423 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zPBbdmGTu19hgrMbnwSG1
|
Reviewed at What I verified rather than took on trustThe test discriminates. Working the fixture's arithmetic by hand: over the whole window it is 18 The SQL does what the prose says. The strict WHOOP 5 branch is extracted verbatim, not reworked. Its Parity is real. Same CTE name, same bucket expression, same join and predicates, same ORDER BY and Two things worth stating plainly, both of which the description already doesIt changes historical night scoring. 18 hours across 40 nights flip to green, all at sleep edges or Daytime values are restored, not validated. The "Not covered" section says the daytime 0x80 beats The Real-data evidence of 48 beats and 1 point becoming 10,548 and 367 over |
Refs #2624, Refs #2423
Problem
#2423 made the scoring read keep one Oura beat channel for the whole requested window. Over a night that is right: 0x60 and 0x80 overlap, and reading both over-counts. Over a window that crosses wake it is wrong, because the channels are disjoint in time: 0x60 is banked overnight only, 0x80 carries every daytime beat. On a real ring,
[00:00, 17:29]held 38,667 amplitude beats (night) and 11,377 green (day), so the read kept amplitude and dropped the whole day. The Deep Timeline's Windowed rMSSD for "Today" stopped after the night.Change
WhoopStore.rrIntervals(Swift) andRR_INTERVALS_SQL(Kotlin) choose the Oura channel per UTC hour, counted inside the requested window with the same time/suspect predicates as before. This is the shape the WHOOP 4 per-hour source choice already uses (whoop4HourChoice).AS MATERIALIZED, for the minSdk reason already documented onWHOOP4_RR_INTERVALS_SQL.WHOOP 4 and strict WHOOP 5 reads are unchanged: their branches are separate and untouched.
Verification
New twin test, one fixture shared byte-for-byte (
testTheChannelIsChosenPerHourAcrossANightAndADay/theChannelIsChosenPerHourAcrossANightAndADay): a night hour (0x60 full, 0x80 partial), a green-only hour, and a mixed hour that amplitude wins. Against the old read it fails exactly as reported: the green-only hour's beats are missing. It passes on the new read.swift testinPackages/WhoopStore: 632 tests, 0 failures.Android:
OuraOneChannelReadSqliteTest7,RrSourceChannelTest14,Whoop5RRSqliteTest29, 0 failures. The Room@Querycompiles through KSP.doc_comment_lint: no new sites.parity_ratchet.py --base upstream/main: errors=0.Real data. The shipped
WhoopStore.rrIntervals+HRVAnalyzer.rollingRmssd(600 s window, 75 s stride, as the Deep Timeline uses for a day span) were run over[00:00, 17:29]of a real Oura database, old read v new read:macOS app built from this branch with that database loaded: Deep Timeline → Windowed rMSSD → Today now draws through the day.
Effect on nights. Over 40 nights of the same ring, 18 hours carry both channels with green holding more beats. All of them sit at a sleep edge (07 h, 20–22 h) or in daytime. In those hours the night now takes the fuller green train instead of the thinner amplitude one, which is the fix(oura): score one R-R beat channel per window, not 0x80 on top of 0x60 #2423 rule applied at hour resolution. Every other night hour reads as before.
Not covered
Packages/WhoopStoreand the Android data layer, and no app-target Swift changed.