Skip to content

04: Stage a growing night without re-extracting unchanged epochs - #106

Draft
mikenrafter wants to merge 14 commits into
OpenStrap:mainfrom
mikenrafter:split/area-4-sleep-staging
Draft

mikenrafter wants to merge 14 commits into
OpenStrap:mainfrom
mikenrafter:split/area-4-sleep-staging

Conversation

@mikenrafter

Copy link
Copy Markdown
Contributor

Depends on 03 / #105.

Depends on 02 / #104.

Depends on 01 / #103.

This is part 04 of the split of #99. It targets main, so its diff also contains the earlier parts until they merge. Its own changes are the commits listed under "Suggested review order". After each earlier part merges, I'll merge main into this branch so the diff shrinks to this part alone.

Staging a growing night repeats feature extraction for unchanged epochs and can mix incompatible epoch anchors across cache entries. This adds exact feature-row caches and anchored epoch accumulation while preserving the production session-clipped staging path, and keeps the separate causal research stager explicit.

What changes

  • Add CardioRowCache, anchored EpochRows and phase-aware memoization.
  • Keep pooled night baselines, session/run coverage gates and gap-bounded continuity in production staging.
  • Add causal observations/state, hypnogram aggregation and per-stage minute bounds. Hypnogram accounting retains Berry et al., The AASM Manual for the Scoring of Sleep and Associated Events, and the existing application conventions.
  • Add row, grid, checkpoint, frozen-hypnogram, causal and stage-range tests plus the causal validation tool.

The source retains van Hees 2013, Long 2016 and Fonseca 2015/2018 method citations and marks fitted/internal staging rules ESTIMATE. Deep sleep remains a low-confidence HR-flatness overlay; stage-minute bounds are evidence bounds, not calibrated confidence intervals. This area retains the parent's RR/spectral behavior and preserves the pre-bump session-onset/post-gap grid. segmentSleep remains the sleep entry point. The causal stager is separately callable research behavior, not a replacement night score.

Algebraic breakdown

Algorithm/structure State and identity Combine; lift; finish Laws and equality
CardioRowKey, CardioEpochRow, CardioRowCache Map from start/epoch/dependency fingerprint to seven raw features; empty map Left-biased map union via merge; lift extracts exact epoch features; finish is lookup and normal classification. Union is associative/idempotent with empty identity, but not commutative for inconsistent equal-key payloads. Under the valid-key contract identical keys mean identical rows. Work counters are not part of that algebra. cardio_row_cache_test.dart checks merge, stale/mutated dependencies, phases, persistence and label parity. Row/label observations are exact with NaN-aware checks. Hash collision freedom is not proven. Merge costs O(other rows), not O(1).
cardioEpochFeatures, production cardioStager Session/run rows, robust pooled baselines, coverage and continuity context; an empty/insufficient session abstains No general combine. Lift rows within session clips; finish baseline scoring, stages and consolidation. Batch pipeline with reusable row cache, not a monoid: new rows can change global medians and old labels. Epoch-feature, cache/run-cache, advanced-stager and frozen wall-clock tests pin feature/label behavior and gaps. Frozen hypnogram agreement retains the existing 0.97 minimum; this is an acceptance threshold, not exact agreement for arbitrary inputs.
EpochFeatureRow, EpochRows, _Run Anchored runs, retained context, final rows and provisional suffix; empty accumulator with fixed configuration/anchor Ordered fold, no combine. Lift new HR/accel/RR channels; finish allRows/provisionalRows, or stageWindow. Late edge rows are recomputed until enough future context arrives. Ordered checkpointed fold, not a monoid. epoch_rows_test.dart and anchor tests check exact chunk/restart row parity, source-grid anchors, context/gaps and clipped-window behavior. Row comparisons explicitly handle nonfinite fields. Stage labels are tested against the frozen/reference paths under the tests' stated eligibility conditions.
EpochRowsMemo, _PhaseEntry Phase-keyed map of compatible row accumulators and prefix/dependency evidence; empty map rowsFor reuses a compatible phase or rebuilds it. No arbitrary merge; lift a source revision under an anchor; finish selected EpochRows. Memoization, not a monoid. Anchor/cache tests pin correct phase ownership and retained-prefix validity. No commutativity law for histories/edits.
CausalStagerConfig, CausalSampleWindow, CausalEpochRow, CausalStagerState Fixed config, latest clock/usable time, bounded channel tails and closed epoch rows; initial(config) No combine. Lift observe(window, priorState) merges ordered evidence, closes past-only epochs and returns CausalStageObservation; finish latest evidence/decision/abstention plus next checkpoint. Ordered state transition with overlap and clock rules, not a monoid. causal_stager_test.dart checks future isolation, split/restart continuity, clock regression, gaps, coverage and explicit absence. JSON/current-version behavior is tested. Full parity with the retrospective stager is not an invariant because the information available differs.
Causal _Merged/_Decision feature/decision helpers, CausalStage, CausalAbstention Past-only feature rows and decision context; absent decision when evidence is insufficient Batch reductions over retained history followed by threshold/gate decisions; no combine API. Lift supported past samples; finish reasoned stage/absence. Same synthetic tests and optional real-night replay. The optional corpus check may skip; this is not evidence of clinical accuracy. No universal numeric tolerance or associative merge law is claimed.
StageSegment, SleepSession, HypnogramMetrics, HypnogramAggregator, _HypnogramState Sleep/stage durations, onset/end/first REM, ordered wake spans; zeros/no sleep/no REM Sum durations, select ordered first/last evidence and concatenate wake spans; lift start-sorted segments and sum sleep durations as supplied, retaining wake spans for finish clipping; finish WASO, latency, efficiency, disturbances and stage percentages. Ordered monoid for fixed session bounds and input semantics; retained wake spans make combine/finish nonconstant. hypnogram_aggregator_test.dart uses exact finished fields, including NaN-aware absent REM, across identities/association/splits/slicing/checkpoints. conversion_check_test.dart tests the law checker against real hypnogram conversion.
StageEpochEvidence, MinuteRange, StageMinuteRanges, stageMinuteRanges and evidence extension Per-stage lower/upper integer epoch counts and total eligible epochs; zero-count conceptual identity, while public empty input returns null Conceptual componentwise addition of disjoint per-epoch contributions, then one scale to minutes for fixed margin/epoch width. Implementation is a batch fold with no public combine. Lift score/margin/coverage evidence; finish bounds. Do not combine already-normalized outputs with incompatible context. stage_minute_ranges_test.dart and its property file check conservation, bounds, widening with uncertainty/margin and label preservation. Counts are exact; floating range checks use absolute 1e-9 in those files. No public monoid-law claim.
Causal validation tool, reference stager and fixture support Synthetic/capture input and comparison counts; empty report Replay and compare; no scientific accumulator API or new estimator. Tests retain fixed-grid and coverage guards; validation depends on the supplied corpus.

Where the speedup comes from

Sleep staging repeatedly extracts features for unchanged epochs, so this PR caches those rows while still checking their inputs and classifying the whole night. This PR: 29.725 of the 109.065 s saved per night (27.25%). Overall after the full stack: 13.98×.

Workload / calculation #102 batch s Retained s This PR saves s
night / Staging 33.569365 3.844473 29.724892

See the sync-work explanation in #103 for the timeline, overall table and Edge adoption requirement; these are AOT medians from at least five full schedules on generated data, with this PR credited only beyond #102 and its inherited gains included in the overall saving.

How each structure saves work

Row cache hits avoid the expensive feature windows for identical dependencies. Anchored rows retain final epochs and recompute only the unresolved context near the newest data. Neither feature caching nor epoch accumulation makes global baseline classification O(1): fingerprints, input validation, result assembly and whole-night scoring can still scan history. Causal observations retain past context instead of rescanning a whole night, but overlapping-window validation and retained-row summaries still cost work. Hypnogram combine retains wake spans; WASO finishing scans them. Stage bounds remain O(epoch count), and have no measured incremental speedup.

Fresh timings are in "Where the speedup comes from" above. Older numbers in the docs predate this split.

Tests

Full suite on this branch with Dart 3.13.0: 2,068 passed, 7 skipped, 0 failed. dart analyze (Dart 3.11.4) reports no issues. Counts include the earlier parts' tests. The 7 skips are existing checks that need data outside the repo: six need a local whoop_hist.jsonl capture, and one wall-clock agreement test needs the DREAMT corpus.

Suggested review order

  1. ed13f25 Sleep staging repeats feature extraction for unchanged epochs. Read the state and append transitions first, then the finish rules, then the law, checkpoint and reference tests.
  2. d29ee84 merges the current 03: Update HRV and respiration windows incrementally #105 head, including the review fixes on 00: Make RR/HRV output changes independently reproducible #102 and Abdul's merges. It merged without conflicts.

Change size

This part's own changes, measured against #105: lib/ +3,022/-214 in 11 files; tests, tools and docs +5,413 in 20 files.

Questions for the reviewer

  • Should the standalone EpochRows and memo APIs stay public before an app consumer adopts their anchoring contract?
  • Are the causal abstention reasons and the evidence-bound presentation clear enough that they don't suggest calibrated sleep-stage certainty?

@coderabbitai

coderabbitai Bot commented Nov 13, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @mikenrafter, this pull request is too large to review

It changes 29,089 lines. Sourcery can review pull requests with up to 20,000 changed lines.

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.

2 participants