The purpose of this wiki page is to specify what a groovy template is, and how it will be used to help create hl7 messages.
During the weekly design call held on the 18th July 2012, (see here) it was decided to use groovy templates to create the hl7 messages.
We decided not to use the HAPI api in order to create hl7 messages. Instead, we will use groovy templates to create the required message in xml format.
Once the xml format of the message has been created, it may be translated into the pipe delimited format via conversion using the HAPI API.
Why use groovy to help create the message ?
We belive that using groovy will alow us to make the message specification as flexible as possible. For example, the HAPI API would allow us to set a series of specific message segments as follows-
pid.getPatientName(0).getGivenName().setValue(pat.getGivenName());
However,if we were to allow users to fill in message segments in this manner, it would prevent them from using specific data which they may need. For example, some users may want to store the data represented in a concept such as CIVIL STATUS as an obs, while yet others may want to store it in the PID segment of the message.
Furthermore, some users may prefer to use PatientIdentifier objects to set the patient id's, while yet others may use Person attributes to do so.
Groovy allows us to create a resuable template which gathers these data, and displayes them in a manner which makes it easy to edit / manipulate.
Specific guidelines for the hl7 message template:
The template is built of a series of sub-templates
A sub template is responsible for creating each of the main message segments (MSH, PID, PV1, ORC, OBR and OBX)
Each sub template may be in turn composed of several other sub templates.
The MSH segment is made up of all constants, and hence may not need to be composed of other segments.
The OBR segment is used to represent an OpenMRS obs group
Converting the HAPI object into the pipe delimited format is done using a HAPI PipeParser
PipeParser parser = new PipeParser(); String msg = null; try { msg = parser.encode(r01String); } catch (HL7Exception e) { log.error("Exception parsing constructed message."); }
pseudo-template for a R01 message (contributed by Darius)
/* template requires: patient, encounters */ <msh> (hard coded for this template)...</msh> ${ callTemplate("PID", patient) } <% encounters.each { encounter -> %> ${ callTemplate("PV1", encounter) } <% encounter.getAllObs().filter({ /* ungrouped only */ }) { obs -> %> ${ callTemplate("OBX", obs) } <% } %> <% encounter.getAllObs().filter({ /* obs groups only */ }) { group -> %> ${ callTemplate("obs-group-as-OBR-and-OBX", group) } <% } %> <% } %> Template with name "PID": <!-- assumes these vars are made available: patient == Patient object --> <pid> <> ${ callTemplate("XPN", [name: patient.personName)] }</> </pid> Template with name "XPN": <!-- assumes these vars are made available: name == PersonName object useFieldX == boolean for whether or not to show something. (optional parameter) --> <XPN> <XPN.1> <FN.1>${ name.familyName }</FN.1> <% if (name.familyName2) { %> <FN.2>${ name.familyName2 }</FN.2> <% } %> <% if (param.useFieldX == true) { %> <FN.X> ${name.getAttribute('asdf')}</FN.X> <% } %> </XPN.1> <XPN.2>${ name.givenName }</XPN.2> </XPN> Template with name "PatientIdentifier-to-CX": ... Template with name "PersonAttribute-to-CX": <!-- assumes these vars are made available: attribute == PersonAttribute identifierTypeHl7Code == String -->
Darius also recommends that we avoid calling the service layer directly from the templates. Therefore, to populate the fields with data, we will do as follows -
class R01Controller { public String handle() { Map<String, Object> bindings = new HashMap(); bindings.put("patient", ...); bindings.put("encounters", ...); bindings.put("fn", new TemplateFunctions()); return templateEngine.render(service.getTemplate("ORU-R01"), bindings); } }
Testing the hl7query module.
We will focus on test driven development to test the module
public class Hl7MessageGeneratorTest { Map<String, Object> defaultBindings; @Before public void beforeEachTest() throws Exception { // build a Patient and an Encounter in-memory } /** * Each template that we're going to write should be developed based on a test like this. * Basically you can build and debug the whole thing without ever even having to fully build the * module. */ public void testPatientIdentifierToCxTemplate() throws Exception { Hl7MessageGenerator mock = Mockito.mock(Hl7MessageGenerator.class); when(mock.getTemplate("PatientIdentifier-to-CX")).thenReturn(patientIdentifierToCxTemplate()); Map<String, Object> bindings = new HashMap<String, Object>(); bindings.put("patientIdentifier", buildPatientIdentifier()); String output = mock.applyTemplate("PatientIdentifier-to-CX", bindings); Assert.assertEquals("<CX>what it should look like</CX>", output); } /** * I've put this in its own method because we know that higher-level templates (e.g. Patient-to-PID) * will also need to include this same template. */ private String patientIdentifierToCxTemplate() { return "Template goes here, maybe you want to actually load it from an XML file"; } private PatientIdentifier buildPatientIdentifier() { // TODO } }
Converting the xml version of the hl7 message into the pipe delimited format.
1. Convert the xml object into string
2. Convert the string into the HAPI object
3. Use HAPI to convert the hl7 message into the pipe delimited format
Converting an hl7 String into the HAPI object
public Message processMessage(String hl7, String enterpriseId) throws HL7Exception { log.debug("Register handler applications for R01 and A28"); // TODO draw registered applications from database or configuration file router = new MessageTypeRouter(); router.registerApplication("ORU", "R01", new RHEA_ORU_R01Handler(enterpriseId)); router.registerApplication("ADT", "A28", new ADTA28Handler()); // TODO: any pre-parsing for HL7 messages would go here // First, try and parse the message Message message; try { message = parser.parse(hl7); } catch (EncodingNotSupportedException e) { throw new HL7Exception("HL7 encoding not supported", e); } catch (HL7Exception e) { throw new HL7Exception("Error parsing message", e); } // TODO: any post-parsing (pre-routing) processing would go here // If parsing succeeded, then try to route the message Message response; try { if (!router.canProcess(message)) throw new HL7Exception("No route for message"); response = router.processMessage(message); } catch (ApplicationException e) { throw new HL7Exception("Error routing HL7 message", e); } return response; } }