From 9a08ad66d1b4df6c178674da2f47b1f3aed5a0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 14:29:15 +0200 Subject: [PATCH] feat(measurements): cutoffHours grace window for drainPerSampleCumulative The drain previously collapsed every per-sample APPLE_HEALTH cumulative row it could see, including today's still-in-flight watch syncs. When called from the scheduled nightly job we want to leave a grace window around now so the list view keeps surfacing late watch reconciliations in real time before the day stabilises. cutoffHours adds an optional window: rows newer than now() - cutoffHours are excluded from the scan. CLI + admin-endpoint callers keep the previous all-rows behaviour by omitting the option. --- .../drain-per-sample-cumulative.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/lib/measurements/drain-per-sample-cumulative.ts b/src/lib/measurements/drain-per-sample-cumulative.ts index dcc5a7481..1e491bafe 100644 --- a/src/lib/measurements/drain-per-sample-cumulative.ts +++ b/src/lib/measurements/drain-per-sample-cumulative.ts @@ -77,6 +77,20 @@ export interface DrainOptions { dryRun?: boolean; /** Logger sink — defaults to `console.log`. */ log?: (line: string) => void; + /** + * v1.4.37 W7c — protect recent per-sample rows from collapse so late + * watch syncs still surface in real time before the list view shows + * the day's total. When set, rows whose `measuredAt` is newer than + * `now() - cutoffHours` are excluded from the scan; the drain only + * acts on completed days that have had enough time to stabilise. + * + * The scheduled nightly call passes `36` so the previous day's + * trailing sync window (Apple Watch reconciliations land up to a few + * hours after midnight when the watch wasn't worn) is fully covered. + * The CLI + admin endpoint default to `undefined` (drain everything + * the operator points at) for explicit one-shot use. + */ + cutoffHours?: number; } /** @@ -190,6 +204,14 @@ export async function drainPerSampleCumulative( ): Promise { const dryRun = options.dryRun ?? false; const log = options.log ?? ((line) => console.log(line)); + // v1.4.37 W7c — when the scheduler passes a cutoff window we leave + // rows newer than the cutoff alone so the user's "today" view keeps + // updating in real time. Cutoff is computed once per invocation so + // every per-user-type scan uses the same boundary instant. + const cutoffAt = + typeof options.cutoffHours === "number" && options.cutoffHours > 0 + ? new Date(Date.now() - options.cutoffHours * 60 * 60 * 1000) + : null; const users = options.userId ? await prismaClient.user.findMany({ @@ -224,6 +246,10 @@ export async function drainPerSampleCumulative( userId: user.id, source: "APPLE_HEALTH", type, + // v1.4.37 W7c — exclude rows that fall inside the grace + // window so the nightly scheduled drain never collapses + // today's still-in-flight watch syncs. + ...(cutoffAt ? { measuredAt: { lt: cutoffAt } } : {}), }, select: { id: true,