Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion docs/WHOOP5_DEEP_DATA.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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 <label> --postable`
and paste the postable summary on [#103](https://github.com/ryanbr/noop/issues/103). Keep the capture
and CSV private; only the aggregate r / MAE / checklist line is needed.
and CSV private; only the aggregate r / MAE / checklist line is needed. See the **`@82` validation
checklist** above for what “PASS” is meant to mean before any promote.

Credit to **judes.club**, **Asherlc/dofek**, and **b-nnett/goose** for the public protocol work this
builds on.