v0.9.1 — Resting heart rate alarm was inverted
⛔ The illness alarm ran backwards
daily_readiness.contributors.resting_heart_rate looks like a heart rate. It is a 0–100 score where higher is better — the inverse of a pulse. alert_system read it as bpm, so:
| What actually happened | Score | Old alarm |
|---|---|---|
| Pulse falls 73 → 58 bpm (recovery) | rises | 🔴 "CRITICAL — possible illness, consult doctor" |
| Pulse rises during an infection | falls | silent |
The alarm that exists to catch illness was quiet precisely when it should fire.
On 15 days of real data the score and the true resting pulse correlate at −0.920. The statistics report printed a resting-HR "median 97 bpm" while the actual nightly pulse was 61 — bpm do not cap at exactly 100.
What changed
Resting heart rate now comes from the nightly trough (sleep.lowest_heart_rate) in real bpm, through one shared helper (utils/resting_hr.py) so the consumers cannot drift apart again.
alert_system— direction corrected; thresholds were already documented in bpm and now mean what they say.illness_detection— the direction was already handled correctly here, but the score saturates at 100 (where most healthy nights sit), so deterioration barely moved it. Real bpm has no ceiling.analytics_tools— now fetches detailed sleep sessions;daily_sleepcarries scores only.
🐛 Second bug, found while verifying
Naps report average_heart_rate = 0 and the daily aggregate averaged them in, turning a real 67.75 bpm into 22.58. The guard tested is not None, which 0 passes.
🧪 Tests
9 new cases pin the direction, including one asserting that a falling pulse stays silent — verified to fail against the old logic.
tests/conftest.py makes src/ importable everywhere: two pre-existing files lacked the path insert, which aborted collection, so the suite ran zero tests. It now runs 21.
Version bumped to 0.9.1 — it still read 0.8.0, missed during the 0.9.0 release.
Full Changelog: v0.9.0...v0.9.1