Mapping between CCDA and FHIR

Apr 18, 2016

Across 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:

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