fix(insights): invalidate per-metric assessment caches on measurement edit and delete

The targeted status-insight invalidator only fired on insert and batch
ingest, so editing or deleting a reading left the affected per-metric
assessment cards serving their pre-mutation text for the rest of the day.
Add the fire-and-forget invalidation to the single-measurement PUT
(covering both the old and the new type when an edit re-types a row),
the single-measurement DELETE, and the iOS by-external-ids
reconciliation DELETE (keyed off the distinct types it tombstoned).
This commit is contained in:
Marc-André Bombeck committed 2026-05-31 22:43:24 +02:00
1 parent eee6ab2e7e
commit c775861d8c
2 files changed
+32

No files matched your search

+20
View File
@@ -12,6 +12,7 @@ import {
} from "@/lib/api-response";
import { updateMeasurementSchema } from "@/lib/validations/measurement";
import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
import { invalidateStatusInsightsForTypes } from "@/lib/insights/comprehensive-generate";
import { recomputeBucketsForMeasurement } from "@/lib/rollups/measurement-rollups";
import { Prisma } from "@/generated/prisma/client";
import { NextRequest } from "next/server";
@@ -172,6 +173,18 @@ export const PUT = apiHandler(
console.warn("[measurements] rollup recompute failed", err);
}
// v1.8.0 — drop the cached per-metric assessment rows this edit
// dirties so the next mount / nightly warm pass regenerates against
// the new value. An edit can re-type a row, so invalidate both the
// old and the new type's scopes. Fire-and-forget: never blocks the
// user's edit.
invalidateStatusInsightsForTypes(user.id, [
existing.type,
measurement.type,
]).catch((err) => {
console.warn("[measurements] status-insight invalidate failed", err);
});
return apiSuccess(measurement);
},
);
@@ -238,6 +251,13 @@ export const DELETE = apiHandler(
console.warn("[measurements] rollup recompute failed", err);
}
// v1.8.0 — drop the cached per-metric assessment rows this deletion
// dirties so the next mount / nightly warm pass regenerates against
// the reduced history. Fire-and-forget: never blocks the user's delete.
invalidateStatusInsightsForTypes(user.id, [existing.type]).catch((err) => {
console.warn("[measurements] status-insight invalidate failed", err);
});
return apiSuccess({ deleted: true });
},
);
@@ -35,6 +35,7 @@ import {
safeJson,
} from "@/lib/api-response";
import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
import { invalidateStatusInsightsForTypes } from "@/lib/insights/comprehensive-generate";
import {
recomputeBucketsForMeasurement,
collapseToTypeDayKeys,
@@ -152,6 +153,17 @@ async function deleteByExternalIds(request: NextRequest): Promise<Response> {
} catch (err) {
console.warn("[measurements] rollup recompute failed", err);
}
// v1.8.0 — drop the cached per-metric assessment rows the deleted
// types dirty so the next mount / nightly warm pass regenerates
// against the reconciled history. Fire-and-forget: never blocks the
// iOS reconciliation.
invalidateStatusInsightsForTypes(
user.id,
affectedRows.map((row) => row.type),
).catch((err) => {
console.warn("[measurements] status-insight invalidate failed", err);
});
}
return apiSuccess({ deletedCount: result.count });