fix(labs): review a unit that was only a calc note, keep unseen category places

A document unit that held only "(calc)" read as no unit and went straight to the record; it now goes to review. Saving a category order keeps the saved keys not on screen after the shown ones. The changelog says what each fix does and no more.
This commit is contained in:
Marc-André Bombeck committed 2026-11-10 19:53:47 +01:00
1 parent 69ba8bec91
commit 4dcb041612
6 files changed
+64 -15

No files matched your search

+6 -1
View File
@@ -49,6 +49,7 @@ import {
type ModuleGroupsOpen,
type ModuleSortDir,
} from "@/lib/module-list-prefs";
import { mergeCategoryOrder } from "@/lib/settings/module-list-layout";
import { BiomarkerManager } from "./biomarker-manager";
import type {
@@ -134,7 +135,11 @@ export function LabsSettings() {
/>
{prefs.groupBy === "category" ? (
<LabsCategoryOrderCard onChange={setCategoryOrder} />
<LabsCategoryOrderCard
onChange={(shown) =>
setCategoryOrder(mergeCategoryOrder(shown, prefs.categoryOrder))
}
/>
) : null}
{manual ? (
@@ -221,6 +221,9 @@ describe("runInboundExtraction — stated-status-only mapping", () => {
expect(
result.facts.map((f) => (f.data as ObservationFactData).unit),
).toEqual(["g/dL", null]);
// A unit that was only the note goes to review: no unit is a choice a
// person makes, not one a read makes for them.
expect(result.facts.map((f) => f.needsReview)).toEqual([false, true]);
});
it("stores the document's real span, not the model's echo of it", async () => {
+7 -1
View File
@@ -399,9 +399,15 @@ export async function runInboundExtraction(
// A unit of "1" is the user's explicit no-unit choice. A read that
// states it itself goes to review rather than straight to the record,
// so a dropped unit never passes as a dimensionless value.
// So does a unit column that held only a "(calc)" note: it reads as no
// unit, and a person decides whether the value really has none.
needsReview:
confidence < INBOUND_CONFIDENCE_FLOOR ||
(raw.type === "OBSERVATION" && isDimensionlessUnit(raw.unit)),
(raw.type === "OBSERVATION" &&
(isDimensionlessUnit(raw.unit) ||
(raw.unit !== null &&
raw.unit.trim() !== "" &&
observationUnit(raw.unit) === null))),
data,
provenance: {
sourceText: anchor.sourceText,
@@ -6,6 +6,7 @@ import {
normaliseModuleListOrder,
resolveModuleListLayout,
storedModuleListLayout,
mergeCategoryOrder,
} from "../module-list-layout";
describe("resolveModuleListLayout", () => {
@@ -143,3 +144,15 @@ describe("storedModuleListLayout", () => {
).toEqual({ labs: { sortDir: "manual" } });
});
});
describe("mergeCategoryOrder", () => {
it("keeps the place of a saved category that is not on screen", () => {
expect(
mergeCategoryOrder(["lipids", "liver"], ["liver", "thyroid", "lipids"]),
).toEqual(["lipids", "liver", "thyroid"]);
});
it("is the shown order when nothing else was saved", () => {
expect(mergeCategoryOrder(["b", "a"], [])).toEqual(["b", "a"]);
});
});
+18
View File
@@ -62,6 +62,24 @@ export const MODULE_LIST_ID_MAX = 64;
*/
export const MODULE_LIST_CATEGORY_ORDER_MAX = 200;
/**
* The category order to save after a move: the categories on screen in
* their new order, then every saved key that is not on screen now, in its
* saved order. A category without readings at the moment (or one of another
* record a guardian arranged) keeps its place in the list instead of being
* dropped by a move that never showed it.
*/
export function mergeCategoryOrder(
shown: readonly string[],
saved: readonly string[],
): string[] {
const onScreen = new Set(shown);
return [...shown, ...saved.filter((key) => !onScreen.has(key))].slice(
0,
MODULE_LIST_CATEGORY_ORDER_MAX,
);
}
export interface ModuleListPrefs {
view: ModuleListView;
order: string[];