fix(insights): exclude soft-deleted measurements in remaining measurement.findMany sites

W-DELETED closed the dashboard, analytics, summaries and rollup tiers
but the Insights cluster (tile-strip, AI Coach snapshot, GLP-1 plateau
detector, pulse-status helper) still walked `prisma.measurement.findMany`
without filtering tombstoned rows. Once iOS sync starts emitting
deletions any soft-deleted reading would still contribute to:

- /api/insights/targets 30-day average + 1-year latest-ever tile values
  (recentMeasurements, latestEverByType, glucoseRows)
- /api/insights/cards 90-day weight/BP/pulse window the iOS-adapter card
  stream consumes
- /api/insights/generate per-type aggregate payload feeding the AI prompt
- Coach prompt feature aggregator (lib/insights/features.ts)
- GLP-1 plateau detection window (lib/insights/glp1-plateau.ts)
- Pulse-status snapshot input (lib/insights/pulse-status.ts)

Add `deletedAt: null` to every remaining production `where` clause.
`comprehensive-aggregator.ts` already filtered; `ai/coach/snapshot.ts`
already filtered. Closes senior H1 from the v1.4.40 QA round.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 12:40:37 +02:00
1 parent 99b4fde002
commit 926ac36dea
6 files changed
+12 -4

No files matched your search

+1
View File
@@ -77,6 +77,7 @@ export const GET = apiHandler(async () => {
in: ["WEIGHT", "BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA", "PULSE"],
},
measuredAt: { gte: ninetyDaysAgo },
deletedAt: null,
},
orderBy: { measuredAt: "asc" },
select: { type: true, value: true, measuredAt: true },
+1 -1
View File
@@ -134,7 +134,7 @@ async function buildComparisonSnapshotForUser(
const rows = await Promise.all(
types.map(async (type) => {
const measurements = await prisma.measurement.findMany({
where: { userId, type },
where: { userId, type, deletedAt: null },
orderBy: { measuredAt: "asc" },
select: { measuredAt: true, value: true },
});
+6 -1
View File
@@ -178,11 +178,15 @@ async function buildTargetsResponse(user: AuthedUser) {
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
// Fetch all measurements in the last 30 days + the latest for each type
// Soft-delete filter mirrors the W-DELETED pattern: tombstoned rows
// must not contribute to tile averages / latest values once iOS sync
// starts emitting deletions.
const recentMeasurements = await prisma.measurement.findMany({
where: {
userId,
type: { in: types },
measuredAt: { gte: thirtyDaysAgo },
deletedAt: null,
},
orderBy: { measuredAt: "desc" },
select: { type: true, value: true, measuredAt: true },
@@ -207,6 +211,7 @@ async function buildTargetsResponse(user: AuthedUser) {
userId,
type: { in: types },
measuredAt: { gte: oneYearAgo },
deletedAt: null,
},
orderBy: { measuredAt: "desc" },
distinct: ["type"],
@@ -1193,7 +1198,7 @@ async function buildTargetsResponse(user: AuthedUser) {
};
const thirtyDaysAgoGlucose = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const glucoseRows = await prisma.measurement.findMany({
where: { userId, type: "BLOOD_GLUCOSE" },
where: { userId, type: "BLOOD_GLUCOSE", deletedAt: null },
orderBy: { measuredAt: "desc" },
select: { value: true, measuredAt: true, glucoseContext: true },
});
+2 -2
View File
@@ -377,8 +377,8 @@ export async function extractFeatures(
: null;
const measurements = await prisma.measurement.findMany({
where: sinceCutoff
? { userId, measuredAt: { gte: sinceCutoff } }
: { userId },
? { userId, measuredAt: { gte: sinceCutoff }, deletedAt: null }
: { userId, deletedAt: null },
orderBy: { measuredAt: "asc" },
});
+1
View File
@@ -74,6 +74,7 @@ export async function detectGlp1Plateau(
userId,
type: "WEIGHT",
measuredAt: { gte: windowStart },
deletedAt: null,
},
orderBy: { measuredAt: "asc" },
select: { value: true, measuredAt: true },
+1
View File
@@ -158,6 +158,7 @@ export async function generatePulseStatusForUser(
where: {
userId,
type: "PULSE",
deletedAt: null,
},
orderBy: { measuredAt: "desc" },
take: 365,