fix(labs): stop projecting lab trends from stale or short series

A marker whose newest reading is more than a year old is now reported
as of that reading (projection "stale"), with no exit date and no
already-outside verdict. The projection horizon is capped at the span
the readings cover, so a line is never carried further ahead than the
data reaches back. The detail read keeps the newest 2000 readings
rather than the oldest.
This commit is contained in:
Marc-André Bombeck committed 2026-10-21 22:40:43 +02:00
1 parent 5f563623d7
commit e482f76508
16 files changed
+165 -30

No files matched your search

@@ -20,6 +20,7 @@ const OK: BiomarkerTrendDto = {
pValue: 0.02,
fittedNow: 130,
asOf: "2026-10-21",
horizonYears: 3,
projection: "exits",
exitDate: "2027-11-02",
exitBound: "high",
@@ -71,4 +72,25 @@ describe("<LabTrendCard>", () => {
).toBe("");
expect(render(undefined)).toBe("");
});
it("words a stale trend as of its newest reading, and a span-bound horizon", () => {
const stale = render({
...OK,
asOf: "2026-01-10",
projection: "stale",
exitDate: null,
exitBound: null,
});
expect(stale).toContain('data-projection="stale"');
expect(stale).toContain("more than a year ago");
expect(stale).not.toContain("forecast");
const beyond = render({
...OK,
horizonYears: 1.5,
projection: "beyond_horizon",
exitDate: null,
exitBound: null,
});
expect(beyond).toContain("for more than 1.5 years");
});
});
+9 -1
View File
@@ -48,7 +48,15 @@ export function LabTrendCard({
})
: trend.projection === "no_band"
? null
: t(`labs.trend.projection.${trend.projection}`);
: trend.projection === "stale"
? t("labs.trend.projection.stale", {
date: fmt.dateShortSmartCalendar(trend.lastAt),
})
: trend.projection === "beyond_horizon"
? t("labs.trend.projection.beyond_horizon", {
years: fmt.number(trend.horizonYears, 1),
})
: t(`labs.trend.projection.${trend.projection}`);
return (
<Card data-slot="lab-trend">
+3 -1
View File
@@ -125,13 +125,15 @@ export type BiomarkerTrendDto =
pValue: number;
fittedNow: number;
asOf: string;
horizonYears: number;
projection:
| "exits"
| "beyond_horizon"
| "no_exit"
| "already_outside"
| "no_clear_trend"
| "no_band";
| "no_band"
| "stale";
exitDate: string | null;
exitBound: "low" | "high" | null;
}