mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
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:
1 parent
362818418e
commit
3e968f03a9
21 files changed
+518
-40
No files matched your search
@@ -0,0 +1,94 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import type { CoachReadStripData } from "@/lib/insights/derived/coach-read-shape";
|
||||
|
||||
/**
|
||||
* A glucose day still in progress is placed against the same hours of the
|
||||
* earlier days. The range is then the usual one for this time of day and the
|
||||
* figure is today's mean so far, and the sentence has to say so: printed as
|
||||
* "your usual range", a morning range would read as the whole-day one.
|
||||
*/
|
||||
|
||||
vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({
|
||||
user: { timezone: "UTC", dateOfBirth: null, gender: null },
|
||||
isAuthenticated: true,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("@/hooks/use-mounted", () => ({ useMounted: () => true }));
|
||||
|
||||
import { CoachReadStrip } from "../coach-read-strip";
|
||||
|
||||
const METRIC = "BLOOD_GLUCOSE";
|
||||
|
||||
function render(data: CoachReadStripData, locale: "en" | "de" = "en"): string {
|
||||
const client = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false, staleTime: Infinity } },
|
||||
});
|
||||
client.setQueryData(queryKeys.insightsCoachRead(METRIC, locale), data);
|
||||
return renderToStaticMarkup(
|
||||
<QueryClientProvider client={client}>
|
||||
<I18nProvider initialLocale={locale}>
|
||||
<CoachReadStrip metricType={METRIC} unit="mg/dL" />
|
||||
</I18nProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
function sameHours(
|
||||
latest: number,
|
||||
placement: "within" | "above" | "below",
|
||||
): CoachReadStripData {
|
||||
return {
|
||||
baseline: {
|
||||
low: 80,
|
||||
high: 90,
|
||||
latest,
|
||||
placement,
|
||||
sampleDays: 14,
|
||||
basis: "sameHours",
|
||||
},
|
||||
learning: false,
|
||||
driver: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe("CoachReadStrip — a glucose day in progress", () => {
|
||||
it("names the range as the one for this time of day", () => {
|
||||
const markup = render(sameHours(86, "within"));
|
||||
expect(markup).toContain("By this time of day");
|
||||
expect(markup).toContain("today so far, 86 is within that");
|
||||
expect(markup).not.toContain("Your usual range");
|
||||
});
|
||||
|
||||
it("keeps the placement", () => {
|
||||
expect(render(sameHours(95, "above"))).toContain("is above that");
|
||||
expect(render(sameHours(70, "below"))).toContain("is below that");
|
||||
});
|
||||
|
||||
it("says it in the reader's language", () => {
|
||||
expect(render(sameHours(86, "within"), "de")).toContain(
|
||||
"Bis zu dieser Tageszeit",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the whole-day sentence without the basis", () => {
|
||||
const markup = render({
|
||||
baseline: {
|
||||
low: 110,
|
||||
high: 150,
|
||||
latest: 132,
|
||||
placement: "within",
|
||||
sampleDays: 30,
|
||||
},
|
||||
learning: false,
|
||||
driver: null,
|
||||
});
|
||||
expect(markup).toContain("Your usual range is 110–150");
|
||||
});
|
||||
});
|
||||
@@ -20,7 +20,8 @@ import type { CoachReadStripData } from "@/lib/insights/derived/coach-read-shape
|
||||
* A compact two-line read rendered ABOVE the chart on each metric sub-page:
|
||||
*
|
||||
* 1. own-baseline — "Your usual range is X–Y; today's Z sits within /
|
||||
* above / below". Below the engine's 7-day history floor it reads
|
||||
* above / below". For a glucose day still in progress the range is the
|
||||
* one for this time of day and Z today's mean so far. Below the engine's 7-day history floor it reads
|
||||
* "still learning your range" — never a fabricated band.
|
||||
* 2. one lagged association — the single strongest discovered driver whose
|
||||
* outcome is this metric, stated in the engine's own never-causal voice.
|
||||
@@ -105,21 +106,31 @@ export function CoachReadStrip({
|
||||
if (data.learning || !data.baseline) {
|
||||
return t("insights.coach.readStrip.insufficient");
|
||||
}
|
||||
const { low, high, latest, placement } = data.baseline;
|
||||
const { low, high, latest, placement, basis } = data.baseline;
|
||||
// A band whose ends format to the same figure ("61–61 bpm") is a steady
|
||||
// value, not a range; say it as one.
|
||||
const steady = fmt(low) === fmt(high);
|
||||
const key = steady
|
||||
? placement === "above"
|
||||
? "insights.coach.readStrip.steadyAbove"
|
||||
: placement === "below"
|
||||
? "insights.coach.readStrip.steadyBelow"
|
||||
: "insights.coach.readStrip.steadyWithin"
|
||||
: placement === "above"
|
||||
? "insights.coach.readStrip.baselineAbove"
|
||||
: placement === "below"
|
||||
? "insights.coach.readStrip.baselineBelow"
|
||||
: "insights.coach.readStrip.baselineWithin";
|
||||
// A glucose day still in progress is held against the same hours of the
|
||||
// earlier days, so the range is the one for this time of day and today's
|
||||
// figure is its mean so far; the sentence says both.
|
||||
const key =
|
||||
basis === "sameHours"
|
||||
? placement === "above"
|
||||
? "insights.coach.readStrip.sameHoursAbove"
|
||||
: placement === "below"
|
||||
? "insights.coach.readStrip.sameHoursBelow"
|
||||
: "insights.coach.readStrip.sameHoursWithin"
|
||||
: steady
|
||||
? placement === "above"
|
||||
? "insights.coach.readStrip.steadyAbove"
|
||||
: placement === "below"
|
||||
? "insights.coach.readStrip.steadyBelow"
|
||||
: "insights.coach.readStrip.steadyWithin"
|
||||
: placement === "above"
|
||||
? "insights.coach.readStrip.baselineAbove"
|
||||
: placement === "below"
|
||||
? "insights.coach.readStrip.baselineBelow"
|
||||
: "insights.coach.readStrip.baselineWithin";
|
||||
return t(key, {
|
||||
low: fmt(low),
|
||||
high: fmt(high),
|
||||
|
||||
Reference in new issue
Block a user