Mapping between CCDA and FHIR
Apr 18, 2016Across the board, many FHIR implementers have the same challenge: mapping between some other format, and a set of FHIR resources. Right now, the pressing issue is mapping between CCDA and FHIR (in both directions). At the HL7 Orlando meeting in January 2016, we held a Birds of a Feather session about mapping. At this meeting, there was general consensus that we - the FHIR community - would like to have a mapping framework that * allows us to exchange maps that express detailed transforms from one format to another (in terms of aprevious post, executional maps)
- means that the transforms are portable e.g. the same transform can be run on multiple different system
- can be shared through a FHIR repository
- allows to move data into and out of FHIR resources (or between them)
While we were at it, we noted that it would be pretty cool if the language could be used for transforms that didn’t involve FHIR either. And we decided that we’d focus on CCDA <-> FHIR as a primary use case, since that’s an operational issue for so many people. Also, we noted that there’s no existing standard that meets these requirements, or that can easily meet them. MDMI was brought forward as a candidate, but it’s not clear that MDMI solves the problems we usually encounter.
After the meeting, I sat down with Keith Duddy, one of the editors of the other leading candidate specification, which is QVT. After a long discussion about what we were trying to do, and a review of the possible candidates, Keith and I designed a mapping language for FHIR that is very much based on QVT, but leverages a number of design features and philosophies from the existing FHIR work. The work includes an abstract syntax (which is a resource, the StructureMap resource), and a concrete syntax, and a transform host API that delegates implementation specific transformation actions to the host engine. In addition, we’ve prototyped it in the context of CCDA –> FHIR Mapping. Btw, this arrangement means that Keith can take all the credit for the good parts, and I can get the blame for all the other parts.
So this mapping language is published, and open for community review. I expect that there will be robust discussion: is it rich enough? too complex? why do we need to do this? can’t we just use [insert name here]. And maybe there’s some candidate out there we haven’t found… so to help with the evaluation, we’re working on the transform from CCDA to FHIR for the Montreal connectathon.
So here’s an initial contribution to get discussion going:
- Structure Definitions for CDA
- Mapping files for CCDA -> FHIR (as yet, header only)
- Transform engine that can transform from CDA to FHIR given the maps (see below for instructions)
- FHIR source files for the engine
- example ccd for the conversion
Download all these, and then you can execute the validator jar with the following parameters:
ccd.xml -transform -defn validation-min.xml.zip
-txserver http://fhir2.healthintersections.com.au/open
-folder [cda structure definitions=] -folder [map files folder]
-map http://hl7.org/fhir/StructureMap/cda
-output bundle.xml
The transform will produce bundle.xml from the CCDA input files. Alternatively, if you want the java source, see org.hl7.fhir.dstu3.utils.Transformer in the FHIR svn
Transform Pseudo Code
To help other implementations along, here’s a bunch of pseudo code for the transform engine (it’s actually pretty simple!). Note that to use this, you need to two things:
Object model - both the source object model, and a target object model express the same meta-level API, which has the following features:
-
- list children (name) returns list of Value : given a name, return all the children that have the given name
-
- make child (name) returns Value: create the appropriate object, and return it (can’t be used if the object is polymorphic)
-
- create (typename) returns Value: create an object of the specified type name (type name comes from the mapping language)
-
- set property(name, Value): set the property name to value. if name is cardinality 0..*, add to the list
And then you need a FluentPath engine that compiles and evaluates fluent path expressions:
-
- parse(string) returns expression: parse the string expression
-
- evalute(expression, Value) returns boolen: apply the expression to a value, and see whether it is true or not
Here’s the logic for the structure map transform
transform(Value src, StructureMap map, Value tgt)
create new variables
add src as new variable "src" in mode source to variables
add tgt as new variable "tgt" in mode target to variables
run transform by group (first group in map)
transform by group(group, variables)
transform for each rule in the group
transform by rule(rule, variables)
clone the variables (make us a new copy so changes don't propagate backwards)
check there's only one source (we don't handle multiple sources yet)
for each matching source element (see logic below - a list of variables)
- for each target, apply the targets
- for each nested rule, transform by rule using the variables
- for each dependent rule, apply the dependent rule
processing dependent rules
look through the current map for a matching rule (by name)
look through all the other known maps (in the library) for a matching group (by name)
if match count != 0, it's an error
check the variable cont provided matches the variable count expected by the group (and check whether they are source or target)
make a new variables
for each parameter, get the variable given the name, and add it to the new variables (in order)
transform by the group using the new variables
Finding Match Source Elements(source, variables)
get the source variable with the name "source.context"
- check it's not null (error if it is)
if there's a condition, get the fluent path engine to check it against the source variable
- if it fails, return an empty list
if there's a check, get the fluent path engine to check it against the source variable
- if it fails, blow up with some error message
if there's a source.element
get a list of the children with the name source.element from the source variable
else
turn the source variable into a list
for each element in the list from the previous step,
clone source variables
if there's a source.variable, add the element as to the cloned variable list using the source.element name
add the closed variable list to the results
return the results
Apply a target(target, variables)
get the target variable with the name "target.context"
- check it's not null (error if it is)
check there's an element (todo: support this)
if there's a transform
value = run the tranform
set the given name on the target variable to the value
else
value = make the given name on the target variable
Run the transform(parameters, vars)
depends on the transform:
create: ask the object API to create an object of the type in parameter 1
copy: return the value of parameter 1 (see below)
evaluate: use the fluent path engine to execute aginst the p value of
parameter 1 using the string value of parameter 2 as the expression
pointer: return the correct URL to reference the object that is the value of parameter 1
- which is right is a deployment decision
The value of a parameter
if it's an id (e.g. not a constant, just a word)
then it's a reference to the named variable
else
it's some kind of constant