mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 05:42:19 +00:00
fix(documents): compare a confirmed lab fact's unit with the shared spelling normaliser
This commit is contained in:
1 parent
62d47d7056
commit
6c262f2099
2 files changed
+46
-4
No files matched your search
@@ -71,7 +71,7 @@ describe("commitApprovedFact — OBSERVATION unit integrity", () => {
|
||||
expect(prisma.labResult.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("commits when the stated unit matches the marker's unit (case/space-insensitive)", async () => {
|
||||
it("commits when the stated unit is the marker's unit spelled another way", async () => {
|
||||
vi.mocked(decryptFactData).mockReturnValue({
|
||||
label: "Glucose",
|
||||
value: 95,
|
||||
@@ -103,4 +103,45 @@ describe("commitApprovedFact — OBSERVATION unit integrity", () => {
|
||||
expect(takenAt.toISOString()).toBe("2025-01-01T12:00:00.000Z");
|
||||
expect(userDayKey(takenAt, "America/Los_Angeles")).toBe("2025-01-01");
|
||||
});
|
||||
|
||||
// The comparison is the shared spelling normaliser every other lab write
|
||||
// path uses, not a case fold. Mutation check: put the lower-cased equality
|
||||
// back → the first case commits (milli read as mega) and the second refuses
|
||||
// a µg/L reading the other paths accept.
|
||||
function observation(unit: string, markerUnit: string) {
|
||||
vi.mocked(decryptFactData).mockReturnValue({
|
||||
label: "TSH",
|
||||
value: 2.1,
|
||||
unit,
|
||||
referenceLow: null,
|
||||
referenceHigh: null,
|
||||
effectiveDate: "2025-01-01",
|
||||
} as never);
|
||||
vi.mocked(resolveOrMintBiomarker).mockResolvedValue({
|
||||
id: "bm-2",
|
||||
name: "TSH",
|
||||
unit: markerUnit,
|
||||
lowerBound: null,
|
||||
upperBound: null,
|
||||
panel: null,
|
||||
});
|
||||
vi.mocked(prisma.labResult.create).mockResolvedValue({
|
||||
id: "lr-2",
|
||||
} as never);
|
||||
}
|
||||
|
||||
it("refuses MIU/L against a marker in mIU/L: the capital is mega, not milli", async () => {
|
||||
observation("MIU/L", "mIU/L");
|
||||
await expect(
|
||||
commitApprovedFact("user-1", observationFact()),
|
||||
).rejects.toMatchObject({ code: "observation.unitMismatch" });
|
||||
expect(prisma.labResult.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("accepts ug/L against a marker in µg/L, as the other lab write paths do", async () => {
|
||||
observation("ug/L", "µg/L");
|
||||
await expect(
|
||||
commitApprovedFact("user-1", observationFact()),
|
||||
).resolves.toEqual({ recordType: "labResult", recordId: "lr-2" });
|
||||
});
|
||||
});
|
||||
@@ -28,6 +28,7 @@ import { invalidateUserHealthScore } from "@/lib/cache/invalidate";
|
||||
import { emitDataArrival } from "@/lib/arrivals/emit-shared";
|
||||
import { decryptFactData } from "@/lib/documents/store";
|
||||
import { resolveOrMintBiomarker } from "@/lib/labs/biomarker-store";
|
||||
import { sameLabUnit } from "@/lib/labs/unit-normalise";
|
||||
import {
|
||||
isUnreadableRange,
|
||||
parseReferenceRange,
|
||||
@@ -134,10 +135,10 @@ async function commitObservation(
|
||||
// the stated number under the catalog's unit would corrupt the reading AND
|
||||
// break the "transcribe verbatim" contract — so reject the fact and let the
|
||||
// user reconcile on the review screen, exactly like the `unitRequired` gate.
|
||||
// Spelling is compared through the shared normaliser, the same one the
|
||||
// manual and scan paths use: `ug/L` is `µg/L`, but `MIU/L` is not `mIU/L`.
|
||||
if (!isQualitative) {
|
||||
const stated = (data.unit as string).trim().toLowerCase();
|
||||
const target = biomarker.unit.trim().toLowerCase();
|
||||
if (stated !== target) {
|
||||
if (!sameLabUnit(data.unit as string, biomarker.unit)) {
|
||||
throw new FactCommitError(
|
||||
"observation.unitMismatch",
|
||||
`The stated unit (${(data.unit as string).trim()}) does not match the saved unit for ${biomarker.name} (${biomarker.unit || "none"}). Reconcile the unit before saving.`,
|
||||
|
||||
Reference in new issue
Block a user