Handling FHIR without getting burned
Health & Fitness 高级 16m

安全处理 FHIR 数据:避开常见陷阱

Handling FHIR without getting burned

2020年6月25日

在 Apple 官方观看视频

一句话判断

FHIR(Fast Healthcare Interoperability Resources)是医疗数据交换的标准格式,iOS 14 的 HealthKit 支持导入 FHIR 数据——但这不是简单的 JSON 解析,Session 深入讲了 FHIR 的版本兼容性、数据验证、隐私合规等「真正让你踩坑」的问题。

这场 Session 讲了什么

这场 Session 面向需要在 iOS app 中处理 FHIR 数据的医疗健康开发者。FHIR 是 HL7 组织定义的医疗数据交换标准,正在被越来越多的医疗机构采用。

iOS 14 的 HealthKit 支持从 FHIR 格式的医疗记录中提取数据。但这涉及复杂的映射:FHIR 的数据模型(Patient、Observation、Condition、Medication 等)需要映射到 HealthKit 的数据类型(HKClinicalRecord)。

Session 重点讲了几个容易踩的坑:FHIR 版本差异(DSTU2 vs STU3 vs R4)、Resource 引用解析(FHIR 资源之间通过 ID 互相引用,需要正确解析引用链)、数据验证(FHIR 数据可能不完整或不符合 profile 约束)、以及隐私合规(FHIR 数据属于 PHI,需要 HIPAA 合规)。

值得深挖的点

FHIR 版本兼容是最大的坑

FHIR 标准目前有三个活跃版本:DSTU2(Draft Standard for Trial Use 2)、STU3 和 R4。不同医疗机构可能使用不同版本,而且它们的 Resource 结构不兼容。比如 Observation.value 在 DSTU2 中是 valueQuantity,在 R4 中可能变成了 value[x](多态类型)。Session 建议在 app 中明确支持哪些版本,对不支持的版本给出清晰的错误提示。

FHIR Profile 的验证

FHIR 允许医疗机构定义自己的 Profile(约束扩展)。一个「实验室检查结果」的 Profile 可能要求额外的必填字段,如果你的解析器不检查 Profile 约束,会静默地丢失数据。Session 建议使用 StructureDefinition 来验证 FHIR 数据是否符合预期的 Profile。

代码片段

import HealthKit

// 从 HealthKit 获取 FHIR 格式的临床记录
class FHIRManager {
    let healthStore = HKHealthStore()
    
    func fetchClinicalRecords() {
        guard let clinicalType = HKObjectType.clinicalType(forIdentifier: .labResultRecordType) else { return }
        
        let query = HKSampleQuery(sampleType: clinicalType, predicate: nil, limit: 50, sortDescriptors: nil) { _, samples, error in
            guard let records = samples as? [HKClinicalRecord] else { return }
            
            for record in records {
                // 获取 FHIR 数据
                if let fhirResource = record.fhirResource {
                    let fhirData = fhirResource.data
                    let fhirVersion = fhirResource.fhirVersion
                    
                    // 解析 FHIR JSON
                    self.parseFHIR(data: fhirData, version: fhirVersion)
                }
            }
        }
        
        healthStore.execute(query)
    }
    
    func parseFHIR(data: Data, version: HKFHIRVersion) {
        guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return }
        
        let resourceType = json["resourceType"] as? String
        
        switch resourceType {
        case "Observation":
            parseObservation(json, version: version)
        case "Patient":
            parsePatient(json)
        case "MedicationRequest":
            parseMedicationRequest(json)
        default:
            print("未支持的资源类型: \(resourceType ?? "unknown")")
        }
    }
    
    func parseObservation(_ json: [String: Any], version: HKFHIRVersion) {
        // 提取观测值
        let code = (json["code"] as? [String: Any])?["text"] as? String
        let effectiveDate = json["effectiveDateTime"] as? String
        
        // 根据版本差异提取 value
        if let valueQuantity = json["valueQuantity"] as? [String: Any] {
            let value = valueQuantity["value"] as? Double
            let unit = valueQuantity["unit"] as? String
            print("\(code ?? ""): \(value ?? 0) \(unit ?? "")")
        }
        
        // 检查数据完整性
        if json["valueQuantity"] == nil && json["valueString"] == nil && json["dataAbsentReason"] != nil {
            print("数据缺失,原因: \(json["dataAbsentReason"] ?? "未知")")
        }
    }
}

最佳实践

  1. 永远不要假设 FHIR 数据的完整性。医疗数据经常有缺失字段(设备故障、录入遗漏等)。解析时要检查每个可选字段是否存在,用 dataAbsentReason 解释为什么数据缺失。
  2. 在 app 中显示数据来源和采集时间。FHIR 数据来自医疗机构,数据的新鲜度和准确性取决于采集时间。在 UI 上清楚标注「此数据来自 XX 医院于 YYYY-MM-DD 采集」。
  3. 遵守 HIPAA 合规要求。FHIR 数据属于受保护的健康信息(PHI)。传输必须加密(HTTPS),存储必须加密(Keychain 或加密数据库),日志中不能包含明文的医疗数据。

还有什么值得关注

  • HealthKit 目前支持的 FHIR 资源类型有限:Allergy Intolerance、Condition、Immunization、Lab Result、Medication、Procedure。其他类型需要通过自定义解析处理。
  • 苹果的 Health Records API 允许用户从支持的医疗机构直接将 FHIR 数据导入 HealthKit。
  • FHIR R4 是目前推荐使用的版本,DSTU2 逐渐被淘汰,新项目建议直接用 R4。
WWDC 2020