Wave 0 scoring: wire computed-but-disconnected methodology into Rest + readiness#456
Wave 0 scoring: wire computed-but-disconnected methodology into Rest + readiness#456vishk23 wants to merge 4 commits into
Conversation
…into Rest (Wave 0 · SL1/T1) Rest was scored on a flat neutral-0.5 consistency and a fixed 8h need: despite the "caller supplies it from history" contract, IntelligenceEngine never threaded either into AnalyticsEngine.analyzeDay (it only threaded habitualMidsleepSec). - T1: new AnalyticsEngine.Rest.personalizedNeedHours(nightlyHours:age:) — upper-quartile of the user's unrestricted nights, floored at the age-appropriate population TARGET (8h adult / 9h <18) so a chronic under-sleeper's need can only adjust UP for genuine long sleepers, never collapse toward their own deficit. Cold-start returns the population default. - SL1: thread real VitalityEngine.sleepConsistency + that personal need into the per-day analyzeDay call, computed once from the trailing per-night durations (recent 28 nights for regularity, full history for the need quantile). Extends computeHabitualMidsleep's existing read to also yield nightly durations — one read, midsleep result byte-identical. - The nightly durations are TST (span x efficiency), NOT in-bed span: the need/regularity estimate must match the tstSeconds Rest scores against. Real-data validation on VK's noop-cloud history showed in-bed span over-counts need ~0.85h (his correct need ~8.0h TST). Both degrade honestly on thin history (nil consistency -> neutral; need -> default). Validation: RestWiringImpactTests archetype table + real-data replay on ~90d of VK's nights (need 8.0h TST, consistency 0.57); RestNeedTests; full StrandAnalytics suite green; macOS build green.
…narrow) ReadinessEngine scored its HRV signal as a raw-ms z-score, but RMSSD is right-skewed, so a symmetric z over-weights the long upper tail and misstates tail rarity. lnRMSSD is closer to normal (Plews/Altini; the app's own HRVReadiness already works in the log domain). Add a logDomain path to the shared zSignal builder, used ONLY by HRV — RHR/resp are ~normal and stay linear. Evidence stays in ms, but the reported baseline is the geometric mean, not an arithmetic mean a few big-recovery nights inflate. Deliberately NARROW: domain-correctness only. The full HRVReadiness SWC-tier engine stays behind its experimental flag until validated on real data (its header notes n=1; roadmap anti-#194). Tests: RD1LogDomainTests pins the geometric-mean baseline; ReadinessEngineTests unchanged; suite green.
…Wave 0 · RD-confidence) ReadinessEngine scored off as few as 7 baseline nights with no confidence shown, while Charge already carries a ScoreConfidence tier. Add ScoreConfidence.readiness(hasRead:baselineNights: fullWindow:) + a `confidence` field on Readiness, from the HRV baseline density (the primary readiness driver): calibrating (no read), building (7-29 of the 30-night window), solid (full window). Analytics enabler; surfacing it on the Today card is a separate design pass. Tests: RDConfidenceTests (tiers + engine building/solid/calibrating); ReadinessEngineTests unchanged; full suite green; macOS Strand build green.
Mirror the StrandAnalytics Wave 0 changes on Android so Swift and Kotlin produce byte-identical numbers: - RestScorer: personalizedNeedHours(nightlyHours, age) + populationNeedFloorHours, minNeedNights=7, maxNeedHours=9.5 (population-anchored, age-floored, upper-quartile sleep need; chronic under-sleeper's need never drifts below the population target). - ReadinessEngine: HRV z-score in the LOG domain (lnRMSSD); baseline shown is the geometric mean. Readiness now carries a ScoreConfidence from HRV baseline density. - ScoreConfidence.readiness(hasRead, baselineNights, fullWindow). Tests: RestNeedTest, RDConfidenceTest, RD1LogDomainTest, RestWiringImpactTest (same inputs + expected values as the Swift tests). testFullDebugUnitTest green, 13 new tests; existing analytics suites unchanged.
|
Reviewed. This is in much better shape than the branch stacked on it — Kotlin twins are here, all six package suites are green, and the real-data validation caught a genuine bug (in-bed span vs TST, need over-counting ~0.85 h). The "computed but never reaching the score" framing is the right way to find this class of defect. Verified as safe
Three things before merge1. SL1 and T1 are wired on Swift only, so Rest will diverge between platforms. Your notes list the Kotlin capabilities ported —
Worth noting the So post-merge, the same wearer on the same night gets a different Rest score on iOS than on Android. RD1 and RD-confidence are fine — those live inside 2. The green CI is 11 days and 236 commits stale. The passing run is from 14 July against a tree 236 commits behind current main. 3. This changes scores for existing users, and the PR does not say so. SL1 replaces "a flat +5 for everyone, every night" with real consistency, and T1 replaces a fixed 8 h with a personalised need. Both are corrections rather than tuning, but the visible effect is that Rest moves for everyone, and any re-scored history moves with it. That deserves a line in the release notes so it reads as an intended fix rather than a regression — this is exactly the kind of change users notice and report. MinorThe rename left two stale doc references to |
Stack-level noteReviewing #456 and #469 back to back turned up one systemic thing that neither review says on its own, since there is no tracking issue carrying the pair. Both PRs leave Android's scoring behind, one layer apart.
Landing both as they stand means iOS and Android disagree on Rest and Readiness — two headline scores — for the same wearer on the same night. RD1 and RD-confidence are fine; those live inside engines that were ported. Suggested order:
The Swift work in both is the hard part and it is done — the analysis, the candidate replay, and the reasoning for not reusing One more thing worth flagging for whoever lands these: both change scores for existing users. #456's SL1 replaces a flat +5-for-everyone consistency term and T1 replaces a fixed 8 h need; #469 changes the readiness HRV baseline. All corrections rather than tuning, but Rest and Readiness will visibly move, and re-scored history moves with them. Worth a release-note line so it reads as an intended fix. |
Wave 0 scoring: wire methodology that's computed but wasn't reaching the score
Four small, additive scoring fixes where the rigorous input already existed but wasn't feeding the score. Each is one source file + tests, degrades honestly on thin history, and leaves cold-start behavior unchanged. Opening for review + validation — if any of these read wrong on your own data, that's exactly the feedback I'm after.
SL1 · real sleep regularity into Rest
IntelligenceEnginethreadedhabitualMidsleepSecintoAnalyticsEngine.analyzeDaybut neversleepConsistency— so Rest's consistency term ran on the neutral-0.5 default (a flat +5 for everyone, every night), whileVitalityEngine.sleepConsistencywas computed and used only by the vitality result. Now threaded through, computed once from the trailing 28-night history off the same session read as the midsleep learner (midsleep result byte-identical).T1 · population-anchored, age-floored sleep need
Rest's duration term ran against a fixed 8h. New
Rest.personalizedNeedHours(nightlyHours:age:): the upper quartile of the user's unrestricted nights, floored at the age-appropriate population target (8h adult / 9h <18) so a chronic under-sleeper's need can only adjust up for genuine long sleepers, never collapse toward their own deficit. Uses TST (span×efficiency), not in-bed span.RD1 · log-domain readiness HRV z (narrow)
ReadinessEnginez-scored HRV in raw ms; RMSSD is right-skewed, so a symmetric z over-weights the long upper tail. Now lnRMSSD (Plews/Altini; matches this app's ownHRVReadiness), for the HRV signal only — RHR/resp are ~normal and stay linear. Deliberately narrow: the fullHRVReadinessSWC-tier engine stays behind its experimental flag until validated on real data.RD-confidence · baseline-density confidence on the readiness read
Readiness scored off as few as 7 baseline nights with no confidence shown, while Charge already carries a
ScoreConfidence.Readinessnow carries one (calibrating / building / solid) from HRV baseline density. Analytics enabler — surfacing it on the Today card is a follow-up.Validation
RestNeedTests,RestWiringImpactTests,RD1LogDomainTests,RDConfidenceTests.StrandAnalyticssuite green; macOS build green.Notes
RestScorer.personalizedNeedHours/populationNeedFloorHours, log-domain readiness HRV z-score, andScoreConfidence.readiness, withRestNeedTest/RestWiringImpactTest/RD1LogDomainTest/RDConfidenceTesttranscribing the Swift inputs + expected values (byte-identical parity)../gradlew :app:testFullDebugUnitTestgreen — 13 new tests, existing analytics suites unchanged.