TestsTested | ✓ |
LangLanguage | SwiftSwift |
License | Apache 2 |
ReleasedLast Release | Mar 2016 |
SPMSupports SPM | ✗ |
Maintained by Eric Whitley.
Depends on: | |
GRMustache.swift | ~> 0.11.0 |
Fuzi | = 0.3.0 |
Try | ~> 1.0.0 |
CDAKit for iOS, the Open Source Clinical Document Architecture Library with HealthKit Connectivity. Helping you quickly manage CDA-structured health data.
CDAKit provides C32 and C-CDA import and export functionality as well as the ability to connect CDA concepts with HealthKit samples. This allows for bridging between CDA and HealthKit so you can integrate with an Electronic Medical Records system.
CDAKit’s models, importer, and exporter are based on the Ruby Health-Data-Standards project (HDS), which was funded by the US Office of the National Coordinator for Health Information Technology (ONC) and built primarily by MITRE Corporation (MITRE).
You can read more about some of the rationale behind CDAKit here: LinkedIn posts.
CDAKit wouldn’t exist without the Ruby Health-Data-Standards library. In particular, the following individuals whose leadership, commitment, and hard work are helping drive healthcare interoperability:
let doc = ((supply your CDA XML string))
do {
//let's try to import from CDA
let record = try CDAKRecord(fromXML: doc)
//let's create a new vital
// use the coded values to govern "meaning" (height, weight, BMI, BP items, etc.)
let aVital = CDAKVitalSign()
aVital.codes.addCodes("LOINC", code: "3141-9") //weight
aVital.values.append(CDAKPhysicalQuantityResultValue(scalar: 155.0, units: "lb"))
aVital.start_time = NSDate().timeIntervalSince1970
aVital.end_time = NSDate().timeIntervalSince1970
//append our height to our record
record.vital_signs.append(aVital)
//OK, let's convert our CDAK record to HealthKit
let hkRecord = CDAKHKRecord(fromCDAKRecord: record)
//let's explicitly set our preferred units to metric for a few things
CDAKHealthKitBridge.sharedInstance.CDAKHKQuantityTypeDefaultUnits[.HKQuantityTypeIdentifierHeight] = "cm"
CDAKHealthKitBridge.sharedInstance.CDAKHKQuantityTypeDefaultUnits[.HKQuantityTypeIdentifierBodyMass] = "kg"
//now let's convert back from HealthKit to our model
let cdakRecord = hkRecord.exportAsCDAKRecord()
//render from our model to CDA - format set to .ccda (could also do .c32)
print(cdakRecord.export(inFormat: .ccda))
}
catch {
//do something
}
CDAKit proudly uses the following projects:
Reference CDAKit in your app
import CDAKit
CDAKit uses a “Record” object (CDAKRecord
) that represents the core CDA document. Within the record are individual entries (CDAKEntry
) with various more precise types for elements like encounters, medications, allergies, etc.
CDAKRecord: Central record
CDAKEntry: Your detailed CDA content. These may either be CDAKEntry
or more specific subclasses like CDAKAllergy
, CDAKMedication
, CDAKEncounter
, etc.
CDAKCodedEntries and CDAKCodedEntry: These represent the pairing (or collections of pairings) or a medical terminology system and a concept code.
CDAKPhysicalQuantityResultValue: A pairing of a unit of measure (lb, in, mm, etc.) and an associated value (1, 2.3, 75, etc).
Entries can contain codes
which represent vocabulary-encoded concept codes. A “BMI,” for example, can be represented with LOINC:39156-5 (Body mass index (BMI) [Ratio]). In the absence of coded data, the concept is effectively “meaningless” for the purposes of interoperability. While you and I may be able to read a textual “BMI” description, coded vocabulary entries are essential for machines to interpret the intended meaning of a piece of information.
Adding coded data to an Entry
let aVital = CDAKVitalSign()
aVital.codes.addCodes("LOINC", code: "3141-9") //weight
The vocabulary keys are flexible, but there are some fixed keys to help ensure concepts that have “preferred” vocabularies are able to resolve the choice of preferred vs. translation entries.
The list of known, commonly-used keys is available through CDAKVocabularyKeys
.
You could then use these constants as follows:
let aVital = CDAKVitalSign()
aVital.codes.addCodes(CDAKVocabularyKeys.LOINC, code: "3141-9") //weight
This is probably preferred just to ensure consistency and ensure any associated OID lookups succeed.
You can also supply an optional descriptive displayName
which would appear in the finalized coded results.
let aVital = CDAKVitalSign()
aVital.codes.addCodes(CDAKVocabularyKeys.LOINC, code: "3141-9", displayName: "Body Weight") //weight
Import your CDA XML from wherever it might be. If you need some sample CDA files, Bostron Children’s Hospital has set up a great repository.
Once you have your XML, you can try
to create a CDAKRecord
from it by parsing the String
. The XML is expected to be a well-formed XML document with a ClinicalDocument
root element.
let myXML:String = ((Get some CDA XML))
do {
//try to import some CDA XML
let record = try CDAKRecord(fromXML: myXML)
}
catch {
}
CDA parsing-specific errors may be of a few types, all defined in CDAKImportError
public enum CDAKImportError : ErrorType {
case NotImplemented
case UnableToDetermineFormat
case NoClinicalDocumentElement
case InvalidXML
}
ClinicalDocument
, but contains no known template OIDs.ClinicalDocument
root elementOnce your CDAKRecord is populated you can export it to one of the supported CDA XML formats.
The formats are enumerated
let myOutboundCDAXML = cdakRecord.export(inFormat: .ccda)
print(myOutboundCDAXML)
CDAKit uses the templateId
OIDs within the XML file’s ClinicalDocument
header to determine format (found in bulk_record_importer
).
By default, failing to detect any of those OIDs will result in a document parsing exception. Some document templates may contain valid CDA, but will not use one of those OIDs. They might, instead, use only the “US General Realm” header.
You can attempt to find any “might be supported” files by specifying the attemptNonStandardCDAImport
flag in the global CDAKit singleton before attempting import.
CDAKGlobals.sharedInstance.attemptNonStandardCDAImport = true
From there you can then attempt to import an unspecified CDA file
CDAKGlobals.sharedInstance.attemptNonStandardCDAImport = true //enable wider support
let myCDAXMLWithoutTheRightType:String = ((Get some non-standard CDA XML))
do {
//try to import some CDA XML
let record = try CDAKRecord(fromXML: myCDAXMLWithoutTheRightType:String)
}
catch {
}
You can transform between the CDA models and HealthKit models in order to bridge the two different representations. An extended discussion of the approach can be found here.
This process uses an intermediate CDAKHKRecord
model to house the HealthKit samples. If you have existing HealthKit samples, just just need to add them to the samples
collection. Ideally, you’re also able to provide some basic name and gender information.
You will likely experience two challenges:
The HealthKit bridge provides default behaviors for both of these, but you will almost certainly wish to alter them to suit your specific needs.
Importing from CDA to HealthKit can be done fairly quickly by converting a CDA-oriented CDAKRecord into HealthKit-compatible record. To do so, you can just initialize a (HealthKit) CDAKHKRecord
from a (CDA) CDAKRecord
.
let doc = ((my CDA XML))
do {
//try to import some CDA XML
let record = try CDAKRecord(fromXML: doc)
//OK, let's convert our CDAK record to HealthKit
let hkRecord = CDAKHKRecord(fromCDAKRecord: record)
for sample in hkRecord.samples {
print(sample)//these are all HKQuantitySamples
}
}
catch {
}
You can quickly transform a HealthKit CDAKHKRecord
into a CDA-oriented CDAKHKRecord
using the exportAsCDAKRecord
method. This reads all samples
in the HealthKit bridging record and attempts to transform the clinical concept and associated unit representations into a CDA structure.
//our wrapping record for HealthKit stuff
let hkRecord = CDAKHKRecord()
//Let's create a HealthKit height
let aUnit = HKUnit(fromString: "in")
let aQty = HKQuantity(unit: aUnit, doubleValue: 72 )
let aQtyType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierHeight)
let hkHeight = HKQuantitySample(type: aQtyType!, quantity: aQty, startDate: NSDate(), endDate: NSDate())
//store the sample in our record
hkRecord.samples.append(hkHeight)
//convert the HealthKit record to CDAKRecord
let cdakRecord = hkRecord.exportAsCDAKRecord()
The HealthKit bridge defaults output units to settings managed through a preferences file.
If you wish to default preferred units for a given set of quantity types you can do so per HealthKit sample type identifier.
CDAKHealthKitBridge.sharedInstance.CDAKHKQuantityTypeDefaultUnits[.HKQuantityTypeIdentifierHeight] = "cm"
You can modify large groups of default units by changing CDAKHKQuantityTypeDefaultUnits
, which is the dictionary where unit maps are stored at runtime.
Example:
["HKQuantityTypeIdentifierBasalBodyTemperature", "degF"]
This is initially loaded from the local CDAKitDefaultSampleTypeIdentifierSettings
resource file in CDAKit.
The plist uses the unit
key and associated string
value
<key>HKQuantityTypeIdentifierBasalBodyTemperature</key>
<dict>
<key>unit</key>
<string>degF</string>
If you want to export HealthKit objects, you may want to let the user’s personal unit settings drive the HKUnit selection (where possible). For example, we may import a “body temperature” in Fahrenheit, but a user may prefer to see that information rendered in Celsius. If their personal unit settings for a quantity type are set, this will overwrite the global defaults with the user’s preferred setting.
This feature uses HKHealthStore. It is assumed all authorizations will be managed by your application.
This functionality modifies the HealthKit samples - NOT the native CDA data stored in the CDAKRecord. You must set the unit preferences BEFORE you export a HKRecord (exportAsCDAKRecord). Once exported to a CDAKRecord, any changes to the bridge’s unit types will not be reflected. CDA unit types are fixed strings.
HealthKit does this on a background thread, so be careful how you handle things on the UI.
//create an instance of the HealthKit management service
let healthKitStore:HKHealthStore = HKHealthStore()
//NOTE: you'd need to handle authorizations for your specific sample types
//attempt to set the HealthKit bridge's desired target units to whatever the user's device preferences might be
CDAKHealthKitBridge.sharedInstance.setCDAKUnitTypesWithUserSettings(self.healthManager.healthKitStore)
This is the most complex and “personal preference” aspect of mapping between HealthKit and CDA. When, for example, you export a HealthKit glucose HKQuantityTypeIdentifierBloodGlucose
sample to CDA, which clinical vocabulary and concept code would you like to use? Which measurement units would be most appropriate? Likewise, when importing from CDA to HealthKit, how do you infer that “LOINC:2345-7” is a blood glucose measurement? And how do you transform the variety of CDA-based UCUM units into Apple’s expected “mg/dl”?
To help, CDAKit provides two key mechanisms:
HKQuantityTypeIdentifierBloodGlucose
and asserts a specific set of vocabulary types and coded valuescdaStringUnitFinder
closure variable may (and should) be used by developers to change this behavior to suit their particular needs.Vocabulary mapping simply tries to tie a HealthKit quantity sample type identifier to a set of known vocabulary types and associated codes.
In CDA, you might receive a lab result with the following coded entry XML:
<code
codeSystem="2.16.840.1.113883.6.1"
codeSystemName="LOINC"
code="2345-7"
displayName="Glucose, Serum" />
How do we transform that into a HKQuantityTypeIdentifierBloodGlucose
?
We simply provide a map from “LOINC:2345-7” to HKQuantityTypeIdentifierBloodGlucose
.
CDAKit provides two maps to help with this:
import
: used exclusively when importing values from CDA to HealthKitexport
: used exclusively when exporting values from HealthKit to CDACDAKit provides default curated “starter” maps, but you can override all values via the loadHealthKitTermMap
method. This method accepts a Swift dictionary [String:AnyObject] based on a structure defined below:
code
, displayName
, and mapping restriction. code
is the vocabulary concept code (EX: 39156-5) for the associated displayName
(EX: “Body mass index (BMI) [Ratio]”). <key>HKQuantityTypeIdentifierBodyMassIndex</key>
<dict>
<key>LOINC</key>
<array>
<dict>
<key>code</key>
<string>39156-5</string>
<key>displayName</key>
<string>Body mass index (BMI) [Ratio]</string>
<key>mapRestriction</key>
<string>both</string>
</dict>
</array>
... (more vocabularies)
</dict>
The default unit mapping is a prototype and very brittle. It simply uses a combination of regular expressions and “best guesses” to attempt to transform a set of UCUM-like unit strings into something HealthKit can utilize.
If developers have known CDA structures they would like to handle, it is suggested that they override the default unit matching algorithm.
To do so, simply provide your own custom closure via the cdaStringUnitFinder
variable.
public var cdaStringUnitFinder : ((unit_string: String?, typeIdentifier: String? ) -> HKUnit?)?
This mock example would simply look for anything like “beats” and transform it into the HealthKit-compatible “count/min”.
var cdaStringUnitFinder : ((unit_string: String?, typeIdentifier: String? ) -> HKUnit?) = {
(unit_string: String?, typeIdentifier: String?) -> HKUnit? in
if unit_string == "beats" {
return HKUnit(fromString: "count/min")
}
return nil
}
//Tell CDAKit to use your custom unit finder
CDAKHealthKitBridge.sharedInstance.cdaStringUnitFinder = cdaStringUnitFinder
HKQuantitySample created by CDAKit’s record import process will include a few custom metadata keys. These are intended to help you identify and manage CDA-based samples (merging, deletion, etc.).
public enum CDAKHKMetadataKeys: String {
case CDAKMetadataRecordIDRoot
case CDAKMetadataEntryHash
}
You can also supply your own Swift [String:AnyObject]
dictionary to add additional custom metadata.
This work is Apache 2 licensed.