Lab Results Export Implementation Guide
Lab Results Export Implementation Guide
Overview
Lab result export — including Hemoglobin A1C, lipid panel, CBC, CMP / BMP, thyroid, and other tracked labs — is part of the current HealthExporter release. Users can opt into exporting Clinical Health Records lab results and the app will include matching records in the combined CSV output. The lab pipeline is registry-driven: adding a new lab requires only appending a LabMetric to LabMetricRegistry.all.
Testing status: A1C export has been verified working end-to-end on a physical device with Clinical Health Records enabled. Byte-identical CSV output is pinned by
A1CCSVBytePinningTests.
Current Architecture
Registry
LabMetricRegistry.swiftdefines the metric registryLabPanelgroups metrics into logical panels (lipid,cbc,cmp,thyroid,other)LabMetricdescribes a single lab observation:name,loincCode(alsoid),group, andvaluePrecisionLabMetricRegistry.allis the single source of truth; current entries include Hemoglobin A1C, lipid panel, CBC, CMP / BMP, thyroid, and other tracked labs- Helpers:
LabMetricRegistry.metric(forLoincCode:)andLabMetricRegistry.metrics(in:)
Data Model
HealthSampleTypes.swiftdefinesLabResultSample, the generic lab result rowLabResultSamplecarriesmetricName,loincCode,effectiveDateTime,value,unit, andsource- Initializers parse FHIR JSON (
init?(fromFHIRData:loincCode:source:)) or anHKClinicalRecord(init?(from:loincCode:)) - LOINC constants live in
LOINCCode; for example,4548-4identifies Hemoglobin A1C and2093-3identifies Total Cholesterol
HealthKit Authorization
HealthKitManager.requestAuthorization(includeLabs:vitalMetrics:completion:)adds the clinical-records read type when any lab metric is selected- Production authorization is read-only;
toShareis an empty set - The app targets iOS 26+, while the clinical-records code path is guarded with
#available(iOS 15.0, *)
Fetch Path
HealthKitManager.fetchLabResults(metrics:dateRange:limit:completion:)runs a singleHKSampleQueryagainstHKClinicalType.labResultRecord- For each returned record, the fetch tries every requested LOINC code; all matches become
LabResultSamplerows - Date filtering happens after parsing via
HealthKitQueryHelpers.filterLabResultsByDateRange
UI and Settings
SettingsManagerpersistsselectedLabPanels: Set<LabPanel>andfavoriteLabCodes: Set<String>inUserDefaults- A one-time legacy migration seeds
favoriteLabCodes = ["4548-4"]for upgrade installs that hadexportA1C == true; the legacy boolean is no longer read after migration DataSelectionViewshows one section perLabPanel. Each section has a panel-level toggle plus per-metric toggles for fine-grained selection.ExportLogic.resolveLabMetrics(selectedPanels:favoriteCodes:registry:)produces the deduplicated fetch listSettingsViewexposes simulator-only test data generation, but the write path is not part of the production export flow
CSV Output
CSVGenerator.appendLabResultRows(to:samples:dateFormat:sortOrder:)appends rows to the combined export- The metric label comes from
LabMetric.name(e.g.Hemoglobin A1CorTotal Cholesterol) - Per-metric precision comes from
LabMetric.valuePrecision
Required Configuration
The app uses generated Info.plist values from HealthExporter.xcodeproj/project.pbxproj. Keep these build settings present:
INFOPLIST_KEY_NSHealthClinicalHealthRecordsShareUsageDescriptionINFOPLIST_KEY_NSHealthShareUsageDescriptionINFOPLIST_KEY_NSHealthUpdateUsageDescription
The Clinical Health Records usage string should clearly explain why the app needs lab-result access.
Device Verification
- Clinical Records are not available in the simulator
- Validate lab-result export on a physical iOS device
- Make sure the device has clinical records synced in Apple Health
Data Flow
- User toggles lab sections or individual labs in
DataSelectionView ExportLogic.resolveLabMetricsproduces the deduplicated list ofLabMetrics- The app requests HealthKit read access; clinical records are included when the list is non-empty
HealthKitManager.fetchLabResultsruns a single clinical-records query- Matching FHIR payloads are parsed into
LabResultSamplevalues CSVGenerator.appendLabResultRowsappends rows into the combined CSV- The system file picker handles the save/export step
Adding a New Lab
- Add a LOINC constant in
LOINCCodeif it is not already there - Append a
LabMetrictoLabMetricRegistry.allwith the rightgroupandvaluePrecision - The favorites/panel toggles, fetch path, and CSV output light up automatically — no other code changes needed
- Add a pinning test if byte-identical CSV output matters for the new metric
Notes
- Lab export is opt-in; no labs are selected by default
- Existing weight, steps, and glucose export behavior is unchanged
- The simulator-only write path remains behind
#if targetEnvironment(simulator)