Files
HealthLog/src/lib/fhir/loinc-map.ts
T
Marc-André Bombeck e4875db93e feat(signals): register the v1.25 clinical signals through the registry
Add the clinical-signals wave's new measurement types and wire them through
the signal registry so FHIR, correlation eligibility, ingestion, and the
measurement-list surfaces derive from one definition each:

- MeasurementType: PHQ9_SCORE, GAD7_SCORE, GRIP_STRENGTH, PAIN_NRS,
  WAIST_CIRCUMFERENCE, WAIST_TO_HEIGHT (+ AssessmentInstrument enum and the
  MentalHealthAssessment model that backs the screener scores).
- Registry: the six measurement/score signals plus the longevity lab panel as
  kind:"biomarker" entries; the FHIR golden fixture is regenerated in lockstep.
  Grip-strength and waist-to-height carry a null LOINC (NEEDS-VERIFY) and emit a
  local text concept until a stable code is confirmed.
- FHIR: add the survey observation category for the screener / pain signals.
- Labs: extend the biomarker catalogue with ApoB, Lp(a), fasting insulin, and
  the omega-3 index; add LOINC codes for those plus GGT and hs-CRP; add a
  computed HOMA-IR helper.
- Exhaustive maps updated to keep the enum coverage, category, list-meta, and
  personal-record drift guards green.
2026-06-28 15:47:25 +02:00

109 lines
4.5 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.7.0 — HealthLog `MeasurementType` → FHIR R4 coding (LOINC + UCUM).
*
* Per the R-export spec table (§4.2). Types without a stable LOINC fall
* back to a local `text`-only `CodeableConcept` with the UCUM unit, and
* the absence of a `loinc` code documents that at the call site.
*
* BP is handled specially by the builder (panel 85354-9 with sys/dia
* components 8480-6 / 8462-4), so the two BP component types are NOT in
* this single-value map.
*/
import { deriveMeasurementLoinc } from "@/lib/signals/adapters/fhir";
export const LOINC_SYSTEM = "http://loinc.org";
export const UCUM_SYSTEM = "http://unitsofmeasure.org";
/**
* Shared custom CodeSystem for HealthKit placeholder metrics that have no
* published LOINC term. A non-LOINC code under `http://loinc.org` is a FHIR
* conformance violation, so these route here instead. Byte-aligned with the
* iOS exporter (confirmed 2026-06-01) — both clients emit the identical
* `system` + raw `HKQuantityTypeIdentifier…` `code`.
*/
export const HEALTHKIT_CODESYSTEM =
"https://healthlog.dev/fhir/CodeSystem/healthkit";
export type FhirObservationCategory =
| "vital-signs"
| "laboratory"
| "activity"
// v1.25 — patient-reported screener / questionnaire observations (PHQ-9 /
// GAD-7 totals, pain NRS). The builder already emits a `survey` category for
// the descriptive wellness composites; `categoryConcept` takes any string.
| "survey";
export interface LoincMapping {
/** LOINC code, or null when no stable LOINC applies (local text fallback). */
loinc: string | null;
display: string;
/** UCUM unit string (also used as the `code`). */
unit: string;
category: FhirObservationCategory;
}
/**
* Single-value measurement-type mapping. Keyed by `MeasurementType` enum
* string. BP components are intentionally absent (the builder emits a BP
* panel). Glucose is handled per-context by the builder using
* `GLUCOSE_LOINC`.
*/
export const MEASUREMENT_LOINC: Record<string, LoincMapping> =
deriveMeasurementLoinc();
/** BP panel + component LOINC codes. */
export const BP_PANEL_LOINC = "85354-9";
export const BP_SYS_LOINC = "8480-6";
export const BP_DIA_LOINC = "8462-4";
export const BP_UNIT = "mm[Hg]";
/**
* Per-context glucose LOINC, byte-aligned to the iOS table:
* - random / unspecified / bedtime → 2339-0 (Glucose in Blood)
* - fasting / beforeMeal → 1558-6 (Fasting glucose in Serum/Plasma)
* - afterMeal (POSTPRANDIAL) → 1521-4 (Glucose in Serum/Plasma 2h post meal)
*
* The server's `GlucoseContext` enum has no separate beforeMeal/afterMeal; its
* POSTPRANDIAL value is the afterMeal case and maps to 1521-4.
*/
export const GLUCOSE_LOINC: Record<string, { loinc: string; display: string }> =
{
FASTING: {
loinc: "1558-6",
display: "Fasting glucose [Mass/volume] in Serum or Plasma",
},
POSTPRANDIAL: {
loinc: "1521-4",
display: "Glucose [Mass/volume] in Serum or Plasma --2 hours post meal",
},
RANDOM: { loinc: "2339-0", display: "Glucose [Mass/volume] in Blood" },
BEDTIME: { loinc: "2339-0", display: "Glucose [Mass/volume] in Blood" },
};
/* ── Clinical glucose-panel LOINCs (v1.18.0) ───────────────────────────
* The CGM/spot-reading composite metrics the doctor report computes by the
* one literature-locked engine. Each carries the published LOINC term where
* one exists; the variability CV% has no published LOINC and is emitted as a
* survey-category text-only concept by the builder (honest about what we have).
*/
/** Time in range 70–180 mg/dL [Battelino 2019]. */
export const GLUCOSE_TIR_LOINC = "97510-2";
/** Glucose Management Indicator (GMI) [Bergenstal 2018]. */
export const GLUCOSE_GMI_LOINC = "97506-0";
/** Mean glucose over the reporting period. */
export const GLUCOSE_MEAN_LOINC = "97507-8";
/** Estimated A1C (ADAG, derived from mean glucose). */
export const GLUCOSE_EA1C_LOINC = "41995-2";
/** Medication-adherence Observation LOINC. */
export const MEDICATION_ADHERENCE_LOINC = "71799-1";
/** Mood Observation LOINC (opt-in only). */
export const MOOD_LOINC = "76542-6";
/* ── Cycle / reproductive-health LOINCs (v1.15.0, opt-in only) ──────── */
/** Last menstrual period (LMP) start date. */
export const LMP_LOINC = "8665-2";
/** Menstrual cycle length [time]. */
export const CYCLE_LENGTH_LOINC = "64700-8";
/** Length of menses (period / bleeding duration). */
export const PERIOD_LENGTH_LOINC = "64698-4";