WWDC Quick Look 💓 By SwiftGGTeam
Handling FHIR without getting burned

Handling FHIR without getting burned

观看原视频

Highlight

Apple 发布开源 Swift 包 FHIRModels,把 HealthKit 里的 FHIR 临床记录解码为 DSTU2、R4 等原生 Swift 模型,并在编解码时阻止结构无效的资源。

核心内容

Health app 从 2018 年开始支持 Fast Healthcare Interoperability Resources(FHIR)临床记录。流程从 Health app 开始:它直接连接医疗机构 API,把 FHIR 数据下载到 iOS 设备,再安全地存入 HealthKit。开发者拿到的是 HealthKit API 暴露出来的临床记录。

真正麻烦的部分在 App 这一侧。FHIR 资源里有字符串、受限 code、ISO-8601 风格的日期字符串、布尔值、数字和多层嵌套结构。只看处方的 status、date、medication 时,自己写 Swift Codable 似乎够用;一旦面对完整处方资源,模型数量和字段复杂度会迅速放大。

Apple 给出的解法是 FHIRModels。它是一个开源 Swift package,提供同一 FHIR release 下所有 resource 的原生数据模型,并在 encode、decode 时检查 resource integrity。package 还包含日期时间解析、code 字段枚举、value polymorphic 属性枚举,以及 DSTU2、R4 和 latest build release 的库。

这场 session 的例子很小:做一个 medication list app,只请求 medication 访问权限,然后把 HKClinicalRecord 里的 FHIR JSON 解码成处方模型。这个例子也暴露了两个真实边界:HealthKit 授权要遵守只请求当前功能需要的数据原则,FHIR release 在外部世界会长期并存,App 需要按 release 选择对应模型。

详细内容

从 HealthKit 临床记录解码 FHIR 模型

04:32)FHIRModels 的第一条路径,是把 HealthKit 返回的临床记录变成 Swift 模型。session 使用 medication prescription 举例:App 拿到 HKClinicalRecord,读取关联的 fhirResource,再用 JSONDecoder 解码成 DSTU2 里的 MedicationOrder

// 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)")

关键点:

  • import HealthKit 对应临床记录来源,import ModelsDSTU2 对应当前 FHIR release 的模型库。
  • HKClinicalRecord 来自 HealthKit API,clinicalRecord.fhirResource 保存原始 FHIR JSON 数据。
  • JSONDecoder 不再解码到开发者手写的结构体,而是解码到 FHIRModels 提供的 MedicationOrder
  • prescription.note 说明解码后的对象已经是 Swift 模型,后续读取字段不需要手动走 JSON 字典。

给嵌套结构补一层业务展示

05:04)FHIR 资源会把实际业务字段放在很深的嵌套里。session 用 dosage instruction 举例:处方说明里可能包含一个有效日期区间,日期区间位于 TimingRepeatbounds period 里。示例用 extension 把这个嵌套访问封装成展示字符串。

// 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)"
}

关键点:

  • TimingRepeat 是 FHIRModels 提供的类型,extension 只负责增加 App 自己的展示逻辑。
  • if case .period(let period) = bounds 处理 value polymorphic 属性里的 period 分支。
  • period.startperiod.end 来自嵌套 period,示例把它们拼成日期范围。
  • prescription.dosageInstruction?.map 遍历处方的所有用药说明。
  • guard let period = ... 保留没有日期范围的情况,直接返回 dosage.text
  • 返回字符串把日期范围和用药说明放在一起,适合 medication list 这类界面展示。

同时支持多个 FHIR release

06:20)FHIR release 不会随着新版本发布而统一迁移。session 特别点名 DSTU2 还会流行几年,所以 App 需要支持多个 release。FHIRModels 的做法是按 release 暴露不同 module,App 根据资源所属 release 选择对应模型。

// 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)"
}

关键点:

  • import ModelsDSTU2import ModelsR4 表示同一个 App 可以同时引用多个 release 的模型库。
  • release: FHIRRelease 是示例假设已有的 release 判断入口。
  • switch release 把解析分支显式写出来,避免把 DSTU2 数据塞进 R4 模型。
  • DSTU2 示例解码到 MedicationOrder,R4 示例解码到 MedicationRequest,这反映了不同 release 的资源模型差异。
  • 两个分支读取 note 的路径不同,说明 release 差异会影响字段访问方式。
  • default 分支给出 unsupported release 文案,避免无声失败。

授权和数据范围要按功能收窄

01:13)在拿到 HKClinicalRecord 前,App 必须先向用户请求授权。session 的 medication list app 只请求 medication 访问权限,遵守 “proportional to what you need” 原则。授权界面还会展示 App 在 Info.plist 里提供的说明文字和 privacy policy 链接,Apple 会审核这份 privacy policy。

关键点:

  • FHIR 临床数据默认只分享设备上已有的数据。
  • 每次查询 FHIR 数据前都应该请求授权,让用户有机会批准新数据访问。
  • 如果没有新的内容需要授权,系统 sheet 不会出现。
  • 这个授权模型和 FHIRModels 解码流程相连:先让用户批准精确的数据范围,再解析 HealthKit 返回的 FHIR resource。

核心启发

  • Medication list:做什么:显示用户处方里的 note 和 dosage instruction。为什么值得做:session 的代码直接从 HKClinicalRecord 解码 MedicationOrder,并演示如何展示处方说明。怎么开始:只请求 medication 访问权限,拿到 fhirResource.data 后用 ModelsDSTU2.MedicationOrder 解码,再把 note 和 dosage instruction 映射到列表。
  • 用药说明时间轴:做什么:把每条 dosage instruction 按有效日期范围展示。为什么值得做:TimingRepeatbounds period 能给说明补上起止日期。怎么开始:为 TimingRepeat 增加展示 extension,读取 period.startperiod.end,没有 period 时回退到原始说明文字。
  • 多 release 处方解析器:做什么:在同一个 App 中处理 DSTU2 与 R4 处方数据。为什么值得做:session 明确说已有 API 不会随新 release 自动更新,DSTU2 还需要继续支持。怎么开始:保存或判断资源的 FHIRRelease,用 switch 选择 ModelsDSTU2.MedicationOrderModelsR4.MedicationRequest
  • FHIR 数据质量检查面板:做什么:在调试或导入流程中标出无法解码、release 不支持或结构无效的 resource。为什么值得做:FHIRModels 在 encode 和 decode 时会检查 resource integrity,阻止结构无效的资源进入业务流程。怎么开始:把解码错误和 unsupported release 分支集中记录,给开发者或测试人员一个按 resource 查看问题的列表。
  • Care app 的临床记录入口:做什么:在 care app 中把用户授权的 HealthKit 临床记录转成可展示的 care 数据。为什么值得做:session 结尾建议继续看 CareKit 如何结合 FHIR 构建 care apps。怎么开始:先完成 HealthKit 授权和 FHIRModels 解码,再把处方 note、用药说明、日期范围接到 CareKit 或自定义照护界面。

关联 Session

评论

GitHub Issues · utterances