diff --git a/src/lib/measurements/__tests__/apple-health-mapping.test.ts b/src/lib/measurements/__tests__/apple-health-mapping.test.ts index 00f8dd029..ac8475f94 100644 --- a/src/lib/measurements/__tests__/apple-health-mapping.test.ts +++ b/src/lib/measurements/__tests__/apple-health-mapping.test.ts @@ -119,6 +119,69 @@ describe("HK_QUANTITY_TYPE_DEFERRED", () => { } }); + // v1.4.25 W16a — explicit coverage gate for the iOS-17 + iOS-18 + // long-tail. The brief flagged these as "the iOS app may emit them + // when the user has the relevant HealthKit category enabled"; the + // deferred set means the batch endpoint treats them as + // skipped-but-known rather than unknown-and-dropped. + it("covers the iOS-17 + iOS-18 long-tail identifiers", () => { + const expectedLongTail = [ + // Cardiovascular / clinical + "HKQuantityTypeIdentifierAtrialFibrillationBurden", + "HKQuantityTypeIdentifierPeripheralPerfusionIndex", + // Mobility + "HKQuantityTypeIdentifierAppleWalkingSteadiness", + "HKQuantityTypeIdentifierNumberOfTimesFallen", + "HKCategoryTypeIdentifierAppleWalkingSteadinessEvent", + // Respiratory / pulmonary + "HKQuantityTypeIdentifierForcedExpiratoryVolume1", + "HKQuantityTypeIdentifierForcedVitalCapacity", + "HKQuantityTypeIdentifierPeakExpiratoryFlowRate", + "HKQuantityTypeIdentifierInhalerUsage", + // Other quantity identifiers + "HKQuantityTypeIdentifierInsulinDelivery", + "HKQuantityTypeIdentifierUVExposure", + "HKQuantityTypeIdentifierElectrodermalActivity", + "HKQuantityTypeIdentifierBloodAlcoholContent", + "HKQuantityTypeIdentifierNikeFuel", + // Heart-rhythm event flags + "HKCategoryTypeIdentifierLowHeartRateEvent", + "HKCategoryTypeIdentifierHighHeartRateEvent", + "HKCategoryTypeIdentifierIrregularHeartRhythmEvent", + "HKCategoryTypeIdentifierLowCardioFitnessEvent", + // Audio-exposure events + "HKCategoryTypeIdentifierEnvironmentalAudioExposureEvent", + "HKCategoryTypeIdentifierHeadphoneAudioExposureEvent", + "HKCategoryTypeIdentifierEnvironmentalSoundReduction", + // Behavioural / habit + "HKCategoryTypeIdentifierHandwashingEvent", + "HKCategoryTypeIdentifierToothbrushingEvent", + // Reproductive / fertility / pregnancy + "HKCategoryTypeIdentifierContraceptive", + "HKCategoryTypeIdentifierLactation", + "HKCategoryTypeIdentifierPregnancy", + "HKCategoryTypeIdentifierPregnancyTestResult", + "HKCategoryTypeIdentifierProgesteroneTestResult", + "HKCategoryTypeIdentifierSexualActivity", + "HKCategoryTypeIdentifierSleepChanges", + "HKCategoryTypeIdentifierPersistentIntermenstrualBleeding", + "HKCategoryTypeIdentifierProlongedMenstrualPeriods", + "HKCategoryTypeIdentifierIrregularMenstrualCycles", + "HKCategoryTypeIdentifierInfrequentMenstrualCycles", + ]; + for (const id of expectedLongTail) { + expect( + HK_QUANTITY_TYPE_DEFERRED.has(id), + `${id} should be in the deferred set`, + ).toBe(true); + } + }); + + it("does not duplicate any deferred identifier", () => { + const ids = Array.from(HK_QUANTITY_TYPE_DEFERRED); + expect(new Set(ids).size).toBe(ids.length); + }); + it("converts oxygen saturation from 0..1 fraction to 0..100 percent", () => { const mapping = APPLE_HEALTH_TYPE_MAP.HKQuantityTypeIdentifierOxygenSaturation; diff --git a/src/lib/measurements/apple-health-mapping.ts b/src/lib/measurements/apple-health-mapping.ts index f642e94d2..a780a447b 100644 --- a/src/lib/measurements/apple-health-mapping.ts +++ b/src/lib/measurements/apple-health-mapping.ts @@ -382,6 +382,59 @@ export const HK_QUANTITY_TYPE_DEFERRED = new Set([ // Scored assessments (iOS 18) — v1.5 (PHQ-9 / GAD-7 mapping TBD) "HKScoredAssessmentTypeIdentifierGAD7", "HKScoredAssessmentTypeIdentifierPHQ9", + // ── v1.4.25 W16a — iOS-17 + iOS-18 long-tail closure ── + // Cardiovascular / clinical (iOS 16+) — no HealthLog counterpart yet; + // clinical decision-support territory, defer behind a stricter user + // opt-in than the existing Health-share prompt. + "HKQuantityTypeIdentifierAtrialFibrillationBurden", + "HKQuantityTypeIdentifierPeripheralPerfusionIndex", + // Mobility (iOS 15+) — surface as a wellness signal in v1.5 once the + // Insights cardio sub-page has room for a steadiness gauge. + "HKQuantityTypeIdentifierAppleWalkingSteadiness", + "HKQuantityTypeIdentifierNumberOfTimesFallen", + "HKCategoryTypeIdentifierAppleWalkingSteadinessEvent", + // Respiratory / pulmonary clinical (iOS 17) — pair with FHIR clinical + // bucket; no Measurement enum mapping today. + "HKQuantityTypeIdentifierForcedExpiratoryVolume1", + "HKQuantityTypeIdentifierForcedVitalCapacity", + "HKQuantityTypeIdentifierPeakExpiratoryFlowRate", + "HKQuantityTypeIdentifierInhalerUsage", + // Other quantity identifiers — explicit hold (privacy / not in scope). + "HKQuantityTypeIdentifierInsulinDelivery", + "HKQuantityTypeIdentifierUVExposure", + "HKQuantityTypeIdentifierElectrodermalActivity", + "HKQuantityTypeIdentifierBloodAlcoholContent", + "HKQuantityTypeIdentifierNikeFuel", // legacy Nike+iPod fitness points + // Heart-rhythm event flags (iOS 9+ but watch-detected; iOS 18 + // refreshed surfaces) — would land as Measurement annotations rather + // than rows; defer until the annotation column lands. + "HKCategoryTypeIdentifierLowHeartRateEvent", + "HKCategoryTypeIdentifierHighHeartRateEvent", + "HKCategoryTypeIdentifierIrregularHeartRhythmEvent", + "HKCategoryTypeIdentifierLowCardioFitnessEvent", + // Audio-exposure events (iOS 13+) — the continuous AUDIO_EXPOSURE_* + // quantity identifiers ARE mapped above; these are the + // "loud-event-fired" flags that pair with them. Defer until we + // surface event chips in the Insights audio sub-page. + "HKCategoryTypeIdentifierEnvironmentalAudioExposureEvent", + "HKCategoryTypeIdentifierHeadphoneAudioExposureEvent", + "HKCategoryTypeIdentifierEnvironmentalSoundReduction", + // Behavioural / habit category-types — not in HealthLog scope yet. + "HKCategoryTypeIdentifierHandwashingEvent", + "HKCategoryTypeIdentifierToothbrushingEvent", + // Reproductive / fertility / pregnancy — explicit privacy hold + // (matches the menstrual-flow stance already encoded above). + "HKCategoryTypeIdentifierContraceptive", + "HKCategoryTypeIdentifierLactation", + "HKCategoryTypeIdentifierPregnancy", + "HKCategoryTypeIdentifierPregnancyTestResult", + "HKCategoryTypeIdentifierProgesteroneTestResult", + "HKCategoryTypeIdentifierSexualActivity", + "HKCategoryTypeIdentifierSleepChanges", + "HKCategoryTypeIdentifierPersistentIntermenstrualBleeding", + "HKCategoryTypeIdentifierProlongedMenstrualPeriods", + "HKCategoryTypeIdentifierIrregularMenstrualCycles", + "HKCategoryTypeIdentifierInfrequentMenstrualCycles", ] as const); /** Input to `mapAppleHealthEntry()`. */