In the previous post, we described a simple application to help develop transformation maps conformant with the FHIR mapping language. Let’s now take a look at using that app to actually develop a simple map.
As described in that post, the overall process we’ll follow is:
Determine the input format, and create a logical model (StructureDefinition) that represents that format. We’ll also need a sample file to work with that is conformant to that model.
Determine the output format and create any models needed (this is not required if we’re going to use the standard FHIR types – which we will do in this case)
Create the map and convert it to a StructureMap resource
Execute it using the sample file.
Fix the bugs / enhance the map and repeat.
We’ll start with a simple input file – a list of patient data that we want to convert into a FHIR bundle and send to a server. We’ll make a few assumptions to keep things simple (later posts will examine some of these assumptions)
The file is already in an appropriate json format.
The elements are all FHIR compliant – e.g. the date of birth is in the correct format and the gender values are conformant to the FHIR values.
Each patient has got an id that we can use which is correct for the target server.
And the output will be a Bundle of Patient resources.
Creating the source definition
The next first is to create the model to represent the input file. We’ll use the clinFHIR Logical Modeller and the hapi-3 public server for this – though any server that supports StructureDefinitions will to. It does need to be the same FHIR version as the mapping server – so that means we’ll be using the Vonk STU3 server to convert the maps and execute the transform.
Load clinFHIR, and set the servers to public hapi-3 (actually we really only need the conformance server). Select the Logical Modeler and create a new model, specifying a unique id (remember this id as you’ll need it soon). Don’t base the new model on any existing resource type. Next, add the elements to define the model – for now, make all elements a string datatype. Here’s the model used for this example:
Enter a caption
Make sure the model has been saved on the hapi server, and return to the Mapping app.
Creating the map
Now we’re ready to create the map.
Start by logging in to the clinFHIR mapping application (if you haven’t already) then click the gear icon at the upper right to set the server configuration. Here’s what I had (and refer to the previous post for a description of what each one does). The 2 that are most significant for now are the Conformance server (where the logical models describing the source are created) and the location of the Mapping server. I’m using the STU3 version – I could just as easily used Release 4.
Now click the ‘new map’ button at the top in the navbar. Give the map an id (this will become the id of the StructureMap resource so needs to be valid and unique on the mapping server – the ‘Check’ button confirms this) and a name and description. Leave the ‘Use sample’ checkbox unchecked, and save the map.
Into the ‘sample input’ edit area on the left of the Workspace enter the sample file:
Note that the sample file has a ‘resourceType’ element. This is needed by the Vonk/Healex engine to associate it with the definition (I’m not sure if this is a specific requirement of this engine or not – it doesn’t need to be included in the definition). We’ll talk in a minute about that definition.
Into the ‘mapping file’ edit area on the right enter the following text:
Some notes about this map.
The first line gives the map a name. It doesn’t really matter (for now) what this name is.
Line 3 refers to the model you just created that describes the input model, and describes it as an input – or source – definition to the map. Note that the url domain is http://hl7.org/fhir/StructureDefinition – this is required at the moment by the Vonk engine and is set when the input model is imported as described below. This requirement will change in future versions. The id is the same as the one you used for the model. The alias is not used in this example.
After this comes the definition of the output – a core FHIR Patient resource. You don’t need to add the Bundle resource – this comes automatically (I presume that this is supplied by the Vonk mapping engine – other mapping engines may require it)
Then there are 2 groups, each containing a number of rules with each rule ending with a semicolon (;). Each rule has a similar structure – it declares variables, then there’s a ‘->’ and then it does something with those variables. All of the variables must have a value, or the rule won’t execute (ie it will be ignored).
The first group is the one that is invoked by the engine when the map executes. It can call other groups as required (and those groups can in turn call other groups). The parameters to this group are the input file as the source, and a bundle as the target.
This group has 3 rules:
The first rule gives the bundle an id using the function uuid() which is supplied by the mapping engine.
The next rule sets the bundle type to ‘transaction’. Note that at this time we’re not creating a valid transaction bundle – but we will be in later posts.
The final rule in this group is the most interesting. It declares the variable ‘pat’ from the source then creates an entry for the bundle, creates a Patient resource and sets it to the entry.resource element, and finally calls the makePatient group passing in the pat variable (from the input file) as the source and the newly created patient resource as the target. This rule will execute for each patient element in the sample file, thus creating – and populating – a Patient resource for each one from that element.
The second group is like a subroutine that receives the patient element from the input file (as the source variable ‘pat’) and the Patient resource created in the parent group as the target. It is responsible for populating the elements of the patient resource (the target) from the source. We’ve placed each element as a separate rule – we could have them all in a single rule if we wanted (remembering that all variables must have a value for the rule to execute)
Here’s what it looks like so far:
Once you have the sample and the map entered, click the ‘Update StructureMap resource’ button to save the updates to the Mapping server.
If you have syntax errors in the map, then it won’t save and you have to correct the errors first. Errors are displayed above the map. Once it has saved, an ‘Execute Map’ button appears. If you click it now then you’ll get an error – you haven’t imported the definition of the input file – which we’ll do now.
Note that the at this point that StructureMap resource (which also has the sample file map and map as extensions) has been saved on the mapping server).
To import the source definition, click the ‘Projects’ tab at the top of the screen. You should see the definitions referenced by the map appear to the bottom left.
(If the definitions aren’t present then just reload the page. Provided that the map updated OK then it will have been saved (ie a StructureMap resource created).
Click on the ‘Import’ link to the right of the resource name. This will cause the app to read the model from the Conformance server (that you configured above – it must be the same as where you created the model), adapts it to Vonks needs (this is a temporary requirement) and saves it to the Mapping Server. You’ll get a message when this is done – and a tree view of the model will appear to the right. You can click on any element in the tree to see its definition from the model.
As an aside, you can import the definition ay number of times – and you will need to do so whenever you updated the model There’s a handy link that will load the Logical Modeler with the model selected, ready to edit. If you do make a change, remember to save the model in the Logical Modeler and re-import in the Mapping app.
Here’s a screen dump of the projects page with the source model displayed (after importing the source model):
Once that is done, you can return to the Workspace tab and click execute. All going well, the transform should succeed and the ‘Results’ tab will appear with the various views of the output present.
If there are errors they will be displayed below the maps and you’ll need to update the map and try again. You shouldn’t need to import the definitions again (until you change the structure of the sample input) – just repeat the process of updating the map then executing until everything is working as you expect.
Note that any errors returned should be in an OperationOutcome – but sometimes are not. I’ll also display the raw return – but for now you can check the browser network history if you don’t get a sensible error.
So that’s enough for a simple transform. In the next post we’ll go a bit deeper and think about:
Making this a real transaction
An identifier rather than an id
Adding other resources into the mix
And “What To Do” when the values for coded elements (like gender) don’t match. (Though, it’s likely that this deserves a post on its own).
And a final word – this works at the time of writing. It is not the only way that this could be done – nor necessarily the best way. Future iterations of the mapping language may requires changes. And I’ve not tested it against other mapping engines, so changes may be required for them also. As much as anything, the idea is to stimulate interest and discussion in the mapping language – and hopefully persuade other mapping engine vendors and server vendors to support the $convert and $transform operations used by the app.
I’d also like to acknowledge Vadim’s support in helping me understand how maps work – any faults are mine!