Skip to main content

Additional Steps

This chapter discusses the additional steps needed to add HL7 processing to a production. It includes the following topics:

Be sure to perform these tasks in the same namespace that contains your production. When you create rule sets, transformations, and search tables, do not use reserved package names; see “Reserved Package Names” in Developing Ensemble Productions.

Also see “Overriding the Validation Logic” in Ensemble Virtual Documents.

Defining Routing Rule Sets for HL7

When creating a routing rule set for an HL7 interface, your goal is to tell Ensemble what to do with the source message based on the segments found within it. Sometimes it matters which segments are found; sometimes it matters which values are found within those segments.

In ordinary rule sets, each rule returns a value to the business process that invoked the rule set. In a routing rule set, a rule usually directs an HL7 message to a destination, possibly transforming the HL7 message before sending it.

For information on using the rule editor, see “Creating and Editing Rules Sets” in Developing Business Rules.

  1. When you create a new HL7 routing process using the Business Rule Wizard, Ensemble creates a new, empty routing rule set to accompany the new routing process. Its information tables contain the following values:

    • Package Name — The package that contains the production class. For example, if you used the wizard to add a routing process to a production called TestRule.MyTest, the associated routing rule has a Package Name of:

      TestRule

    • Rule Name — The simple Routing Rule Name that you chose in the wizard, such as:

      MyRule

      The combination of the Package Name and Rule Name identify the rule uniquely within the Ensemble namespace. The full name for any rule definition is the Package Name and Rule Name connected by a dot (.) as in:

      TestRule.MyRule

      This full name, rather than the Rule Name, is the correct value to use in the BusinessRuleName field when configuring an HL7 routing process. The Business Process Wizard sets this up automatically.

    • Routing Engine Class — The default Router Class from the wizard; do not change it:

      EnsLib.HL7.MsgRouter.RoutingEngine

  2. You may also enter additional fields for your own rule reporting purposes:

    • Report Group — Value to be used to group rules for reporting

    • Report Name — Display value for the rule report group

    • Short Description — Optional short description of this Rule Definition.

  3. See “Using the Rule Constraint Editor” in Developing Business Rules for details on entering the constraints of a routing rule.

  4. Once you have created a new rule, you may add Conditions and Actions to it. For details, see chapter “Creating and Editing Rule Sets” in Developing Business Rules. As you add Conditions to each rule, remember these tips:

    • Conditions evaluate AND operators first, then OR.

    • Conditions can refer to properties of the HL7 message object. Within Conditions, the special variable Document represents the HL7 message object, as in the following examples. For HL7 batch documents, you can use the special variable Document.Parent to represent the parent message object.

      Document.Name
      Document.Parent.DocType
      Document.{PIDgrp.PV1grp.PV1:18}
      Document.{PIDgrp.PID:PatientName.familylastname}
      Document.{ORCgrp(1).OBRuniongrp.OBRunion.OBR:4.3}
      
      Note:

      For compatibility with past releases, the special variables HL7 and HL7.Parent are supported as alternatives to Document and Document.Parent.

      A dot separates the Document variable from the property name. This name may be:

      • Any of the class properties: DocType, TypeCategory, BuildMapStatus, or Name.

      • A virtual property, referenced using any of the following conventions:

        Details are available in the “Syntax Guide” section of Ensemble Virtual Documents. However, you do not need to enter properties by typing; you can browse for properties as described in “Creating and Editing Rule Sets” in Developing Business Rules.

  5. Complete the instructions in the next several topics for creating any Source, Target, or Transform items that your routing rule set needs. The items might be:

    As you create items, return to the Message Routing Rule Editor to add them to the appropriate fields of the rule definition.

  6. Return to the diagram on the Production Configuration page. Select the corresponding HL7 routing process. In the BusinessRuleName field, enter the full name of the new routing rule set.

Defining DTL Data Transformations for HL7

Each interface may require some number of data transformations.

Important:

Do not manually change HL7 escape sequences in the data transformation; Ensemble handles these automatically.

You use the Data Transformation Builder page to create data transformations (navigate to Ensemble > Build > Data Transformations). For general information on using this page, see Developing DTL Transformations.

The following figure shows this page, displaying MsgRouter.ADTLastNameTransform from the ENSDEMO namespace:

generated description: dtl editor sample

Note the following tips:

  • Have Developing DTL Transformations handy. This book explains how to add each kind of DTL action.

  • Be clear about the value you want for the create option of the data transformation class. create may have one of the following values:

    • new — Create a new object of the target type, before executing the elements within the data transformation. Any source segments that you do not explicitly assign to the target object are ignored. This is the default.

    • copy — Create a copy of the source object to use as the target object, before executing the elements within the transform.

    • existing — Use an existing object, provided by the caller of the data transformation, as the target object.

    To create a target object that is an exact copy of the source, do not use an action like this:

    <assign property='target' value='source' />
    
    

    Instead use the create='copy' attribute in the data transformation class.

  • Ensure that the data transformation class identifies the correct schema category for:

    • The sourceDocType attribute

    • The targetDocType attribute

    The schema category may be the same or different for the source and target objects.

  • Ensure that the Transform tab specifies the scripting language that you want to use for all the expressions and code actions within that data transformation. ObjectScript is the default language.

  • Use assign actions to assign HL7 segments from the source message to the target message by drag and drop operations.

    Possibly, you will want to use a combination of techniques. You can first generate a line of code by dragging, then fine-tune the code by editing the text.

  • The data transformation can refer to properties of the HL7 message object, including:

    • The class properties DocType, TypeCategory, BuildMapStatus, and Name.

    • Virtual document properties as described in “Curly Bracket {} Syntax” in the “Syntax Guide” section of Ensemble Virtual Documents.

    Within the data transformation class code, the special variables source and target represent the respective HL7 message objects, as in the following examples:

    source.Name

    target.DocType

    source.{PIDgrp.PV1grp.PV1:18}

    target.{PIDgrp.PID:PatientName.familylastname}

    source.{ORCgrp(1).OBRuniongrp.OBRunion.OBR:4.3}

  • Assign any literal string values, or make conditional assignments. See the chapter “Syntax Rules” in Developing DTL Transformations.

    Note:

    A string literal cannot include XML reserved characters. It also cannot include separator characters used by HL7.

    Also, the HL7 null mapping code "" requires special handling. The following example tests for the null mapping code in the source message and replaces it with a blank string in the target message:

    <if condition='source.{PV1:7().4}=""""""'>
    <true>
    <assign property='target.{PV1:7().4}' value='""' />
    </true>
    </if>
    

    For details, see “Null Mapping Codes” in the chapter “Syntax for a Routing Production.”

  • For simple calculations, the DTL data transformation can:

    • Invoke Ensemble utility functions

    • Use ObjectScript or Caché Basic expressions in the DTL code action

    • Invoke the DTL sql action

    For more complex calculations, you can write your own class methods and invoke them from a code action, or from the value string in another DTL element.

  • Include descriptions of the transformation and each action.

  • Compiling the data transformation also saves it.

  • To use the DTL data transformation in the production, simply enter its full package and class name in the Transform field of a routing rule set.

Null Mapping Codes

There are HL7 applications that use a null mapping convention. According to this convention, the source application can send a field that consists of two consecutive double-quote characters ("") to signify if you have data in this field, delete it from your application.

Many target applications do not expect these instructions and are not designed to respond to them. If this is the case, and the double quotes are saved in the target application as actual patient data, the application users see double-quote characters on their screens. This can be annoying and misleading.

When your source application uses a null mapping convention, your HL7 data transformation can check for null mapping entries in HL7 fields and replace them with empty strings, or otherwise fill in data for the benefit of the target application.

The following <if> statement represents the simplest case. It checks for a null mapping in the source and replaces it with an empty string in the target. The <if> condition tests for the null mapping code "" using a string of 2 quoted quotes. This results in a total of 6 double-quote characters, not including the single quotes that wrap the entire condition value. (Count carefully!)

<if condition='source.{PV1:7().4}=""""""'>
<true>
<assign property='target.{PV1:7().4}' value='""' />
</true>
</if>

In the above example, the <assign> value indicates a empty string using 2 consecutive double-quote characters, with single quotes wrapping the entire value.

The following syntax is equally valid:

<if condition='source.{PV1:7().4}=&quot;&quot;&quot;&quot;&quot;&quot;'>
<true>
<assign property='target.{PV1:7().4}' value='&quot;&quot;' />
</true>
</if>

You can achieve more sophisticated goals for handling null mappings. The following example takes alternative actions based on whether there is actually a value in {PV1:3}. If the field contains a null mapping code the <true> element executes. Otherwise the <false> element executes.

<if condition='source.{PV1:3}=""""""'>
<true>
<assign property='target.{PV1:3.1}' value='source.{PV1:PatientType}' />
<assign property='target.{PV1:3.2}' value='source.{PV1:PatientType}' />
</true>
<false>
<code><![CDATA[
 // Dr Chart pulls subfields as follows:
 // 1 location, 2 desc, 3 room, 4 bed, 5 wing, 6 floor
]]></code>
<assign property='target.{PV1:3.1}' value='source.{PV1:3.1}' />
<assign property='target.{PV1:3.2}' value='source.{PV1:3.1}' />
<assign property='target.{PV1:3.3}' value='source.{PV1:3.2}'  />
<assign property='target.{PV1:3.4.1}' value='source.{PV1:3.3}'  />
<assign property='target.{PV1:3.5}' value='source.{PV1:3.1}'  />
</false>
</if>

Transforming Long Segment Fields

The ObjectScript method used by DTL transformations, GetValueAt, truncates HL7 segment fields at 32K. Therefore, when transforming a field that is longer than 32K, you cannot use the left-to-right drag action in the DTL Editor. For example, if an OBX:5 field exceeds 32K, you cannot use the DTL Editor to drag the source field to the target because it will be truncated. Similarly, custom code should not call GetValueAt if you are transforming fields longer than 32K.

To transform fields longer than 32K, you need to use a Code action to add custom code to the transformation. This custom code must include one of the following methods in the EnsLib.HL7.Segment class to read the value of the field from the source into a stream: GetFieldStreamRaw(), GetFieldStreamUnescaped(), or GetFieldStreamBase64()

These Get methods take 3 arguments: the stream output argument, the VDoc path of the field, and the pRemainder output argument. The pRemainder argument will be filled with all fields that come after the one being extracted. For example:

/// Segment:  OBX|1|2|3|4|5|6|7
do GetFieldStreamRaw(.stream, "OBX:5", .rem)
/// rem contains: |6|7

Once the custom code has the field value in a stream, it must use one of the following methods of EnsLib.HL7.Segment to store the value in the target: StoreFieldStreamRaw(), StoreFieldStreamUnescaped(), or StoreFieldStreamBase64().

These Store methods accept three arguments: the stream to store in the field, the VDoc path of the field in which to store the stream, and a pRemainder argument. If the pRemainder argument is not specified, then all target fields after the field being stored are deleted. For example:

/// Before: OBX|1|2|3|4|5|6|7
do StoreFieldStreamRaw(stream, "OBX:5")
/// After: OBX|1|2|3|4|<stream> 
/// |6|7 are gone because a remainder was not specified.

If pRemainder is specified, then all fields after the one being stored will be replaced with what is in pRemainder.

/// Before: OBX|1|2|3|4|5|6|7
do StoreFieldStreamRaw(stream, "OBX:5", "|six|seven")
/// After: OBX|1|2|3|4|<stream>|six|seven
Important:

The target segment becomes immutable once a Store method is called, therefore it must be the last change made to the segment.

The following example shows how to extract the field from the source and store it in the target. It assumes that the custom code made edits to the target fields that come after the 32K+ field before calling the Store method; this approach is required because the segment becomes immutable after calling the Store method. The sample code does not take the remainder from the source and store that in the target because that would revert edits that were already made to fields that come after the target’s long field. Therefore, you would take the stream field from the source, but the remainder from the target, and store both of those in the target:

/// previous code makes edits to fields that come after OBX:5 in the target
do source.GetFieldStreamRaw(.stream, "OBX:5")
do target.GetFieldStreamRaw(.dummy, "OBX:5", .rem)
do target.StoreFieldStreamRaw(stream, "OBX:5", rem)
///Segment is now immutable

Doing it this way prevents edits made to target fields that come after OBX:5 from being reverted or deleted.

Defining HL7 Search Tables

The HL7 search table class, EnsLib.HL7.SearchTableOpens in a new tab, automatically indexes popular HL7 properties; see the subsection “Properties That Are Indexed by Default.”

If you need more items to search, you can create a subclass. The subclass inherits the Identifier property, plus the infrastructure that makes search tables work. For details, see “Defining a Search Table Class” in Ensemble Virtual Documents.

For HL7, Ensemble supports an additional value for PropType. You can use DateTime:HL7 in addition to the types listed in Ensemble Virtual Documents.

Properties That Are Indexed by Default

When you choose EnsLib.HL7.SearchTableOpens in a new tab as your search table class, it enables Ensemble to search HL7 messages for the following virtual properties.

Choose... To refer to this value...
MSHTypeName

The message structure name. To create this string, Ensemble concatenates the following values from the HL7 message:

  • The MSH message header segment

    Field 9 (message type)

    Subfield 1 (message type: ADT, ORM, etc)

  • The literal character _

  • The MSH message header segment

    Field 9 (message type)

    Subfield 2 (trigger event: A01, A12, O01_2, etc)

The result is a message structure name in the format ADT_A01, ADT_A12, ORM_O01_2, etc.

MSHControlID

The unique identifying number for this message. Ensemble retrieves this value from:

  • The MSH message header segment

    Field 10 (message control ID)

Ensemble interprets this value as a case-sensitive string.

PatientID

The patient identifier for this message. This is a field whose location has shifted as the HL7 standard has evolved. For this reason, Ensemble looks for this value in all of the following locations. That way, the patient identifier can be found regardless of which HL7 schema category the message conforms to:

  • The PID patient identifier segment

    Field 2 (patient external identifier)

    Subfield 1 (patient identifier)

  • The PID patient identifier segment

    Field 3 (patient identifier list), all entries in the list

    Subfield 1 (patient identifier)

  • The PID patient identifier segment

    Field 4 (patient identifier list), all entries in the list

    Subfield 1 (patient identifier)

PatientName

The PID patient identifier segment

Field 5 (patient name)

PatientAcct

The PID patient identifier segment

Field 18 (patient account number)

Subfield 1 (ID)

Examples

The following example consists of one virtual property path that uses {} syntax. This <Item> element refers to the value at Segment 1, Field 10 of the HL7 message:

<Item DocType=""
      PropName="MSHControlID"
      PropType="String:CaseSensitive"
      StoreNulls="true" >
      {1:10}
</Item>

The following more complex <Item> element uses the ObjectScript _ operator to concatenate three strings. From left to right, these are:

  • The value within Segment 1, Field 4

  • A literal - character

  • The value within Segment 1, Field 3

<Item DocType=""
      PropName="SendingFacilApp" >
      {1:4}_"-"_{1:3}
</Item>

The following <Item> example uses most of the possible syntax options: concatenation, virtual properties, a literal hyphen character (-), and the ObjectScript string function $PIECE:

XData SearchSpec [ XMLNamespace="http://www.intersystems.com/EnsSearchTable" ]
{
<Items>
 <Item DocType="Mater:ORM_O01 "
       PropName="RelationKey" >
 $P(
 {ORCgrp(1).OBRuniongrp.OBRunion.OBR:UniversalServiceID.text},"-",1,2
 )_"-"_{MSH:12}
 </Item>
</Items>}

The following sample search table class provides several examples of valid <Item> entries. This class inherits from EnsLib.HL7.SearchTableOpens in a new tab, as is required for HL7 search tables. Comments above each group of <Item> entries describe the purpose of that set of entries. For details about {} or [] syntax, see the “Syntax Guide” section of Ensemble Virtual Documents.

Class Demo.HL7.MsgRouter.SearchTable Extends EnsLib.HL7.SearchTable
{

XData SearchSpec [ XMLNamespace="http://www.intersystems.com/EnsSearchTable" ]
{
  <Items>
   <!-- Items that do not depend on DocType, indexing any HL7 message -->
   <Item DocType="" PropName="SendingFacilApp" >{1:4}_"|"_{1:3}</Item>
   <Item DocType="" PropName="RecvingFacilApp" >{1:6}_"|"_{1:5}</Item>
   <Item DocType="" PropName="MSHDateTime" PropType="DateTime:HL7" >{1:7}</Item>

   <!-- Get fields from named segments found in any HL7 message -->
   <Item DocType="" PropName="PatientName" >[PID:5]</Item>
   <Item DocType="" PropName="InsuranceCo" >[IN1:4]</Item>

   <!-- Get patient name from any HL7 message declared type ADT_A05 -->
   <Item DocType=":ADT_A05" PropName="PatientName" >{3:5}</Item>

   <!-- Get specific field from specific segment when the        -->
   <!-- HL7 message is assigned a specific DocType. Only in this -->
   <!-- case can you use names for segments, instead of numbers. -->
   <Item DocType="Demo.HL7.MsgRouter.Schema:ORM_O01 " PropName="ServiceId" >
     {ORCgrp().OBRuniongrp.OBRunion.OBR:UniversalServiceID.text}
   </Item>
   <Item DocType="2.3.1:ORU_R01 " PropName="ServiceId" >
     {PIDgrpgrp().ORCgrp(1).OBR:UniversalServiceID.text}
   </Item>
  </Items>
}

}
FeedbackOpens in a new tab