perf(insights): wire six sibling status routes to the shared timeout-stub helper

Bring blood-pressure, weight, general, pulse, mood, and
medication-compliance into parity with bmi-status. On a 20 s
provider stall every route now persists a sentinel auditLog row
keyed to today's Berlin day so the next mount short-circuits at
the cache lookup instead of re-racing the same provider call on
every cold visit. Eliminates the ~14 s warm response on every
dashboard open after a one-off provider hiccup.

The seven routes share one persist path through
`persistTimeoutStubAndReturn`; ~120 LOC of duplicate
`auditLog.create` calls collapse into the helper.

medication-compliance returns a richer `{summary, medications}`
envelope rather than the standard `{text}` shape, so it calls
the helper for the persist side-effect and maps `summary` ←
`text` on the way out. Its cache-read also gains a stub-row
recogniser so the second-mount short-circuit fires on the
helper's `text` + `timeout: true` payload.

Tests cover both arms per route: the persist-on-timeout path
asserts a single auditLog.create with `timeout: true`, model
`timeout-stub`, and today's Berlin dateKey; the
short-circuit-on-stub path asserts a subsequent mount returns
the cached stub text without invoking the provider.
This commit is contained in:
Marc-André Bombeck committed 2026-05-21 18:18:53 +02:00
1 parent e4d46de22d
commit 134566466d
13 files changed
+663 -38

No files matched your search

@@ -109,6 +109,106 @@ describe("generateBloodPressureStatusForUser — v1.4.6 bucketed payload", () =>
});
});
describe("generateBloodPressureStatusForUser — v1.4.41 timeout-stub persistence", () => {
it("persists a sentinel row keyed to today when the provider times out", async () => {
vi.mocked(prisma.user.findUnique).mockResolvedValue({
dateOfBirth: null,
} as never);
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(null);
vi.mocked(prisma.measurement.findMany).mockResolvedValue([
{ type: "BLOOD_PRESSURE_SYS", value: 132, measuredAt: new Date() },
{ type: "BLOOD_PRESSURE_DIA", value: 84, measuredAt: new Date() },
] as never);
vi.mocked(prisma.medication.findMany).mockResolvedValue([] as never);
vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue(
[] as never,
);
vi.mocked(prisma.moodEntry.findMany).mockResolvedValue([] as never);
vi.mocked(prisma.auditLog.create).mockResolvedValue({
createdAt: new Date(),
} as never);
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: vi.fn(
() =>
new Promise(() => {
/* never resolves */
}),
),
} as never);
vi.useFakeTimers();
const promise = generateBloodPressureStatusForUser("user-1", {
locale: "en",
});
await vi.advanceTimersByTimeAsync(25_000);
const result = await promise;
vi.useRealTimers();
expect(result.text).toBeTruthy();
expect(result.cached).toBe(true);
expect(result.updatedAt).toBeTruthy();
const createCalls = vi.mocked(prisma.auditLog.create).mock.calls;
expect(createCalls.length).toBe(1);
const details = (createCalls[0][0] as { data: { details: string } }).data
.details;
const parsed = JSON.parse(details) as {
dateKey?: string;
text?: string;
timeout?: boolean;
model?: string;
};
expect(parsed.text).toBeTruthy();
expect(parsed.timeout).toBe(true);
expect(parsed.model).toBe("timeout-stub");
expect(parsed.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/);
});
it("subsequent mounts short-circuit at the cache lookup and skip the race", async () => {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
}).formatToParts(new Date());
const y = parts.find((p) => p.type === "year")!.value;
const m = parts.find((p) => p.type === "month")!.value;
const d = parts.find((p) => p.type === "day")!.value;
const todayKey = `${y}-${m}-${d}`;
const stubRow = {
createdAt: new Date(),
details: JSON.stringify({
dateKey: todayKey,
locale: "en",
text: "Blood pressure assessment fallback…",
timeout: true,
}),
};
vi.mocked(prisma.user.findUnique).mockResolvedValue({
dateOfBirth: null,
} as never);
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(stubRow as never);
const providerCall = vi.fn();
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: providerCall,
} as never);
const result = await generateBloodPressureStatusForUser("user-1", {
locale: "en",
});
expect(providerCall).not.toHaveBeenCalled();
expect(result.text).toBe("Blood pressure assessment fallback…");
expect(result.cached).toBe(true);
expect(result.updatedAt).toBe(stubRow.createdAt.toISOString());
expect(prisma.auditLog.create).not.toHaveBeenCalled();
});
});
describe("generateBloodPressureStatusForUser — token-leak hardening (v1.4.27 F16)", () => {
it("strips metric: tokens out of the cached text before persisting", async () => {
vi.mocked(prisma.user.findUnique).mockResolvedValue({
@@ -100,6 +100,102 @@ describe("generateGeneralStatusForUser — v1.4.6 bucketed payload", () => {
});
});
describe("generateGeneralStatusForUser — v1.4.41 timeout-stub persistence", () => {
it("persists a sentinel row keyed to today when the provider times out", async () => {
vi.mocked(prisma.user.findUnique).mockResolvedValue({
dateOfBirth: null,
} as never);
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(null);
vi.mocked(prisma.measurement.findMany).mockResolvedValue([
{ type: "WEIGHT", value: 82, measuredAt: new Date() },
] as never);
vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue(
[] as never,
);
vi.mocked(prisma.moodEntry.findMany).mockResolvedValue([] as never);
vi.mocked(prisma.auditLog.create).mockResolvedValue({
createdAt: new Date(),
} as never);
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: vi.fn(
() =>
new Promise(() => {
/* never resolves */
}),
),
} as never);
vi.useFakeTimers();
const promise = generateGeneralStatusForUser("user-1", { locale: "en" });
await vi.advanceTimersByTimeAsync(25_000);
const result = await promise;
vi.useRealTimers();
expect(result.text).toBeTruthy();
expect(result.cached).toBe(true);
expect(result.updatedAt).toBeTruthy();
const createCalls = vi.mocked(prisma.auditLog.create).mock.calls;
expect(createCalls.length).toBe(1);
const details = (createCalls[0][0] as { data: { details: string } }).data
.details;
const parsed = JSON.parse(details) as {
dateKey?: string;
text?: string;
timeout?: boolean;
model?: string;
};
expect(parsed.text).toBeTruthy();
expect(parsed.timeout).toBe(true);
expect(parsed.model).toBe("timeout-stub");
expect(parsed.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/);
});
it("subsequent mounts short-circuit at the cache lookup and skip the race", async () => {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
}).formatToParts(new Date());
const y = parts.find((p) => p.type === "year")!.value;
const m = parts.find((p) => p.type === "month")!.value;
const d = parts.find((p) => p.type === "day")!.value;
const todayKey = `${y}-${m}-${d}`;
const stubRow = {
createdAt: new Date(),
details: JSON.stringify({
dateKey: todayKey,
locale: "en",
text: "General status fallback…",
timeout: true,
}),
};
vi.mocked(prisma.user.findUnique).mockResolvedValue({
dateOfBirth: null,
} as never);
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(stubRow as never);
const providerCall = vi.fn();
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: providerCall,
} as never);
const result = await generateGeneralStatusForUser("user-1", {
locale: "en",
});
expect(providerCall).not.toHaveBeenCalled();
expect(result.text).toBe("General status fallback…");
expect(result.cached).toBe(true);
expect(result.updatedAt).toBe(stubRow.createdAt.toISOString());
expect(prisma.auditLog.create).not.toHaveBeenCalled();
});
});
describe("generateGeneralStatusForUser — token-leak hardening (v1.4.27 F16)", () => {
it("strips metric: tokens out of the cached text before persisting", async () => {
vi.mocked(prisma.user.findUnique).mockResolvedValue({
@@ -108,6 +108,114 @@ describe("generateMedicationComplianceStatusForUser — v1.4.6 bucketed payload"
});
});
describe("generateMedicationComplianceStatusForUser — v1.4.41 timeout-stub persistence", () => {
it("persists a sentinel row keyed to today when the provider times out", async () => {
const now = new Date();
const medication = {
id: "med-1",
name: "Ramipril",
dose: "5mg",
active: true,
createdAt: new Date(now.getTime() - 60 * dayMs),
schedules: [{ id: "s1", time: "08:00" }],
};
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(null);
vi.mocked(prisma.medication.findMany).mockResolvedValue([
medication,
] as never);
vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue(
[] as never,
);
vi.mocked(prisma.auditLog.create).mockResolvedValue({
createdAt: new Date(),
} as never);
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: vi.fn(
() =>
new Promise(() => {
/* never resolves */
}),
),
} as never);
vi.useFakeTimers();
const promise = generateMedicationComplianceStatusForUser("user-1", {
locale: "en",
});
await vi.advanceTimersByTimeAsync(25_000);
const result = await promise;
vi.useRealTimers();
// Route returns the richer `{summary, medications}` shape — the
// helper writes the persist row and the route maps `summary` ←
// `text` on the way out.
expect(result.summary).toBeTruthy();
expect(result.medications).toEqual([]);
expect(result.cached).toBe(true);
expect(result.updatedAt).toBeTruthy();
const createCalls = vi.mocked(prisma.auditLog.create).mock.calls;
expect(createCalls.length).toBe(1);
const details = (createCalls[0][0] as { data: { details: string } }).data
.details;
const parsed = JSON.parse(details) as {
dateKey?: string;
text?: string;
timeout?: boolean;
model?: string;
};
expect(parsed.text).toBeTruthy();
expect(parsed.timeout).toBe(true);
expect(parsed.model).toBe("timeout-stub");
expect(parsed.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/);
});
});
describe("generateMedicationComplianceStatusForUser — v1.4.41 timeout-stub short-circuit", () => {
it("subsequent mounts short-circuit when a stub row with text+timeout:true exists", async () => {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
}).formatToParts(new Date());
const y = parts.find((p) => p.type === "year")!.value;
const m = parts.find((p) => p.type === "month")!.value;
const d = parts.find((p) => p.type === "day")!.value;
const todayKey = `${y}-${m}-${d}`;
const stubRow = {
createdAt: new Date(),
details: JSON.stringify({
dateKey: todayKey,
locale: "en",
text: "Medication compliance fallback…",
timeout: true,
}),
};
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(stubRow as never);
const providerCall = vi.fn();
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: providerCall,
} as never);
const result = await generateMedicationComplianceStatusForUser("user-1", {
locale: "en",
});
expect(providerCall).not.toHaveBeenCalled();
expect(result.summary).toBe("Medication compliance fallback…");
expect(result.medications).toEqual([]);
expect(result.cached).toBe(true);
expect(result.updatedAt).toBe(stubRow.createdAt.toISOString());
expect(prisma.auditLog.create).not.toHaveBeenCalled();
});
});
describe("generateMedicationComplianceStatusForUser — token-leak hardening (v1.4.27 F16)", () => {
it("strips metric: tokens out of the cached summary + per-medication text", async () => {
const now = new Date();
@@ -88,6 +88,97 @@ describe("generateMoodStatusForUser — v1.4.6 bucketed payload", () => {
});
});
describe("generateMoodStatusForUser — v1.4.41 timeout-stub persistence", () => {
it("persists a sentinel row keyed to today when the provider times out", async () => {
const t = new Date();
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(null);
vi.mocked(prisma.moodEntry.findMany).mockResolvedValue([
{
date: t.toISOString().slice(0, 10),
score: 4,
tags: [],
moodLoggedAt: t,
},
] as never);
vi.mocked(prisma.measurement.findMany).mockResolvedValue([] as never);
vi.mocked(prisma.auditLog.create).mockResolvedValue({
createdAt: new Date(),
} as never);
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: vi.fn(
() =>
new Promise(() => {
/* never resolves */
}),
),
} as never);
vi.useFakeTimers();
const promise = generateMoodStatusForUser("user-1", { locale: "en" });
await vi.advanceTimersByTimeAsync(25_000);
const result = await promise;
vi.useRealTimers();
expect(result.text).toBeTruthy();
expect(result.cached).toBe(true);
expect(result.updatedAt).toBeTruthy();
const createCalls = vi.mocked(prisma.auditLog.create).mock.calls;
expect(createCalls.length).toBe(1);
const details = (createCalls[0][0] as { data: { details: string } }).data
.details;
const parsed = JSON.parse(details) as {
dateKey?: string;
text?: string;
timeout?: boolean;
model?: string;
};
expect(parsed.text).toBeTruthy();
expect(parsed.timeout).toBe(true);
expect(parsed.model).toBe("timeout-stub");
expect(parsed.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/);
});
it("subsequent mounts short-circuit at the cache lookup and skip the race", async () => {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
}).formatToParts(new Date());
const y = parts.find((p) => p.type === "year")!.value;
const m = parts.find((p) => p.type === "month")!.value;
const d = parts.find((p) => p.type === "day")!.value;
const todayKey = `${y}-${m}-${d}`;
const stubRow = {
createdAt: new Date(),
details: JSON.stringify({
dateKey: todayKey,
locale: "en",
text: "Mood fallback text…",
timeout: true,
}),
};
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(stubRow as never);
const providerCall = vi.fn();
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: providerCall,
} as never);
const result = await generateMoodStatusForUser("user-1", { locale: "en" });
expect(providerCall).not.toHaveBeenCalled();
expect(result.text).toBe("Mood fallback text…");
expect(result.cached).toBe(true);
expect(result.updatedAt).toBe(stubRow.createdAt.toISOString());
expect(prisma.auditLog.create).not.toHaveBeenCalled();
});
});
describe("generateMoodStatusForUser — token-leak hardening (v1.4.27 F16)", () => {
it("strips metric: tokens out of the cached text before persisting", async () => {
const t = new Date();
@@ -7,6 +7,13 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
* cached-style envelope so the InsightStatusCard renders the
* fallback text instead of spinning behind React-Query's default
* retries.
*
* v1.4.41 — the timeout branch now persists a sentinel keyed to
* today (see `persistTimeoutStubAndReturn`) so subsequent mounts
* short-circuit at the cache lookup. The persisted row carries
* `timeout: true` + `model: "timeout-stub"` so the daily pre-warm
* job can recognise and overwrite the stub instead of treating it
* as a real assessment.
*/
vi.mock("@/lib/db", () => ({
@@ -51,6 +58,9 @@ describe("generatePulseStatusForUser — provider timeout fallback", () => {
{ value: 72, measuredAt: new Date() },
] as never);
vi.mocked(prisma.moodEntry.findMany).mockResolvedValue([] as never);
vi.mocked(prisma.auditLog.create).mockResolvedValue({
createdAt: new Date(),
} as never);
// Provider stalls indefinitely.
vi.mocked(resolveProvider).mockResolvedValue({
@@ -69,9 +79,19 @@ describe("generatePulseStatusForUser — provider timeout fallback", () => {
expect(result.cached).toBe(true);
expect(typeof result.text).toBe("string");
expect(result.text?.length ?? 0).toBeGreaterThan(0);
// The route must NOT persist a fallback into the audit cache —
// a transient provider stall should not poison tomorrow's cache
// hit.
expect(prisma.auditLog.create).not.toHaveBeenCalled();
// v1.4.41 — the timeout branch now persists a sentinel row so
// subsequent mounts short-circuit at the cache lookup. The
// daily pre-warm worker overwrites the stub by detecting
// `timeout: true` + `model: "timeout-stub"`.
expect(prisma.auditLog.create).toHaveBeenCalledTimes(1);
const createArg = vi.mocked(prisma.auditLog.create).mock.calls[0][0] as {
data: { details: string };
};
const parsed = JSON.parse(createArg.data.details) as {
timeout?: boolean;
model?: string;
};
expect(parsed.timeout).toBe(true);
expect(parsed.model).toBe("timeout-stub");
});
});
@@ -82,6 +82,53 @@ describe("generatePulseStatusForUser — v1.4.6 bucketed payload", () => {
});
});
describe("generatePulseStatusForUser — v1.4.41 timeout-stub short-circuit", () => {
// The persist-on-timeout path is covered by
// `pulse-status-timeout.test.ts`. This block pins the second-mount
// behaviour: when a sentinel row already exists for today the cache
// lookup short-circuits and the provider is never invoked.
it("subsequent mounts short-circuit at the cache lookup and skip the race", async () => {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
}).formatToParts(new Date());
const y = parts.find((p) => p.type === "year")!.value;
const m = parts.find((p) => p.type === "month")!.value;
const d = parts.find((p) => p.type === "day")!.value;
const todayKey = `${y}-${m}-${d}`;
const stubRow = {
createdAt: new Date(),
details: JSON.stringify({
dateKey: todayKey,
locale: "en",
text: "Pulse fallback text…",
timeout: true,
}),
};
vi.mocked(prisma.user.findUnique).mockResolvedValue({
dateOfBirth: null,
gender: null,
} as never);
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(stubRow as never);
const providerCall = vi.fn();
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: providerCall,
} as never);
const result = await generatePulseStatusForUser("user-1", { locale: "en" });
expect(providerCall).not.toHaveBeenCalled();
expect(result.text).toBe("Pulse fallback text…");
expect(result.cached).toBe(true);
expect(result.updatedAt).toBe(stubRow.createdAt.toISOString());
expect(prisma.auditLog.create).not.toHaveBeenCalled();
});
});
describe("generatePulseStatusForUser — token-leak hardening (v1.4.27 F16)", () => {
it("strips metric: tokens out of the cached text before persisting", async () => {
vi.mocked(prisma.user.findUnique).mockResolvedValue({
@@ -82,6 +82,94 @@ describe("generateWeightStatusForUser — v1.4.6 bucketed payload", () => {
});
});
describe("generateWeightStatusForUser — v1.4.41 timeout-stub persistence", () => {
it("persists a sentinel row keyed to today when the provider times out", async () => {
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(null);
vi.mocked(prisma.measurement.findMany).mockResolvedValue([
{ type: "WEIGHT", value: 82, measuredAt: new Date() },
] as never);
vi.mocked(prisma.moodEntry.findMany).mockResolvedValue([] as never);
vi.mocked(prisma.auditLog.create).mockResolvedValue({
createdAt: new Date(),
} as never);
// Provider stalls indefinitely so the timeout race wins.
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: vi.fn(
() =>
new Promise(() => {
/* never resolves */
}),
),
} as never);
vi.useFakeTimers();
const promise = generateWeightStatusForUser("user-1", { locale: "en" });
await vi.advanceTimersByTimeAsync(25_000);
const result = await promise;
vi.useRealTimers();
expect(result.text).toBeTruthy();
expect(result.cached).toBe(true);
expect(result.updatedAt).toBeTruthy();
const createCalls = vi.mocked(prisma.auditLog.create).mock.calls;
expect(createCalls.length).toBe(1);
const details = (createCalls[0][0] as { data: { details: string } }).data
.details;
const parsed = JSON.parse(details) as {
dateKey?: string;
text?: string;
timeout?: boolean;
model?: string;
};
expect(parsed.text).toBeTruthy();
expect(parsed.timeout).toBe(true);
expect(parsed.model).toBe("timeout-stub");
expect(parsed.dateKey).toMatch(/^\d{4}-\d{2}-\d{2}$/);
});
it("subsequent mounts short-circuit at the cache lookup and skip the race", async () => {
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
}).formatToParts(new Date());
const y = parts.find((p) => p.type === "year")!.value;
const m = parts.find((p) => p.type === "month")!.value;
const d = parts.find((p) => p.type === "day")!.value;
const todayKey = `${y}-${m}-${d}`;
const stubRow = {
createdAt: new Date(),
details: JSON.stringify({
dateKey: todayKey,
locale: "en",
text: "Weight is a directional metric…",
timeout: true,
}),
};
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(stubRow as never);
const providerCall = vi.fn();
vi.mocked(resolveProvider).mockResolvedValue({
type: "anthropic",
generateCompletion: providerCall,
} as never);
const result = await generateWeightStatusForUser("user-1", {
locale: "en",
});
expect(providerCall).not.toHaveBeenCalled();
expect(result.text).toBe("Weight is a directional metric…");
expect(result.cached).toBe(true);
expect(result.updatedAt).toBe(stubRow.createdAt.toISOString());
expect(prisma.auditLog.create).not.toHaveBeenCalled();
});
});
describe("generateWeightStatusForUser — token-leak hardening (v1.4.27 F16)", () => {
it("strips metric: tokens out of the cached text before persisting", async () => {
vi.mocked(prisma.auditLog.findFirst).mockResolvedValue(null);
+13 -6
View File
@@ -28,6 +28,7 @@ import {
withTimeout,
STATUS_PROVIDER_TIMEOUT_MS,
} from "@/lib/insights/with-timeout";
import { persistTimeoutStubAndReturn } from "@/lib/insights/persist-timeout-stub";
import { annotate } from "@/lib/logging/context";
const BERLIN_DAY_FORMATTER = new Intl.DateTimeFormat("en-US", {
@@ -542,12 +543,18 @@ export async function generateBloodPressureStatusForUser(
);
if (raced.timedOut || raced.value === null) {
return {
hasProvider: true,
text: getNoKeyBloodPressureStatusText(locale),
cached: true,
updatedAt: null,
};
// v1.4.37 — persist a sentinel row keyed to today so the next
// mount short-circuits at the cache lookup above instead of
// re-racing the same 20 s provider call on every cold visit.
// See `persistTimeoutStubAndReturn` for the full rationale.
return persistTimeoutStubAndReturn({
userId,
cacheAction,
todayKey,
locale,
providerType: provider.type,
stubText: getNoKeyBloodPressureStatusText(locale),
});
}
const result = raced.value;
+13 -6
View File
@@ -18,6 +18,7 @@ import {
withTimeout,
STATUS_PROVIDER_TIMEOUT_MS,
} from "@/lib/insights/with-timeout";
import { persistTimeoutStubAndReturn } from "@/lib/insights/persist-timeout-stub";
import { annotate } from "@/lib/logging/context";
const BERLIN_DAY_FORMATTER = new Intl.DateTimeFormat("en-US", {
@@ -390,12 +391,18 @@ export async function generateGeneralStatusForUser(
);
if (raced.timedOut || raced.value === null) {
return {
hasProvider: true,
text: getNoKeyGeneralStatusText(locale),
cached: true,
updatedAt: null,
};
// v1.4.37 — persist a sentinel row keyed to today so the next
// mount short-circuits at the cache lookup above instead of
// re-racing the same 20 s provider call on every cold visit.
// See `persistTimeoutStubAndReturn` for the full rationale.
return persistTimeoutStubAndReturn({
userId,
cacheAction,
todayKey,
locale,
providerType: provider.type,
stubText: getNoKeyGeneralStatusText(locale),
});
}
const result = raced.value;
@@ -18,6 +18,7 @@ import {
withTimeout,
STATUS_PROVIDER_TIMEOUT_MS,
} from "@/lib/insights/with-timeout";
import { persistTimeoutStubAndReturn } from "@/lib/insights/persist-timeout-stub";
import { annotate } from "@/lib/logging/context";
// 360 daily days + 24 monthly windows ≈ 1080 days of intake history.
@@ -104,6 +105,8 @@ export async function generateMedicationComplianceStatusForUser(
const parsed = JSON.parse(latestCache.details) as {
dateKey?: string;
summary?: string;
text?: string;
timeout?: boolean;
medications?: MedicationSummaryItem[];
};
@@ -126,6 +129,25 @@ export async function generateMedicationComplianceStatusForUser(
updatedAt: latestCache.createdAt.toISOString(),
};
}
// v1.4.41 — timeout-stub rows persist `text` + `timeout: true`
// instead of the standard `summary` + `medications` envelope.
// Recognise the stub so the second mount short-circuits at the
// cache lookup instead of re-racing the provider call.
if (
parsed.dateKey === todayKey &&
parsed.timeout === true &&
typeof parsed.text === "string" &&
parsed.text.trim().length > 0
) {
return {
hasProvider: true,
summary: parsed.text,
medications: [],
cached: true,
updatedAt: latestCache.createdAt.toISOString(),
};
}
} catch {
// ignore invalid cache payload
}
@@ -332,12 +354,28 @@ export async function generateMedicationComplianceStatusForUser(
);
if (raced.timedOut || raced.value === null) {
// v1.4.37 — persist a sentinel row keyed to today so the next
// mount short-circuits at the cache lookup above instead of
// re-racing the same 20 s provider call on every cold visit.
// See `persistTimeoutStubAndReturn` for the full rationale.
// This route returns a richer shape (`summary`, `medications`)
// than the standard `text` envelope, so the helper is called
// for its persist side-effect and the route maps `summary` ←
// `text` on the way out.
const stubReturn = await persistTimeoutStubAndReturn({
userId,
cacheAction,
todayKey,
locale,
providerType: provider.type,
stubText: getNoKeyMedicationComplianceStatusText(locale),
});
return {
hasProvider: true,
summary: getNoKeyMedicationComplianceStatusText(locale),
summary: stubReturn.text,
medications: [],
cached: true,
updatedAt: null,
updatedAt: stubReturn.updatedAt,
};
}
+13 -6
View File
@@ -20,6 +20,7 @@ import {
withTimeout,
STATUS_PROVIDER_TIMEOUT_MS,
} from "@/lib/insights/with-timeout";
import { persistTimeoutStubAndReturn } from "@/lib/insights/persist-timeout-stub";
import { annotate } from "@/lib/logging/context";
const BERLIN_DAY_FORMATTER = new Intl.DateTimeFormat("en-US", {
@@ -447,12 +448,18 @@ export async function generateMoodStatusForUser(
);
if (raced.timedOut || raced.value === null) {
return {
hasProvider: true,
text: getNoKeyMoodStatusText(locale),
cached: true,
updatedAt: null,
};
// v1.4.37 — persist a sentinel row keyed to today so the next
// mount short-circuits at the cache lookup above instead of
// re-racing the same 20 s provider call on every cold visit.
// See `persistTimeoutStubAndReturn` for the full rationale.
return persistTimeoutStubAndReturn({
userId,
cacheAction,
todayKey,
locale,
providerType: provider.type,
stubText: getNoKeyMoodStatusText(locale),
});
}
const result = raced.value;
+17 -8
View File
@@ -19,6 +19,7 @@ import {
withTimeout,
STATUS_PROVIDER_TIMEOUT_MS,
} from "@/lib/insights/with-timeout";
import { persistTimeoutStubAndReturn } from "@/lib/insights/persist-timeout-stub";
import { annotate } from "@/lib/logging/context";
const BERLIN_DAY_FORMATTER = new Intl.DateTimeFormat("en-US", {
@@ -315,8 +316,10 @@ export async function generatePulseStatusForUser(
// (or upstream failure) return the no-key fallback text in a
// cached-style envelope so the status card renders deterministically
// instead of spinning behind React-Query's default retry ladder.
// The fallback is NOT persisted to the audit cache — a transient
// upstream stall would otherwise poison tomorrow's hit.
// v1.4.41 — the timeout branch now persists a sentinel keyed to
// today (see `persistTimeoutStubAndReturn`) so subsequent mounts
// short-circuit at the cache lookup instead of re-racing the
// upstream call. The daily pre-warm job overwrites the stub.
const raced = await withTimeout(
() =>
provider.generateCompletion({
@@ -335,12 +338,18 @@ export async function generatePulseStatusForUser(
);
if (raced.timedOut || raced.value === null) {
return {
hasProvider: true,
text: getNoKeyPulseStatusText(locale),
cached: true,
updatedAt: null,
};
// v1.4.37 — persist a sentinel row keyed to today so the next
// mount short-circuits at the cache lookup above instead of
// re-racing the same 20 s provider call on every cold visit.
// See `persistTimeoutStubAndReturn` for the full rationale.
return persistTimeoutStubAndReturn({
userId,
cacheAction,
todayKey,
locale,
providerType: provider.type,
stubText: getNoKeyPulseStatusText(locale),
});
}
const result = raced.value;
+13 -6
View File
@@ -23,6 +23,7 @@ import {
withTimeout,
STATUS_PROVIDER_TIMEOUT_MS,
} from "@/lib/insights/with-timeout";
import { persistTimeoutStubAndReturn } from "@/lib/insights/persist-timeout-stub";
import { annotate } from "@/lib/logging/context";
const BERLIN_DAY_FORMATTER = new Intl.DateTimeFormat("en-US", {
@@ -440,12 +441,18 @@ export async function generateWeightStatusForUser(
);
if (raced.timedOut || raced.value === null) {
return {
hasProvider: true,
text: getNoKeyWeightStatusText(locale),
cached: true,
updatedAt: null,
};
// v1.4.37 — persist a sentinel row keyed to today so the next
// mount short-circuits at the cache lookup above instead of
// re-racing the same 20 s provider call on every cold visit.
// See `persistTimeoutStubAndReturn` for the full rationale.
return persistTimeoutStubAndReturn({
userId,
cacheAction,
todayKey,
locale,
providerType: provider.type,
stubText: getNoKeyWeightStatusText(locale),
});
}
const result = raced.value;