feat(schema): extend MeasurementType with audio-exposure + time-in-daylight (Migration 0052)

Adds three new enum values that close the iOS-17/18 HealthKit coverage
gap before the v1.5 iOS-Swift session locks against Postgres:

  - AUDIO_EXPOSURE_ENV (dBA, environmental noise)
  - AUDIO_EXPOSURE_HEADPHONE (dBA, AirPods listening)
  - TIME_IN_DAYLIGHT (minutes/day, iOS 17+)

Wire-up touches the canonical surfaces:

  - prisma/schema.prisma: MeasurementType + comment block
  - src/lib/validations/measurement.ts: enum + unitMap + VALUE_RANGES
  - src/lib/__tests__/measurement-type-enum-coverage.test.ts: 25 -> 28
    expected types; new entries added to PDF_VITAL_EXCLUSIONS under the
    existing v1.5 clinical-layout gate
  - src/components/measurements/measurement-list-meta.ts: i18n keys,
    Volume2 / Headphones / Sun icons, palette assignments
  - src/lib/insights/chart-tokens.ts: ALLOWED_CHART_TOKENS allow-list
    plus orphan-enum stripper coverage so the LLM cannot leak raw
    upper-snake names into rendered prose
  - messages/{en,de,fr,es,it,pl}.json: typeAudioExposureEnv,
    typeAudioExposureHeadphone, typeTimeInDaylight (en/de translated;
    fr/es/it/pl carry English placeholders matching their existing
    convention for the W5d additions)

The task brief originally listed a fourth value (WORKOUT_ROUTE) as a
sentinel slot. Dropped here because workouts are first-class entities
landing in Migration 0053 with their own table (Workout + WorkoutRoute);
HealthKit also separates HKWorkout from HKQuantitySample. A reserved
sentinel that no real Measurement row will ever take would be a dead
slot for analytics, doctor-PDF allow-lists, and chart registries.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 15:46:56 +02:00
1 parent 5d5a934774
commit 41ba79b5d5
6 files changed
+105 -1

No files matched your search

@@ -30,6 +30,9 @@ import {
Thermometer,
Gauge,
Dumbbell,
Volume2,
Headphones,
Sun,
type LucideIcon,
} from "lucide-react";
@@ -61,6 +64,10 @@ export const MEASUREMENT_TYPE_LABEL_KEYS: Record<string, string> = {
PULSE_WAVE_VELOCITY: "measurements.typePulseWaveVelocity",
VASCULAR_AGE: "measurements.typeVascularAge",
VISCERAL_FAT: "measurements.typeVisceralFat",
// ── v1.4.25 W8d Apple Health server-prep ──
AUDIO_EXPOSURE_ENV: "measurements.typeAudioExposureEnv",
AUDIO_EXPOSURE_HEADPHONE: "measurements.typeAudioExposureHeadphone",
TIME_IN_DAYLIGHT: "measurements.typeTimeInDaylight",
};
export const MEASUREMENT_TYPE_ICONS: Record<string, LucideIcon> = {
@@ -96,6 +103,13 @@ export const MEASUREMENT_TYPE_ICONS: Record<string, LucideIcon> = {
PULSE_WAVE_VELOCITY: Activity,
VASCULAR_AGE: HeartPulse,
VISCERAL_FAT: Droplets,
// ── v1.4.25 W8d Apple Health server-prep ──
// Volume2 carries the ambient-audio family (concert/traffic icon
// convention), Headphones is the obvious AirPods-listening cue, and
// Sun mirrors Apple Health's own time-in-daylight tile.
AUDIO_EXPOSURE_ENV: Volume2,
AUDIO_EXPOSURE_HEADPHONE: Headphones,
TIME_IN_DAYLIGHT: Sun,
};
export const MEASUREMENT_TYPE_COLORS: Record<string, string> = {
@@ -128,4 +142,11 @@ export const MEASUREMENT_TYPE_COLORS: Record<string, string> = {
PULSE_WAVE_VELOCITY: "bg-chart-5/20 text-chart-5",
VASCULAR_AGE: "bg-chart-3/20 text-chart-3",
VISCERAL_FAT: "bg-chart-4/20 text-chart-4",
// ── v1.4.25 W8d Apple Health server-prep ──
// chart-5 (pulse / sound family) carries audio exposure; chart-2
// (activity / daylight family) carries time-in-daylight so the
// existing palette conventions hold.
AUDIO_EXPOSURE_ENV: "bg-chart-5/20 text-chart-5",
AUDIO_EXPOSURE_HEADPHONE: "bg-chart-5/20 text-chart-5",
TIME_IN_DAYLIGHT: "bg-chart-2/20 text-chart-2",
};