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,