mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
perf(reads): ask the latest reading per type with one probe per type
The briefing's last-seen read and the MCP search presence probe grouped every live row of the account to learn the newest instant or whether a type exists at all. Both now use readLatestEver, one index lookup per type. The latest-per-type guard also catches the Prisma spelling: a groupBy by type with nothing but _max, or no aggregate, and no window.
This commit is contained in:
1 parent
05503e2b82
commit
f0c20151a9
6 files changed
+126
-28
No files matched your search
@@ -14,6 +14,12 @@
|
||||
* the one cell; `computeSummariesSlice` is its builder and is called nowhere
|
||||
* else in production code.
|
||||
*
|
||||
* The Prisma spelling of the same question is `measurement.groupBy` by type
|
||||
* with nothing but `_max` (or nothing at all, a presence probe) and no time
|
||||
* window: it groups every live row the account has to answer a question one
|
||||
* index probe per type answers. Grouped reads that count, sum or average, or
|
||||
* that are bounded in time, ask something else and pass.
|
||||
*
|
||||
* Comments are stripped before matching, and the planted cases prove each
|
||||
* matcher finds what it claims to.
|
||||
*/
|
||||
@@ -37,6 +43,36 @@ const LATEST_PER_TYPE = /DISTINCT\s+ON\s*\(\s*(?:\w+\s*\.\s*)?"?type"?\s*\)/i;
|
||||
|
||||
const SLICE_BUILDER_CALL = /\bcomputeSummariesSlice\s*\(/;
|
||||
|
||||
/** Grouped presence reads that ask something the per-type probe cannot. */
|
||||
const GROUPBY_ALLOWED: Record<string, string> = {
|
||||
"lib/measurements/import-apple-health-export.ts":
|
||||
"Which types hold a MANUAL reading: a source-filtered presence the per-type probe does not take.",
|
||||
"lib/jobs/insight-pregenerate.ts":
|
||||
"Which types exist at all, with no type list to drive a per-type probe; a nightly job, not a request path.",
|
||||
};
|
||||
|
||||
/** Each `measurement.groupBy(...)` argument that asks only latest or presence. */
|
||||
function latestOnlyGroupBys(code: string): string[] {
|
||||
const out: string[] = [];
|
||||
for (const m of code.matchAll(/\.measurement\s*\.\s*groupBy\s*\(/g)) {
|
||||
let i = m.index + m[0].length;
|
||||
let depth = 1;
|
||||
while (depth > 0 && i < code.length) {
|
||||
if (code[i] === "(") depth += 1;
|
||||
else if (code[i] === ")") depth -= 1;
|
||||
i += 1;
|
||||
}
|
||||
const arg = code.slice(m.index + m[0].length, i - 1);
|
||||
const byType = /by:\s*\[\s*"type"\s*\]/.test(arg);
|
||||
const asksMore = /\b_(?:count|sum|avg|min)\b/.test(arg);
|
||||
const windowed = /\bmeasuredAt\s*:/.test(
|
||||
arg.replace(/_max:\s*\{[^}]*\}/, ""),
|
||||
);
|
||||
if (byType && !asksMore && !windowed) out.push(arg.replace(/\s+/g, " "));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function productionFiles(): string[] {
|
||||
return walkSourceFiles(SRC, { floor: 3000 }).filter(
|
||||
(file) =>
|
||||
@@ -71,6 +107,49 @@ describe("latest-per-type reads", () => {
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("the groupBy matcher finds a latest-only or presence-only read", () => {
|
||||
expect(
|
||||
latestOnlyGroupBys(
|
||||
`prisma.measurement.groupBy({ by: ["type"], where: { userId }, _max: { measuredAt: true } })`,
|
||||
),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
latestOnlyGroupBys(
|
||||
`prisma.measurement\n .groupBy({ by: ["type"], where: { userId, deletedAt: null } })`,
|
||||
),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
latestOnlyGroupBys(
|
||||
`prisma.measurement.groupBy({ by: ["type"], where: { userId }, _count: { _all: true }, _max: { measuredAt: true } })`,
|
||||
),
|
||||
).toEqual([]);
|
||||
expect(
|
||||
latestOnlyGroupBys(
|
||||
`prisma.measurement.groupBy({ by: ["type"], where: { userId, measuredAt: { gte: since } }, _max: { measuredAt: true } })`,
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("no production read groups every row for the latest per type", () => {
|
||||
const files = productionFiles();
|
||||
const grouped = files.filter((file) =>
|
||||
/\.measurement\s*\.\s*groupBy\s*\(/.test(
|
||||
readFileSync(join(SRC, file), "utf8"),
|
||||
),
|
||||
);
|
||||
// The tree groups measurements in a dozen places; finding none means
|
||||
// the walk read the wrong tree.
|
||||
expect(grouped.length).toBeGreaterThanOrEqual(8);
|
||||
const offenders = files
|
||||
.filter((file) => !(file in GROUPBY_ALLOWED))
|
||||
.flatMap((file) =>
|
||||
latestOnlyGroupBys(
|
||||
stripComments(readFileSync(join(SRC, file), "utf8")),
|
||||
).map((arg) => `${file}: ${arg.slice(0, 100)}`),
|
||||
);
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it("the summaries slice is built only behind its cache cell", () => {
|
||||
const callers = productionFiles().filter((file) =>
|
||||
SLICE_BUILDER_CALL.test(
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
import { dailyBriefingSchema, type DailyBriefing } from "@/lib/ai/schema";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { readLatestEver } from "@/lib/dashboard/summary-reads";
|
||||
import type { Locale } from "@/lib/i18n/config";
|
||||
import { readBriefingGeneratedAt } from "@/lib/insights/briefing-generated-at";
|
||||
import { userDayKey } from "@/lib/tz/format";
|
||||
@@ -41,21 +42,17 @@ export async function readSignalLastSeen(
|
||||
userId: string,
|
||||
): Promise<(type: string) => string | null> {
|
||||
try {
|
||||
const rows = await prisma.measurement.groupBy({
|
||||
by: ["type"],
|
||||
where: {
|
||||
userId,
|
||||
deletedAt: null,
|
||||
type: {
|
||||
in: Object.values(SIGNAL_READING_TYPE) as MeasurementType[],
|
||||
},
|
||||
},
|
||||
_max: { measuredAt: true },
|
||||
});
|
||||
// One index probe per type for its latest live reading
|
||||
// (`readLatestEver`), rather than a grouped max over every row.
|
||||
const rows = await readLatestEver(
|
||||
prisma,
|
||||
userId,
|
||||
Object.values(SIGNAL_READING_TYPE) as MeasurementType[],
|
||||
);
|
||||
const byType = new Map(
|
||||
rows.map((row) => [
|
||||
String(row.type),
|
||||
row._max.measuredAt?.toISOString() ?? null,
|
||||
new Date(row.measured_at).toISOString(),
|
||||
]),
|
||||
);
|
||||
return (type) => byType.get(type) ?? null;
|
||||
|
||||
@@ -121,6 +121,13 @@ vi.mock("@/lib/db", () => ({
|
||||
vaccinationRecord: table("vaccinationRecord"),
|
||||
},
|
||||
}));
|
||||
|
||||
// The search presence probe is one index lookup per type in Postgres; the
|
||||
// suite states which types hold a live reading through this stub.
|
||||
vi.mock("@/lib/dashboard/summary-reads", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/lib/dashboard/summary-reads")>()),
|
||||
readLatestEver: vi.fn(async () => []),
|
||||
}));
|
||||
vi.mock("@/lib/links", () => ({
|
||||
listTargets: vi.fn(
|
||||
async (
|
||||
|
||||
@@ -42,6 +42,13 @@ vi.mock("@/lib/db", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// The search presence probe is one index lookup per type in Postgres; the
|
||||
// suite states which types hold a live reading through this stub.
|
||||
vi.mock("@/lib/dashboard/summary-reads", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/lib/dashboard/summary-reads")>()),
|
||||
readLatestEver: vi.fn(async () => []),
|
||||
}));
|
||||
|
||||
import { MCP_TOOLS } from "../tools";
|
||||
import { buildCoachDataInventory } from "@/lib/ai/coach/tools/inventory";
|
||||
import { executeCoachTool } from "@/lib/ai/coach/tools/executor";
|
||||
|
||||
@@ -60,6 +60,13 @@ vi.mock("@/lib/db", () => ({
|
||||
ecgRecording: { findMany: vi.fn(async () => []) },
|
||||
},
|
||||
}));
|
||||
|
||||
// The search presence probe is one index lookup per type in Postgres; the
|
||||
// suite states which types hold a live reading through this stub.
|
||||
vi.mock("@/lib/dashboard/summary-reads", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("@/lib/dashboard/summary-reads")>()),
|
||||
readLatestEver: vi.fn(async () => []),
|
||||
}));
|
||||
// v1.24 — the operational reads delegate to existing server-authoritative
|
||||
// engines; stub them so the registry-wide loops never reach a real engine.
|
||||
vi.mock("@/lib/medications/scheduling/next-due", () => ({
|
||||
@@ -157,6 +164,7 @@ import { loadIntradayPulse } from "@/lib/analytics/intraday-pulse-io";
|
||||
import { listTargetsBySource } from "@/lib/links";
|
||||
import { decryptFromBytes } from "@/lib/ai/coach/bytes-codec";
|
||||
import type { McpAuthContext } from "../auth";
|
||||
import { readLatestEver } from "@/lib/dashboard/summary-reads";
|
||||
|
||||
const CTX: McpAuthContext = {
|
||||
userId: "user-1",
|
||||
@@ -1229,8 +1237,8 @@ describe("v1.25 clinical signals on the MCP surface", () => {
|
||||
});
|
||||
|
||||
it("search surfaces a present clinical signal as metric:<KEY>", async () => {
|
||||
vi.mocked(prisma.measurement.groupBy).mockResolvedValue([
|
||||
{ type: "GRIP_STRENGTH" },
|
||||
vi.mocked(readLatestEver).mockResolvedValue([
|
||||
{ type: "GRIP_STRENGTH", value: 1, measured_at: new Date() },
|
||||
] as never);
|
||||
const result = (await tool("search").run(CTX, { query: "grip" })) as {
|
||||
results: Array<{ id: string; title: string; url: string }>;
|
||||
@@ -1242,7 +1250,7 @@ describe("v1.25 clinical signals on the MCP surface", () => {
|
||||
});
|
||||
|
||||
it("search omits a clinical signal with no recorded data", async () => {
|
||||
vi.mocked(prisma.measurement.groupBy).mockResolvedValue([] as never);
|
||||
vi.mocked(readLatestEver).mockResolvedValue([] as never);
|
||||
const result = (await tool("search").run(CTX, { query: "grip" })) as {
|
||||
results: Array<{ id: string }>;
|
||||
};
|
||||
@@ -1250,7 +1258,7 @@ describe("v1.25 clinical signals on the MCP surface", () => {
|
||||
});
|
||||
|
||||
it("fetch hydrates a clinical signal via the baseline read (not the Coach path)", async () => {
|
||||
vi.mocked(prisma.measurement.groupBy).mockResolvedValue([] as never);
|
||||
vi.mocked(readLatestEver).mockResolvedValue([] as never);
|
||||
vi.mocked(getMetricBaseline).mockResolvedValue({
|
||||
present: true,
|
||||
metric: "Grip strength",
|
||||
@@ -1292,8 +1300,8 @@ describe("metric-status-only discovery on search/fetch (v1.30 coverage review G5
|
||||
});
|
||||
|
||||
it("search surfaces a present metric-status-only id (undiscoverable before this wave)", async () => {
|
||||
vi.mocked(prisma.measurement.groupBy).mockResolvedValue([
|
||||
{ type: "WRIST_TEMPERATURE" },
|
||||
vi.mocked(readLatestEver).mockResolvedValue([
|
||||
{ type: "WRIST_TEMPERATURE", value: 1, measured_at: new Date() },
|
||||
] as never);
|
||||
const result = (await tool("search").run(CTX, { query: "wrist" })) as {
|
||||
results: Array<{ id: string; title: string }>;
|
||||
@@ -1303,7 +1311,7 @@ describe("metric-status-only discovery on search/fetch (v1.30 coverage review G5
|
||||
});
|
||||
|
||||
it("fetch hydrates a metric-status-only id via the baseline read", async () => {
|
||||
vi.mocked(prisma.measurement.groupBy).mockResolvedValue([] as never);
|
||||
vi.mocked(readLatestEver).mockResolvedValue([] as never);
|
||||
vi.mocked(getMetricBaseline).mockResolvedValue({
|
||||
present: true,
|
||||
metric: "Wrist skin temperature",
|
||||
@@ -1387,7 +1395,7 @@ describe("nutrients on search / fetch (v1.30 coverage review G1)", () => {
|
||||
} as never);
|
||||
vi.mocked(prisma.medication.findMany).mockResolvedValue([] as never);
|
||||
vi.mocked(prisma.labResult.findMany).mockResolvedValue([] as never);
|
||||
vi.mocked(prisma.measurement.groupBy).mockResolvedValue([] as never);
|
||||
vi.mocked(readLatestEver).mockResolvedValue([] as never);
|
||||
});
|
||||
|
||||
it("search resolves 'water' to nutrient:water when the user has logged it", async () => {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import { readLatestEver } from "@/lib/dashboard/summary-reads";
|
||||
import { resolveMarkerForRead } from "@/lib/labs/biomarker-resolve";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { getDayArgsSchema } from "@/lib/ai/coach/tools/definitions";
|
||||
@@ -584,14 +585,13 @@ function searchAndFetchTools(): McpToolDefinition[] {
|
||||
// v1.41.2 — live rows only: a deleted reading is not a signal the
|
||||
// record holds, and the filter lets the probe use the live-rows
|
||||
// partial index instead of walking the tombstones.
|
||||
const discoverablePresent = await prisma.measurement.groupBy({
|
||||
by: ["type"],
|
||||
where: {
|
||||
userId: ctx.userId,
|
||||
type: { in: discoverableSignals.map((s) => s.measurementType) },
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
// A presence probe is one index lookup per type (`readLatestEver`
|
||||
// drops a type with no live reading), not a grouping of every row.
|
||||
const discoverablePresent = await readLatestEver(
|
||||
prisma,
|
||||
ctx.userId,
|
||||
discoverableSignals.map((s) => s.measurementType),
|
||||
);
|
||||
const presentTypes = new Set(discoverablePresent.map((r) => r.type));
|
||||
for (const sig of discoverableSignals) {
|
||||
if (!presentTypes.has(sig.measurementType)) continue;
|
||||
|
||||
Reference in new issue
Block a user