refactor(fhir,insights): derive FHIR and correlation tables from the registry

Replace the hand-maintained MEASUREMENT_LOINC literal and the BUCKETED_TYPES
correlation list with signal-registry projections. Both reads are byte-identical
to the previous tables (guarded by the registry-invariant test), so there is no
behaviour change — only one source of truth for each.
This commit is contained in:
Marc-André Bombeck committed 2026-06-28 15:12:09 +02:00
1 parent 794d598150
commit dfce3e2710
2 files changed
+9 -241

No files matched your search

+3 -232
View File
@@ -9,6 +9,7 @@
* 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";
@@ -39,238 +40,8 @@ export interface LoincMapping {
* panel). Glucose is handled per-context by the builder using
* `GLUCOSE_LOINC`.
*/
export const MEASUREMENT_LOINC: Record<string, LoincMapping> = {
WEIGHT: {
loinc: "29463-7",
display: "Body weight",
unit: "kg",
category: "vital-signs",
},
BODY_MASS_INDEX: {
loinc: "39156-5",
display: "Body mass index (BMI) [Ratio]",
unit: "kg/m2",
category: "vital-signs",
},
PULSE: {
loinc: "8867-4",
display: "Heart rate",
unit: "/min",
category: "vital-signs",
},
RESTING_HEART_RATE: {
loinc: "40443-4",
display: "Heart rate --resting",
unit: "/min",
category: "vital-signs",
},
RESPIRATORY_RATE: {
loinc: "9279-1",
display: "Respiratory rate",
unit: "/min",
category: "vital-signs",
},
BODY_TEMPERATURE: {
loinc: "8310-5",
display: "Body temperature",
unit: "Cel",
category: "vital-signs",
},
OXYGEN_SATURATION: {
loinc: "59408-5",
display: "Oxygen saturation in Arterial blood by Pulse oximetry",
unit: "%",
category: "vital-signs",
},
BODY_FAT: {
loinc: "41982-0",
display: "Percentage of body fat Measured",
unit: "%",
category: "vital-signs",
},
VO2_MAX: {
loinc: "96402-2",
display: "Oxygen consumption maximum during exercise",
unit: "mL/min/kg",
category: "vital-signs",
},
HEART_RATE_VARIABILITY: {
loinc: "80404-7",
display: "R-R interval.standard deviation (Heart rate variability)",
unit: "ms",
category: "vital-signs",
},
ACTIVITY_STEPS: {
loinc: "41950-7",
display: "Number of steps in 24 hour Measured",
unit: "{steps}",
category: "activity",
},
// Stored canonically in MINUTES; the builder converts the emitted value to
// hours so it matches the UCUM `h` unit (and the iOS table). PDF unaffected.
SLEEP_DURATION: {
loinc: "93832-4",
display: "Sleep duration",
unit: "h",
category: "activity",
},
ACTIVE_ENERGY_BURNED: {
loinc: "41981-2",
display: "Calories burned",
unit: "kcal",
category: "activity",
},
WALKING_SPEED: {
loinc: "41957-2",
display: "Gait speed [Velocity] Measured",
unit: "m/s",
category: "vital-signs",
},
WALKING_ASYMMETRY: {
loinc: "91557-1",
display: "Walking asymmetry percentage",
unit: "%",
category: "vital-signs",
},
WALKING_STEP_LENGTH: {
loinc: "41955-6",
display: "Step length Measured",
unit: "m",
category: "vital-signs",
},
// Body-composition family with iOS-locked LOINC codes.
TOTAL_BODY_WATER: {
loinc: "73704-9",
display: "Body water by Bioelectrical impedance analysis",
unit: "kg",
category: "vital-signs",
},
BONE_MASS: {
loinc: "73708-0",
display: "Bone mineral content by DXA",
unit: "kg",
category: "vital-signs",
},
// HK-placeholder codes — no published LOINC term. Both iOS and the server
// emit the HealthKit identifier STRING as the `code` under the shared
// `HEALTHKIT_CODESYSTEM` (NOT the LOINC namespace — a non-LOINC code under
// http://loinc.org fails FHIR conformance). The `loinc` field below carries
// the placeholder string verbatim; the builder routes any
// `HKQuantityTypeIdentifier…` code onto the custom system.
WALKING_DOUBLE_SUPPORT: {
loinc: "HKQuantityTypeIdentifierWalkingDoubleSupportPercentage",
display: "Walking double support percentage",
unit: "%",
category: "vital-signs",
},
AUDIO_EXPOSURE_ENV: {
loinc: "HKQuantityTypeIdentifierEnvironmentalAudioExposure",
display: "Environmental audio exposure",
unit: "dB[A]",
category: "vital-signs",
},
AUDIO_EXPOSURE_HEADPHONE: {
loinc: "HKQuantityTypeIdentifierHeadphoneAudioExposure",
display: "Headphone audio exposure",
unit: "dB[A]",
category: "vital-signs",
},
FLIGHTS_CLIMBED: {
loinc: "HKQuantityTypeIdentifierFlightsClimbed",
display: "Flights climbed",
unit: "{flights}",
category: "activity",
},
WALKING_RUNNING_DISTANCE: {
loinc: "HKQuantityTypeIdentifierDistanceWalkingRunning",
display: "Distance walking/running",
unit: "m",
category: "activity",
},
TIME_IN_DAYLIGHT: {
loinc: "HKQuantityTypeIdentifierTimeInDaylight",
display: "Time in daylight",
unit: "min",
category: "activity",
},
MUSCLE_MASS: {
loinc: null,
display: "Muscle mass",
unit: "kg",
category: "vital-signs",
},
FAT_MASS: {
loinc: null,
display: "Fat mass",
unit: "kg",
category: "vital-signs",
},
FAT_FREE_MASS: {
loinc: null,
display: "Fat-free mass",
unit: "kg",
category: "vital-signs",
},
LEAN_BODY_MASS: {
loinc: null,
display: "Lean body mass",
unit: "kg",
category: "vital-signs",
},
VISCERAL_FAT: {
loinc: null,
display: "Visceral fat",
unit: "1",
category: "vital-signs",
},
// ── v1.10.0 — additive HealthKit signals (WX-A) ──
// No stable LOINC term for these Apple-specific quantities, so each
// routes through the shared HEALTHKIT_CODESYSTEM with the raw
// HKQuantityTypeIdentifier… string as the code (a non-LOINC code under
// the LOINC namespace would fail FHIR conformance).
CARDIO_RECOVERY: {
loinc: "HKQuantityTypeIdentifierHeartRateRecoveryOneMinute",
display: "Cardio recovery (1-minute heart-rate recovery)",
unit: "/min",
category: "vital-signs",
},
WRIST_TEMPERATURE: {
loinc: "HKQuantityTypeIdentifierAppleSleepingWristTemperature",
display: "Sleeping wrist temperature",
unit: "Cel",
category: "vital-signs",
},
FALL_COUNT: {
loinc: "HKQuantityTypeIdentifierNumberOfTimesFallen",
display: "Number of times fallen",
unit: "{falls}",
category: "activity",
},
SIX_MINUTE_WALK_DISTANCE: {
loinc: "HKQuantityTypeIdentifierSixMinuteWalkTestDistance",
display: "Six-minute walk test distance",
unit: "m",
category: "activity",
},
STAIR_ASCENT_SPEED: {
loinc: "HKQuantityTypeIdentifierStairAscentSpeed",
display: "Stair ascent speed",
unit: "m/s",
category: "vital-signs",
},
STAIR_DESCENT_SPEED: {
loinc: "HKQuantityTypeIdentifierStairDescentSpeed",
display: "Stair descent speed",
unit: "m/s",
category: "vital-signs",
},
BREATHING_DISTURBANCES: {
loinc: "HKQuantityTypeIdentifierAppleSleepingBreathingDisturbances",
display: "Sleeping breathing disturbances",
unit: "{count}",
category: "vital-signs",
},
};
export const MEASUREMENT_LOINC: Record<string, LoincMapping> =
deriveMeasurementLoinc();
/** BP panel + component LOINC codes. */
export const BP_PANEL_LOINC = "85354-9";
+6 -9
View File
@@ -29,6 +29,7 @@ import { getMedicationCategories } from "@/lib/medication-category";
import { classifyReferenceRange } from "@/lib/labs/reference-range";
import { resolveLabFields } from "@/lib/labs/serialise";
import { readRollupBuckets } from "@/lib/rollups/measurement-rollups";
import { deriveBucketedTypes } from "@/lib/signals/adapters/correlation";
import {
ensureUserMoodRollupsFresh,
readMoodDayRollups,
@@ -464,15 +465,11 @@ const BUCKET_WINDOWS: Array<{
* block was suppressed. New `MeasurementType` enum values flow in by
* adding one row; the rollup populator already covers every type.
*/
const BUCKETED_TYPES: MeasurementType[] = [
"WEIGHT",
"BLOOD_PRESSURE_SYS",
"BLOOD_PRESSURE_DIA",
"PULSE",
"BODY_FAT",
"SLEEP_DURATION",
"ACTIVITY_STEPS",
];
// Derived from the signal registry: every signal flagged
// `surfaces.correlationEligible` projects to its DB `MeasurementType`. The list
// is a membership/iteration set (each type is read independently), so order is
// not significant; the registry-invariant test pins the set byte-for-byte.
const BUCKETED_TYPES: MeasurementType[] = deriveBucketedTypes();
function stdDev(values: number[]): number | null {
if (values.length < 2) return null;