fix(cockpit): surface amber/red prevention alerts in web Tier-1 + hourly escalation guard#105
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
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.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).prevention_watchtier 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
class="panel hero"untouched; style attr appended only on override).🤖 Generated with Claude Code