Files
HealthLog/src/lib/labs/reference-range.ts
T
Marc-André Bombeck bf5deb33e2 refactor(labs): one shared reference-range classifier and range formatter
Reference-range classification + low/high range rendering was implemented three
times (validations, PDF core, lab list). Extract a single classifier and a
structure-only range formatter that takes a per-surface number formatter, so
each surface keeps its own number formatting while sharing the canonical
classification. Behaviour is unchanged per surface and now covered by one test.
2026-06-16 00:58:27 +02:00

64 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* v1.18.0 — single source of truth for lab reference-range classification
* and range-string structure.
*
* Three surfaces need the same logic: the API response (`rangeStatus`), the
* doctor-report PDF table, and the lab list card. They were drifting copies.
* The CLASSIFICATION (below / in-range / above / unknown) and the range
* STRUCTURE (`low–high` / `≤ high` / `≥ low` / empty) are shared here; the
* per-surface NUMBER FORMATTING stays a caller concern — the PDF wants a
* locale-aware one-decimal formatter, the list trims to int/2-dec — so
* `formatReferenceRange` takes a `formatNumber` callback and never imposes a
* formatter of its own.
*/
/**
* Reference-range classification. Deliberately a three-state, NEUTRAL
* verdict — the badge that renders it must stay calm and informative, NOT
* alarming (no red "out of range" tint). `"unknown"` when the lab reported
* no usable bounds.
*
* Bounds are treated as inclusive: a value exactly on the reference limit
* reads as in-range, matching how labs print "≤" / "≥" reference notation.
*/
export type ReferenceRangeStatus = "in-range" | "below" | "above" | "unknown";
export function classifyReferenceRange(
value: number,
referenceLow: number | null | undefined,
referenceHigh: number | null | undefined,
): ReferenceRangeStatus {
const hasLow = referenceLow !== null && referenceLow !== undefined;
const hasHigh = referenceHigh !== null && referenceHigh !== undefined;
if (!hasLow && !hasHigh) return "unknown";
if (hasLow && value < (referenceLow as number)) return "below";
if (hasHigh && value > (referenceHigh as number)) return "above";
return "in-range";
}
/**
* Render the reference range as text. Owns only the STRUCTURE — which of the
* four shapes (`low–high`, `≤ high`, `≥ low`, empty) applies — and defers
* every digit to the caller-supplied `formatNumber`, so each surface keeps
* its own number formatting byte-for-byte.
*
* `emptyText` is what to return when the lab reported no bounds at all; it
* defaults to the empty string (the lab list never renders the no-bounds
* case), while the PDF passes a neutral em-dash placeholder.
*/
export function formatReferenceRange(
low: number | null | undefined,
high: number | null | undefined,
formatNumber: (value: number) => string,
opts?: { emptyText?: string },
): string {
const hasLow = low !== null && low !== undefined;
const hasHigh = high !== null && high !== undefined;
if (hasLow && hasHigh) {
return `${formatNumber(low as number)}–${formatNumber(high as number)}`;
}
if (hasHigh) return `≤ ${formatNumber(high as number)}`;
if (hasLow) return `≥ ${formatNumber(low as number)}`;
return opts?.emptyText ?? "";
}