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';
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 messageOBX[0]
refers to the first OBX segment in the messageOBX[2]
refers to the third OBX segment in the messageOBX[1]-5[1]
refers to the second repetition of OBX-5 within the second OBX segment in the messageField/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).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
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();
Repeated segments can also be iterated with forEach:
rawMsg.forEach('DG1', function(dg1) {
// Perform some action on each DG1 segment
})
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:
PID
segment, as this is the first direct child of the group that is not another group.ORC
segment, or an ORU
segment if no ORC
segment is present.OBX
segment.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:
PID
segment and everything following it.OBR
segment.OBX
and two NTE
repetitions.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 pathRepetitions 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
.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'];
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.
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)
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
In addition to accessing and populating various parts of a message, this content can also be cleared.
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
This section shows how to execute conditional logic depending on the existing contents of message fields.
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
}
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
}
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');
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, ''));
The Hl7V2
object provides utility methods that are useful when working with HL7 v2.x messaging.
The newMessage(messageCode, messageTriggerEvent, processingId) function can be used to create a new message.
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.).
This function returns an HL7 v2.x Message, which is the same type as Hl7V2ReceivedMessage.rawMessage
.
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'];
...
}
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!
These functions take the following parameter:
message
– This parameter is an HL7 v2.x Message, which is the same type as Hl7V2ReceivedMessage.rawMessage
.These functions return a new, unpopulated object of the specified HL7 v2.x datatype.
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';
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.
The setData(object) function works at the field, component, and sub-component levels where such an element allows for variable HL7 v2.x datatypes.
This function takes the following parameters:
object
– A JavaScript object of an HL7 v2.x datatype.This function does not currently return a value, and any returned value will be ignored.
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);
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';