28.1.1HL7 v2.x Support: Inbound Messaging (to Smile CDR)

 

The following diagram shows the processing path for Inbound HL7 v2.x Support.

Inbound Support

To enable Inbound HL7 v2.x Support, create a module of type HL7 v2.x Listening Endpoint. This module type has many settings that affect the processing of various message types.

See Transactions for a list of supported message types.

28.1.2Security

 

28.1.2.1Anonymous Access

To access HL7 v2.x Listening Endpoint without credentials, Anonymous Access can be enabled by the Allow Anonymous Access property. For additional details, see Anonymous Access. Please note that Allow Anonymous Access is applicable only for HL7 over HTTP Transport Protocol and ignored for MLLP over TCP.

28.1.2.2HTTP Basic Auth

HTTP Basic Security can be enabled for the HL7 v2.x Listening Endpoint module. In this case, the message sender should provide a request that contains a header field in the form of Authorization: Basic <credentials>, where credentials is the Base64 encoding of username and password joined by a single colon :.

Example: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

For additional details, see the HL7 over HTTP specification. Please note that HTTP Basic Security is applicable only for HL7 over HTTP Transport Protocol and ignored for MLLP over TCP.

28.1.2.3TLS/SSL Encryption

To enable TLS/SSL encryption for HL7 v2.x Listening Endpoint module, TLS Enabled property can be used. Both HL7 over HTTP and MLLP over TCP Transport Protocol support TLS/SSL encryption. For configuration details, see TLS and HTTPS.

28.1.3Processing

 

When an HL7 v2.x message is received, the HL7 v2.x Listening Endpoint module will automatically convert the contents from HL7 v2.x into a FHIR Transaction Bundle which will then be ingested into the FHIR Storage module.

Smile CDR contains a built-in translation for converting HL7 v2.x to FHIR that has been tested across many implementations. In some cases it can be used with no further modification, but it is also common to need to enhance it by adding specific customizations in order to meet project requirements.

28.1.3.1Creating a MessageHeader

When the Create MessageHeader for Each Message property is enabled, every HL7 v2.x message will result in the creation of one MessageHeader resource, in addition to all of the other resources.

This can be useful for several reasons:

  • It creates a traceable record of which resources are sourced from which HL7 v2.x messages. The stored MessageHeader resource will have links to all other FHIR resources that were potentially created/modified as a result of the source message. Note that this link does not imply that the source message actually affected each resource, only that it had the potential to. For example, an ADT^A08 (Update Visit) message contains a PID segment with Patient details. A Patient resource will be included in the transaction bundle created by the HL7 v2.x to FHIR mapper, but if the demographics for the Patient have not actually changed, the existing Patient resource in the CDR will not be updated. However, the MessageHeader will contain a reference to this Patient, since it is the relevant Patient resource for the message.

  • It can be useful as criteria for Subscription Resources if a new subscription payload should be created for every incoming HL7 v2.x transaction.

28.1.3.2Storing HL7v2 Messages As-Is

When the Store Original HL7v2 Message property is enabled, Smile will store the HL7v2 message it received before any processing has occurred. The message is stored on a MessageHeader resource at an extension with URL "https://smilecdr.com/fhir/ns/StructureDefinition/messageheader-source-full-message." Note that if Create MessageHeader for Each Message is enabled, the same MessageHeader resource is used for both purposes.

28.1.3.3MessageHeader Required Fields

Note that MessageHeader.event and MessageHeader.source are both required for the MessageHeader resource.

These fields will be populated if either the Create MessageHeader for Each Message property or the Store Original HL7v2 Message property are enabled.

Note that there is DSTU3 mode mapping behaviour for these fields.

28.1.4Callback Scripts

 

The listening endpoint makes several points in the processing pipeline available for modification using callback scripts.

The following diagram shows the processing flow when processing an incoming HL7 v2.x message.

Inbound Support

The following callback functions may be provided if desired. Note that it is possible but not neccessary to implement all functions.

These scripts may throw an exception, but this will result in an error and a processing failure being returned to the client.

28.1.5Function: onPreConvertHl7V2ToFhir(theMessage, theConversionResult)

 

If present, this method is called once for each message that is received on the HL7 v2.x Listening Endpoint, immediately after the message is logged to the transaction log and before the message is converted to FHIR for storage.

28.1.5.1Parameters

This function takes the following parameters:

  • theMessage – This parameter is of type Hl7V2ReceivedMessage. The method can perform any manipulation required in order to make the received message conform to the Smile CDR HL7 v2.x structure and segment specifications.

  • theConversionResult – This parameter contains the result of an HL7 v2.x message runtime mapping. This parameter also exposes a function for adding messages to the conversion result. This parameter is of type Hl7V2ReceivedMessageConversionResult.

28.1.5.2Output

This function does not currently return a value, and any returned value will be ignored. It is possible however to modify the message that is passed in as an input parameter, making the parameter effectively an output as well.

28.1.5.3Example: Adding Identifier Systems

A common issue when receiving HL7 v2.x messages from legacy systems is the lack of namespaces in identifiers, or having insufficiently descriptive identifiers.

For example, the PID-3 (Patient Identifier) field is used to store a Patient's identifers. In FHIR, an identifier should have at a minimum an identifier value and an identifier system.

These are mapped from PID-3-1 (Identifer Value) and PID-3-4 (Identifier System). Many sending systems will not populate a value in PID-3-4 however, or will populate a non-descriptive value such as a simple 2 letter mnemonic instead of a full URI as required by FHIR. A similar issue occurs with PV1-19, which is needed in order to provide a stable identifier for Encounter resources.

/**
 * 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
   var rawMsg = theMessage.rawMessage;

   // Treat the first repetition of PID-3 as a Patient MRN. Note that the value
   // of PID-3-4 is hardcoded and will be placed in the FHIR resource in
   // Patient.identifier[0].system -- You should replace the placeholder value
   // below with something that is appropriate for the actual sending system.
   rawMsg['PID-3-4'] = 'http://example.com/mrns';
   rawMsg['PID-3-5'] = 'MR';

   // If the second repetition of PID-3 has a secondary identifier,
   // the following might be useful too:
   // rawMsg['PID-3[1]-4'] = 'http://acme.com/patientSecondaryIds';

   // Test if PV1 exists because not all ADT messages have this segment (but most do)
   if (rawMsg['PV1']) {

      // Treat the PV1-19 as the primary visit number. Note that the value
      // of PV1-19-4 is hardcoded and will be placed in the FHIR resource in
      // Encounter.identifier[0].system -- You should replace the placeholder value
      // below with something that is appropriate for the actual sending system.
      rawMsg['PV1-19-4'] = 'http://example.com/visitNumbers';
      rawMsg['PV1-19-5'] = 'VN';

      // Create a composite identifier for PV1-3
      var locationId = rawMsg['PV1-3-1'] + '-' + rawMsg['PV1-3-2'] + '-' + rawMsg['PV1-3-3'] + '-' + rawMsg['PV1-3-4'];
      rawMsg['PV1-3-10-1'] = locationId;                  // Location Identifier Value
      rawMsg['PV1-3-10-2'] = 'http://acme.com/locations'; // Location Identifier System

   }

}

28.1.6Function: onPostConvertHl7V2ToFhir(theMessage, theConversionResult)

 

If present, the onPostConvertHl7V2ToFhir(theMessage, theConversionResult) function will be invoked immediately following the conversion from HL7 v2.x to FHIR.

This function may make modifications to the resulting FHIR transaction Bundles before they are passed to the storage module for persisting.

The function also has access to the HL7 v2.x message in case it is needed.

28.1.6.1Parameters

This function takes the following parameters:

  • theMessage – This parameter contains the received HL7 v2.x message, including any modifications made by the onPreConvertHl7V2ToFhir method. This parameter is of type Hl7V2ReceivedMessage. Note that modifications to the HL7 v2.x message do not have any effect here, as the conversion has already taken place.

  • theConversionResult – This parameter contains the FHIR transaction Bundles that were created during the conversion from HL7 v2.x. The resources may be modified by this function. This parameter also exposes a function for adding messages to the conversion result. This parameter is of type Hl7V2ReceivedMessageConversionResult.

28.1.6.2Output

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

28.1.6.3Example

The following example shows a function that creates a link (using a FHIR extension on Patient resources) between a child and their mother using the identifier found in PID-21 (Mother's Identifier). It creates a stub resource if one is not already present with the given identifier using the Smile CDR JavaScript FHIR REST API.

/**
 * 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 resulting FHIR transaction(s)
 */
function onPostConvertHl7V2ToFhir(theMessage, theConversionResult) {

	// Loop through all transaction Bundles. Generally a conversion will produce 0..1
	// of them, but technically it is possible for one HL7 v2.x message to produce
	// more than one.
	theConversionResult.transactionBundles.forEach(function(nextBundle) {

		// Loop through any resulting patients. Again, this will typically only be
		// 0..1 but more is possible
		nextBundle.entryResources('Patient').forEach(function(nextPatient) {

			var mothersIdentifierValue = theMessage.rawMessage['PID-21-1'];
			var mothersIdentifierSystem = theMessage.rawMessage['PID-21-4'];
			Log.info("Mapping " + mothersIdentifierSystem + " - " + mothersIdentifierValue);

			if (mothersIdentifierValue && mothersIdentifierSystem) {

				// Search for any patients matching the mother's patient identifier
				var foundParentPatients = Fhir
					.search()
					.forResource('Patient')
					.whereToken('identifier', mothersIdentifierSystem, mothersIdentifierValue)
					.asList();

				var parentPatient;
				if (foundParentPatients.length > 0) {

					// If we found an existing match, that is the mother.
					parentPatient = foundParentPatients[0];

				} else {

					// If there is no Patient resource with the mother's identifier,
					// we'll create a stub patient so we have something to link to.
					parentPatient = ResourceBuilder.build('Patient');
					parentPatient.identifier.system = mothersIdentifierSystem;
					parentPatient.identifier.value = mothersIdentifierValue;
					Fhir.create(parentPatient);

				}

				// Add an extension to the Patient that has a reference to the mother
				nextPatient
					.addExtension('http://acme.com/ns/mother-reference')
					.valueReference = parentPatient;

			}

		});

	});

}

28.1.7Adding Processing Messages

 

Function addMessage(thePath, theMessageLevel, theIssue) is exposed by parameter theConversionResult of type Hl7V2ReceivedMessageConversionResult. This method adds a message to the conversion result.

Note that messages of any level will appear in the transaction log, and messages with a level of ERROR will abort processing.

28.1.7.1Parameters

This function takes the following parameters:

  • thePath – The path within a given HL7 v2.x message or FHIR resource with which the message is concerned (e.g. PID-3, Patient.identifier, etc.). This parameter is of type string.

  • theMessageLevel – Acceptable message levels are INFO, WARNING, and ERROR. This parameter is of type string.

  • theIssue – The message to be added to the conversion result. This parameter is of type string.

28.1.7.2Output

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

28.1.7.3Example

The following example illustrates use of this function.

/**
 * 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 resulting FHIR transaction(s)
 */
function onPostConvertHl7V2ToFhir(theMessage, theConversionResult) {

	// Add an info-level message to the conversion result.
	var path = 'PID-18';
	var messageLevel = 'INFO';
	var issue = 'PID-18 (Patient Account Number) is good!';
	theConversionResult.addMessage(path, messageLevel, issue);

	// Add a warning-level message to the conversion result.
	var path = 'PID-19';
	var messageLevel = 'WARNING';
	var issue = 'PID-19 (SSN Number - Patient) is okay.';
	theConversionResult.addMessage(path, messageLevel, issue);

	// Add an error-level message to the conversion result.
	var path = 'PID-20';
	var messageLevel = 'ERROR';
	var issue = 'PID-20 (Driver\'s License Number - Patient) is bad!';
	theConversionResult.addMessage(path, messageLevel, issue);

}

28.1.8Lookup message segments by type

 

Function findSegments(theSegmentType) is exposed via parameter theMessage of type Hl7V2ReceivedMessage. This method extracts segments of a particular type from the received HL7 v2.x message. This function can be invoked within a callback Javascript function.

28.1.8.1Parameters

This function takes the following parameters:

  • theMessage – This parameter is the HL7 v2.x message.

  • theSegmentType – The type of HL7 V2.x segment (e.g. NTE). Standard as well as non-standard segments are supported.

28.1.8.2Output

The function returns the list of segments within the message which match the provided segment type. Returned segments are COPIES of the segments making this message and should serve for debugging purposes only. Use direct segment access to permanently modify a segment.

28.1.8.3Example

This Javascript function will log all the NTE segments for an HL7v2 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 resulting FHIR transaction(s)
 */
function onPostConvertHl7V2ToFhir(theMessage, theConversionResult) {
	var segmentType = 'NTE';
	   
	// Loop through all the NTE segments and log them to the output
	let segments = theMessage.rawMessage.findSegments(segmentType);
	for (segment of segments) {
		Log.info('Found ' + segmentType + ' segment: ' + segment.encode());
	}
}

28.1.9Custom Processing Logic

 

Smile CDR has built-in data converters for all HL7 v2 message/transaction types specified on the Transactions page.

By default, any received message corresponding to a supported transaction type will be automatically converted to FHIR and submitted for processing. Any received message which is not a supported transaction type will result in an error.

If you wish to add support for a transaction type which Smile CDR does not support, or if you wish to entirely replace the built-in Smile CDR conversion for a supported type, the doNotAutoConvert flag may be used on the conversion result.

The following example shows processing for an unsupported transaction type:

/**
 * 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 resulting FHIR transaction(s)
 */
function onPreConvertHl7V2ToFhir(theMessage, theConversionResult) {

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

   // If the message is an OUL^R21 message, handle it directly here
   if (rawMsg['MSH-9-1'].toString() === 'OUL' && rawMsg['MSH-9-2'].toString() === 'R21') {

      // In this simple example we'll just pull out some basic Patient
      // demographics. In a real message processor, more segments would
      // probbably be handled.
      let patient = ResourceBuilder.build('Patient');
      patient.identifier[0].system = rawMsg['*/PID-3[0]-4'];
      patient.identifier[0].value = rawMsg['*/PID-3[0]-1'];
      patient.name[0].family = rawMsg['*/PID-5[0]-1'];
      patient.name[0].given[0] = rawMsg['*/PID-5[0]-2'];

      let transaction = TransactionBuilder.newTransactionBuilder();
      transaction.createConditional(patient).onToken('identifier', patient.identifier[0].system, patient.identifier[0].value);
      theConversionResult.addTransaction(transaction);

      // Because we're manually handing this message type, set the following
      // to true. This ensures that Smile CDR's internal HL7v2 -> FHIR
      // translator will not try to handle this message type
      theConversionResult.doNotAutoConvert = true;
   }

   // In this script, we do nothing if the message type is not OUL^R21. This
   // means that all other message types will be processed normally. You
   // could also put a blanket "theConversionResult.doNotAutoConvert = true;"
   // statement in if you wanted to ensure that all other message types
   // are ignored.

}

The utility function printStructure() can be used during development to examine the full structure of an incoming HL7 message.

	Log.info(theMessage.rawMessage.printStructure());
Note that this should not be used in Production environments as it could expose PHI/PII in the system logs!

28.1.10Pointcuts

 

You can see all the available pointcuts for HL7 v2.x messages here.

28.1.11Java Pre-Convert and Post-Convert Interceptor Example Project

 

A sample project that uses the HL7V2IN_PRE_HL7V2_TO_FHIR_MAPPING_PROCESSING and HL7V2IN_POST_HL7V2_TO_FHIR_MAPPING_PROCESSING pointcuts is available at the following links:

28.1.11.1Using Terminology Translate operation in interceptors

HL7V2IN_PRE_HL7V2_TO_FHIR_MAPPING_PROCESSING and HL7V2IN_POST_HL7V2_TO_FHIR_MAPPING_PROCESSING pointcuts can autowire an ITermConceptClientMappingSvc in order to perform ContextMap translations.

For such bean to be available for autowiring, a dependency must be configured to module FHIR Storage Module (any FHIR version)

A sample project that uses the HL7V2IN_PRE_HL7V2_TO_FHIR_MAPPING_PROCESSING and HL7V2IN_POST_HL7V2_TO_FHIR_MAPPING_PROCESSING pointcuts with an autowired ITermConceptClientMappingSvc which allows performing ConceptMap translations, is available at the following links (under com.example.endpoint.hl7v2.inbound.term):

28.1.11.2Using Transaction Bundle Building Helper in interceptors

HL7V2IN_PRE_HL7V2_TO_FHIR_MAPPING_PROCESSING and HL7V2IN_POST_HL7V2_TO_FHIR_MAPPING_PROCESSING pointcuts can instantiate an BundleBuilder by autowiring an FhirContext and using it as constructor parameter, in order to facilitate transaction bundle building.

A sample project that uses the HL7V2IN_PRE_HL7V2_TO_FHIR_MAPPING_PROCESSING pointcut to instantiate a BundleBuilder is available at the following links (under com.example.endpoint.hl7v2.inbound.transaction):