feat(schema): Apple Health measurement types + sleep stages + dedup index

Extend MeasurementType with the seven HealthKit-shaped values the iOS
batch ingest needs (HRV, resting HR, active energy, flights climbed,
distance, VO2 max, body temperature) and add APPLE_HEALTH to the
MeasurementSource enum. Sleep duration unit shifts from hours to
minutes so per-stage HKCategorySample rows persist without precision
loss; the new SleepStage enum mirrors the existing GlucoseContext
column shape and a CHECK constraint scopes it to SLEEP_DURATION rows.

The composite unique index `(user_id, type, source, external_id)`
gives the new batch endpoint an idempotent dedup key while the legacy
`(user_id, type, measured_at, source)` index continues to back the
manual UI's no-duplicate-at-the-same-wall-clock guard. NULL externalId
rows stay distinct under Postgres semantics so manual entries never
collide.

Migration is strictly additive: new enum values append, new columns
are nullable, no rows mutate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-11 00:15:56 +02:00
1 parent 8569a1879f
commit 95209ebf79
9 files changed
+256 -8

No files matched your search

@@ -24,6 +24,11 @@ import {
Footprints,
Bone,
Wind,
HeartPulse,
Flame,
TrendingUp,
Thermometer,
Gauge,
type LucideIcon,
} from "lucide-react";
@@ -39,6 +44,14 @@ export const MEASUREMENT_TYPE_LABEL_KEYS: Record<string, string> = {
TOTAL_BODY_WATER: "measurements.typeTotalBodyWater",
BONE_MASS: "measurements.typeBoneMass",
OXYGEN_SATURATION: "measurements.typeOxygenSaturation",
// ── v1.4.23 Apple Health additions ──
HEART_RATE_VARIABILITY: "measurements.typeHeartRateVariability",
RESTING_HEART_RATE: "measurements.typeRestingHeartRate",
ACTIVE_ENERGY_BURNED: "measurements.typeActiveEnergyBurned",
FLIGHTS_CLIMBED: "measurements.typeFlightsClimbed",
WALKING_RUNNING_DISTANCE: "measurements.typeWalkingRunningDistance",
VO2_MAX: "measurements.typeVo2Max",
BODY_TEMPERATURE: "measurements.typeBodyTemperature",
};
export const MEASUREMENT_TYPE_ICONS: Record<string, LucideIcon> = {
@@ -53,6 +66,14 @@ export const MEASUREMENT_TYPE_ICONS: Record<string, LucideIcon> = {
TOTAL_BODY_WATER: Droplet,
BONE_MASS: Bone,
OXYGEN_SATURATION: Wind,
// ── v1.4.23 Apple Health additions ──
HEART_RATE_VARIABILITY: HeartPulse,
RESTING_HEART_RATE: Heart,
ACTIVE_ENERGY_BURNED: Flame,
FLIGHTS_CLIMBED: TrendingUp,
WALKING_RUNNING_DISTANCE: Footprints,
VO2_MAX: Gauge,
BODY_TEMPERATURE: Thermometer,
};
export const MEASUREMENT_TYPE_COLORS: Record<string, string> = {
@@ -67,4 +88,12 @@ export const MEASUREMENT_TYPE_COLORS: Record<string, string> = {
TOTAL_BODY_WATER: "bg-chart-2/20 text-chart-2",
BONE_MASS: "bg-chart-4/20 text-chart-4",
OXYGEN_SATURATION: "bg-chart-5/20 text-chart-5",
// ── v1.4.23 Apple Health additions ──
HEART_RATE_VARIABILITY: "bg-chart-5/20 text-chart-5",
RESTING_HEART_RATE: "bg-chart-3/20 text-chart-3",
ACTIVE_ENERGY_BURNED: "bg-chart-4/20 text-chart-4",
FLIGHTS_CLIMBED: "bg-chart-2/20 text-chart-2",
WALKING_RUNNING_DISTANCE: "bg-chart-2/20 text-chart-2",
VO2_MAX: "bg-chart-1/20 text-chart-1",
BODY_TEMPERATURE: "bg-chart-4/20 text-chart-4",
};