Skip to content

Apple: same skin-temp absolute gap as #1851, but the display needs #1848 first #1853

Open

Description

@ryanbr

#1852 backfills the nightly absolute on Android. Apple has the identical gap:

  • skinTempC is 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's DailyMetric.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:

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:

  1. 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.

  2. 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.

  3. 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

  1. added a commit that references this issue on Sep 6, 2026
  2. evoveotech commented on Sep 10, 2026

    @evoveotech

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions