Found after the v0.9.2 reconnect: the weekly report showed Sleep Score 0/100, Best Night 0 while the statistics report showed a mean of 70 for the same period.
Cause
/v2/usercollection/sleep (detailed sessions) carries durations, efficiency and heart rate but no score — that lives in daily_sleep. Every consumer that aggregated sessions and then read session['score'] got None.
Measured: 0 of 30 nights had a score. The same nights via daily_sleep ranged 54–86.
⛔ Why it stayed hidden
It failed silently — a check with nothing to iterate over reports "no alert", which reads exactly like "all clear".
| Check | State |
|---|---|
_check_sleep_quality_alerts |
could never fire |
_check_consecutive_bad_nights |
could never fire |
| weekly report | printed 0, dragged the weekly total to 39.6 instead of 69.4, then "recommended" fixing the sleep it had not measured |
Fix
merge_daily_sleep_scores(), applied where the score is actually needed.
Live: weekly score 39.6 → 69.4 · sleep 0 → 74.5 · best night 0 → 85.
⭐ Mechanism, not vigilance
check_all_alerts now records which checks it had to skip for missing data, and the report names them:
⚠️ Checks that could not run
These were skipped for missing data — not evaluated, not cleared.
- sleep quality / consecutive bad nights — no sleep score in the data (30 nights present, 0 scored)
The next time a field moves, it says so instead of going quiet.
Also
Fixes a regression introduced in v0.9.2: the previous-week comparison called _analyze_readiness_metrics without sleep data, so its resting heart rate silently read 0.
Known, deliberately not fixed here
Sleep consistency reads 0%. Different class: the formula is max(0, 100 - cv*10), so a 10% spread in sleep duration already floors it. That belongs in its own change rather than being smuggled into a bugfix release.
Tests: 27 (+4), including a positive control asserting the coverage note stays empty when data is complete.
Full Changelog: v0.9.2...v0.9.3