From d4e611d1b3644343669de6b3ea35908439906620 Mon Sep 17 00:00:00 2001 From: Dan Urbanek Date: Mon, 10 Aug 2026 10:19:51 +0200 Subject: [PATCH] docs(whoop5): SpO2 @82 promote checklist + band sleep flag note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Narrow the remaining #807 content after PROTOCOL.md §10 landed elsewhere: - explicit five-point promote bar for spo2_candidate_82 (incl. incomplete-night falsification and no recovery/illness gating) - band sleep_state @81 vs hypnogram quick reference - cross-link to PROTOCOL §10 and the existing validate_spo2_candidate harness No PROTOCOL.md changes; no model-id table; no app/decode behaviour change. --- docs/WHOOP5_DEEP_DATA.md | 49 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/WHOOP5_DEEP_DATA.md b/docs/WHOOP5_DEEP_DATA.md index c79e94e6b6..ade528ec0e 100644 --- a/docs/WHOOP5_DEEP_DATA.md +++ b/docs/WHOOP5_DEEP_DATA.md @@ -147,6 +147,52 @@ tracking the WHOOP app's own SpO₂ across many nights on **multiple devices** ( coincidental match), including on the device where the two checked nights currently move opposite. Until that clears the bar, SpO₂ stays import-only on the 5.0. +Wire-level facts (no SpO₂ opcode, export vs on-device aggregation, sleep-only product) are also summarised +in [`PROTOCOL.md` §10](PROTOCOL.md#10-spo₂-on-50--mg--what-the-wire-does-and-does-not-carry). This section +keeps the **promotion bar** and the harness that measures it. + +### `@82` validation checklist (what would promote the candidate) + +Only research — never a silent UI flip. A promote of `spo2_candidate_82` → `spo2Pct` needs all of: + +1. **Multiple devices / firmwares** (not one lucky strap): the nightly aggregate of in-band (70–100) + `@82` samples during `sleep_state = asleep` tracks the official app or CSV `blood_oxygen_pct` with + real night-to-night spread (not a flat 98 %). +2. **Offset specificity:** nearby bytes (the 74–92 scan the harness already runs) must *not* track + better than `@82`. +3. **Incomplete nights:** when the export omits SpO₂, the wire candidate should be empty or + out-of-band — not invent a number. This is a falsification test: an "always 97 %" decoder fails it. +4. **Resolution of the #103 contradiction** on the original capture device (or a documented + extraction / phase / duty-cycle error on one side). +5. **No recovery / illness gating** on the candidate until (1)–(4) clear — same rule as other + derived biosignals. + +The multi-device tool below implements the **measurable** half of this list: default gates include +≥5 paired nights, export range ≥1 %, r ≥ 0.7, MAE ≤ 1.0, best offset = 82, in-band value variance, +and duty-window coverage (with `feature_absent` when a long-enough asleep capture never emits `@82`). +Points 4–5 stay human judgment on [#103](https://github.com/ryanbr/noop/issues/103). + +Until that bar is met, SpO₂ stays **import-only** on the 5.0, with `@82` available as instrumentation +for owners who opt into deep-timeline / experimental logging. + +Related capability / UX roadmap: [#761](https://github.com/ryanbr/noop/issues/761) (honest labels when +SpO₂ / skin temp / stages are unavailable vs experimental). + +### Band sleep flag vs hypnogram (quick reference) + +v18 byte `@81` high nibble is the strap's **coarse on-device sleep flag** (decoded as `sleep_state`): + +| High nibble | Name | Meaning | +|------------:|------|---------| +| 0 | wake | awake / active | +| 1 | still | on-wrist still (not yet scored as sleep) | +| 2 | asleep | scored night / sleep window | +| 3 | up | post-sleep up | + +Useful for sleep *detection* and for gating sleep-only products (including SpO₂ candidates). It is +**not** Light / SWS / REM — those stages are off-band. Full field notes live with the historical +decode in `Interpreter.swift` / the Android twin. + ### Multi-device validation tool (`validate_spo2_candidate.py`) To make that bar concrete and privacy-preserving, `Tools/linux-capture/validate_spo2_candidate.py` @@ -237,7 +283,8 @@ lands in `parseFrameWhoop5` / `whoop_protocol.json`. 5. **SpO₂ multi-device check:** after you have a history capture + your data export, run `python3 Tools/linux-capture/validate_spo2_candidate.py capture.json export/ --device