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.
This commit is contained in:
Marc-André Bombeck committed 2026-11-07 01:04:26 +01:00
1 parent c7b3f6e4d0
commit 126bab8c00
2 files changed
+45 -7

No files matched your search

+19 -6
View File
@@ -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) {
@@ -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,