fix(insights): place a glucose day in progress against the same hours

The usual-range strip on a metric page placed the latest single reading
against a band built from whole-day means. For glucose the day mean climbs
with every meal, so at nine in the morning a fasting reading sat under a
band of whole days and the strip said "below your range" on an ordinary
morning; the MCP baseline read reported the same.

Glucose is now placed by its day mean. A day still in progress, in the
reader's own zone, is held against the earlier days cut at the local time
of today's latest reading, the comparison the health status already makes;
with fewer than seven earlier days that have readings in those hours there
is no verdict. The baseline carries `basis: "sameHours"` on the coach-read
route and the MCP read, and the strip names the range as the one for this
time of day, in all seven locales. Other metrics keep their placement.
This commit is contained in:
Marc-André Bombeck committed 2026-10-06 20:29:57 +02:00
1 parent 362818418e
commit 3e968f03a9
21 files changed
+518 -40

No files matched your search

@@ -13,9 +13,17 @@ import { NextRequest } from "next/server";
*/
const buildCoachReadStrip = vi.fn();
const zoneSeen = vi.fn();
vi.mock("@/lib/insights/derived/coach-read", () => ({
buildCoachReadStrip: (userId: string, metric: string, locale: string) =>
buildCoachReadStrip(userId, metric, locale),
buildCoachReadStrip: (
userId: string,
metric: string,
locale: string,
opts: { tz: string },
) => {
zoneSeen(opts.tz);
return buildCoachReadStrip(userId, metric, locale);
},
}));
vi.mock("@/lib/db", () => ({
@@ -58,7 +66,7 @@ import { GET } from "../route";
import { getSession } from "@/lib/auth/session";
import { checkAnalyticsReadRateLimit } from "@/lib/rate-limit";
function sessionWithLocale(locale: string | null) {
function sessionWithLocale(locale: string | null, timezone?: string) {
return {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
user: {
@@ -66,6 +74,7 @@ function sessionWithLocale(locale: string | null) {
username: "testuser",
role: "USER" as const,
locale,
...(timezone ? { timezone } : {}),
},
};
}
@@ -126,3 +135,19 @@ describe("GET /api/insights/coach-read — the reader's language", () => {
]);
});
});
describe("GET /api/insights/coach-read — the reader's day", () => {
it("passes the reader's own zone, which decides whether a day is still in progress", async () => {
vi.mocked(getSession).mockResolvedValue(
sessionWithLocale(null, "America/New_York") as unknown as Awaited<
ReturnType<typeof getSession>
>,
);
zoneSeen.mockReset();
const res = await GET(REQUEST());
expect(res.status).toBe(200);
expect(zoneSeen).toHaveBeenCalledWith("America/New_York");
});
});
+6 -1
View File
@@ -24,6 +24,7 @@ import { checkAnalyticsReadRateLimit } from "@/lib/rate-limit";
import { measurementTypeEnum } from "@/lib/validations/measurement";
import { buildCoachReadStrip } from "@/lib/insights/derived/coach-read";
import { resolveServerLocale } from "@/lib/i18n/server-locale";
import { resolveStoredTimezone } from "@/lib/tz/resolver";
import type { MeasurementType } from "@/generated/prisma/client";
export const dynamic = "force-dynamic";
@@ -66,7 +67,11 @@ export const GET = apiHandler(async (request: NextRequest) => {
// English, one line under the other.
const locale = await resolveServerLocale({ userLocale: user.locale ?? null });
const strip = await buildCoachReadStrip(user.id, metric, locale);
// The reader's zone decides which day is "today": a glucose day still in
// progress is placed against the same hours of the earlier days.
const strip = await buildCoachReadStrip(user.id, metric, locale, {
tz: await resolveStoredTimezone(user.timezone),
});
annotate({
action: { name: "insights.coach-read" },