From 03c91c7a3c3774c5486787548d461f4b06c60de5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Mon, 18 May 2026 12:17:28 +0200 Subject: [PATCH] perf(measurements-groupby-day): cumulative path reads sum_value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `GET /api/measurements?source=rollup&aggregate=daily` previously reconstructed cumulative daily totals from `mean * count`. The v1.4.39 writer populates the new `sum_value` column on every fold, so the read path consumes it directly. The legacy `mean * count` arithmetic stays as the fallback for boot-backfill convergence where existing rows still carry `sumValue = null`. Algebraically equivalent (AVG = SUM / COUNT for a single-source day), so this is a no-op for byte parity until the cumulative single-source assumption breaks — at which point the bucket-level SUM is the canonical reference rather than the reconstructed value. --- src/app/api/measurements/route.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/app/api/measurements/route.ts b/src/app/api/measurements/route.ts index 6edebf6a0..36ee85b15 100644 --- a/src/app/api/measurements/route.ts +++ b/src/app/api/measurements/route.ts @@ -285,19 +285,29 @@ export const GET = apiHandler(async (request: NextRequest) => { bucketStart: true, mean: true, count: true, + // v1.4.39 W-SUM — the writer now populates `sum_value` on + // every fold. The cumulative metric path consumes the column + // directly instead of reconstructing `mean * count`; the + // legacy fallback below covers the boot-backfill convergence + // window when an existing bucket pre-dates v1.4.39. + sumValue: true, }, }); if (rollupRows.length > 0) { // Cumulative metrics (steps, active energy, distance, flights, // daylight) store `mean` per bucket but the chart needs the - // daily SUM. Reconstruct as `mean * count` — algebraically - // equivalent to SUM(value) for a single-source day because - // AVG = SUM / COUNT. + // daily SUM. v1.4.39 W-SUM — read `sumValue` directly; fall back + // to `mean * count` only when the legacy NULL surfaces (pre- + // v1.4.39 rows still waiting for the boot-time backfill to + // converge). `mean * count` is algebraically equivalent to + // SUM(value) for a single-source day because AVG = SUM / COUNT. const useSum = type != null && CUMULATIVE_HK_TYPES.has(type as MeasurementType); const measurements = rollupRows.map((r) => ({ type: r.type, - value: useSum ? r.mean * r.count : r.mean, + value: useSum + ? (r.sumValue ?? r.mean * r.count) + : r.mean, measuredAt: r.bucketStart.toISOString(), count: r.count, }));