Files
HealthLog/docs/api/cycle-healthkit-mapping.md
T
Marc-André BombeckandClaude Opus 4.8 e984ada5f1 feat(cycle): import Apple Health reproductive data into cycle, add cycle export
Promote the reproductive HealthKit category types out of the
Apple-Health-import privacy-hold list and route them into cycle day-logs
instead of measurements, and surface cycle data on the three export
surfaces.

Import promotion:
- A new HealthKit -> cycle mapping module is the single source of truth for
  how MenstrualFlow, IntermenstrualBleeding, CervicalMucusQuality,
  OvulationTestResult, SexualActivity, PregnancyTestResult,
  ProgesteroneTestResult, Contraceptive and the symptom category types fold
  into CycleDayLog fields / seeded symptom keys. The XML importer
  accumulates same-day reproductive samples and writes one canonical
  day-log per day with source APPLE_HEALTH and a synthetic per-day
  externalId for idempotent re-import. The fold is gated on cycle tracking
  being enabled, so a non-cycle export never provisions cycle rows. A
  contraceptive method nudges the profile goal toward avoiding pregnancy
  only when the goal is still on its default. Basal body temperature stays
  a measurement and mirrors onto the day-log. Pregnancy/Lactation status
  and the pregnancy-bleeding types stay deferred; the cycle awareness types
  remain server-derived.

Export:
- FHIR emits LMP (LOINC 8665-2), average cycle length (64700-8), period
  length (64698-4) and the current phase as opt-in reproductive-health
  observations grouped into their own document section.
- The doctor PDF gains an opt-in cycle section (recent cycles, average
  length and variability, current phase), privacy-default off like mood.
- The full-backup export, the weekly backup worker and the off-host DR
  snapshot include the cycle tables, and the admin restore re-creates them;
  the day-log note round-trips as ciphertext and is never decrypted onto a
  report surface.

Mapping table documented in docs/api/cycle-healthkit-mapping.md. Adds unit
+ integration coverage for the mapping, the import routing, the FHIR
observations and the backup round-trip. OpenAPI regenerated. Six-locale
strings added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 10:16:54 +02:00

3.7 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.