HealthKitOnFhir 是一个 Swift 库,可自动将 Apple HealthKit 数据导出到 FHIR® 服务器。HealthKit 数据可以通过 Azure 的 IoMT FHIR 连接器 路由,以将高频数据分组,从而减少生成的 Observation 资源数量。 HealthKit 数据也可以直接导出到 FHIR 服务器(适用于低频数据)。最基本的实现需要
有关如何使用 HealthKitOnFhir 的详细示例,请参见 Sample 目录中的示例应用程序。
HealthKitOnFhir 使用 Swift Package Manager 来管理依赖项。建议您使用 Xcode 11 或更高版本将 HealthKitOnFhir 添加到您的项目中。
HealthKitOnFhir 是 HealthDataSync Swift 库的实现。HealthKitOnFhir 将 External Store 定义为 FHIR 服务器,并提供了一种简单的方法来使用 Azure 的 IoMT FHIR 连接器导出高频数据,并使用 Swift-FHIR FHIRServer 的实例导出低频数据。
// Use the HDSManagerFactory to get the singleton instance of the HDSManager (Health Data Sync Manager).
// The syncManager should be fully configured before the AppDelegate didFinishLaunchingWithOptions completes
// so changes to HealthKit data can be handled if the application is not running.
let syncManager = HDSManagerFactory.manager();
// Create an IoMT FHIR Client to handle the transport of high frequency data.
let iomtFhirClient = try IomtFhirClient.CreateFromConnectionString(connectionString: { YOUR_CONNECTION_STRING_HERE })
// Initialize the external store object with the Client.
let iomtFhirExternalStore = IomtFhirExternalStore(iomtFhirClient: iomtFhirClient)
// Set the object types that will be synchronized and the destination store.
syncManager?.addObjectTypes([HeartRateMessage.self, StepCountMessage.self], externalStore: iomtFhirExternalStore)
// Start observing HealthKit for changes.
// If the user has not granted permissions to access requested HealthKit types, the start call will be ignored.
syncManager?.startObserving()
下面,我们正在初始化一个 Swift-Smart 客户端用于 SMART on FHIR 应用程序,并使用客户端提供的 FHIRServer 实例化 FhirExternalStore。我们还设置了一个转换器来处理 HealthKit 数据到 FHIR 资源的转换 - 请参阅:HealthKitToFhir。
// Create the Swift-Smart Client
let client = Client(
baseURL: URL(string: { YOUR_FHIR_URL })!,
settings: [ "client_id": { YOUR_CLIENT_ID }, "redirect": { YOUR_REDIRECT } ])
// Create the FHIR external store using the client.server to handle low frequency data.
let fhirExternalStore = FhirExternalStore(server: client.server)
// Set the object types that will be synchronized and the destination store.
syncManager?.addObjectTypes([BloodPressureContainer.self, BloodGlucoseContainer.self], externalStore: fhirExternalStore)
// REQUIRED: Set a HKObject to FHIR Resource converter to convert HealthKit Data to Fhir resources.
// In this case we are using the HealthKitToFhir ObservationFactory to handle Observation Resources.
syncManager?.converter = Converter(converterMap: [Observation.resourceType : try ObservationFactory()])
// Start observing HealthKit for changes.
// When new types are added startObserving must be called to begin 'listening' for changes.
// If the user has not granted permissions to access requested HealthKit types the start call will be ignored.
syncManager?.startObserving()
创建一个 IomtFhirExternalStoreDelegate 将为您的应用程序提供一种方法,在 HealthKit 数据导出之前和导出完成之后接收回调。可以实现 2 个可选方法来接收这些回调
/// Called once for each EventData after a HealthKit query has completed but before the data is sent to the IoMT FHIR Client.
///
/// - Parameters:
/// - eventData: The EventData object to be sent.
/// - object: The original underlying HealthKit HKObject.
/// - completion: Must be called to start the upload of the EventData. Return true to start the upload and false to cancel. Optional Error will be passed to the IomtFhirExternalStore.
func shouldAdd(eventData: EventData, object: HKObject?, completion: @escaping (Bool, Error?) -> Void)
/// Called after ALL data is sent to the IoMT FHIR Client.
///
/// - Parameters:
/// - eventDatas: The EventData that was sent to the IoMT FHIR Client.
/// - success: Bool representing whether or not the request was successful.
/// - error: An Error with detail about the failure (will be nil if the operation was successful).
func addComplete(eventDatas: [EventData], success: Bool, error: Error?)
与 IomtFhirExternalStoreDelegate 类似,FhirExternalStore 委托也可以为您的应用程序提供在 HealthKit 数据导出到 FHIR 服务器 (POST) 之前和之后的回调。但是,FhirExternalStore 还支持 GET、PUT 和 DELETE,因此可以添加委托方法以接收在任何这些操作之前和之后的回调。任何这些方法的实现都是可选的
/// Called after a HealthKit query has completed but before the data is fetched from the FHIR server.
///
/// - Parameters:
/// - objects: The collection of HDSExternalObjectProtocol objects used to fetch resources from the Server.
/// - completion: MUST be called to start the fetch of the FHIR.Resources. Return true to start the fetch process and false to cancel. Optional Error will be passed to the FhirExternalStore.
func shouldFetch(objects: [HDSExternalObjectProtocol], completion: @escaping (Bool, Error?) -> Void)
/// Called after all data is fetched from the FHIR Server.
///
/// - Parameters:
/// - objects: The collection of HDSExternalObjectProtocol objects used to fetch resources from the Server.
/// - success: Bool representing whether or not the request was successful.
/// - error: An Error with detail about the failure (will be nil if the operation was successful).
func fetchComplete(objects: [HDSExternalObjectProtocol]?, success: Bool, error: Error?)
/// Called after a HealthKit query has completed but before the data is sent to the FHIR server.
///
/// - Parameters:
/// - resource: The FHIR.Resource object to be sent.
/// - object: The original underlying HealthKit HKObject.
/// - completion: MUST be called to start the upload of the FHIR.Resource. Return true to start the upload and false to cancel. Optional Error will be passed to the FhirExternalStore.
func shouldAdd(resource: Resource, object: HKObject?, completion: @escaping (Bool, Error?) -> Void)
/// Called after all data is sent to the FHIR Server.
///
/// - Parameters:
/// - objects: The collection of HDSExternalObjectProtocol objects used to add resources to the Server.
/// - success: Bool representing whether or not the request was successful.
/// - error: An Error with detail about the failure (will be nil if the operation was successful).
func addComplete(objects: [HDSExternalObjectProtocol], success: Bool, error: Error?)
/// Called after a HealthKit query has completed but before the data is updated in the FHIR server.
///
/// - Parameters:
/// - resource: The FHIR.Resource object to be updated.
/// - object: The original underlying HealthKit HKObject.
/// - completion: MUST be called to initiate the update on the FHIR.Resource. Return true to start the update request and false to cancel. Optional Error will be passed to the FhirExternalStore.
func shouldUpdate(resource: Resource, object: HKObject?, completion: @escaping (Bool, Error?) -> Void)
/// Called after all data is updated on the FHIR Server.
///
/// - Parameters:
/// - containers: The collection of HDSExternalObjectProtocol objects used to update resources on the Server.
/// - success: Bool representing whether or not the request was successful.
/// - error: An Error with detail about the failure (will be nil if the operation was successful).
func updateComplete(objects: [HDSExternalObjectProtocol], success: Bool, error: Error?)
/// Called after a HealthKit query has completed but before the delete request is sent the FHIR server.
///
/// - Parameters:
/// - resource: The FHIR.Resource object to be deleted.
/// - object: The HealthKit HKDeletedObject.
/// - completion: MUST be called to initiate the deletion of the FHIR.Resource. Return true to delete and false to cancel. Optional Error will be passed to the FhirExternalStore.
func shouldDelete(resource: Resource, deletedObject: HKDeletedObject?, completion: @escaping (Bool, Error?) -> Void)
/// Called after all deletes are completed on the FHIR Server.
///
/// - Parameters:
/// - success: Bool representing whether or not the request was successful.
/// - error: An Error with detail about the failure (will be nil if the operation was successful).
func deleteComplete(success: Bool, error: Error?)
HealthKitOnFhir 目前支持以下 IoMTFhirClients 和 FhirClients 的类型
为 IomtFhirExternalStore 添加新的导出类型需要创建 IomtFhirMessageBase 的子类,并实现 HealthDataSync Swift 库的 HDSExternalObjectProtocol。IomtFhirMessages 被序列化为 JSON 有效负载并发送到 Azure 的 IoMT FHIR 连接器,并且必须将新类型添加到 Azure 的 IoMT FHIR 连接器配置文件。有关如何设置配置文件的详细信息,请参见此处。
以下是为导出血糖创建的类的示例。
open class BloodGlucoseMessage : IomtFhirMessageBase, HDSExternalObjectProtocol {
// These 2 properties will be serialized into the JSON payload.
internal var bloodGlucose: Double?
internal let unit = "mg/dL"
public init?(object: HKObject) {
guard let sample = object as? HKQuantitySample,
sample.quantityType == BloodGlucoseMessage.healthKitObjectType() else {
return nil
}
super.init(uuid: sample.uuid, startDate: sample.startDate, endDate: sample.endDate)
self.update(with: object)
self.healthKitObject = object
}
// Required because the super class conforms to Codable protocol.
public required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}
// HDSExternalObjectProtocol method. HKObjectTypes returned here will be used for obtaining permissions from the user.
// Types returned here will be displayed to the user in the OS controlled health data permissions UI.
public static func authorizationTypes() -> [HKObjectType]? {
if let bloodGlucoseType = healthKitObjectType() {
return [bloodGlucoseType]
}
return nil
}
// HDSExternalObjectProtocol method. HKObjectTypes returned here will be used for querying HealthKit.
public static func healthKitObjectType() -> HKObjectType? {
return HKObjectType.quantityType(forIdentifier: .bloodGlucose)
}
// HDSExternalObjectProtocol method. Return an initialized IomtFhirMessageBase object here.
// The object will be serialized and exported using the IomtFhirExternalStore.
public static func externalObject(object: HKObject, converter: HDSConverterProtocol?) -> HDSExternalObjectProtocol? {
return BloodGlucoseMessage.init(object: object)
}
// HDSExternalObjectProtocol method. Should return nil.
// Deleting objects using the IomtFhirExternalStore is currently not supported.
public static func externalObject(deletedObject: HKDeletedObject, converter: HDSConverterProtocol?) -> HDSExternalObjectProtocol? {
return nil
}
// HDSExternalObjectProtocol method.
public func update(with object: HKObject) {
if let sample = object as? HKQuantitySample {
bloodGlucose = sample.quantity.doubleValue(for: HKUnit(from: unit))
}
}
// Required for serialization.
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(bloodGlucose, forKey: .bloodGlucose)
try container.encode(unit, forKey: .unit)
}
// Required for serialization.
private enum CodingKeys: String, CodingKey {
case bloodGlucose
case unit
}
}
为 FhirExternalStore 添加新的导出类型需要创建 ResourceContainer 的子类,并实现 HealthDataSync Swift 库的 HDSExternalObjectProtocol。重要的是要确保 HKObject 到 FHIR 资源转换器支持添加的任何新导出类型。HealthKitOnFhir 示例应用程序使用 HealthKitToFhir Swift 库来处理 HKObjects 的转换。
open class BloodGlucoseContainer : ResourceContainer<Observation>, HDSExternalObjectProtocol {
internal let unit = "mg/dL"
// HDSExternalObjectProtocol method. HKObjectTypes returned here will be used for obtaining permissions from the user.
// Types returned here will be displayed to the user in the OS controlled health data permissions UI.
public static func authorizationTypes() -> [HKObjectType]? {
if let bloodGlucoseType = healthKitObjectType() {
return [bloodGlucoseType]
}
return nil
}
// HDSExternalObjectProtocol method. HKObjectTypes returned here will be used for querying HealthKit.
public static func healthKitObjectType() -> HKObjectType? {
return HKObjectType.quantityType(forIdentifier: .bloodGlucose)
}
// HDSExternalObjectProtocol method. Return an initialized ResourceContainer here.
// The object and converter are passed to the super on initialization.
public static func externalObject(object: HKObject, converter: HDSConverterProtocol?) -> HDSExternalObjectProtocol? {
if let sample = object as? HKSample,
sample.sampleType == BloodGlucoseContainer.healthKitObjectType() {
return BloodGlucoseContainer(object: object, converter: converter)
}
return nil
}
// HDSExternalObjectProtocol method. Return an initialized ResourceContainer here.
// The deletedObject and converter are passed to the super on initialization.
public static func externalObject(deletedObject: HKDeletedObject, converter: HDSConverterProtocol?) -> HDSExternalObjectProtocol? {
return BloodGlucoseContainer(deletedObject: deletedObject, converter: converter)
}
// HDSExternalObjectProtocol method. Update the resource with the HKObject.
public func update(with object: HKObject) {
if let sample = object as? HKQuantitySample {
resource?.valueQuantity?.value = FHIRDecimal(Decimal(floatLiteral: sample.quantity.doubleValue(for: HKUnit(from: unit))))
}
}
}
此项目欢迎贡献和建议。大多数贡献需要您同意贡献者许可协议 (CLA),声明您有权授予我们使用您的贡献的权利,并且实际上也确实授予了我们这些权利。有关详细信息,请访问 https://cla.opensource.microsoft.com。
当您提交拉取请求时,CLA 机器人将自动确定您是否需要提供 CLA 并适当地修饰 PR(例如,状态检查、注释)。只需按照机器人提供的说明进行操作即可。您只需在所有使用我们的 CLA 的存储库中执行一次此操作。
还有许多其他方法可以为 HealthDataSync 项目做出贡献。
有关更多信息,请参阅 贡献 HealthKitOnFhir。
本项目已采用 Microsoft 开放源代码行为准则。有关更多信息,请参阅 行为准则常见问题解答 或联系 opencode@microsoft.com,提出任何其他问题或意见。
FHIR® 是 HL7 的注册商标,经 HL7 许可使用。使用 FHIR 商标并不构成 HL7 对本产品的认可。