Repository navigation
PulseStages 0.1.1
Naps are written to Health Connect as asleep / awake by default. A pre-registered experiment tried to make the
stage breakdown of naps realistic and found no nap stage model that passed its checks (per-epoch discrimination,
following the heart rate across sleep regimes, realism against published compositions of afternoon naps), so the
nap model stays and its stages, still shown in the app, are not validated. Nights are unchanged. Details in
ALGORITHM.md §13.13
and the user guide.
Added
- On-device end-to-end suite (
app/src/androidTest,tools/device-test/run-device-tests.sh). Runs against the
debug build installed on a phone and its real Health Connect data without reinstalling the app: snapshots of
everything the suite could touch before and after the run (the app's own records must stay byte-identical, other
apps' records unchanged); the backfill, sync and recompute pipeline over the real history with every write captured
in memory, with invariant, idempotency, recompute and policy checks and a per-session report; one test record in the
ps-e2e:namespace that is upserted, versioned and deleted within a minute; a read-only tour of every screen with
screenshots, whose clicks an allow-list restricts to what writes nothing; the app's job schedule before and after. A
safety guard refuses every write or delete of the suite outsideps-e2e:, an in-memory WorkManager keeps the app's
own background work out of the test processes, the read budget keeps the suite within Health Connect's background
quota across runs, and a cleanup step that always runs removes every test record (first by the ids recorded before
the write, without a read) and puts back the demo-mode flag
(tools/device-test/README.md). - Context margin of naps (Settings → "Naps: algorithm" → "Heart rate around naps" → "Context margin of naps",
algo.napMode.contextMarginMin): how many minutes of heart rate and steps (and of the optional channels) before and
after a nap its estimate uses. Empty, the default, is the night's "Context margin" (30 min), as before; 0 is the nap
itself only; at most the night's value, which sets how much is read from Health Connect. Nap-only: nights never read
it. Checked by the core-algo and settings validators and by the import; English and Russian texts
(settings reference). - Nap stage bias per session length in the model bundle: an optional
nap.stageLogBiasByLengthtable (lengths
and one log-bias row per length, linear interpolation in the session length, the first or last row outside;
exclusive with the nap section'sstageLogBias). It is left out of the canonical JSON when absent, so existing
bundles keep their fingerprints and personal models keyed to them stay valid. The shipped bundle does not use it
(docs/INTERCHANGE.md). - Realistic-nap experiment (pre-registered:
docs/prereg/prereg-naps-realistic.md
with its Amendment 1, digest and command log;
ALGORITHM.md §13.13):
train.py naps-realistic-gates,naps-realistic-timing,naps-realistic,naps-realistic-report,
naps-realistic-final,naps-realistic-external,naps-realistic-installandnaps-realistic-verify, with
pulsetrain/napsrealistic.py,pools.py(training tiles, evaluation pools, featureless sessions),litprior.py(the
published nap compositions: arm table, inclusion rule, targets, bands and guards),realism.py(per-epoch AUC, regime
contrasts, realism metrics, the decision rules) andperturb.py; sample weights inmodels.fit_classifier, a
per-length bias fit (bias.fit_stage_bias_table);algo-cli evaluateshares a preparation only between grid points
with the same mode and effective preprocessing, so a grid over the nap context margin prepares only the naps again
(tools/train/README.md). - Test-only pipeline for OpenNeuro ds006576 (real afternoon naps with ECG and EEG, CC0):
tools/train/external.py
(snapshot pin against the committed key listexternal-ds006576-v1.0.5.keys.tsv, a streamed fetch that keeps only
reduced signals, ECG → heart rate, test windows, night files, the digests that freeze the test set) and
tools/train/external_stage.py(YASA 0.7.0 sleep staging, which only places the test windows, in its own environment
tools/train/.venv-externalpinned with hashes byrequirements-external.lock, OpenMP runtime included). The data
are never used for training, for any parameter or for choosing between models, and are never committed
(tools/train/DATASETS.md). - Tests: the nap input restriction and preparation key, the length-bias table and its validation, the shipped bundle's
fingerprints pinned, and a personalized-nights test showing that personal bias deltas still apply after a bundle
update that keeps the night model; the training tests run on synthetic data only.
Changed
-
Naps are written to Health Connect as asleep / awake by default. "Written for a nap" (
algo.napMode.outputPolicy)
now defaults to "Asleep / awake" (SLEEP_WAKE) instead of "Stages", because no nap stage model passed the
pre-registered realistic-nap experiment
(ALGORITHM.md §13.13).
Rule (i) adopted no candidate in either tier, so the shipped nap model (N1) and the bundle stay unchanged. Rule (ii)
gives N1 the asleep / awake output: it fails the evidence check F and the realism check R, and on the
literature-reweighted pool its accuracy is 0.055 below calling every epoch light sleep (at least +0.03 was required
for stages; 4-class κ 0.246), while its sleep / wake κ is 0.269 (at least 0.20 required). Failing criteria (Walch,
held-out subjects, worse sampling variant):- N1: F, REM contrast r = 0.012 (the predicted late-night minus sleep-onset difference of the REM share of sleep, as a
fraction of the true difference; ≥ 0.25 required), the lower bound of its 95 % bootstrap interval −3.1 pp and its
BIDSleep difference −1.8 pp (> 0 required); R, featureless sessions of 90–135 min with 3.5–5.2 % REM of sleep, below
the lower bound of the published range (5.1 % at 90–105 min, 5.7 % at 120–135 min), and a late-night deep-sleep tail
of 0.226 (the share of 60-min windows from late in the night decoded as at least
half deep sleep, minus the true share; ≤ 0.10). - C0 (N1's classifier with a literature-anchored level per length): F, REM r = 0.122, lower bound −3.4 pp, BIDSleep
−1.6 pp; R, featureless REM above the range at 30–75 min and deep sleep below it at 105 and 135–170 min (mean2min),
late-night deep-sleep tail 0.104. - C1 (a heart-rate-only evidence model with a night-level bias): D, sleep-onset deep-sleep AUC 0.066 below N1's (at
most 0.05 allowed); R, featureless REM above and deep sleep below the range, guard excess 0.170 (implausible
single-nap compositions beyond those of the true compositions; ≤ 0.05), mid-anchor excess 2.46 pp (distance from the
published composition between the anchor lengths minus at them; ≤ 2.0), distance from the published composition
14.31 pp, above N1's 12.66. - C2 (the evidence model with a literature-anchored level per length): D, sleep-onset deep-sleep AUC 0.057 below
N1's; F, REM r = 0.070, lower bound −6.3 pp; R, featureless deep sleep below the range at 75–170 min, late-night
deep-sleep tail 0.132, guard excess 0.104, mid-anchor excess 2.10 pp.
The test on real afternoon naps (OpenNeuro ds006576) changed nothing: N1 stays in effect with the asleep / awake
output. The app still estimates the stages of a nap and shows them as its own estimate; they are not validated, and
"Stages" writes them anyway. Settings store only changed values, so the new default applies wherever the setting was
left at "Stages" (an explicit "Stages" of 0.1.0 included, since it was the default). Nap sessions already in Health
Connect keep their stages until they are recomputed: the new default and the new setting change the params hash, so
every estimate is offered for recomputation, and nights decode exactly as before (568 of 568 validation nights, per
epoch) (user guide). - N1: F, REM contrast r = 0.012 (the predicted late-night minus sleep-onset difference of the REM share of sleep, as a
-
Nap caution in the night detail: an estimated nap now says "Nap stages are not validated: the nap model failed
pre-registered checks, and on test sessions with the stage mix of published naps it was less accurate than calling
everything light sleep." (other estimators: "Nap stages are not validated; take them as the app's estimate only.")
instead of "Nap estimates are rough: … about 6 in 10 …", and the hint of "Written for a nap" says that nap stages are
not validated instead of "rough"; English and Russian. -
Documentation of naps: the README, the user guide and PLAN.md describe the asleep / awake default and say that the
nap stages shown in the app are not validated; the earlier figure of about 6 in 10 nap epochs right
(ALGORITHM.md §13.12)
is no longer given as the accuracy of naps, because it came almost entirely from windows at sleep onset.
tools/train/README.md and DATASETS.md cover the new experiment, ds006576 and the open nap hypnograms used for the
guards.
Downloads
| File | Use |
|---|---|
PulseStages-0.1.1.apk |
Signed release build for everyday use. Later releases install over it as updates. |
PulseStages-0.1.1-debug.apk |
Debug build for testing, with the demo mode. |
SHA256SUMS.txt |
SHA-256 checksums of both APKs: sha256sum -c --ignore-missing SHA256SUMS.txt |
The two APKs are signed with different keys, so neither can update the other. To switch, uninstall first.
The sessions PulseStages wrote stay in Health Connect; grant its permissions again after the reinstall.
Release signing certificate SHA-256: ce5546e83c03eac9837b93fc2c04d2e37abd1b778c3ef9e01b67cd1602e35be5
Debug signing certificate SHA-256: b557d5d55ea3c6d98e72493b1e82f228465201e7271d93f7ace577ff1c5b35cc (a later debug APK installs over an earlier one only if this value is the same)