From de1e65d4a17d0d5239f86443572e48bb877e81b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 11:52:42 +0200 Subject: [PATCH] fix(medications-intake): recompute compliance rollup after bulk projection backfill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the v1.4.39.4 hook gap surfaced by the QA reconcile pass. The `scope=today` branch's bulk `createMany` mints fresh `(medicationId, scheduledFor)` PENDING rows when the morning projection lands; without this hook the compliance rollup for the affected `(user, medication, day)` tuples stays at its pre-projection `scheduled` count, inflating the apparent compliance % until the user actually logs against the new row. Fires `recomputeMedicationComplianceForEvent` once per distinct `(medication_id, dayKey)` (coalesced by Set) so a 5-meds-3-times-a- day projection drops 15 createMany rows down to ≤3 recompute round- trips. Best-effort: errors stay caught inside the helper so a populator hiccup doesn't surface as a 5xx on the intake request. --- src/app/api/medications/intake/route.ts | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/app/api/medications/intake/route.ts b/src/app/api/medications/intake/route.ts index 73dc12607..dd6fc5e0a 100644 --- a/src/app/api/medications/intake/route.ts +++ b/src/app/api/medications/intake/route.ts @@ -156,6 +156,34 @@ export const GET = apiHandler(async (request: NextRequest) => { // structural backstop, this is the defense-in-depth. skipDuplicates: true, }); + + // v1.4.40 — close the compliance-rollup hook gap from v1.4.39.4. + // The bulk projection mints fresh `(medicationId, scheduledFor)` + // rows in PENDING state; without this hook the rollup for the + // affected `(user, medication, day)` tuples stays at its previous + // (pre-projection) `scheduled` count, which inflates the apparent + // compliance % until the user actually logs against the new + // row. Coalesce by `(medicationId, dayKey)` so we fire one + // recompute per distinct day-tuple instead of one per row. + // Best-effort: errors stay caught inside the helper so a + // populator hiccup doesn't surface as a 5xx on the intake + // request. Mirror call site in `dashboard/summary/route.ts`. + const seenDayKeys = new Set(); + const recomputeJobs: Array> = []; + for (const m of missing) { + const key = `${m.medicationId}|${m.scheduledFor.toISOString().slice(0, 10)}`; + if (seenDayKeys.has(key)) continue; + seenDayKeys.add(key); + recomputeJobs.push( + recomputeMedicationComplianceForEvent({ + userId: user.id, + medicationId: m.medicationId, + scheduledFor: m.scheduledFor, + tz: userTz, + }), + ); + } + await Promise.all(recomputeJobs); } const events = await prisma.medicationIntakeEvent.findMany({