OxyDex node (SpO₂ · pulse rate · motion), Tepna physiological-signal suite
Background. A fingertip ring oximeter reports three signals at 1 Hz: SpO₂, pulse rate and a movement byte. The best published figure for staging from the first two is κ 0.73–0.78 on 17 303 polysomnograms with a whole-night network (Vaquerizo-Villar et al., 2025); a proprietary ring stager reports κ 0.39 on a sealed Challenge-2018 holdout. Question. How much of the night can those three signals stage, what part of it a stack of published formulas can carry, and how the two compare blind. Methods. Ten cohorts (9 100 nights) harmonised to what the ring reports; 466 named features; three learners on identical subject-grouped folds — gradient-boosted trees, a whole-night sequence network used only as a control, and a literature stack (named terms → auto-context → linear-chain CRF) fitted to labels; every band stated before its run; Challenge 2018 and ISRUC never fitted, scored blind; a half-night label shift that must collapse κ as the plant. Results. PENDING — control, trees, stack, bridge and symbolic-regression cells fill from the brief when v21/v22 land; the external blind number fills after the third-party blind run. Conclusion. PENDING.
Keywords: sleep staging · pulse oximetry · pulse rate · actigraphy · pre-registration · knowledge distillation · conditional random field · auto-context · blind validation
A sleep lab decides whether you are awake, in light sleep, deep sleep or dreaming by reading your brain waves. A ring on your finger cannot see your brain; it sees how much oxygen is in your blood, how fast your pulse is, and whether your hand moved. We asked how far those three signals can go. We built the best model we could as a yardstick, then tried to reach the same result with formulas other scientists have already published, because formulas can be read, checked and shipped on a tiny device. Every target was written down before the computer ran, and two groups of nights were kept locked away so that the final numbers could not have been tuned to them.
Two human scorers reading the same polysomnogram agree at κ ≈ 0.83 (measured here on ISRUC, Khalighi et al., 2016). A wearable has none of the channels they read. The question of this paper is therefore not whether a ring equals a lab, but how much of the night a ring's three 1-Hz signals contain, and whether that information can be expressed in formulas rather than in a black box.
Comparators. A proprietary stager for the same ring family reports κ 0.367 live / 0.390 offline on a sealed 134-night Challenge-2018 holdout (published figures only; its model and outputs were never used, per its licence §3g). Sridhar et al. (2020) reached κ 0.66 from instantaneous heart rate alone with a whole-night CNN on SHHS/MESA. Vaquerizo-Villar et al. (2025) reached κ 0.73–0.78 from 1-Hz pulse rate and SpO₂ on 17 303 PSGs and published the frequency bands their network attends to, which this paper adopts as formula terms.
Design. Three learners on one feature matrix and identical folds: trees as the floor, a sequence network as the ceiling and teacher ("control", never shipped), and a stack of published formulas as the candidate for release. The owner's rule "train on everything in-house, publish only what the formulas carry" is what the design implements.
| cohort | nights | SpO₂ | pulse source | motion | labels | licence | role |
|---|---|---|---|---|---|---|---|
| SHHS1 (NSRR) | 5 136 | 1 Hz | oximeter H.R. | — | R&K → 4-class | NSRR DUA | fit |
| SHHS2 (NSRR) | 2 651 | 1 Hz | oximeter PR | — | R&K → 4-class | NSRR DUA | fit (same subjects as SHHS1; folds grouped by subject) |
| Challenge 2018 (PhysioNet/CinC) | 994 | 1 Hz | ECG-derived | — | AASM | ODC-By | blind |
| CAP (PhysioNet) | 108 | 1 Hz | HR / ECG-derived | — | R&K | ODC-By | fit |
| UCDDB (PhysioNet) | 25 | 8 Hz → 1 Hz | oximeter Pulse | — | R&K | ODC-By | fit |
| BOAS (OpenNeuro ds005555) | 128 | 1 Hz | none readable | — | AASM consensus | CC0 | fit |
| ISRUC-Sleep | 127 | 12.5 Hz → 1 Hz | ECG-derived (X2) | — | AASM, two scorers | cite-only | blind |
| Sleep-Accel (PhysioNet) | 31 | — | Apple Watch HR | wrist ACC | PSG | ODC-By | fit |
| BIDSleep (PhysioNet) | 165 | — | Apple Watch HR | wrist ACC | Dreem-EEG expert | open | fit |
| Newcastle (Zenodo 1160410) | 27 | — | — | GENEActiv wrist | PSG | CC BY 4.0 | fit |
Nights with a present-but-empty SpO₂ channel (< 50 % valid) are skipped and counted; a cohort without a signal yields NaN in that signal's columns, never a filled value. Aggregate statistics only are reported; no record identifiers leave the corpus (NSRR DAUA §5).
SpO₂ is reduced to an 8-s moving mean at 1 Hz in whole percent, which is what the ring writes (one byte per second per signal). Pulse rate is the oximeter's own 1-Hz rate where the PSG has one; otherwise 60/RR from ECG beats found by the suite's Pan–Tompkins detector (Pan & Tompkins, 1985; validated at Se/PPV ≥ 99 % on MIT-BIH, SLPDB and NSRDB, Table S1) with a 4-s mean. Motion is the per-second standard deviation of wrist acceleration magnitude; a 1-bit "moved this second" flag (> 0.02 g) is carried beside it, as the poorest signal the ring could report. Every signal is standardised per night (median/IQR).
oxy1, 466 columns)Multi-scale statistics (1–10 min), desaturation counts and depths, SpO₂ and pulse-rate band powers including the 0.004–0.020 Hz and 0.18–0.40 Hz bands of Vaquerizo-Villar et al. (2025), pulse-rate variability terms after Redmond & Heneghan (2006), actigraphy terms after Cole et al. (1992) and Sadeh et al. (1994), each with ±2/±10/±30-epoch context. The complete list with citations, where each term enters and whether its constants are pinned or fitted, is docs/STAGE-LAB-FORMULAS.md.
Each unit's expected range, pass line and reading rule were written in briefs/STAGE-LAB-2026-09-27-BRIEF.md before the unit ran (§§5–20); measured numbers were appended beside them, including every failed expectation. The gate for all units: pooled κ ≥ 0.415, half the human inter-scorer κ (0.829). Plant P1: labels shifted by half a night must collapse κ below half its value, or the run is void. Folds are a hash of the subject key (SHHS1/SHHS2 visits of one person share a fold); Challenge 2018 and ISRUC are never in any fit.
PENDING. Each table below is filled from the brief section named in its caption when that run lands; the values are copied from the bench summaries under lab-cache/bench-*/, never typed from memory.
| learner | signals | pooled κ [CI] | Challenge (blind) | ISRUC (blind) | SHHS1 | SHHS2 | brief § |
|---|---|---|---|---|---|---|---|
| trees | SpO₂ + PR + motion | PENDING | — | — | — | — | 17 |
| control (sequence) | SpO₂ + PR + motion | PENDING | — | — | — | — | 17 |
| literature stack, CRF | same | PENDING | — | — | — | — | 18 |
| stack + bridge | same | PENDING | — | — | — | — | 19 |
| step | Δκ pooled | where it showed | brief § |
|---|---|---|---|
| PENDING | |||
| idea | expected | measured | verdict |
|---|---|---|---|
| PENDING (HSMM time prior; single ultradian wave on raw features; distillation into the formula student, twice; third auto-context round; fitting the stack with the blind cohorts) | |||
| model | pre-stated band | κ [CI] | per-stage recall | P1 |
|---|---|---|---|---|
| PENDING | ||||
PENDING. Stubs to be written from the measured sections: (i) where the gap between formulas and the sequence control lives (deep and REM recall; long-range structure the oracle bound shows is present); (ii) why distillation into a small closed-form student lost to label fitting both times; (iii) what the ring's motion byte is worth and why a 1-bit flag equals the full accelerometer for trees; (iv) the data curve against the 17 303-night benchmark; (v) what can ship, under which licence, and the role of the blind cohorts.
# features (one tag per feature set; a changed layout never mixes with an older cache)
node tools/stage-lab.mjs features --harmonize --blocks spo2,pulse,motion --tag oxy1 --cohort <c> --dir <dir> --cache <cache> --workers 4
# trees and control on identical subject-grouped folds
node tools/stage-lab.mjs bench --cache <cache> --cohorts <ten> --features oxy1 --group-subjects --model gbdt --fold <f>
python seqnet.py --cache <cache> --cohorts <ten> --features oxy1 --group-subjects --fold <f> --save-proba <dir>
# literature stack (label-fitted, signed, distilled, bridge) and the residual search
python litstack.py --cache <cache> --cohorts <ten> --features oxy1 --group-subjects --fold <f> [--signed] [--distill <dir>] [--bridge]
python symreg.py --cache <cache> --control <dir> --stack <dir> --out <dir>
stage-lab.mjs on the same epochs.tepna.verdict/1 with the pre-stated criterion embedded.With 9 100 nights (≈ 9 million epochs) the night-bootstrap 95 % CI on pooled κ is ±0.004 (measured: 0.608 [0.604, 0.612] for the control, brief §17), so any difference ≥ 0.01 between learners on the same folds is resolvable; the pre-stated reading rules use 0.01 as the smallest difference worth a claim. The blind cohorts (994 and 127 nights) resolve ≈ ±0.015 and ±0.04 respectively.