Skip to content

oura: exclude unwritten hypnogram tail from layout - #2579

Merged
ryanbr merged 1 commit into
ryanbr:mainfrom
kavemang:fix/oura-hypnogram-tail-padding-2564
Sep 29, 2026
Merged

ryanbr merged 1 commit into
ryanbr:mainfrom
kavemang:fix/oura-hypnogram-tail-padding-2564

Conversation

@kavemang

Copy link
Copy Markdown

Summary

  • Exclude trailing unwritten SleepNet codes from the time axis on both Swift and Kotlin, so the final written code ends at the paired 0x49 sleep end.
  • Preserve time slots for unwritten runs between real codes, and retain the existing onset clip.
  • Add mirrored regression cases for an interior gap followed by tail padding and for a later, longer pass with a 40-code unwritten tail.

Refs #2564.

Verification

  • Android HypnogramAssemblerTest: 15 passed (testFullDebugUnitTest --tests com.noop.oura.HypnogramAssemblerTest --no-build-cache --rerun-tasks).
  • python3 Tools/doc_comment_lint.py: passed.
  • git diff --check: passed.
  • Swift package tests will run in PR CI; this Linux host has no Swift toolchain.

@ryanbr

ryanbr commented Sep 29, 2026

Copy link
Copy Markdown
Owner

@kavemang Reviewed. 17/17 green, and clean against current main (I rebased it and ran the repository-acceptance suite: 50 tests, pass). Your base predates five merges including #2582, which put that suite on every PR about an hour ago, so your own run never included it.

The mechanism is the right one, and the choice of n is where it lives:

let n = (phases.lastIndex { !$0.unwritten } ?? -1) + 1

That spans everything through the LAST WRITTEN code, so an interior unwritten run stays inside n and keeps its slots, while trailing pad falls outside and takes no time. The Kotlin indexOfLast { !it.unwritten } + 1 is the same thing, including the all-unwritten case where both give 0 and the burst yields [].

A property worth stating that the summary does not. The SET of returned codes is unchanged by this. Every written code is inside n by construction, so the filter after the lay removes exactly what it removed before; only the timestamps move, later by the pad length. That is why OuraLiveSource's hypnogram trimmed N code(s) line stays truthful with no edit: laid.count against totalCodes counts the same codes it always did. A reader checking whether that diagnostic needed updating can stop there.

I also checked that totalCodes does not lose its last production caller, since this replaces its use here. It has eight in OuraLiveSource.swift and more in the Kotlin twin, so nothing is orphaned. That check is not idle: a change merged earlier today removed the only production call to another function and turned main red, so it is now the first thing I look for when a call disappears.

Tests are mirrored and the second one is named for the reported symptom rather than the mechanism, which is the right way round: a later pass losing to an older one is what #2564 actually reports. The document update matches the code, including the sentence about interior runs keeping their slots.

Taking it.

@ryanbr
ryanbr merged commit 1d66594 into ryanbr:main Sep 29, 2026
17 checks passed
@pipiche38

Copy link
Copy Markdown

Hardware confirmation on iOS (Oura Gen3, NOOP 11.8.0 with this change, first night on it: 2026-09-29/30):

before (night 09-28/29) after (night 09-29/30)
ring passes 13, final 1144 codes 14, final 1144 codes
unwritten tail codes 52 (= 26 min) 0
stored row end 06:48:52 v 0x49 / Oura app 07:14 (−26 min) 07:25:12 v Oura app 07:25 (≤ 1 min)
  • The final pass (07:33:22) won, and the stored row is its hypnogram code for code: deep 83.0, light 289.5, REM 66.5, wake 129.0 min. The only difference from the raw final pass is the existing onset clip (8 codes = 4 min).
  • The arithmetic closes: 1144 codes × 30 s = 572 min, minus the 4 min onset clip = 568 min = 21:57:00 → 07:25:12.
  • Every pass that night landed on its own 0x49 end: 24 of 24 persisted rows end exactly at the paired hypnogram burst end refined by 0x49 time (22:35:32 through 07:25:12). The night before, the final one missed it by 26 min.

One night so far; I'll add a line if a later one disagrees. Thanks @kavemang. Refs #2564.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants