WWDC Quick Look 💓 By SwiftGGTeam
Handling FHIR without getting burned

Handling FHIR without getting burned

Watch original video

Highlight

Apple released the open source Swift package FHIRModes, which decodes FHIR clinical records in HealthKit into native Swift models such as DSTU2 and R4, and prevents resources with invalid structures during encoding and decoding.

Core Content

Health app supports Fast Healthcare Interoperability Resources (FHIR) clinical records starting in 2018. The process starts in the Health app: it connects directly to the healthcare institution’s API, downloads the FHIR data to the iOS device, and securely stores it in HealthKit. What developers get are clinical records exposed by the HealthKit API.

The real troublesome part is on the app side. FHIR resources include strings, restricted codes, ISO-8601-style date strings, Boolean values, numbers, and multi-level nested structures. When only looking at the status, date, and medication of the prescription, write Swift yourselfCodableIt seems sufficient; once faced with complete prescription resources, the number of models and field complexity will quickly increase.

The solution given by Apple is FHIRModels. It is an open source Swift package that provides native data models for all resources under the same FHIR release, and checks resource integrity when encoding and decoding. The package also includes datetime parsing, code field enumeration, value polymorphic attribute enumeration, and libraries for DSTU2, R4, and latest build release.

The example of this session is very small: make a medication list app, request only medication access, and then putHKClinicalRecordDecode the FHIR JSON into a prescription model. This example also exposes two real boundaries: HealthKit authorization must comply with the principle of only requesting data required by the current function, FHIR release will coexist in the external world for a long time, and the App needs to select the corresponding model according to release.

Detailed Content

Decoding FHIR models from HealthKit clinical records

(04:32) The first path of FHIRModels is to turn the clinical records returned by HealthKit into Swift models. session uses medication prescription example: App getsHKClinicalRecord, read the associatedfhirResource, then useJSONDecoderDecoded into DSTU2MedicationOrder

// Use with Health Records FHIR data from HealthKit
import HealthKit
import ModelsDSTU2

// Grab HKClinicalRecord from HealthKit API
let clinicalRecord: HKClinicalRecord
let resource = clinicalRecord.fhirResource!

// Print the prescription note
let decoder = JSONDecoder()
let prescription = try decoder.decode(MedicationOrder.self, from: resource.data)
print("\(prescription.note)")

Key points:

  • import HealthKitCorresponding clinical record source,import ModelsDSTU2Model library corresponding to the current FHIR release. -HKClinicalRecordFrom the HealthKit API,clinicalRecord.fhirResourceSave raw FHIR JSON data. -JSONDecoderInstead of decoding to the structure handwritten by the developer, it is decoding to the structure provided by FHIRModelsMedicationOrder
  • prescription.noteNote that the decoded object is already a Swift model, and there is no need to manually go through the JSON dictionary to read fields later.

Add a layer of business display to the nested structure

(05:04) FHIR resources place the actual business fields deeply nested. session uses dosage instruction. For example: the prescription instructions may contain an effective date range, and the date range is betweenTimingRepeatofboundsperiod. The example uses extension to encapsulate this nested access into a display string.

// Make using "TimingRepeat" period dates easier by writing an extension
extension TimingRepeat {
    var periodDisplayString: String? {
        if case .period(let period) = bounds {
            return "\(period.start) - \(period.end)"
        }
        return nil
    }
}

// Collect all dosage instructions on medication prescriptions
let instructions: [String] = prescription.dosageInstruction?.map { dosage in
    guard let period = dosage.timing?.repeat?.periodDisplayString else {
        return "\(dosage.text)"
    }
    return "\(period): \(dosage.text)"
}

Key points:

  • TimingRepeatIt is a type provided by FHIRModes. The extension is only responsible for adding the App’s own display logic. -if case .period(let period) = boundsHandle the period branch in the value polymorphic attribute. -period.startandperiod.endFrom nested periods, the example spells them into date ranges. -prescription.dosageInstruction?.mapGo through all medication instructions for your prescription. -guard let period = ...Keep the case without date range and return directlydosage.text.
  • The returned string puts the date range and medication instructions together, which is suitable for interface display such as medication list.

Support multiple FHIR releases at the same time

(06:20) FHIR release will not be uniformly migrated with the release of new versions. session specifically mentioned that DSTU2 will be popular for several more years, so the App needs to support multiple releases. FHIRModes’ approach is to expose different modules according to release, and the App selects the corresponding model according to the release to which the resource belongs.

// Supporting multiple releases
import ModelsDSTU2
import ModelsR4

let decoder = JSONDecoder()
let release: FHIRRelease
let data: Data

let note: String? = nil
switch release {
case .dstu2:
    let model = try decoder.decode(ModelsDSTU2.MedicationOrder.self, from: data)
    note = model.note?.value?.string
case .r4:
    let model = try decoder.decode(ModelsR4.MedicationRequest.self, from: data)
    note = model.note?.compactMap({ $0.text.value?.string }).joined(separator: "\n")
default:
    note = "Unsupported FHIR release \(release)"
}

Key points:

  • import ModelsDSTU2andimport ModelsR4Indicates that the same App can reference multiple release model libraries at the same time. -release: FHIRReleaseThis example assumes that there is an existing release judgment entry. -switch releaseMake parsing branches explicit to avoid stuffing DSTU2 data into the R4 model.
  • DSTU2 example decoded toMedicationOrder, R4 example decodes toMedicationRequest, which reflects the differences in resource models of different releases.
  • The two branches use different paths to read notes, indicating that release differences will affect field access methods. -defaultGive the branch unsupported release copy to avoid silent failure.

Authorization and data scope should be narrowed according to function

(01:13) After gettingHKClinicalRecordBefore doing so, the App must first request authorization from the user. The session’s medication list app only requests medication access permissions and adheres to the “proportional to what you need” principle. The authorization interface will also display the App’sInfo.plistThe description text and privacy policy link provided here, Apple will review this privacy policy.

Key points:

  • FHIR clinical data by default only shares data that is already on the device.
  • Authorization should be requested before each query of FHIR data, giving users an opportunity to approve new data access.
  • If there is no new content requiring authorization, the system sheet will not appear.
  • This authorization model is connected to the FHIRModels decoding process: first let the user approve the precise data range, and then parse the FHIR resource returned by HealthKit.

Core Takeaways

  • Medication list: What to do: Display the notes and dosage instructions in the user’s prescription. Why it’s worth doing: The session code is directly fromHKClinicalRecorddecodingMedicationOrder, and demonstrate how to display prescription instructions. How to start: Request medication access only, getfhirResource.datafor later useModelsDSTU2.MedicationOrderDecode and map note and dosage instruction to lists.
  • Dosage Instruction Timeline: What to do: Display each dosage instruction according to the effective date range. Why it’s worth doing:TimingRepeatofboundsperiod can add start and end dates to the description. How to start: forTimingRepeatAdd display extension, readperiod.startandperiod.end, fall back to the original description text when there is no period.
  • Multi-release recipe parser: What it does: Process DSTU2 and R4 recipe data in the same app. Why it’s worth doing: The session clearly states that existing APIs will not be automatically updated with new releases, and DSTU2 needs to continue to be supported. How to start: Save or determine resourcesFHIRRelease,useswitchchooseModelsDSTU2.MedicationOrderorModelsR4.MedicationRequest.
  • FHIR Data Quality Inspection Panel: What to do: Flag resources in the debug or import process that cannot be decoded, have unsupported releases, or have invalid structures. Why it’s worth doing: FHIRModels checks resource integrity when encoding and decoding to prevent resources with invalid structures from entering the business process. How to get started: Centralize logging of decoding errors and unsupported release branches, giving developers or testers a list of issues that can be viewed by resource.
  • Care app’s clinical record entrance: What to do: Convert user-authorized HealthKit clinical records into displayable care data in the care app. Why it’s worth doing: At the end of the session, I recommend continuing to see how CareKit combines FHIR to build care apps. How to start: First complete HealthKit authorization and FHIRModes decoding, then connect prescription notes, medication instructions, and date ranges to CareKit or a customized care interface.

Comments

GitHub Issues · utterances