From 126bab8c002fc3a91e45cdd61dc6e948c219d7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 7 Nov 2026 01:04:26 +0100 Subject: [PATCH] fix(labs): preview a CSV text row with the window the write keeps The dry run showed the printed range beside a text result, which the write stores on the new marker and not on the reading. The preview and the write now read the reading's window from one helper, and the placeholder marker of a dry run takes its bounds through the same rule as a real mint. --- src/lib/import/labs-csv.ts | 25 ++++++++++++----- .../lab-text-marker-keeps-range.test.ts | 27 ++++++++++++++++++- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/lib/import/labs-csv.ts b/src/lib/import/labs-csv.ts index 0a0bcecff..f4cef611a 100644 --- a/src/lib/import/labs-csv.ts +++ b/src/lib/import/labs-csv.ts @@ -39,6 +39,7 @@ import { type MarkerIndex, } from "@/lib/labs/biomarker-resolve"; import { resolveOrMintBiomarker } from "@/lib/labs/biomarker-store"; +import { mintedBounds } from "@/lib/labs/minted-bounds"; import { parseReferenceRange } from "@/lib/labs/parse-reference-range"; import { readingInMarkerUnit } from "@/lib/labs/reading-conversion"; import { @@ -255,7 +256,21 @@ interface Planned { marker: IndexedMarker | null; } +/** + * The window the write keeps on the reading: the printed one for a number, none + * for a text result (its range seeds a new marker, not the reading). + */ +function readingWindow(row: LabCsvRow): { + low: number | null; + high: number | null; +} { + return row.valueText === null + ? { low: row.sourceReferenceLow, high: row.sourceReferenceHigh } + : { low: null, high: null }; +} + function displayRow(row: LabCsvRow, marker: string) { + const window = readingWindow(row); return { line: row.line, marker, @@ -263,8 +278,8 @@ function displayRow(row: LabCsvRow, marker: string) { valueText: row.valueText, unit: row.unit, takenAt: row.takenAt.toISOString(), - referenceLow: row.sourceReferenceLow, - referenceHigh: row.sourceReferenceHigh, + referenceLow: window.low, + referenceHigh: window.high, panel: row.panel, note: row.note !== null, }; @@ -346,8 +361,7 @@ export async function importLabsCsv(args: { id: `pending:${pendingId++}`, name: row.analyte, unit, - lowerBound: row.sourceReferenceLow, - upperBound: row.sourceReferenceHigh, + ...mintedBounds(row.sourceReferenceLow, row.sourceReferenceHigh), panel: row.panel, analyteKey: matchAnalyteKey({ name: row.analyte, unit }), hidden: false, @@ -484,8 +498,7 @@ export async function importLabsCsv(args: { if (!marker) created.add(row.analyte); let storedValue = row.value; - let storedLow = row.valueText === null ? row.sourceReferenceLow : null; - let storedHigh = row.valueText === null ? row.sourceReferenceHigh : null; + let { low: storedLow, high: storedHigh } = readingWindow(row); let convertedFrom: { sourceValue: number; sourceUnit: string } | null = null; if (row.value !== null) { diff --git a/tests/integration/lab-text-marker-keeps-range.test.ts b/tests/integration/lab-text-marker-keeps-range.test.ts index edf95dbcb..70183d960 100644 --- a/tests/integration/lab-text-marker-keeps-range.test.ts +++ b/tests/integration/lab-text-marker-keeps-range.test.ts @@ -368,15 +368,40 @@ describe("a text marker's range and the first unit it adopts", () => { // --- CSV import ------------------------------------------------------------- describe("a CSV text result", () => { - const query = () => + const query = (dryRun = false) => labsCsvQuerySchema.parse({ analyte: "0", value: "1", unit: "2", date: "3", referenceRange: "4", + dryRun: dryRun ? "1" : "0", }); + it("previews a text row the way it writes it", async () => { + const csv = [ + "Analyte;Value;Unit;Date;Range", + "BUN/Creatinine ratio;SEE NOTE;;2026-09-01;10 - 20", + "Sodium;140;mmol/L;2026-09-01;135 - 145", + ].join("\n"); + const preview = await importLabsCsv({ + userId: USER, + tz: "UTC", + stream: textToStream(csv), + maxBytes: 1_000_000, + query: query(true), + }); + // The text reading stores no window of its own; the number keeps its. + expect(preview.preview).toMatchObject([ + { + marker: "BUN/Creatinine ratio", + referenceLow: null, + referenceHigh: null, + }, + { marker: "Sodium", referenceLow: 135, referenceHigh: 145 }, + ]); + }); + it("gives a new marker a numeric range and leaves a text range out", async () => { const result = await importLabsCsv({ userId: USER,