Files
HealthLog/docs/api/cycle-healthkit-mapping.md
Marc-André Bombeck 1f4ba5939d style: apply prettier across the tree
One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
2026-06-19 22:20:31 +02:00

5.5 KiB

Apple Health → cycle mapping

How HealthLog folds Apple Health reproductive samples into the cycle model. Both the export.xml importer and the iOS batch contract share one mapping table: src/lib/cycle/healthkit-mapping.ts. This page is the human-readable companion — keep the two in sync.

Reproductive samples route into cycle day-logs (CycleDayLog), never into Measurement. Each Apple Health day collapses into one canonical day-log per (user, date); the importer writes source = APPLE_HEALTH with a synthetic per-day externalId (hkcycle:<YYYY-MM-DD>) so a re-import upserts the same row. The fold only runs when cycle tracking is enabled for the account.

Day-log fields

HealthKit category type CycleDayLog field Values
MenstrualFlow flow unspecified→LIGHT, light→LIGHT, medium→MEDIUM, heavy→HEAVY, none→NONE
IntermenstrualBleeding intermenstrualBleeding presence → true
CervicalMucusQuality cervicalMucus dry→DRY, sticky→STICKY, creamy→CREAMY, watery→WATERY, eggWhite→EGG_WHITE
OvulationTestResult ovulationTest negative→NEGATIVE, lhSurge→POSITIVE_LH_SURGE, indeterminate→INDETERMINATE, estrogenSurge→ESTROGEN_SURGE
SexualActivity sexualActivity + protectedSex presence → true; HKMetadataKeySexualActivityProtectionUsed → protectedSex
PregnancyTestResult pregnancyTest negative→NEGATIVE, positive→POSITIVE, indeterminate→INDETERMINATE
ProgesteroneTestResult progesteroneTest negative→NEGATIVE, positive→POSITIVE, indeterminate→INDETERMINATE
Contraceptive contraceptive (+ profile nudge) unspecified→UNSPECIFIED, implant→IMPLANT, injection→INJECTION, iud→IUD, ring→INTRAVAGINAL_RING, oral→ORAL, patch→PATCH, emergency→EMERGENCY

HealthLog's SPOTTING flow level has no HealthKit counterpart; it is only ever produced by a manual entry. HealthKit unspecified flow maps to LIGHT (a logged-but-unspecified bleeding day is at least light flow).

Symptom category types → seeded symptom keys

Symptom category samples link to the seeded CycleSymptom catalogue by key. A sample whose severity is not present (codepoint 1) does NOT create a link.

HealthKit category type CycleSymptom key
AbdominalCramps cramps
Headache headache
Bloating bloating
Acne acne
BreastPain breast_tenderness
Fatigue fatigue
LowerBackPain back_pain
SleepChanges insomnia
MoodChanges mood_swings
AppetiteChanges food_cravings
Nausea nausea

Profile-level signals

A Contraceptive sample asserting an active method also nudges the CycleProfile.goal to AVOID_PREGNANCY — but only when the goal is still on its GENERAL_HEALTH default, so an explicit user choice is never overwritten.

Basal body temperature

basalBodyTemperature is not routed here. It stays a Measurement(BODY_TEMPERATURE) (the system of record for the rollup tier and the cross-source source-priority ladder) and is mirrored onto CycleDayLog.basalBodyTempC for the cycle chart. Apple Watch wrist/skin temperature already flows to WRIST_TEMPERATURE / SKIN_TEMPERATURE and feeds the temperature-trend ovulation method directly from Measurement.

Deferred

HealthKit category type Status
Pregnancy, Lactation Deferred — no CycleProfile column yet (pregnancy-mode is a later release).
BleedingDuringPregnancy, BleedingAfterPregnancy Deferred — pregnancy-mode.
IrregularMenstrualCycles, InfrequentMenstrualCycles, ProlongedMenstrualPeriods, PersistentIntermenstrualBleeding Server-derived from the cycle engine, never ingested.