Skip to content

feat: v42 personalized self-improving cardio stager - #25

Merged
abdulsaheel merged 2 commits into
mainfrom
feat/v42-stager
Jul 15, 2026
Merged

feat: v42 personalized self-improving cardio stager#25
abdulsaheel merged 2 commits into
mainfrom
feat/v42-stager

Conversation

@abdulsaheel

@abdulsaheel abdulsaheel commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces major improvements to the sleep staging algorithm, focusing on REM detection accuracy and user personalization. The changes add new autonomic features for REM detection, implement a rolling per-user sleep profile for personalized threshold blending, and enhance post-processing to reduce stage flicker. The updates are designed to improve sensitivity to REM without sacrificing specificity and to make the algorithm adapt over time to each user's sleep patterns.

Key changes include:

REM Detection Improvements

  • Added two new secondary REM detection axes: LF/HF elevation and R(k) burst, using robust-z scoring. These axes recover REM that the primary RMSSD rule misses, improving detection accuracy while maintaining specificity. [1] [2] [3] [4] [5]
  • Implemented the _windowRemFeatures function to extract LF/HF and R(k) features from RR intervals using Lomb–Scargle PSD and instantaneous HR variability.

Personalization (Per-User Sleep Profile)

  • Introduced the SleepUserProfile class to store and blend rolling per-user sleep baselines (e.g., HR, RMSSD, motion thresholds) with per-night-local values, improving adaptation to individual sleep patterns. [1] [2] [3] [4]
  • Added mechanisms to record nightly sleep observations (SleepNightObservation), fold them into the user profile, and blend profile values into nightly thresholds, with a capped influence to preserve local adaptation. [1] [2] [3]

Post-processing Enhancements

  • Added a 3-epoch categorical median (mode) filter to remove single-epoch stage flicker before continuity and consolidation steps, resulting in smoother and more reliable stage sequences. [1] [2]

Other Algorithmic Refinements

  • Adjusted the min-session floor logic in AdvancedSleepStager to exempt night-tail continuations from the 60-min minimum, ensuring genuine fragmented sleep is not truncated.
  • Blended deep sleep HR and RMSSD thresholds toward the user profile for more robust and personalized deep sleep detection. [1] [2]

These updates significantly improve both the accuracy and personalization of sleep staging, especially for REM and deep sleep detection.

Summary by CodeRabbit

  • New Features

    • Added personalized sleep staging using a persisted, rolling per-user baseline.
    • Enhanced REM/deep-sleep detection with additional autonomic features derived from heart-rate dynamics and motion thresholds blended from local statistics.
    • Added support for capturing, reviewing, and resetting nightly sleep observations.
  • Bug Fixes

    • Refined continuation handling so short night-tail segments are conditionally accepted without weakening the rest of the sleep-segment validation pipeline.
  • Tests

    • Added a dedicated test suite for sleep user profile behavior, folding/serialization, and an integration-style staging stability check.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d6d3b334-80f9-448a-a712-0ac0b6061112

📥 Commits

Reviewing files that changed from the base of the PR and between 82035b4 and 7919945.

📒 Files selected for processing (3)
  • lib/src/onehz/sleep/advanced_stager.dart
  • lib/src/onehz/sleep/cardio_stager.dart
  • test/onehz/sleep_user_profile_test.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/onehz/sleep_user_profile_test.dart
  • lib/src/onehz/sleep/advanced_stager.dart

📝 Walkthrough

Walkthrough

The advanced sleep detector now preserves short overnight continuations. The cardio stager adds persistent user profiles, blended thresholds, autonomic REM features, revised deep detection, nightly observation capture, and associated tests.

Changes

Advanced sleep tail continuation

Layer / File(s) Summary
Night-tail duration gate
lib/src/onehz/sleep/advanced_stager.dart
Computes continuation state before the minimum-duration check, allowing one qualifying short night-tail per chain while retaining subsequent filters.

Profile-aware cardio staging

Layer / File(s) Summary
Profile and observation contract
lib/src/onehz/sleep/cardio_stager.dart
Adds SleepUserProfile, SleepNightObservation, EWMA folding, JSON serialization, and observation recording controls.
Profile-aware staging pipeline
lib/src/onehz/sleep/cardio_stager.dart, test/onehz/sleep_user_profile_test.dart
Adds explicit profile selection, blended motion and sleep thresholds, LF/HF and R(k) REM features, revised REM/deep classification, nightly observation capture, and profile-focused tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SleepInput
  participant cardioStager
  participant SleepUserProfile
  participant ObservationBuffer
  SleepInput->>cardioStager: epoch HR, RR, and motion data
  cardioStager->>SleepUserProfile: read personalized thresholds
  SleepUserProfile-->>cardioStager: blended baseline values
  cardioStager->>cardioStager: classify REM and deep epochs
  cardioStager->>ObservationBuffer: record qualifying nightly observation
  cardioStager-->>SleepInput: processed sleep stages
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: version 42 personalization improvements to the cardio sleep stager.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/v42-stager

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/src/onehz/sleep/advanced_stager.dart (1)

305-358: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Bound the night-tail exemption to the first post-overnight fragment. chainFromOvernight stays true for every continuesChain hop once the chain starts from an overnight onset, so the new min-session bypass applies to each later sub-60-min fragment within 90 min of the previous accepted end. That can admit a long sequence of short daytime sessions, not just the first tail after sleep onset.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/onehz/sleep/advanced_stager.dart` around lines 305 - 358, Limit the
night-tail min-session exemption in the session loop around chainFromOvernight
and isNightTail to only the first post-overnight fragment. Track whether a
qualifying night-tail has already been accepted, and require that state to be
unset when computing the exemption; preserve normal chain tracking while
preventing subsequent continuesChain hops from bypassing the minimum session
floor.
🧹 Nitpick comments (1)
test/onehz/sleep_user_profile_test.dart (1)

108-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the stability assertion discriminating.

Given both Wake percentages must already be below 20, allowing their difference to be below 50 adds no coverage. Assert that the profile result remains close to the unprofiled result.

       expect(
         (withProfile.wakePct - noProfile.wakePct).abs(),
-        lessThan(50.0),
+        lessThan(5.0),
         reason: 'a consistent profile must not swing staging wildly',
       );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/onehz/sleep_user_profile_test.dart` around lines 108 - 113, Update the
stability assertion comparing withProfile.wakePct and noProfile.wakePct to use a
meaningfully tighter tolerance that verifies the profile result remains close to
the unprofiled result. Keep the existing absolute-difference comparison and
reason, but replace the non-discriminating lessThan(50.0) threshold with the
intended close-result bound.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/src/onehz/sleep/cardio_stager.dart`:
- Around line 168-172: Validate horizonNights at the start of
SleepUserProfile.fold before calculating a, rejecting values less than or equal
to zero with the established argument-validation behavior. Preserve the existing
EWMA calculation for positive horizons.
- Around line 514-517: Update the post-processing flow around _mergeShortDeep so
deepFlag is synchronized with the final consolidated stage: clear the
corresponding deep flag whenever consolidateSleepStages changes an epoch from
deep NREM to REM or Wake, rather than relying on _mergeShortDeep to skip it.
Preserve deep flags for epochs that remain eligible deep NREM stages.
- Around line 728-729: Update the loHz initialization in the cardio staging flow
to explicitly convert the clamped value returned by clamp() to double before
passing it to freqGrid. Keep the existing clamp bounds and lombScargle behavior
unchanged.

---

Outside diff comments:
In `@lib/src/onehz/sleep/advanced_stager.dart`:
- Around line 305-358: Limit the night-tail min-session exemption in the session
loop around chainFromOvernight and isNightTail to only the first post-overnight
fragment. Track whether a qualifying night-tail has already been accepted, and
require that state to be unset when computing the exemption; preserve normal
chain tracking while preventing subsequent continuesChain hops from bypassing
the minimum session floor.

---

Nitpick comments:
In `@test/onehz/sleep_user_profile_test.dart`:
- Around line 108-113: Update the stability assertion comparing
withProfile.wakePct and noProfile.wakePct to use a meaningfully tighter
tolerance that verifies the profile result remains close to the unprofiled
result. Keep the existing absolute-difference comparison and reason, but replace
the non-discriminating lessThan(50.0) threshold with the intended close-result
bound.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 77dbea46-21e3-461e-94a2-34598ed1e331

📥 Commits

Reviewing files that changed from the base of the PR and between 11f610a and 82035b4.

📒 Files selected for processing (3)
  • lib/src/onehz/sleep/advanced_stager.dart
  • lib/src/onehz/sleep/cardio_stager.dart
  • test/onehz/sleep_user_profile_test.dart

Comment thread lib/src/onehz/sleep/cardio_stager.dart
Comment thread lib/src/onehz/sleep/cardio_stager.dart
Comment thread lib/src/onehz/sleep/cardio_stager.dart Outdated
- bound the night-tail 60-min-floor exemption to the first SHORT tail per
  chain: a >60-min continuation is accepted on its own merit and no longer
  burns the one-shot, so a run of short fragments can't chain-extend the
  window while a genuine multi-fragment pre-dawn tail still reaches the wake
- validate horizonNights > 0 in SleepUserProfile.fold before the EWMA alpha
- sync deepFlag with the final consolidated stage (clear the deep flag on
  epochs reclassified to REM/Wake) rather than relying on _mergeShortDeep
- cast the LF/HF loHz clamp() result to double before freqGrid
- tighten the profile-stability assertion (50.0 -> 5.0)
@abdulsaheel
abdulsaheel merged commit 0d243fc into main Jul 15, 2026
1 check passed
@abdulsaheel
abdulsaheel deleted the feat/v42-stager branch July 15, 2026 18:07
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.

1 participant