Skip to content

Backfill skin-temp absolutes for nights the 21-night rescore window never reaches #1851

Description

@ryanbr

After #1850 the skin-temp screen leads with the measured temperature whenever any night has one. On a real install that surfaced the next limit: five nights had a temperature and twenty-three had a deviation, so the temperature view spans four days while the baseline view spans three weeks — and #943 then correctly locks 2W/3W/M, because four days of data would redraw the same five points under every chip.

The ask is to show temperatures across the same history the deviation view already covers.

This must be a re-derivation, not a reconstruction

absolute = baseline + deviation is available arithmetic and is wrong. The baseline is a rolling value that drifts over weeks, so applying one night's baseline to another night's delta produces numbers that look like measurements and are not. Same failure class as #194.

The legitimate route is the one the engine already uses: recompute the nightly mean from the raw skinTempSample rows.

The source data is still there

skinTempSample has no age-based retention. The only DELETE is pruneSkinTempByTs(minTs, maxTs), the #547 plausibility quarantine, which removes rows OUTSIDE a valid timestamp window rather than old ones. So every night whose samples were banked can still have its absolute derived.

Why the gap exists

skinTempC is written by the scoring pass (AnalyticsEngine, #1663, 2026-08-27) and analyzeRecent covers a rolling 21-night window. Nights older than that window when the column shipped were never revisited, and nothing walks them since. A night is therefore missing its absolute if it fell outside every window that ran after 27 Aug — regardless of whether its raw samples are sitting on disk.

Shape

A one-off backfill over nights that have a deviation but no skinTempC:

  1. take the night's stored sleep window from sleepSession (no re-detection);
  2. read the raw skinTempSample rows for that window, plus the HR the worn gate needs;
  3. run the SAME wornNightlySkinTempC the engine uses — identical inputs, identical result, no second derivation that could disagree;
  4. persist skinTempC only; leave skinTempDevC and every scored value untouched.

Deliberately NOT a full re-score: nothing else about the night should move, and re-scoring 100+ nights is the #836/#841 battery path.

Open questions worth settling before building:

  • Trigger. On-demand (Settings or Test Centre) is safest; doing it automatically on upgrade risks a long first launch on a deep history.
  • Cost. One sleep-window sample read per night. Bounded and far cheaper than a re-score, but should be measured on a years-deep import before being offered as a one-tap action.
  • How far back. Every night with samples, or a capped window.

Diagnostic first

Before building the backfill, a per-night report — does this night have skinTempC, how many raw skinTempSample rows does its sleep window hold, and what did the worn gate return — would establish whether the missing nights actually have recoverable data. On the reporting install, 7–11 Aug filled while 12–25 Aug did not, which pruning cannot explain (it removes older rows first, not newer ones), so the answer may be "no samples were banked" rather than "the backfill never ran".

Activity

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