Skip to content

fix(cockpit): surface amber/red prevention alerts in web Tier-1 + hourly escalation guard#105

Merged
RubyWolff27 merged 1 commit into
mainfrom
fix/cockpit-prevention-visibility
Jul 12, 2026
Merged

fix(cockpit): surface amber/red prevention alerts in web Tier-1 + hourly escalation guard#105
RubyWolff27 merged 1 commit into
mainfrom
fix/cockpit-prevention-visibility

Conversation

@RubyWolff27

Copy link
Copy Markdown
Owner

Found live 2026-07-12: the user is ill (PCR-confirmed rhinovirus), the prevention engine fired its RED multi-signal alert — visible on the phone (server-driven hero), invisible on the web cockpit, which showed only "Sabbath — rest is prescribed". The cockpit's Tier-1 renderer never received prevention at all; the red tier lived only in the collapsed Lab panel.

Fix

  • Tier-1 visibility, one source of truth: the cockpit hero now delegates the override decision to whoop_tiles._compose_hero — the exact rule the phone renders. Amber/red prevention adds a banner above the verdict (prevention's own copy), shifts verdict + hero border to the tier color. Sabbath rest copy stays alongside — safety outranks rest-day silencing, and both are true.
  • False-positive guard: override keys on prevention.tier ∈ {amber, red} — NOT on hero severity alone, which would have shown the safety banner on any naturally-Rundown day (caught in review, regression-tested both ways).
  • Hourly escalation guard (sidecar scheduler, :30 offset, lock 900007): compares live prevention_watch tier to the stored snapshot's; escalation rebuilds the snapshot immediately so a midday alert can't hide behind the ≤4h cache. De-escalation deliberately waits for the 4×/day cadence — alarms raise fast, clear calmly.

Verification

  • 14 new unit tests (override paths, Sabbath+red shows both messages, Rundown false-positive guards, 6 escalation-job cases); full unit suite at the documented baseline; ruff/black/isort/mypy clean under CI flags.
  • CI-tree contract check: existing hero assertions unbroken (class="panel hero" untouched; style attr appended only on override).
  • Post-merge live acceptance: the user's CURRENT red alert must appear on the cockpit — a real-world test case is active right now.

🤖 Generated with Claude Code

…ly stale-snapshot guard

The web cockpit's Tier-1 hero rendered from readiness ONLY, so a strong-signal
prevention alert (illness/injury) only appeared in the collapsed Lab prevention
log — a safety-visibility bug where a user trusting the web cockpit misses a RED
"rest & recover" warning the phone shows. This threads prevention into Tier-1 and
adds an hourly guard so a mid-day escalation can't hide under a stale snapshot.

Renderer (whoop_cockpit.py / whoop_analytics.py):
- render_today_story/_hero_html now take the prevention dict and reuse
  whoop_tiles._compose_hero (the SAME override rule the phone's hero rides on) as
  the single source of the amber/red override — no duplicated severity logic.
- A prominent safety banner renders above the verdict on an amber (caution) or red
  (alert) override, on EVERY day incl. the Sabbath; the hero accent shifts to the
  prevention colour (amber #fbbf24 / red #f87171, matching the Lab prevention log).
  Banner message is _compose_hero's headline (prevention's own copy, not re-written).
- Sabbath: "rest is prescribed" copy STAYS; the banner is additional, not a replacement.
- Green/neutral prevention → no banner, no accent shift (byte-identical to before).
- _build_cockpit_page passes the prevention it already computes into the story.

Scheduler (scheduler.py):
- New hourly _prevention_escalation_job (:30, off the 6/9/18/21 snapshot cadence).
  Per active WHOOP user it re-runs the cheap prevention_watch engine and compares to
  the tier baked in the stored snapshot's metrics_json. On ESCALATION (green→amber/red,
  amber→red) it immediately rebuilds+stores via the existing build_cockpit_snapshot,
  so an alarm surfaces without waiting up to 4h. De-escalation/unchanged are left to
  the normal cadence — an alarm raises fast, but "all clear" must not flip on one read.

Tests: 12 pure/monkeypatched unit tests (rivaflow/tests/unit) covering the override
banner+accent (red/amber/green/none), Sabbath+red showing both messages, and the
escalation job's rebuild/no-rebuild/no-snapshot paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@RubyWolff27
RubyWolff27 merged commit 306ca99 into main Jul 12, 2026
8 checks passed
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.

1 participant