8. Examples

8.1. From FHIR to HL7v3 to database inserts

This example shows how the information of a FHIR message, i.e. what it seeks to express, can be coded in a HL7v3 message. Doing this tranformation is useful when the information of that FHIR message needs to be considered in concert with information of other messages, i.e. CDAs.

Note

By expressing al incoming messages as statements in the reference information model we gain the ability to query over all messages.

8.1.1. FHIR POST xml body

This is an instance of the given source document:

We are given a source FHIR Resource POST where the intent is to inform about a new patient, that is cared for by a careprovider. For chronic patients multiple care providers may be in play, and the overall treatment is managed by a managing organization. The patient insurance information is also exchanged.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Payload of the HTTP POST to http://mgrid.net/fhir/Patient This means that
     a new patient has been added to Portavita with the following details. -->
<Patient xmlns:ns2="http://www.w3.org/1999/xhtml"
         xmlns="http://hl7.org/fhir"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://hl7.org/fhir ../src/main/xsd/patient.xsd">
     <identifier>
             <!-- The Portavita role ID. -->
             <use value="official" />
             <system value="urn:oid:2.16.840.1.113883.2.4.3.31.1" />
             <value value="19889115" />
     </identifier>
     <identifier>
             <!-- The Portavita entity ID. -->
             <use value="secondary" />
             <system value="urn:oid:2.16.840.1.113883.2.4.3.31.2" />
             <value value="1629601" />
     </identifier>
     <identifier>
             <!-- The patient's BSN. -->
             <use value="usual" />
             <system value="urn:oid:2.16.840.1.113883.2.4.6.3" />
             <value value="010001128" />
     </identifier>
     <identifier>
             <!-- The patient number as used in the HIS. -->
             <use value="usual" />
             <system value="urn:oid:2.16.840.1.113883.2.4.3.31.3.3.1" />
             <value value="PV16296012637" />
     </identifier>
     <identifier>
             <!-- The polis number of the patient. The assigner is the reference
                  to the entity of the insurance company. -->
             <use value="usual" />
             <system value="urn:oid:2.16.840.1.113883.2.4.3.31.3.2.1.2" />
             <value value="P19889125" />
             <period>
                     <start value="2010-05-21T00:00:00" />
             </period>
             <assigner>
                     <reference value="Organization/27085" />
             </assigner>
     </identifier>
     <name>
             <use value="usual" />
             <text value="Slobsmeed - Schooltschuur, D" />
             <family value="Slobsmeed - Schooltschuur" />
             <given value="Deliah" />
     </name>
     <name>
             <use value="maiden" />
             <family value="Schooltschuur" />
     </name>
     <telecom>
             <system value="email" />
             <value value="deliah@versatel.nl" />
             <use value="home" />
     </telecom>
     <telecom>
             <system value="phone" />
             <value value="0123-456789" />
             <use value="home" />
     </telecom>
     <telecom>
             <system value="phone" />
             <value value="06-12345789" />
             <use value="mobile" />
     </telecom>
     <gender>
             <coding>
                     <system value="http://hl7.org/fhir/v3/AdministrativeGender" />
                     <code value="F" />
             </coding>
     </gender>
     <birthDate value="1951-09-09T00:00:00" />
     <deceasedBoolean value="false" />
     <address>
             <line value="Zijlweg 601-182" />
             <city value="HAARLEM" />
             <zip value="1234AB" />
             <country />
             <period>
                     <start value="2013-05-21T00:00:00" />
             </period>
     </address>
     <careProvider>
             <reference value="Provider/19889123" />
             <!-- Two extensions here: The first one denotes that this care
                  provider is a general practitioner. The second one denotes
                  the time from which the care provider started treating for
                  this patient. -->
             <extension url="http://portavita.eu/fhir/CareProviderRole">
                     <valueString value="gp" />
             </extension>
             <extension url="http://portavita.eu/fhir/CareProviderFromTime">
                     <valueDateTime value="2013-05-21T00:00:00" />
             </extension>
     </careProvider>
     <managingOrganization>
             <reference value="Organization/2637" />
     </managingOrganization>
</Patient>

8.1.2. RMIM Design

The intention of the FHIR message can be presented in the following RMIM design:

_images/rmim_patient_update.png

Note that in the rest of this example we assume that the input data will have the form as detailed by this RMIM design. Converting a FHIR Patient Update post to its corresponding HL7v3 message instance has to have happened apriori.

Note

MGRID XFM allows for the definition of XSL-transforms that can be applied to incoming FHIR messages. XFM operates using workers that communicate via queues in RabbitMQ. An XSL-transform worker will convert FHIR Patient Update to a HL7v3 Patient Update, and another XFM worker will use the converter detailed in this section to convert from HL7v3 Patient Update XML to SQL. A final worker will apply the SQL to a database.

8.1.3. MIF and XSD creation

The output of the RMIM designer is a Microsoft Vision .vsd file, together with a HL7 model .xml file. We need to convert these inputs to a MIF and XSD using the HL7 XML Publication Process, also known as hl7_v3publishingtools.

For this example we have an installation of the hl7_v3publishingtools in V3PUBLISH, and have setup a new domain in V3PUBLISH/defined-environment.properties:

#########################
# FOUNDATION DEFINITION #
#########################
#This section contains a few properties that define the semantic and
#functional foundations for this configured installation. They include the
#HL7 RIM, Vocabulary, and Data Types releases.

#package.target
#==============
#Designates the the target "version" for the publication. This may be a
#ballot or Normative Edition.  At present, values are encoded as "cnn" (as
#"c34"), with the following table of values in use:
#----------------------------------------------------------------------------|
# c08 Normative Ed 2005  |  c11 Normative Ed 2006  |  c19 Normative Ed 2008  |
# c21 Normative Ed 2009  |  c29 Normative Ed 2010  |  c33 Normative Ed 2011  |
# c34 Ballot2011JAN      |  c35 Ballot2011MAY      |  c36 Ballot2011SEP      |
# c36 Normative Ed 2012  |                         |                         |
#-----------------------------------------------------------------------------
#Enter one of these code. If left blank it will default to the most recent version.
#Default:package.target=c36
package.target=c33

#declared.datatype.release
#=========================
#Establishes whether the static model preparation and generation are intended
#to use Data Types release 1 (R1), release 1.1 (R11) or release 2 (R2). (Note
#that the RIM.xml file in the Generator/InputFiles/CommonSourceFiles controls
#the Generation process and can be changed with target 00.70...)
#Default:declared.datatype.release=R2
declared.datatype.release=R1

domains.active=tz

The RMIM design files are placed in V3PUBLISH/input/domains/tzdu/sourcegraphics.

A couple of steps should now be executed in sequence:

  • supp_02.40...POSITION_Source_XML_and_Visio.bat

    to copy the RMIM design file to the appropriate InputFiles location in preparation for the next steps.

  • supp_04.30...Generate_All_MIF_and_Schemas.bat

    to generate both an XSD and a MIF file from the RMIM design file.

After this the MIF can be found in V3PUBLISH/OutputFiles/MIF1-Lite. For parser generation we are looking for TZDU_MT000003UV.mif; i.e. from the Message Type (MT) for message 000003 in domain TZDU.

The XSD file can be found in V3PUBLISH/OutputFiles/Schemas. Here again we are interested in the MT file. Note that this file will depend on the datatype schemas which can be found in V3PUBLISH/OutputFiles/coreschemas, these should be copied also.

A HTML view of the defined message can also be generated by using supp_04.40...Generate_Tables_and_Excel.bat. This will result in

V3PUBLISH/OutputFiles/TableViews/TZDU_MT000003UV-NoEdit.html:

_images/rmim_html_view.png

8.1.4. Converter generation

The XSD can now be used to generate a parser using xsd2parser.py and the MIF can be used to determine the mapping from the message type to the RIM using mif12py.xsl. These steps are illustrated using a Makefile fragment taken from MGRIDMSG/custom directory:

# Message converter to be built and source standards to use
MIFDIR=    $(ROOT)/custom/models
MIFPYDIR=  $(ROOT)/generated/mif/custom
PARSERDIR= $(ROOT)/generated/parser/custom
XSDDIR=    $(ROOT)/custom/models

RIMVER=      233R1
DT=          datatypes.xsd datatypes-base_SDTC.xsd voc.xsd
TOPMIFS=     TZDU_MT000003UV
PARSERMIFS=  TZDU_IN000003UV

ALLMIF=    $(TOPMIFS)

MIFSRC=    $(patsubst %,$(MIFDIR)/%.mif,$(ALLMIF))
MIFPYOBJ=  $(patsubst %,$(MIFPYDIR)/%.py,$(ALLMIF))

PARSER=    $(patsubst %,$(PARSERDIR)/%_parser.py,$(PARSERMIFS))

all: $(PARSER) $(DTOBJ) $(MIFPYOBJ)

$(MIFPYDIR)/%.py: $(MIFDIR)/%.mif $(MIFPYDIR)/__init__.py $(ROOT)/generators/mif12py.xsl
     xsltproc $(ROOT)/generators/mif12py.xsl $< > $@

$(PARSERDIR)/%_parser.py: $(XSDDIR)/%.xsd $(PARSERDIR)/__init__.py
     $(ROOT)/generators/xsd2parser.py -o $@ -f -m --no-dates         \
     --no-versions --silence --member-specs=list --super $* $<

Finally a converter needs to be written:

#
# parse TZDU_MT000003UV aka PatientUpdate messages and rewrite them to MGRID
# SQL
#
# Copyright (c) 2013, MGRID BV Netherlands
#
# -*- coding: utf-8 -*-

from mgridenv import log

from lib.msg2sql import Msg2SQL

import generated.parser.custom.TZDU_IN000003UV_parser as parser
import generated.rim.rim233R1 as rim
from generated.rim.rim233R1_dt import xml2dt as datatypes
import generated.mif.custom.TZDU_MT000003UV

import sys

knownmt = ['TZDU_MT000003UV']

root = parser.parse(sys.argv[1])
converter = Msg2SQL(log = log, rim = rim, parser = parser, \
                             datatypes = datatypes, knownmt = knownmt)
print converter.convert(root)

This converter:

  • Uses the generated parser to build an in memory DOM
  • Builds a Msg2SQL specific to this conversion, containing the generated parser, the generated information about the RIM and datatypes used (not shown in Makefile fragment), and the known message types for this conversion.
  • Performs the conversion using the in memory DOM and Msg2SQL instance.

8.1.5. Converter output

The following Patient Update message instance is to be converted to SQL:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Payload of the HTTP POST to http://mgrid.net/fhir/Patient This means that
a new patient has been added to Portavita with the following details. -->
<PatientUpdate  xmlns="urn:hl7-org:v3" classCode="ACTN" moodCode="EVN">
  <recordTarget typeCode="RCT">
    <patient classCode="PAT">
      <id root="2.16.840.1.113883.2.4.3.31.3.3.1" extension="PV16296012637" />
      <patientPerson classCode="PSN" determinerCode="INSTANCE">
     <id root="2.16.840.1.113883.2.4.3.31.1" extension="19889115" />
     <id root="2.16.840.1.113883.2.4.3.31.2" extension="1629601" />
     <id root="2.16.840.1.113883.2.4.6.3" extension="010001128" />
     <name use="L">
       <given>Deliah</given>
       <family>Slobsmeed - Schooltschuur</family>
       <family qualifier="BR">Schooltschuur</family>
       Slobsmeed - Schooltschuur, D
     </name>
     <telecom use="H" value="deliah@versatel.nl"/>
     <telecom use="HP" value="0123-456789"/>
     <telecom use="MC" value="06-12345789" />
     <administrativeGenderCode code="F" codeSystem="2.16.840.1.113883.5.1"
                               codeSystemName="HL7 AdministrativeGender"
                               displayName="Female"/>
     <birthTime value="19510909"/>
     <deceasedInd value="false"/>
     <addr use="HP">
       <streetAddressLine>Zijlweg 601-182</streetAddressLine>
       <city>HAARLEM</city>
       <postalCode>1234AB</postalCode>
       <useablePeriod value="20130521000000"/>
     </addr>
     <asPolicyHolder classCode="POLHOLD">
       <id root="2.16.840.1.113883.2.4.3.31.3.2.1.2" extension="P19889125" />
       <effectiveTime value="20100521000000"/>
       <underwritingInsurer classCode="ORG" determinerCode="INSTANCE">
         <id root="2.16.840.1.113883.2.4.3.31.2" extension="27085"/>
       </underwritingInsurer>
     </asPolicyHolder>
     <asCareGiver classCode="CAREGIVER">
       <code code="GP"/>
       <effectiveTime value="20130521000000"/>
       <careGiverScoper classCode="PSN" determinerCode="INSTANCE">
         <id root="2.16.840.1.113883.2.4.3.31.1" extension="19889123"/>
       </careGiverScoper>
     </asCareGiver>
      </patientPerson>
      <providerOrganization classCode="ORG" determinerCode="INSTANCE">
     <id root="2.16.840.1.113883.2.4.3.31.2" extension="2637" />
      </providerOrganization>
    </patient>
  </recordTarget>
</PatientUpdate>

We call the converter from the shell command line:

Note

The converters can be called from the shell, but can also be included in your program directly. In XFM, for instance, the converter is contained in a RabbitMQ connected worker.

mgrid@mgdu1004:~/mgrid-messaging/custom$ python convert_TZDU_IN000003UV.py \
                                         tests/source/TZDU_IN000003UV/pat-1-post.xml

Which outputs the information rewritten as insert statements using function calls against the RIM:

DO
$$DECLARE
controlact0 bigint;
person1 bigint;
person2 bigint;
role3 bigint;
organization4 bigint;
role5 bigint;
organization6 bigint;
patient7 bigint;
participation8 bigint;
BEGIN
controlact0 := ControlAct_insert(_mif := 'TZDU_MT000003UV',
   _clonename := 'PatientUpdate', "classCode" := 'ACTN', "moodCode" := 'EVN');
person1 := Person_insert(_mif := 'TZDU_MT000003UV', _clonename := 'Person',
   "addr" := adinval("city" := adxpinval("mediaType" := 'text/plain',
      "representation" := 'TXT', "content" := 'HAARLEM'),
      "postalCode" := adxpinval("mediaType" := 'text/plain',
      "representation" := 'TXT', "content" := '1234AB'),
      "streetAddressLine" := adxpinval("mediaType" := 'text/plain',
         "representation" := 'TXT', "content" := 'Zijlweg 601-182'),
      "use" := 'HP', "useablePeriod" := sxcm_tsinval("operator" := 'I',
         "value" := '20130521000000')),
   "administrativeGenderCode" := ceinval("code" := 'F',
      "codeSystem" := '2.16.840.1.113883.5.1',
      "codeSystemName" := 'HL7 AdministrativeGender', "displayName" := 'Female'),
   "birthTime" := tsinval("value" := '19510909'), "classCode" := 'PSN',
   "deceasedInd" := blinval("value" := False), "determinerCode" := 'INSTANCE',
   "id" := iiinval("extension" := '19889115',
      "root" := '2.16.840.1.113883.2.4.3.31.1') ||
           iiinval("extension" := '1629601',
      "root" := '2.16.840.1.113883.2.4.3.31.2') ||
           iiinval("extension" := '010001128',
      "root" := '2.16.840.1.113883.2.4.6.3'),
   "name" := eninval("family" := enxpinval("mediaType" := 'text/plain',
         "representation" := 'TXT', "content" := 'Slobsmeed - Schooltschuur') ||
      enxpinval("mediaType" := 'text/plain', "qualifier" := 'BR',
         "representation" := 'TXT', "content" := 'Schooltschuur'),
      "given" := enxpinval("mediaType" := 'text/plain',
         "representation" := 'TXT', "content" := 'Deliah'),
      "use" := 'L', "content" := 'Slobsmeed - Schooltschuur, D'),
   "telecom" := telinval("use" := 'H', "value" := 'deliah@versatel.nl') ||
      telinval("use" := 'HP', "value" := '0123-456789') ||
      telinval("use" := 'MC', "value" := '06-12345789'));
person2 := Person_insert(_mif := 'TZDU_MT000003UV', _clonename := 'CareProvider',
   "classCode" := 'PSN', "determinerCode" := 'INSTANCE',
   "id" := iiinval("extension" := '19889123',
      "root" := '2.16.840.1.113883.2.4.3.31.1'));
role3 := Role_insert(_mif := 'TZDU_MT000003UV', _clonename := 'CareGiver',
   "classCode" := 'CAREGIVER', "code" := ceinval("code" := 'GP'),
   "effectiveTime" := sxcm_tsinval("operator" := 'I',
      "value" := '20130521000000'), "player" := person1, "scoper" := person2);
organization4 := Organization_insert(_mif := 'TZDU_MT000003UV',
   _clonename := 'Insurer', "classCode" := 'ORG',
   "determinerCode" := 'INSTANCE', "id" := iiinval("extension" := '27085',
      "root" := '2.16.840.1.113883.2.4.3.31.2'));
role5 := Role_insert(_mif := 'TZDU_MT000003UV', _clonename := 'PolicyHolder',
   "classCode" := 'POLHOLD', "effectiveTime" := sxcm_tsinval("operator" := 'I',
      "value" := '20100521000000'), "id" := iiinval("extension" := 'P19889125',
      "root" := '2.16.840.1.113883.2.4.3.31.3.2.1.2'), "player" := person1,
   "scoper" := organization4);
organization6 := Organization_insert(_mif := 'TZDU_MT000003UV',
   _clonename := 'Organization', "classCode" := 'ORG',
   "determinerCode" := 'INSTANCE', "id" := iiinval("extension" := '2637',
      "root" := '2.16.840.1.113883.2.4.3.31.2'));
patient7 := Patient_insert(_mif := 'TZDU_MT000003UV', _clonename := 'Patient',
   "classCode" := 'PAT', "id" := iiinval("extension" := 'PV16296012637',
   "root" := '2.16.840.1.113883.2.4.3.31.3.3.1'), "player" := person1,
   "scoper" := organization6);
participation8 := Participation_insert(_mif := 'TZDU_MT000003UV',
   _clonename := 'RecordTarget', "act" := controlact0, "role" := patient7,
   "typeCode" := 'RCT');
END$$;

When posted against a database with the used RIM schema and MGRID HDL:

mgrid@mgdu1004:~/mgrid-messaging/custom$ python convert_TZDU_IN000003UV.py \
   tests/source/TZDU_IN000003UV/pat-1-post.xml | psql test
WARNING:  code 'GP' not found in valueset(s) bound (no exceptions) to conceptdomain
          RoleCode
HINT:  Set hdl.concept_input_mode to permissive or enforcing to get a warning or error
       for unknown codes.
PL/pgSQL function role_insert(text,text,cv,set,"II","LIST_II",bigint,bigint,cv,
                              "SET_II",cv,bl,"BAG_EN","BAG_AD","BAG_TEL",cv,"GTS","ED",
                              set,"RTO",integer,list_int) line 3 at SQL statement
PL/pgSQL function inline_code_block line 19 at assignment
DO

This example shows coding errors, indicating that the chosen RoleCode does not yet correspond with allowed RoleCodes.

Finally we pose some queries to the test database containing only this PatientUpdate:

test=# \x
Expanded display (expanded) is on.

test=# select * from "ControlAct";
-[ RECORD 1 ]-------+--------------------------------------------------------------------------------
_id                 | 1
_mif                | TZDU_MT000003UV
_clonename          | PatientUpdate
nullFlavor          |
realmCode           |
typeId              |
templateId          |
classCode           | ACTN:2.16.840.1.113883.5.6@2010-11-10:2.16.840.1.113883.1.11.11527@2010-11-10
moodCode            | EVN:2.16.840.1.113883.5.1001@2010-11-10:2.16.840.1.113883.1.11.10196@2010-11-10
id                  |
code                |
actionNegationInd   |
negationInd         |
derivationExpr      |
title               |
text                |
statusCode          |
effectiveTime       |
activityTime        |
availabilityTime    |
priorityCode        |
confidentialityCode |
repeatNumber        |
interruptibleInd    |
levelCode           |
independentInd      |
uncertaintyCode     |
reasonCode          |
languageCode        |
isCriterionInd      |
payload             |
queryEvent          |

test=# select count(*) from "Person" where
       "administrativeGenderCode" = 'F'::"AdministrativeGender";

 count
-------
     1
(1 row)