docs(api): describe the dimensionless lab unit on every lab unit field

The unit on lab results, biomarkers, the scanned-report commit and the
document fact edit now states that "1" means the value has no unit, that
it matches only itself, and that an empty unit stays refused. The lab CSV
import description names the 1 in the unit column.

Refs #1313
This commit is contained in:
Marc-André Bombeck committed 2026-11-02 20:28:35 +01:00
1 parent f9431da64f
commit e605fe0ae8
8 files changed
+79 -14

No files matched your search

+3 -1
View File
@@ -14,6 +14,8 @@
import type { ZodOpenApiObject } from "zod-openapi";
import { z } from "zod/v4";
import { LAB_UNIT_DESCRIPTION } from "@/lib/labs/dimensionless";
import {
createBiomarkerSchema,
updateBiomarkerSchema,
@@ -54,7 +56,7 @@ const biomarkerRow = z
.object({
id: z.string(),
name: z.string(),
unit: z.string(),
unit: z.string().describe(LAB_UNIT_DESCRIPTION),
lowerBound: z.number().nullable(),
upperBound: z.number().nullable(),
panel: z.string().nullable(),
+1 -1
View File
@@ -553,7 +553,7 @@ export const importPaths: NonNullable<ZodOpenApiObject["paths"]> = {
post: {
tags: ["Import"],
summary: "Import lab results from CSV",
description: `${RECORD_IMPORT_COMMON} Each row's name resolves to the account's marker the way a scanned report's rows do (name, alias, catalog name); a new analyte mints one marker from its first row. A numeric result needs a unit; one in another unit is converted when the conversion is known and keeps the printed value beside it, otherwise the row is refused with \`unit_mismatch\`. Any other text in the value column is a qualitative result. A date without a time is stored as that calendar date. At most 20 000 data rows.`,
description: `${RECORD_IMPORT_COMMON} Each row's name resolves to the account's marker the way a scanned report's rows do (name, alias, catalog name); a new analyte mints one marker from its first row. A numeric result needs a unit: an empty unit cell is refused with \`missing_unit\`, and \`1\` (the UCUM unity) states a value without a unit, such as a ratio or INR. A result in another unit is converted when the conversion is known and keeps the printed value beside it, otherwise the row is refused with \`unit_mismatch\`. Any other text in the value column is a qualitative result. A date without a time is stored as that calendar date. At most 20 000 data rows.`,
requestParams: { query: labsCsvQuerySchema },
requestBody: {
required: true,
+3 -1
View File
@@ -11,6 +11,8 @@ import { withRelated } from "./relation-links";
import type { ZodOpenApiObject } from "zod-openapi";
import { z } from "zod/v4";
import { LAB_UNIT_DESCRIPTION } from "@/lib/labs/dimensionless";
import {
createLabResultSchema,
labOverviewQuerySchema,
@@ -71,7 +73,7 @@ const labResultRow = z
// exactly one of the two is non-null per row.
value: z.number().nullable(),
valueText: z.string().nullable(),
unit: z.string(),
unit: z.string().describe(LAB_UNIT_DESCRIPTION),
converted: z.boolean(),
sourceValue: z.number().nullable(),
sourceUnit: z.string().nullable(),
+4 -2
View File
@@ -15,6 +15,8 @@
*/
import { z } from "zod/v4";
import { LAB_UNIT_DESCRIPTION } from "@/lib/labs/dimensionless";
const requiredText = (max: number) => z.string().trim().min(1).max(max);
const lowerBound = z.number().finite().optional();
@@ -40,7 +42,7 @@ const optionalPanel = z
export const createBiomarkerSchema = z
.object({
name: requiredText(120),
unit: requiredText(40),
unit: requiredText(40).describe(LAB_UNIT_DESCRIPTION),
lowerBound,
upperBound,
context: optionalContext,
@@ -67,7 +69,7 @@ export type CreateBiomarkerInput = z.infer<typeof createBiomarkerSchema>;
export const updateBiomarkerSchema = z
.object({
name: requiredText(120).optional(),
unit: requiredText(40).optional(),
unit: requiredText(40).optional().describe(LAB_UNIT_DESCRIPTION),
lowerBound: lowerBound.or(z.null()),
upperBound: upperBound.or(z.null()),
context: optionalContext.or(z.null()),
+2 -2
View File
@@ -25,6 +25,7 @@
*/
import { z } from "zod/v4";
import { locales } from "@/lib/i18n/config";
import { LAB_UNIT_DESCRIPTION } from "@/lib/labs/dimensionless";
import {
DOCUMENT_BULK_MAX_IDS,
DOCUMENT_CONTENT_INDEX_SOURCES,
@@ -34,7 +35,6 @@ import {
type DocumentContentIndexSourceValue,
type InboundDocumentKindValue,
} from "@/lib/documents/inbound-constants";
import type { AiCapabilityState } from "@/lib/ai/capabilities/types";
import { OCR_MAX_ROWS } from "@/lib/validations/labs-ocr";
@@ -443,7 +443,7 @@ const observationEditSchema = z
codeSystem: optCode,
value: z.number().finite().nullable().optional(),
valueText: optText(300),
unit: optText(80),
unit: optText(80).describe(LAB_UNIT_DESCRIPTION),
referenceLow: z.number().finite().nullable().optional(),
referenceHigh: z.number().finite().nullable().optional(),
referenceText: optText(120),
+8 -1
View File
@@ -19,6 +19,7 @@
import type { AiCapabilityState } from "@/lib/ai/capabilities/types";
import { z } from "zod/v4";
import { LAB_UNIT_DESCRIPTION } from "@/lib/labs/dimensionless";
import type { WrittenOutcome } from "@/lib/outcome/written-outcome";
import { validateEntryInstant } from "@/lib/validations/entry-instant";
@@ -257,7 +258,13 @@ export const ocrCommitRowSchema = z
.or(z.literal("").transform(() => undefined)),
value: z.number().finite().optional(),
valueText: z.string().trim().min(1).max(OCR_VALUE_TEXT_MAX).optional(),
unit: z.string().trim().min(1).max(OCR_UNIT_MAX).optional(),
unit: z
.string()
.trim()
.min(1)
.max(OCR_UNIT_MAX)
.optional()
.describe(LAB_UNIT_DESCRIPTION),
referenceLow,
referenceHigh,
/**
+3 -2
View File
@@ -14,6 +14,7 @@
*/
import { z } from "zod/v4";
import { LAB_UNIT_DESCRIPTION } from "@/lib/labs/dimensionless";
import { SOURCE_REFERENCE_TEXT_MAX } from "@/lib/labs/parse-reference-range";
import { validateEntryInstant } from "@/lib/validations/entry-instant";
import { isoDateTime } from "@/lib/validations/datetime";
@@ -123,7 +124,7 @@ export const createLabResultSchema = z
// (`value`) OR qualitative (`valueText`), never both, never neither.
value: z.number().finite().optional(),
valueText,
unit: requiredText(40).optional(),
unit: requiredText(40).optional().describe(LAB_UNIT_DESCRIPTION),
referenceLow,
referenceHigh,
sourceReferenceLow,
@@ -208,7 +209,7 @@ export const updateLabResultSchema = z
// row's type (numeric ↔ qualitative) is intentionally not supported here —
// delete and re-add — so an omitted key leaves the row's existing type.
valueText: requiredText(120).optional(),
unit: requiredText(40).optional(),
unit: requiredText(40).optional().describe(LAB_UNIT_DESCRIPTION),
referenceLow: referenceLow.or(z.null()),
referenceHigh: referenceHigh.or(z.null()),
sourceReferenceLow: sourceReferenceLow.or(z.null()),