Files
HealthLog/src/lib/labs/format-value.ts
T
Marc-André Bombeck a5b3715a46 feat(labs): record qualitative lab results
Add first-class qualitative lab findings (positive / negative / borderline
serology) alongside the existing numeric path. A LabResult is now EITHER a
numeric reading (value + unit, optional reference bounds) OR a qualitative one
(valueText, e.g. "negativ"), never both, never neither.

- schema: make `value` nullable, add `value_text`; migration 0181 is additive
  (DROP NOT NULL + ADD COLUMN), every existing numeric row is untouched.
- validation: numeric-XOR-qualitative refine on create + update; unit/bounds
  apply to the numeric path only; valueText capped at 120 chars. The single
  resource PUT rejects a cross-type edit.
- serialise: carry valueText through both DTOs; a qualitative row reports the
  neutral `unknown` range verdict (no number to classify).
- FHIR: a qualitative result emits valueCodeableConcept — SNOMED qualifier
  value for confidently-known terms (Negative 260385009, Positive 10828004,
  Not detected 260415000, Detected 260373001), text-only for borderline /
  grenzwertig and any unrecognised term. The raw text always rides .text.
- UI: a numeric/qualitative toggle in the lab form; list, history, detail,
  chart, sparkline, and the doctor-report PDF all render qualitative readings
  as their text and never plot or divide on a null value.
- i18n: new labs.form keys across all six locales.
- openapi: response carries a nullable value + valueText; regenerated.
2026-06-20 11:17:43 +02:00

31 lines
1.0 KiB
TypeScript

/**
* v1.18.1 — shared lab-value number formatting.
*
* The list card, the detail history rows, and the chart tooltip all trim a
* lab reading the same way: whole numbers render bare, fractional values keep
* up to two decimals (no trailing-zero noise). Centralised so the three
* surfaces stay byte-identical.
*/
export function formatLabValue(value: number): string {
return Number.isInteger(value)
? String(value)
: String(Number(value.toFixed(2)));
}
/**
* v1.18.9 — render a reading's display value: a qualitative row (numeric
* `value` is null) shows its `valueText` verbatim; a numeric row shows the
* formatted number plus its unit. One helper so the list card, the history
* rows, and the detail header stay consistent.
*/
export function formatLabReading(reading: {
value: number | null;
valueText: string | null;
unit: string;
}): string {
if (reading.value === null) {
return reading.valueText ?? "";
}
return `${formatLabValue(reading.value)} ${reading.unit}`.trim();
}