fix(labs): pair the panel changes by marker, not by printed name

This commit is contained in:
Marc-André Bombeck committed 2026-10-16 05:32:13 +02:00
1 parent 8ba4d1b0d0
commit 7167ba8cdc
3 files changed
+69 -5

No files matched your search

@@ -45,6 +45,7 @@ export const GET = apiHandler(async () => {
where: { userId: user.id, deletedAt: null, value: { not: null } },
select: {
analyte: true,
biomarkerId: true,
unit: true,
value: true,
referenceLow: true,
@@ -61,6 +62,7 @@ export const GET = apiHandler(async () => {
const summary = summariseLabChanges(
rows.map((r) => ({
analyte: r.analyte,
biomarkerId: r.biomarkerId,
unit: r.unit,
value: r.value as number,
referenceLow: r.referenceLow,
@@ -26,10 +26,12 @@ function row(
sourceLow?: number | null;
sourceHigh?: number | null;
sourceText?: string | null;
biomarkerId?: string | null;
} = {},
): LabChangeRow {
return {
analyte,
biomarkerId: opts.biomarkerId ?? null,
unit: opts.unit ?? "mg/dL",
value,
referenceLow: opts.low ?? null,
@@ -143,4 +145,48 @@ describe("summariseLabChanges", () => {
);
expect(s.present).toBe(true);
});
it("pairs two printed names of one marker by the marker", () => {
const out = summariseLabChanges(
[
row("HbA1c", 5.9, "2026-06-01", { unit: "%", biomarkerId: "bm-1" }),
row("Glykiertes Hämoglobin", 5.6, "2026-03-01", {
unit: "%",
biomarkerId: "bm-1",
}),
],
NOW,
);
expect(out.present).toBe(true);
expect(out.changes).toEqual([
expect.objectContaining({
analyte: "HbA1c",
latest: 5.9,
previous: 5.6,
delta: 0.3,
}),
]);
});
it("keeps two markers apart even when the printed name matches", () => {
const out = summariseLabChanges(
[
row("Glucose", 95, "2026-06-01", { biomarkerId: "fasting" }),
row("Glucose", 140, "2026-03-01", { biomarkerId: "postprandial" }),
],
NOW,
);
expect(out.present).toBe(false);
});
it("reports no delta across two units", () => {
const out = summariseLabChanges(
[
row("Glucose", 5.3, "2026-06-01", { unit: "mmol/L", biomarkerId: "g" }),
row("Glucose", 95, "2026-03-01", { unit: "mg/dL", biomarkerId: "g" }),
],
NOW,
);
expect(out.present).toBe(false);
});
});
+21 -5
View File
@@ -25,6 +25,12 @@ import {
/** A single numeric lab reading, with both windows it could be judged against. */
export interface LabChangeRow extends SourceReferenceRange {
analyte: string;
/**
* The marker the reading is filed under. Two reports print the same marker
* under different names ("HbA1c", "Glykiertes Hämoglobin"); the marker is
* what pairs them. A reading without one pairs by its printed name.
*/
biomarkerId?: string | null;
unit: string;
value: number;
referenceLow: number | null;
@@ -88,9 +94,18 @@ function dayKey(d: Date): string {
return d.toISOString().slice(0, 10);
}
/** Normalised analyte name for cross-panel matching (free-text tolerant). */
function analyteKey(analyte: string): string {
return analyte.trim().toLowerCase();
/**
* Cross-panel matching key: the marker when the reading is filed under one,
* else the normalised printed name (free-text tolerant).
*/
function analyteKey(row: LabChangeRow): string {
return row.biomarkerId
? `marker:${row.biomarkerId}`
: `name:${row.analyte.trim().toLowerCase()}`;
}
function sameUnit(a: LabChangeRow, b: LabChangeRow): boolean {
return a.unit.trim().toLowerCase() === b.unit.trim().toLowerCase();
}
const round2 = (n: number): number => Math.round(n * 100) / 100;
@@ -116,7 +131,7 @@ export function summariseLabChanges(
analytes = new Map();
byDay.set(day, analytes);
}
const key = analyteKey(row.analyte);
const key = analyteKey(row);
const existing = analytes.get(key);
if (!existing || row.takenAt.getTime() >= existing.takenAt.getTime()) {
analytes.set(key, row);
@@ -144,7 +159,8 @@ export function summariseLabChanges(
const changes: LabChange[] = [];
for (const [key, latestRow] of latest) {
const previousRow = previous.get(key);
if (!previousRow) continue;
// A delta across two units is no change, it is a conversion; skip it.
if (!previousRow || !sameUnit(latestRow, previousRow)) continue;
const delta = round2(latestRow.value - previousRow.value);
const direction: LabChange["direction"] =
delta > 0 ? "up" : delta < 0 ? "down" : "flat";