From a9c2e25099836ae31635d7337ce7c187ccdcfe2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 29 Oct 2026 10:13:22 +0100 Subject: [PATCH] fix(rollups): trail the rollup window from the caller's moment readBestGranularityRollups opened its window at the wall clock, while the live fallback beside it used the caller's now. A read anchored on another moment saw different days depending on whether the account already had DAY buckets; the Today overview's integration test turned red once its fixed morning fell out of the wall-clock window. Every caller that holds a now passes it through. --- src/lib/analytics/range-delta.ts | 2 ++ src/lib/illness/correlation-read.ts | 8 +++++++- src/lib/insights/derived/baseline.ts | 8 +++++++- src/lib/insights/graded-series.ts | 2 ++ src/lib/rollups/measurement-read-wmy.ts | 6 +++++- 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/lib/analytics/range-delta.ts b/src/lib/analytics/range-delta.ts index 4bee215a5..64c056aec 100644 --- a/src/lib/analytics/range-delta.ts +++ b/src/lib/analytics/range-delta.ts @@ -316,6 +316,8 @@ export async function computeRangeDelta( userId, type, windowDays * 2, + undefined, + new Date(now), ); if (!resolved) { const empty: WindowAggregate = { diff --git a/src/lib/illness/correlation-read.ts b/src/lib/illness/correlation-read.ts index aa50005d1..2b6f983bd 100644 --- a/src/lib/illness/correlation-read.ts +++ b/src/lib/illness/correlation-read.ts @@ -217,7 +217,13 @@ async function readWindowMeans( 1, Math.ceil((to.getTime() - from.getTime()) / MS_PER_DAY), ); - const resolved = await readBestGranularityRollups(userId, type, windowDays); + const resolved = await readBestGranularityRollups( + userId, + type, + windowDays, + undefined, + now, + ); if ( resolved && resolved.granularity === "DAY" && diff --git a/src/lib/insights/derived/baseline.ts b/src/lib/insights/derived/baseline.ts index f17913712..7b0f2a6bd 100644 --- a/src/lib/insights/derived/baseline.ts +++ b/src/lib/insights/derived/baseline.ts @@ -274,7 +274,13 @@ export async function readDayMeanSeries( // band from WEEK/MONTH `sd`, and the center reads the DAY `mean` // which composes exactly. `readBestGranularityRollups` with a // window < 91 days resolves to DAY by construction. - const resolved = await readBestGranularityRollups(userId, type, windowDays); + const resolved = await readBestGranularityRollups( + userId, + type, + windowDays, + undefined, + now, + ); if ( resolved && resolved.granularity === "DAY" && diff --git a/src/lib/insights/graded-series.ts b/src/lib/insights/graded-series.ts index 4f82da485..616745700 100644 --- a/src/lib/insights/graded-series.ts +++ b/src/lib/insights/graded-series.ts @@ -537,6 +537,7 @@ export async function buildGradedSeriesWithRollups( type, 365, priority, + now, ); // Yearly slice: route a multi-year window through the tier. const yearlyRouted = await readBestGranularityRollups( @@ -544,6 +545,7 @@ export async function buildGradedSeriesWithRollups( type, 1095, priority, + now, ); const monthlyCovered = diff --git a/src/lib/rollups/measurement-read-wmy.ts b/src/lib/rollups/measurement-read-wmy.ts index 7ba0e5648..026b79f7d 100644 --- a/src/lib/rollups/measurement-read-wmy.ts +++ b/src/lib/rollups/measurement-read-wmy.ts @@ -116,12 +116,16 @@ export async function readBestGranularityRollups( type: MeasurementType, windowDays: number, userPriorityJson?: unknown, + now: Date = new Date(), ): Promise<{ granularity: RollupGranularity; rows: RollupBucketRow[]; } | null> { if (!Number.isFinite(windowDays) || windowDays <= 0) return null; - const since = new Date(Date.now() - windowDays * 24 * 60 * 60 * 1000); + // The window trails the caller's `now`, so a read anchored on another + // moment than the wall clock sees the same days through the rollups as + // through the live fallback. + const since = new Date(now.getTime() - windowDays * 24 * 60 * 60 * 1000); // v1.11.1 — load the source-priority blob once and thread it into every // granularity probe so the collapse never re-queries the user per floor. const priority =