Repository navigation
Apple: same skin-temp absolute gap as #1851, but the display needs #1848 first #1853
Description
#1852 backfills the nightly absolute on Android. Apple has the identical gap:
skinTempCis written only by the scoring pass (IntelligenceEngine.swift:1899,skinTempC: night.nightlySkin);- the same rolling 21-night window, so nights already outside it when the column shipped were never revisited;
WhoopStore'sDailyMetric.skinTempC(the v40 GRDB migration, twin of Room's 33→34) holds the same nulls.
The analytics half is already there and public — AnalyticsEngine.skinTempFunnel and wornNightlySkinTempC in StrandAnalytics — so this is a walker plus a fill-only write, not new maths.
Order matters, and it is not "backfill first"
Backfilling Apple now would fill a column Apple mostly does not read yet:
- the Health tile leads with the absolute (health: lead the skin-temp tile with the absolute, deviation beneath #1665) and WOULD benefit immediately;
MetricExplorerViewstill sources skin temp viaRepository.dailyColumn→skinTempDevC(Apple: the skin-temp explorer still leads with the deviation and ignores the Settings choice #1848), so the detail screen would not show any of it;- the window-wide preference rule (health: apply the skin-temp preference across the window, not just the newest night #1850) and the Settings choice's reach are Android-only so far.
So #1848 first, then this — otherwise the work lands as data nothing surfaces, and the one place it does surface (the Health tile) is the place least likely to reveal a mistake in it.
What must carry across verbatim
The Android version's correctness lives in three rules, all of which were re-review findings rather than first-cut design. The Swift twin has to reproduce all three or it will be wrong in the same ways:
-
Attribute sessions by END. The night window is 54 h wide (30 h before local midnight through the next), so it contains the PREVIOUS night as well. The engine's rule is
matched = allSessions.filter { tsInDay(it.end) }; handing the funnel the whole window averages two nights into one temperature and stores it as the wrong day's. -
Page the candidates. A plain "oldest N" returns the same nights every pass, and the oldest are the ones most likely to have lost their raw samples — the sweep latches on page one and never reaches the newer nights that can fill.
-
The WHOOP 4.0 anchor comes from the CURRENT window. It is learned window-wide, and the engine's note says that is safe because the offset "cancels in the deviation" — true for a delta, not for an absolute. A re-learned anchor puts backfilled nights on a different offset from the stored ones and the chart plots two scales as one line. No anchor ⇒ decline, never the global default.
And the same non-negotiable: a single-column write that can only fill a NULL, never an upsert of a rebuilt row.
Activity
health: backfill the skin-temp absolutes the 21-night window never re…
skin-temp: wire the ryanbr#1853 backfill walker behind the Test Centr…
skin-temp: backfill absolutes for nights outside the 21-night rescore…
evoveotech commentedon Sep 10, 2026
Verified fixed: Apple skin-temp absolute backfill is implemented (
Strand/Data/SkinTempBackfillWalker.swift,Packages/StrandAnalytics/Sources/StrandAnalytics/SkinTempBackfill.swift) alongside the Android twin, with tests.