40.10.1HL7 v2.x Mapping API

 

The JavaScript execution API can be used to manipulate HL7 v2.x messages. This is typically done in conjunction with the HL7 v2.x Endpoint modules.

When working with HL7 v2.x messages, the message object may be treated as a special kind of Map where the map key corresponds to a path within the message.

For example, the following examples show getting and setting values from an HL7 v2.x message.

// This variable holds the actual HL7 v2.x message
var rawMsg = theMessage.rawMessage;

// Retrieve a value
var messageType = rawMsg['MSH-9-1'];
// Set a value
rawMsg['PID-5[2]-1'] = 'Jones';

40.10.2Path Syntax: Repetitions

 

For any element that is repeatable (i.e. segments and fields), you may add [n] at the item, where n is 0-indexed. If this is omitted, the first repetition is implied. For example:

  • OBX refers to the first OBX segment in the message
  • OBX[0] refers to the first OBX segment in the message
  • OBX[2] refers to the third OBX segment in the message
  • OBX[1]-5[1] refers to the second repetition of OBX-5 within the second OBX segment in the message

40.10.2.1Path Syntax: Fields, Components, and Subcomponents

Field/Component/Subcomponent numbers are referred to using their field number. These numbers are 1-indexed, so the first field of the PID segment is called PID-1. This often seems confusing at first, but it is done this way to align with the way these items are historically named.

Some fields in HL7 v2.x will have components (a further division within a field) and some components will have subcomponents (a further division still). There is no level of division lower than a subcomponent.

For example:

  • PID-11-1-2 refers to the 11th field of PID (Patient Address), in the first component (Street Address), in the second subcomponent (Street Name).

40.10.2.2Using Repetitions

The path syntax listed above can be used to get/set repetitions of repeatable elements. For example:

rawMsg['PID-5[0]-1'] = 'Smith';      // Set the value
var lastName = rawMsg['PID-5[0]-1']; // Get the value

40.10.2.3Function: getRepetitionCount()

It is also possible to use the getRepetitionCount() function to access the current number of repetitions in a repeatable element. For example:

// Add repetition to the end
var repCount = rawMsg['PID-5'].getRepetitionCount();
rawMsg['PID-5[' + repCount + ']'] = 'Smith';

This function can also be used at the segment level:

var count = rawMsg['NTE'].getRepetitionCount();

40.10.2.3.1Function: forEach()

Repeated segments can also be iterated with forEach:

rawMsg.forEach('DG1', function(dg1) {
	// Perform some action on each DG1 segment
})

40.10.3Path Syntax: Groups and Nesting

 

Many HL7 v2.x structures are organized into segment groups, which are collections of segments that can be repeated as a group.

For example, see the ORU_R01 message structure. In this example, the OBX and NTE segments are nested several group levels below the message level. Specifically:

  • The message contains a group called PATIENT_RESULT. Per the structure definition, the PATIENT_RESULT group starts with a PID segment, as this is the first direct child of the group that is not another group.
  • The PATIENT_RESULT group contains a group called ORDER_OBSERVATION. Per the structure definition, the ORDER_OBSERVATION group starts with either an ORC segment, or an ORU segment if no ORC segment is present.
  • The ORDER_OBSERVATION group contains a group called OBSERVATION. Per the structure definition, the OBSERVATION group starts with an OBX segment.
  • Finally, the OBSERVATION group contains a non-repeatable OBX segment as well as a repeatable NTE segment.

A simple partial message example is shown below (for simplicity, not all field values are shown): MSH|^~\&|||||20200101120000||ORU^R01|001||2.5
PID|....
OBR|....
OBX|observation1
NTE|note1
NTE|note2
OBR|....
OBX|observation2
OBX|observation3

In the example above:

  • One PATIENT_RESULT is present, denoted by the PID segment and everything following it.
  • Two ORDER_OBSERVATION group repetitions are found inside the PATIENT_RESULT. Each one begins with an OBR segment.
  • The first ORDER_OBSERVATION group contains a single OBSERVATION group containing one OBX and two NTE repetitions.
  • The second ORDER_OBSERVATION group contains two OBSERVATION group repetitions, each one containing a single OBX segment.

Each of the group names listed above may repeat within its parent. Therefore, the following path retrieves (creating if necessary) the first OBX segment, using the first of each of its parents:

  • /PATIENT_RESULT/ORDER_OBSERVATION/OBSERVATION/OBX-1 returns observation1.
  • /PATIENT_RESULT/ORDER_OBSERVATION/OBSERVATION/NTE-1 returns note1.
  • /PATIENT_RESULT/ORDER_OBSERVATION/OBSERVATION/NTE[0]-1 returns note1.
  • /PATIENT_RESULT/ORDER_OBSERVATION/OBSERVATION/NTE[1]-1 returns note2.

The * character may be placed at the start of the expression in order to recursively search for a structure with the given name. For example:

  • */NTE refers to a recursive search for the first NTE segment found anywhere in the message. This will search for the first group which can contain an NTE segment (i.e. the first OBSERVATION group in this example) and then return its NTE segment, creating it if necessary. Therefore:
    • */NTE-1 returns note1.
  • */NTE[1] refers to a recursive search for the first OBX segment found anywhere in the message, and then fetches its second repetition. Note that this does not mean the second OBX segment in the whole message, but rather the second OBX segment within the first parent group that can contain an OBX segment. Therefore:
    • */NTE[1]-1 returns note2.
    • */NTE[2]-1 returns null, since the first OBSERVATION group contains only two NTE segments.

You can also navigate to a segment by explicitly specifying the path:

  • /PATIENT_RESULT/ORDER_OBSERVATION/OBSERVATION/OBX refers to an explicit lookup of the OBX segment within a specific path

Repetitions may be specified for group elements in a path as well:

  • /PATIENT_RESULT/ORDER_OBSERVATION[0]/OBSERVATION/OBX-1 returns observation1.
  • /PATIENT_RESULT/ORDER_OBSERVATION[1]/OBSERVATION/OBX-1 returns observation2.

For simplicity, you can also use a * within a path expression to indicate a wildcard group name:

  • /*/ORDER_OBSERVATION[0]/*/OBX-1 returns observation1.
  • /*/ORDER_OBSERVATION[1]/*/OBX-1 returns observation2.

40.10.3.1Duplicate Segments

Many segments within a structure can be repeated, meaning they have a maximum cardinality greater than 1 (or simply a maximum cardinality of *). For example, within the ADT_A01 structure the OBX segment repeats. If there are two instances of this segment in a particular message, they are accessed as repetitions:

var obx0 = rawMsg['OBX[0]'];
var obx1 = rawMsg['OBX[1]'];

However, some structures have multiple instances of the same structure at different points within the same parent structure. This is a confusing concept within HL7 v2.x but it has historical roots and will likely not be changed. For example, the PID segment appears in two locations within the flat message structure of the ADT_A17 structure. Because these two segments are not repetitions of the same segment, but rather refer to separate segments entirely, any segments after the first will automatically have a number (beginning with 2) appended to the segment name.

So, in the ADT_A17 example, the following code would retrieve the two PID segments:

var pid0 = rawMsg['PID'];
var pid1 = rawMsg['PID2'];

40.10.4Structure Interrogation

 

Different message structures consist of different groups and segments. For example, an ADT_A01 message structure includes a PROCEDURE group and ROL segments whereas an ADT_A09 message structure does not. The following method can be used to interrogate a given message's structure at runtime.

40.10.4.1Function: printStructure()

The printStructure() function returns a String containing a complete description of the parsed structure of a message object. Typically this is passed to a logger in order to assist in troubleshooting (the output of this function is generally quite large, and it would be wasteful to log it outside of specific troubleshooting scenarios).

Note that another method for obtaining structure information for received messages is to enable the HL7v2 Troubleshooting Log.

For example, the following code:

Log.info("Message structure:\n" + rawMsg.printStructure());

This will result resembling the following example:

ORU_R01 (start)
   MSH - MSH|^~\&||Acme|||||ORU^R01^ORU_R01|A001|T|2.5
   [ { SFT } ] - Not populated
   PATIENT_RESULT (start)
   {
      PATIENT (start)
      [
         PID - PID|||7000135||Smith^John||19650322|M
         [ PD1 ] - Not populated
         [ { NTE } ] - Not populated
         [ { NK1 } ] - Not populated
         VISIT (start)
         [
            PV1 - PV1||O||E|||||||||||AS1|||QQ|4736455|
            [ PV2 ] - Not populated
         ]
         VISIT (end)
      ]
      PATIENT (end)
      ORDER_OBSERVATION (start)
      {
         [ ORC ] - ORC|NW|12345|||||^^^20160210111213-0400|||||777^Jones^Lisa^Frances
         [ { NTE } ] - NTE|||Comment Line 1~Comment Line 2
                       NTE|||Comment Line 3
                       NTE|||Comment Line 4a ^ Comment Line 4b
         TIMING_QTY (start)
         [{
            TQ1 - Not populated
            [ { TQ2 } ] - Not populated
         }]
         TIMING_QTY (end)
         [ CTD ] - Not populated
         OBSERVATION (start)
         [{
            OBX - OBX||NM|9477-3^Sodium (Plasma)||150|mmol/L|0.2-333||||F
            [ { NTE } ] - Not populated
         }]
         [{
            OBX - OBX||DT|3472-2^Fasting Date||20160213||||||F
            [ { NTE } ] - NTE|||Comment Line 1~Comment Line 2
                          NTE|||Comment Line 3
                          NTE|||Comment Line 4a ^ Comment Line 4b
         }]
         OBSERVATION (end)
         [ { FT1 } ] - Not populated
         [ { CTI } ] - Not populated
         SPECIMEN (start)
         [{
            SPM - Not populated
            [ { OBX } ] - Not populated
         }]
         SPECIMEN (end)
      }
      ORDER_OBSERVATION (end)
   }
   PATIENT_RESULT (end)
   [ DSC ] - Not populated
ORU_R01 (end)
Note that this should not be used in Production environments as it could expose PHI/PII in the system logs!

40.10.4.2Function: hasChild(name)

The hasChild(name) function can be invoked at the message and group levels, and it can be used to interrogate the structure of a given message or group. The name argument should identify either a group or segment. This function returns true if the named child exists in the structure; otherwise it returns false.

For example:

// Message-level interrogation
// rawMsg is an ADT_A01
rawMsg.hasChild('PROCEDURE'); // returns true
rawMsg.hasChild('ROL'); // returns true
// Message-level interrogation
// rawMsg is an ADT_A09
rawMsg.hasChild('PROCEDURE'); // returns false
rawMsg.hasChild('ROL'); // returns false
// Group-level interrogation
// rawMsg is an ORU_R01
rawMsg['/PATIENT_RESULT'].hasChild('ORDER_OBSERVATION'); // returns true
rawMsg['/PATIENT_RESULT/ORDER_OBSERVATION'].hasChild('OBR') // returns true
// Group-level interrogation
// rawMsg is an ORU_R01
rawMsg['/PATIENT_RESULT'].hasChild('PROCEDURE'); // returns false
rawMsg['/PATIENT_RESULT/ORDER_OBSERVATION'].hasChild('PR1') // returns false

40.10.5Content Clearing

 

In addition to accessing and populating various parts of a message, this content can also be cleared.

40.10.5.1Function: clear()

The clear() function can be invoked at the segment, field, component, and sub-component levels.

For example:

rawMsg['PID-5-1-1'].clear(); // clears the value in this sub-component
rawMsg['PID-5-1'].clear();   // clears the value in this component, including the values in any sub-components
rawMsg['PID-5'].clear();     // clears the values in all components and sub-components in this field
rawMsg['PID'].clear();       // clears all values in this segment

40.10.6Content Interrogation

 

This section shows how to execute conditional logic depending on the existing contents of message fields.

40.10.6.1Testing Field Values

When using a path expression to interrogate a message, the output is an HL7v2 object and not a string. Therefore, to examine the value of a field, you must call toString() on the output of an expression before you can compare it to a string literal.

if (rawMsg['MSH-9-1'].toString() === 'ORU' && rawMsg['MSH-9-2'].toString() === 'R01') {
    // do something
}

40.10.6.2Function: isEmpty()

Simply because a given message structure includes a group, segment, or field does not mean that this element is populated with a value. For example, an ADT_A01 message structure includes a PID segment; however, it may be prudent to ensure PID-13 (Phone Number - Home) is populated with a value before attempting to process it. The following method can be used to interrogate a given message's content at runtime.

The isEmpty() function can be invoked at the group, segment, and field levels. This function returns false if a value is populated; otherwise it returns true.

For example:

if (rawMsg['PID-13'].isEmpty()) {
    // do something
}

40.10.7Parsing Raw Field Values

 

By default, values being passed in will be treated as literal strings and will be escaped as necessary. For example, consider the following code:

rawMsg['PID-5'] = 'Smith^John';

In the example above, the ^ character is escaped. If this should be interpreted as raw HL7 v2.x code, you can use the parse(String) method instead, as shown below:

rawMsg['PID-5'].parse('Smith^John');

You can also use the parse() function with repetition separators to parse multiple repetitions of a field. For example:

rawMsg['PID-5'].parse('Smith^John~Smith^Johnnie');

40.10.8Encoding

 

The encode() function can be invoked at the message, segment, and field levels. This function returns an encoded string of the populated value.

This function is commonly used in conjunction with the parse() function when copying populated values from one part of a message to another during translation and conversion. For example, one could easily copy all of the component values of the second repetition of PID-3 into the first repetition as follows:

rawMsg['PID-3[0]'].parse(rawMsg['PID-3[1]'].encode());

This is particularly useful when massaging a message prior translation and conversion. Consider a message that populates components with "" for empty strings. These can easily be removed from the entire message as follows:

rawMsg.parse(rawMsg.encode().replace(/""/g, ''));

40.10.9The Hl7V2 Object

 

The Hl7V2 object provides utility methods that are useful when working with HL7 v2.x messaging.

40.10.9.1Function: newMessage(messageCode, messageTriggerEvent, processingId)

The newMessage(messageCode, messageTriggerEvent, processingId) function can be used to create a new message.

40.10.9.1.1Parameters

This function takes the following parameters:

  • messageCode – This parameter is a string that identifies MSH-9.1 (Message Code) of the new message (e.g. ADT, RAS, etc.).

  • messageTriggerEvent – This parameter is a string that identifies MSH-9.2 (Trigger Event) of the new message (e.g. A01, O17, etc.).

  • processingId – This parameter is a string that identifies MSH-11 (Processing ID) of the new message (e.g. T for testing, P for production, etc.).

40.10.9.1.2Output

This function returns an HL7 v2.x Message, which is the same type as Hl7V2ReceivedMessage.rawMessage.

40.10.9.1.3Example: Converting Message Structures

A good use case would be an incoming HL7 v2.x feed that includes DFT_P03 messages sent to an HL7 v2.x Listening Endpoint module that expects RAS_O17 messages. A callback script with the function onPreConvertHl7V2ToFhir(theMessage, theConversionResult) could make use of the Hl7V2 object to create a new RAS_O17 message, then populate it with values from the original DFT_P03 message.

/**
 * This function will be called any time that a new message is received
 *
 * @param theMessage          The received HL7 v2.x message details
 * @param theConversionResult The result of an HL7 v2.x message runtime mapping
 */
function onPreConvertHl7V2ToFhir(theMessage, theConversionResult) {

	/*
	 * This variable holds the actual HL7 v2.x message
	 * In this case, it is a DFT_P03 message
	 */
	var rawMsg = theMessage.rawMessage;
	
	// Create a new RAS_O17 message
	var newMsg = Hl7V2.newMessage('RAS', 'O17', String(rawMsg['MSH-11']));
	
	// Replace the original message with the new message
	theMessage.rawMessage = newMsg;
	
	// Populate the new message as appropriate
	newMsg['MSH-1'] = rawMsg['MSH-1'];
	newMsg['MSH-2'] = rawMsg['MSH-2'];
	...
}

40.10.9.2Functions: new*(message)

The new*(message) functions return a new, unpopulated object of an HL7 v2.x datatype.

The following functions are available:

Function Returns
newCE(message) Coded Element
newCWE(message) Coded with Exceptions
newDT(message) Date
newFT(message) Formatted Text Data
newNM(message) Numeric
newSN(message) Structured Numeric
newST(message) String Data
newTM(message) Time
newTS(message) Timestamp
newTX(message) Text Data

If there is an HL7 v2.x datatype that you would like to see supported, let us know!

40.10.9.2.1Parameters

These functions take the following parameter:

40.10.9.2.2Output

These functions return a new, unpopulated object of the specified HL7 v2.x datatype.

40.10.9.2.3Example: Creating a New CWE Object

A new, unpopulated CWE (Coded with Exceptions) can be easily created as follows:

var cwe = Hl7V2.newCWE(rawMsg);

The CWE can then be populated as usual:

cwe['CWE-1'] = '123456';
cwe['CWE-1'] = 'Fancy!';
cwe['CWE-3'] = 'http://example.org/code';

40.10.10Working with Variable Datatypes

 

Some HL7 v2.x fields, components, and sub-components allow for variable datatypes. For example, OBX-5 (Observation Value) can be populated with a variety of datatypes, as identified by the value in OBX-2 (Value Type). In such cases, it is necessary to populate the element with an explicit datatype. This is accomplished using the Hl7V2.new*(message) functions in conjunction with the setData(object) function.

40.10.10.1Function setData(object)

The setData(object) function works at the field, component, and sub-component levels where such an element allows for variable HL7 v2.x datatypes.

40.10.10.1.1Parameters

This function takes the following parameters:

  • object – A JavaScript object of an HL7 v2.x datatype.

40.10.10.1.2Output

This function does not currently return a value, and any returned value will be ignored.

40.10.10.1.3Example: Populating OBX-5 with a Structured Numeric

OBX-5 (Observation Value) can be populated with an SN (Structured Numeric) as follows:

// Create the SN
var sn = Hl7V2.newSN(rawMsg);

// Populate the SN (|^100^-^200|, i.e. equal to range of 100 through 200)
sn['SN-2'] = '100';
sn['SN-3'] = '-';
sn['SN-4'] = '200';

// Populate OBX-5 (Observation Value) with the SN
rawMsg['PATIENT_RESULT/ORDER_OBSERVATION/OBSERVATION/OBX-5'].setData(sn);

40.10.11Working with Z-Segments

 

In order to add a Z-Segment to a message, use the following form:

var zzz = rawMsg.addSegment('ZZZ');
zzz['ZZZ-1'] = 'FIELD1';
zzz['ZZZ-2'] = 'FIELD2';