From 702afe5d493933bebe16ab04523e0ea71eabb133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 18:53:35 +0200 Subject: [PATCH] fix(personal-records): exclude soft-deleted measurements from PR detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pr-detection-worker.ts ran its warm-up count and best-measurement scan across every Measurement row including soft-deleted ones. A user who deleted a row that happened to be their PR would not get the next-best row promoted on the next detection pass. Add `deletedAt: null` to both where clauses to align with the v1.4.40 soft-delete completeness sweep. Also document the offhost-backup DR-intent at the matching findMany — that path deliberately includes soft-deleted rows because the offhost upload is a disaster-recovery snapshot, not a user-facing export. --- src/lib/jobs/offhost-backup.ts | 3 +++ src/lib/personal-records/pr-detection-worker.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/jobs/offhost-backup.ts b/src/lib/jobs/offhost-backup.ts index 4cde9d472..0e117809b 100644 --- a/src/lib/jobs/offhost-backup.ts +++ b/src/lib/jobs/offhost-backup.ts @@ -216,6 +216,9 @@ export async function runOffhostBackup( try { const [measurements, medications, intakeEvents, moodEntries] = await Promise.all([ + // includes soft-deleted rows because this is the DR snapshot, + // not a user-facing export — see + // `/api/export/full-backup/route.ts` for the symmetric exclusion. prisma.measurement.findMany({ where: { userId: user.id } }), prisma.medication.findMany({ where: { userId: user.id }, diff --git a/src/lib/personal-records/pr-detection-worker.ts b/src/lib/personal-records/pr-detection-worker.ts index dacd05370..53bcaf85a 100644 --- a/src/lib/personal-records/pr-detection-worker.ts +++ b/src/lib/personal-records/pr-detection-worker.ts @@ -217,7 +217,7 @@ export async function detectPersonalRecordsForUser( // Warm-up gate. Cheap count first so we don't pull a 30k-row // scan on a brand-new account. const sampleCount = await prisma.measurement.count({ - where: { userId, type: metricType }, + where: { userId, type: metricType, deletedAt: null }, }); if (sampleCount < PR_DETECTION_WARMUP_THRESHOLD) continue; @@ -400,7 +400,7 @@ async function findBestMeasurement( direction: PersonalRecordDirection, ): Promise { const row = await prisma.measurement.findFirst({ - where: { userId, type }, + where: { userId, type, deletedAt: null }, orderBy: direction === PersonalRecordDirection.MAX ? { value: "desc" }