Skip to content

Watch five overnight signals for signs of strain together - #126

Open
ak710 wants to merge 2 commits into
saksham2001:mainfrom
ak710:feat/health-watch
Open

Watch five overnight signals for signs of strain together#126
ak710 wants to merge 2 commits into
saksham2001:mainfrom
ak710:feat/health-watch

Conversation

@ak710

@ak710 ak710 commented Aug 3, 2026

Copy link
Copy Markdown

Closes #117

Merge order: 7 of 9. ⚠️ Stacked on #121 — this diff also contains that commit. Merge #121 first.

What

Oura's Symptom Radar, Ultrahuman's Sleep Screener. Every input already existed; nothing read them together.

Five overnight signals against their own 30-day baselines: skin temperature, resting HR, HRV, breathing rate, blood oxygen. Only the strain direction of each counts — a resting HR below baseline or an HRV above it is good news, and flagging it would turn that into an alert.

The scoring exists to make it wait for agreement

Each signal scores 0/1/2; total 2 is minor, 3 is major.

  • A single notable signal never counts — one reading past its knot is a warm duvet or one restless hour.
  • A single strong signal reaches minor but not major — a full degree of overnight temperature rise isn't noise, but isn't enough to interrupt for unaccompanied.
  • Only major fires an alert. Minor still reaches the Today card and the coach, but two signals nudging past their knots happens after a glass of wine often enough that alerting would train people to dismiss the ones that matter.

Baselines are medians of per-night figures

Not a mean of every sample. One night the ring recorded 4× as often would otherwise dominate, and a single feverish night would drag the very baseline it needs judging against — testBaselineIsAMedianOfNightsNotOfSamples builds exactly that case.

Heart rate uses the night's 10th percentile so it's the same quantity as the resting-HR baseline it's compared with; the rest use the mean. Nights are bounded by the sleep session, not a clock window.

Ring coverage

Signals without a baseline simply aren't judged, so a ring with fewer sensors still gets a useful answer from the three it has. Fewer than two judgeable signals returns clear rather than a warning built on one reading.

Safety

Ordered above restingHRDrift, because drift is one of its own signals — when both trip, the multi-signal result is the better-corroborated message about the same night.

Nothing names a condition, and the copy names alcohol, heat and yesterday's session before anything else. testFactsNameTheMundaneExplanationsAndNoDiagnosis asserts both.

The Today card is conditional — a clear night renders nothing, so it never becomes an "all clear" tile people learn to ignore.

Testing

HealthWatchTests — 18 tests, most of them pinning the cases where it deliberately stays quiet. Full suite green (900). Docs: docs/project/anomaly-alerts.md.

ak710 and others added 2 commits August 2, 2026 14:25
restingHRDrift has been a CoachAnomalyKind since the proactive-alert path
landed, with a comment explaining why nothing raised it: the detector reads a
12-hour context packet, and drift is only meaningful against a multi-day
baseline. But the baseline already exists — RestingHRBaselineService learns and
persists it — so the packet just needed to carry it in.

The night and the baseline are both measured as the interpolated 10th percentile
of heart rate, sharing RestingHRBaselineService's own percentile helper.
Comparing a night's mean against a 30-day percentile would have produced a
difference that was mostly an artefact of the two formulas. The night is bounded
by the sleep session rather than a fixed clock window, so a late night or a
shift schedule is measured over the hours actually slept.

Fires at +5 bpm and only upward — a resting HR below baseline is usually good
news and not worth an unprompted alert. Gated on an established baseline, at
least 10 overnight samples (a YCBT ring floors its interval at 30 minutes, so a
full night is only ~14 there against ~84 on a 5-minute Colmi), and a night no
more than two days old.

Ordered last of the three detectors: a short night usually raises resting HR
too, so when both trip the sleep alert names the cause while drift would only
restate its consequence.

Every threshold is documented in docs/project/anomaly-alerts.md, which also
covers the two detectors that were already shipping but undocumented.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The gap analysis put illness early-warning as the single biggest thing the
premium apps have that PulseLoop didn't — Oura's Symptom Radar, Ultrahuman's
Sleep Screener. Every input already existed; nothing read them together.

HealthWatch judges five overnight signals against their own 30-day baselines:
skin temperature, resting heart rate, HRV, breathing rate and blood oxygen. Only
the strain direction of each counts — a resting HR below baseline or an HRV
above it is good news, and flagging it would turn that into an alert.

The scoring exists to make it wait for agreement. Each signal scores 0, 1 or 2;
a total of 2 is minor and 3 is major. A single notable signal therefore never
counts on its own — one reading past its knot is a warm duvet or one restless
hour. A single strong signal reaches minor but not major: a full degree of
overnight temperature rise isn't noise, but it isn't enough to interrupt for
unaccompanied either.

Only major fires an alert. Minor still reaches the Today card and the coach, but
two signals nudging past their knots happens after a glass of wine often enough
that alerting on it would train people to dismiss the ones that matter.

Baselines are the median of preceding nights' figures, not a mean of every
sample. One night the ring recorded four times as often as usual would otherwise
dominate, and a single feverish night would drag the very baseline it needs to
be judged against. Heart rate uses the night's 10th percentile so it is the same
quantity as the resting-HR baseline it's compared with; the rest use the mean.
Nights are bounded by the sleep session, not a clock window.

Signals without a baseline are simply not judged, so a ring with fewer sensors
still gets a useful answer from the three it has — and fewer than two judgeable
signals returns clear rather than a warning built on one reading.

Ordered above restingHRDrift, because drift is one of its own signals: when both
trip, the multi-signal result is the better-corroborated message about the same
night. Drift still fires alone when nothing corroborated it, or when it was the
only signal with a baseline at all.

The Today card is conditional — a clear night renders nothing. A permanent "all
clear" tile would be clutter, and would train people to stop reading it. Nothing
here names a condition, and the copy names alcohol, heat and yesterday's session
before anything else; a test asserts it.

Thresholds and rationale in docs/project/anomaly-alerts.md, which also now
documents the two detectors that were already shipping undocumented.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ak710
ak710 requested a review from saksham2001 as a code owner August 3, 2026 06:25
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.

[Feature]: No illness early-warning (Symptom Radar equivalent)

1 participant