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();