Health Level 7 (HL7) with Perl
Written by Nikos Vaggalis   
Monday, 25 July 2016
Article Index
Health Level 7 (HL7) with Perl
HL7 Grammar Notation
Implementation in Perl
Internal representation


Internal representation

This concludes the making of all segments that make up the message. At this point it is interesting to check on Perl's internal representation of our message:


$VAR1 = bless( {
    'HL7_VERSION' => '2.6',
    'REPETITION_SEPARATOR' => '~',
    'SEGMENTS' => [
                    bless( {
                          'FIELDS' => [
                                       'MSH',
                                       '|',
                                       '^~\\&',
                                       undef,
                                       undef,
                                       undef,
                                       undef,
                                       '20160711141348+0200',
                                       undef,
                                       [
                                         'ADT',
                                         'A01',
                                         'ADT_A01'
                                       ],
                                       'id201',
                                       'P',
                                       '2.6',
                                       undef,
                                       undef,
                                       undef,
                                       undef,
                                       undef,
                                       undef,
                                       undef,
                                       undef,
                                       undef,
                                       'DD015'
                                                      ]
                           }, 'Net::HL7::Segments::MSH' ),
                    bless( {
                          'FIELDS' => [
                                       'PID',
                                        undef,
                                        undef,
                                        [
                                          '100660325',
                                          [
                                             'NationalPN',
                                             '2.16.840.1.113883.19.3',
                                             'ISO'
                                          ],
                                          '0~80253',
                                          '',
                                          '',
                                          '',
                                          '',
                                          1
                                         ],
                                         undef,
                                         [
                                           'GREENING',
                                           'WAYNE',
                                           '',
                                           '',
                                           '',
                                           '',
                                           'L'
                                         ],
                                         undef,
                                         '19610130',
                                         'M',
                                         undef,
                                         undef,
                                         undef,
                                         undef,
                                         undef,
                                         undef,
                                         undef,
                                         undef,
                                         undef,
                                         undef,
                                         303603715,
                                         undef,
                                         undef,
                                         undef,
                                         'LONDON'
                                      ]
                           }, 'Net::HL7::Segment' ),
                    bless( {
                          'FIELDS' => [
                                         'PV1',
                                          undef,
                                          'E',
                                          undef,
                                          undef,
                                          undef,
                                          undef,
                                          '12345678901',
                                          undef,
                                          undef,
                                          undef,
                                          undef,
                                          undef,
                                          undef,
                                          undef,
                                          undef,
                                          undef,
                                          undef,
                                          0,
                                          185,
                                          0
                                      ]
                           }, 'Net::HL7::Segment' ),
                    bless( {
                          'FIELDS' => [
                                          'DG1',
                                          1,
                                          undef,
                                          'S42.1'
                                       ]
                           }, 'Net::HL7::Segment' ),
                    bless( {
                          'FIELDS' => [
                                          'DG1',
                                          2,
                                          undef,
                                          'S42.2'
                                      ]
                           }, 'Net::HL7::Segment' )
                   ],
                 'SUBCOMPONENT_SEPARATOR' => '&',
                 'SEGMENT_SEPARATOR' => '
',
                 'FIELD_SEPARATOR' => '|',
                 'COMPONENT_SEPARATOR' => '^',
                 'ESCAPE_CHARACTER' => '\\'
               }, 'Net::HL7::Message' );
 


We then stringnify this structure into our desired HL7 message with $msg->toString() :

MSH|^~\&|||||20160526110214||ADT^A01^ADT_A01|
 id201|P|2.6||||||||||DD015|
PID|||100660325^^^NationalPN&2.16.840.1.113883.19.3
 &ISO^0~80253^^^^1||GREENING^WAYNE^^^^^L||
 19610130|M|||||||||||303603715||||LONDON|
DG1|||S42.1|
DG1|||S42.2|
PV1||E||||||12345678901|||||||||||0|185|0

Validation

Despite completing the message, our job is not done yet, as we have to make sure that the message conforms to the specifications; and we're going to do that over the Internet by calling into a validation service.

IHE Gazelle HL7 Validator is a web service that accepts XML SOAP messages over HTTP. The service accepts messages that adhere to an HL7 conformance profile
(a description of the data and messages that an interface sends and/or receives) using a profile OID. The XML packet should also contain our raw HL7 message.

The service replies with the errors found, so we just have to keep resubmitting until we eliminate them all,  at which point we are rest assured that we achieved full conformance.

Unfortunately the OID's available, apply to v2.5 only so our 2.6 message will be validated against 2.5 specs, something that is certain to result in errors.
Nevertheless, validation aside, we will go through with the example to also demonstrate how a SOAP web service is accessed, but won't get into further details; we  save them for another part of the article on parsing HL7 CDA's with XML::Twig.

So, the web service API offers two methods:

  • validate(): validates the given message against the given profiles and sends back a validation report 
  • about(): gives information about the running version of the tool

The definition of the web service API is available here and describes its validate() method with the following prototype:   

public String validate(String, String, String) throws SOAPException;

 

  • The second parameter is xmlValidationContext, it is an XML formatted String respecting the XSD schema given at https://gazelle.ihe.net/xsd/ValidationContext.xsd. This parameter is mandatory since it gives information about the HL7 message profile to use for the validation process. 
  • Finally, the third String stands for the message to validate. The message must use ER7 syntax (i.e.. pipe-based syntax)


To cut the story short, we need to call the validate() method with a complex XML validateMessage type that is broken down to three String primitives: 


<xs:complexType name="validateMessage">
    <xs:sequence>
         <xs:element minOccurs="0" name="xmlValidationMetadata" type="xs:string"/>
         <xs:element minOccurs="0" name="xmlValidationContext" type="xs:string"/>
         <xs:element minOccurs="0" name="messageToValidate" type="xs:string"/>
     </xs:sequence>
 </xs:complexType>


xmlValidationMetadata type is optional so we will skip it.

As the method expects a string, xmlValidationContext type will point to the strignified version of the ValidationContext XML complex type whose definition can be found in 'ValidationContext.xsd'.

messageToValidate will point to our raw HL7 message.

The body of an example XML message as expected by the service can be found at  https://gazelle.ihe.net/files/GazelleHL7v2Validator-soapui-project.xml 

 


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.hl7.gazelle.ihe.net/">
    <soapenv:Header/>
         <soapenv:Body>
             <ws:validateMessage>
                <!--Optional:--> <xmlValidationMetadata></xmlValidationMetadata> <!--Optional:-->
                 <xmlValidationContext><![CDATA[<?xml version="1.0" encoding="UTF-8"?> <ValidationContext> <ProfileOID>1.3.6.1.4.12559.11.1.1.36</ProfileOID> <ValidationOptions> <MessageStructure>ERROR</MessageStructure>              <Length>WARNING</Length> <DataType>ERROR</DataType> <DataValue>WARNING</DataValue> </ValidationOptions> <CharacterEncoding>UTF-8</CharacterEncoding> </ValidationContext>]]>
            </xmlValidationContext>
                 <messageToValidate>
                     MSH|^~\&|PAMSimulator|IHE|PAMSimulator|IHE|20120615092215|
|ADT^A28^ADT_A05|20120615092215|P|2.5||||||ASCII EVN||20120615092215 PID|||DDS-37727^^^DDS&1.3.6.1.4.1.12559.11.1.4.1.2
&ISO^PI||Holmberg^Christer^^^^^L|Axelsson^^^^^^M|
19300414|M|||Fabriksgatan^^Västervik^^59391^SWE||||||||||||||||||||N PV1||N
                </messageToValidate>
             </ws:validateMessage>
        </soapenv:Body>
 </soapenv:Envelope>


By comparison, the Perl code to to construct the appropriate structure as well as to call the service is: 


use XML::Compile::SOAP::Trace;
use XML::Compile::WSDL11;
use XML::Compile::SOAP11;
use XML::Compile::Transport::SOAPHTTP;
use XML::Compile::SOAP::Trace ;
use XML::LibXML;
use strict;

my $wsdl = XML::Compile::WSDL11->new("gazelleHL7v2ValidationWS.wsdl");
     $wsdl->importDefinitions("ValidationContext.xsd");

my $message='MSH|^~\&|||||20160712125216+0200|
|ADT^A01^ADT_A01|id201|P|2.5||||||||||DD015|
PID|||100660325^NationalPN&2.16.840.1.113883.19.3
&ISO^0~80253^^^^^1||GREENING^WAYNE^^^^^L||
19610130|M|||||||||||303603715||||LONDON|
PV1||E|||||12345678901|||||||||||0|185|0|
DG1|1||S42.1|
DG1|2||S42.2|';

my $validate=
    {
          ProfileOID => "1.3.6.1.4.12559.11.1.1.60",

        ValidationOptions =>
                  { 
                        MessageStructure => "ERROR",
                        Length => "WARNING",
                        DataType => "ERROR",
                        DataValue => "WARNING",
                 },

       CharacterEncoding => "UTF-8",
};

my $schema = XML::Compile::Schema->new("ValidationContext.xsd");
my $doc    = XML::LibXML::Document->new('1.0', 'UTF-8');
my $write  = $schema->compile(WRITER =>'ValidationContext');
my $xml    = $write->($doc, $validate);
  
   $doc->setDocumentElement($xml);

   my $finalstuct ={
            xmlValidationMetadata=>'',
            xmlValidationContext=>"$doc",
            messageToValidate=>$message
        };

my $call = $wsdl->compileClient('validateMessage');

my ($response, $trace) = $call->($final,'UTF-8');


The service responds with another XML message whose important parts are highlighted below: 


<ValidationTestResult>FAILED</ValidationTestResult>

 <ValidationCounters>
         <NrOfValidationErrors>12</NrOfValidationErrors>
         <NrOfValidationWarnings>23</NrOfValidationWarnings>
         <NrOfValidatedAssertions>410</NrOfValidatedAssertions>
</ValidationCounters>


The NrOfValidatedAssertions are those passing the test, while the errors that occurred are due to the difference in the HL7 versions. Such errors are: 


<Error>
        <Location>ADT_A01/MSH[0]/MSH-3(Sending Application)</Location>
         <Description>Element \'Sending Application\' is specified as required (R) but not present in the message</Description>
         <Type>Required field missing</Type>
</Error>

 <Error>
        <Location>ADT_A01/MSH[0]/MSH-4(Sending Facility)</Location>
        <Description>Element \'Sending Facility\' is specified as required (R) but not present in the message</Description>
        <Type>Required field missing</Type>
</Error>

<Error>
        <Location>ADT_A01/EVN</Location>
         <Description>Element \'EVN\' is specified as required (R) but not present in the message</Description>
         <Type>Segment sequence error</Type>
 </Error>


Let's pick the error related to the EVN segment as an example. EVN indicates the event that happened (in this case the admission of the patient) hence it contains information about the type of the message, but because it duplicates information found in the MSH such as "A01" it was removed from version 2.3 onwards; nevertheless the specific OID profile requires it, so we have to set it. 
Following all the guidelines we finally restructure the message into: 


MSH|^~\&|Sending Application|Sending Facility|
Receiving Application|Receiving Facility|20160712125216+0200|
|ADT^A01^ADT_A01|id201|P|2.5|||||||||||
EVN||20060501140008|||000338475^Author
^Arthur^^^^^|20160711
PID|||100660325^^^NationalPN&2.16.840.1.113883.19.3
&ISO^0~80253^^^XXX^^1||GREENING^WAYNE^^^^^L||
19610130|M|||||||||||||||LONDON|
PV1||E||1|||12345678901|||||||||||0||0|
DG1|1||S42.1|||A|
DG1|2||S42.2|||A|
 

Its resubmission brings 0 errors back: 


<ValidationCounters>
        <NrOfValidationErrors>0</NrOfValidationErrors>
        <NrOfValidationWarnings>36</NrOfValidationWarnings>
        <NrOfValidatedAssertions>583</NrOfValidatedAssertions>
</ValidationCounters>



Connecting to the receiving interface

Validation is one thing, but connecting to a receiving interface and exchange messages, is another. The receiving end will typically respond with an 'acknowledgement' message.
If the operation is successful:


MSH|^~\&|||||20160315174135.704+0200||ACK^A01^ACK|22101|P|2.6
MSA|AA|id201

With errors if unsuccessful:


MSH|^~\&|||||20160315174135.704+0200||ACK^A01^ACK|22101|P|2.6
MSA|AR|id201
ERR||PID^3|102||001
ERR||DG1^3|102||003
ERR||DG1^3|102||003

Check Understanding v2 Acknowledgements for more information

The Transport and the Character set

Hl7 messages were mostly exchanged over TCP/IP with the client and the server opening a socket to communicate. The most popular communication protocol for the client and server to talk to each other is the so called Lower Layer Protocol (LLP) but HL7 messages can also be send via a variety of TCP/IP transports, like  FTP, SOAP and SMTP.

LLP uses special block markers at the start, <SB> or 0x0b, and the end, <EB> or 0x1c, of every HL7 message, followed by a carriage return <CR>:
<SB> <HL7 message> <EB><CR>

The receiver then strips those markers to get at the pure message.

Things have evolved though so that we now can have HL7 over HTTP. 

HL7 over HTTP is intended as a transport level protocol for transactional messaging between systems and is intended as an alternative to the traditional Minimal Lower Layer Protocol (MLLP).
It provides a number of key improvements:

  • HTTP is widely used for a variety of purposes, and is well understood by application developers, network engineers, etc.  
  • HTTP allows for authentication (username/password) and character set encoding to be specified in a standardized way.   
  • Tool and hardware support for HTTP is widespread, with many specialized software and hardware devices providing enhanced support for common protocols such as HTTP. In addition, HTTP is widely supported on many platforms, and may be viewed as a "commodity-level" feature. Many languages and toolsets have built in support for HTTP.

This brings forward the issue of the character set used in the communication. What does HL7 use? The specs are pretty clear on that :

"HL7 doesn't have its own character set. It has a mechanism for escaping multibyte characters, this is mainly/only used by systems that would otherwise mangle a multibyte character (e.g. lots of US 7-bit ASCII systems).
UTF-8 is the de-facto standard encoding for v2 messages in North America, in Europe it's ISO 8859-1 (Latin-1). UTF-8 is the commonly used encoding for UNICODE. Note that UNICODE is an example of a character set, it is not a character encoding.
Use "UNICODE UTF-8" in MSH.18 and you're all set. See UNICODE FAQ for details about UNICODE and its encodings (UTF-8, UTF-16, UTF-32)

Note: HL7 2.x versions prior to version 2.8 indicated that any of the UNICODE encodings are acceptable in the HL7 messages. After some practical experience with using UNICODE, it was determined that only UTF-8 can be safely used as a UNICODE encoding in HL7 messages and as of HL7 version 2.8 only UTF-8 is listed as an allowed encoding. This applies to earlier versions of HL7 messages as well: in order to insure interoperability, UTF-16 or UTF-32 SHALL NOT BE USED IN HL7 v2.x messages.

Hints: Try to avoid using operating system specific character pages (e.g. Windows cp1252, Mac code pages, EBCDIC variations)"


Conclusion


The standard is constantly evolving, as such version 3, CDA, and subsequently FHIR, work with XML messages. Their use is not as widespread as HL7 v2.x though, which occupies 95% of US Health institutions and about the same percentages throughout the world. Therefore having knowledge of HL7 v2.x is, and will be for many years to come, a well sought out skill in the HealthIT industry.

The source code for the examples can be found on Pastebin:

http://pastebin.com/kfggYNPQ
http://pastebin.com/GMegJLdF

hl7logosqMore Information

Caristix HL7 Definition

Perl HL7 toolkit

Net::HL7 module

HAPI

Mirth Connect

Chameleon/Iguana

epSOS

What Are the Different Standards in Healthcare?

HL7 Frequently Asked Questions

XML Implementation Technology Specification - Data Types

What is an HL7 Conformance Profile?

HL7 Conformance profiles and HL7 tables management

HL7 Message Profiles : ADT:A01

Understanding v2 Acknowledgements

HL7apy

Nhapi

IHE Gazelle HL7 Validator

SocketReader for MLLP and HL7

HL7 over HTTP

Related Articles

 

To be informed about new articles on I Programmer, sign up for our weekly newsletter,subscribe to the RSS feed and follow us on, Twitter, FacebookGoogle+ or Linkedin

Banner


Highlights Of The Europe 2024 PostgreSQL Conference
22/11/2024

This year's premium conference for PostgreSQL took place in Athens, Greece between October 22-25. The nice Athenian weather and cultural aspect aside, the conference was a big hit too.



Copilot Improves Code Quality
27/11/2024

Findings from GitHub show that code authored with Copilot has increased functionality and improved readability, is of better quality, and receives higher approval rates than code authored without it.

 [ ... ]


More News

espbook

 

Comments




or email your comment to: comments@i-programmer.info





Last Updated ( Wednesday, 27 July 2016 )