From 03e80c1a94ea655977d25e31f452a5a9ba9efa00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 14 Oct 2026 19:49:33 +0200 Subject: [PATCH] fix(labs): drop the printed range text when an edit retires the conversion Changing the value of a converted reading cleared sourceValue and sourceUnit but kept the printed range text, which is written in the report's unit. With the unit gone every reader presented it as the marker unit's window, beside numbers in another unit. The text now goes with the conversion record (a whole-form echo of it does not bring it back); the bounds, already converted into the marker's unit, stay. --- src/app/api/labs/[id]/route.ts | 25 ++++++- .../lab-source-reference-range.test.ts | 75 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/src/app/api/labs/[id]/route.ts b/src/app/api/labs/[id]/route.ts index e8363b6e5..c828bdf1f 100644 --- a/src/app/api/labs/[id]/route.ts +++ b/src/app/api/labs/[id]/route.ts @@ -192,6 +192,9 @@ export const PUT = apiHandler( const data: Record = {}; if (d.panel !== undefined) data.panel = d.panel; if (d.analyte !== undefined) data.analyte = d.analyte; + // Set when a changed value retires a conversion whose printed range text is + // in the report's unit (see below). + let dropPrintedText = false; if (d.value !== undefined) { data.value = d.value; // A number the person typed is no longer the conversion of what the @@ -205,6 +208,18 @@ export const PUT = apiHandler( ) { data.sourceValue = null; data.sourceUnit = null; + // The printed range text belonged to the printed value and is written + // in the report's unit. With `sourceUnit` gone nothing would say so, + // and every reader would present it as the marker unit's window, so + // it goes with the record. The bounds stay: they were converted with + // the value and are already in the marker's unit. + if ( + existing.sourceUnit !== null && + existing.sourceReferenceText !== null + ) { + data.sourceReferenceText = null; + dropPrintedText = true; + } } } // v1.18.9 — qualitative edit. A row stays single-typed: editing `value` on a @@ -220,7 +235,15 @@ export const PUT = apiHandler( if (d.sourceReferenceHigh !== undefined) { data.sourceReferenceHigh = d.sourceReferenceHigh; } - if (d.sourceReferenceText !== undefined) { + // A whole-form echo of the old printed text does not bring it back once the + // value change retired it; a text that differs is a new statement and wins. + if ( + d.sourceReferenceText !== undefined && + !( + dropPrintedText && + d.sourceReferenceText === existing.sourceReferenceText + ) + ) { data.sourceReferenceText = d.sourceReferenceText; } if (d.takenAt !== undefined) data.takenAt = d.takenAt; diff --git a/tests/integration/lab-source-reference-range.test.ts b/tests/integration/lab-source-reference-range.test.ts index 57e50c028..923e72588 100644 --- a/tests/integration/lab-source-reference-range.test.ts +++ b/tests/integration/lab-source-reference-range.test.ts @@ -297,6 +297,81 @@ describe("a printed reference range, end to end", () => { expect(observations[0].valueQuantity?.unit).toBe("mg/dL"); }); + it("drops the printed text with the conversion record when the value is edited", async () => { + const prisma = getPrismaClient(); + const marker = await prisma.biomarker.create({ + data: { + userId: USER_ID, + name: "Glucose", + unit: "mg/dL", + lowerBound: 70, + upperBound: 100, + }, + }); + const PRINTED_MMOL = "3,9 - 5,5 mmol/l"; + const { POST } = await import("@/app/api/labs/route"); + const created = await POST( + postReq({ + biomarkerId: marker.id, + value: 5.6, + unit: "mmol/L", + sourceReferenceLow: 3.9, + sourceReferenceHigh: 5.5, + sourceReferenceText: PRINTED_MMOL, + takenAt: "2026-07-03T08:00:00.000Z", + }), + ); + expect(created.status).toBe(201); + const id = (await created.json()).data.id as string; + + // The person types a new number in mg/dL, and the form echoes the old + // printed text back with it. + const { PUT } = await import("@/app/api/labs/[id]/route"); + const edited = await PUT( + new NextRequest(`http://localhost/api/labs/${id}`, { + method: "PUT", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ value: 104, sourceReferenceText: PRINTED_MMOL }), + }), + { params: Promise.resolve({ id }) }, + ); + expect(edited.status).toBe(200); + + const row = await prisma.labResult.findUniqueOrThrow({ where: { id } }); + expect(row.sourceUnit).toBeNull(); + expect(row.sourceValue).toBeNull(); + expect(row.sourceReferenceText).toBeNull(); + // The bounds were converted with the value and stay, in mg/dL. + expect(row.sourceReferenceLow).toBeCloseTo(3.9 * 18.0182, 6); + expect(row.sourceReferenceHigh).toBeCloseTo(5.5 * 18.0182, 6); + + // No surface shows mmol/L text beside the mg/dL numbers. + const dto = await readDto("Glucose"); + expect(dto.converted).toBe(false); + expect(dto.sourceReferenceText).toBeNull(); + expect(dto.printedReferenceText).toBeNull(); + expect(dto.referenceOrigin).toBe("source"); + expect(dto.referenceHigh).toBeCloseTo(5.5 * 18.0182, 6); + + const { loadLabResults } = + await import("@/lib/doctor-report/clinical-records"); + const labs = await loadLabResults( + USER_ID, + new Date("2026-01-01T00:00:00.000Z"), + new Date("2026-12-31T23:59:59.000Z"), + ); + expect(labs?.[0].sourceReferenceText).toBeNull(); + + const { labObservations } = await import("@/lib/fhir/resources/labs"); + let seq = 0; + const observations = labObservations( + { labResults: labs } as Parameters[0], + () => `obs-${++seq}`, + ); + expect(observations[0].referenceRange?.[0]?.text).toBeUndefined(); + expect(observations[0].referenceRange?.[0]?.high?.unit).toBe("mg/dL"); + }); + it("keeps a window it cannot read as text and leaves the catalog in force", async () => { const prisma = getPrismaClient(); await seedMarker();