jL7: Coverage report

Here is the current unit test coverage report for jL7. This time I’ve been implementing most of the test as I was writing the code (and for all last changes before writing the code) but as you see, much work is still needed in order to reach the 100% code coverage (whether that is a sign of software quality or not…).

OVERALL COVERAGE SUMMARY

name class, % method, % block, % line, %
all classes 68%  (32/47) 64%  (302/469) 73%  (6247/8614) 70%  (1356.8/1949)

OVERALL STATS SUMMARY

total packages: 8
total executable files: 45
total classes: 47
total methods: 469
total executable lines: 1949

COVERAGE BREAKDOWN BY PACKAGE

name class, % method, % block, % line, %
org.jl7.comm 0%   (0/2) 0%   (0/14) 0%   (0/155) 0%   (0/39)
org.jl7.samples 0%   (0/2) 0%   (0/6) 0%   (0/110) 0%   (0/30)
org.jl7.dsl 20%  (2/10) 8%   (6/80) 7%   (74/1122) 8%   (20/265)
org.jl7.hl7proc 50%  (2/4) 33%  (12/36) 43%  (452/1041) 40%  (87/218)
org.jl7.mllp 100% (3/3) 89%  (16/18) 82%  (294/359) 77%  (82/107)
org.jl7.hl7 100% (9/9) 82%  (102/125) 90%  (1940/2146) 89%  (374/420)
org.jl7.test 94%  (15/16) 87%  (157/181) 94%  (3300/3494) 91%  (752.8/829)
org.jl7.textutils 100% (1/1) 100% (9/9) 100% (187/187) 100% (41/41)

jL7: How to create a Sender

package org.jl7.samples;

import java.io.IOException;

import org.jl7.hl7.HL7Message;
import org.jl7.hl7.HL7Parser;
import org.jl7.mllp.MLLPMetaData;
import org.jl7.mllp.MLLPTransport;
import org.jl7.mllp.MLLPTransportable;

public class Sender {
    private static final String MESSAGE = "MSH|^~\\&||ABCHS||AUSDHSV|20070103112951||ADT^A08^ADT_A01|12334456778893|P|2.5|||NE|NE|AU|ASCII\rEVN|A08|20060705000000\rPID|1||0000112234^^^100^A||XXXXXXXXXX^^^^^^S||10131113|1||4|^^RICHMOND^^3121||||1201||||||||1100|||||||||AAA\rPV1|1|O|^^^^^1|||||||2|||||1||||654345509^^^100^A|1|||||||||||||||||||||||||200607050000||||||V\rPV2|||||||1||||||||||||||||^^^^^^^^^103\rROL|1|AD|SAHCP|XXXXXXXXXX^^^^^^S|||||6|1\rPR1|1||1||20060705|1\rGT1|1||||||||||||||||||||NOT APPLICABLE";
    private static final String HOST = "localhost";
    private static int PORT = 9991;

    /**
     * @param args
     */
    public static void main(String[] args) {
        HL7Message msg = HL7Parser.parseMessage(MESSAGE, true);
        MLLPTransportable transportable = new MLLPTransportable();
        transportable.message = msg;
        transportable.metaData = new MLLPMetaData(HOST, PORT);
        MLLPTransport transport = new MLLPTransport();
        for (int i = 0; i < 10; i++) {
            try {
                MLLPTransportable tr = transport.sendMessage(transportable, true);
                System.out.println(tr.message);
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

jL7: How to create a listener

package org.jl7.samples;

import java.io.IOException;

import org.jl7.hl7.HL7Message;
import org.jl7.hl7.HL7Parser;
import org.jl7.mllp.MLLPMetaData;
import org.jl7.mllp.MLLPTransport;
import org.jl7.mllp.MLLPTransportable;

public class Listener {
    private static final String HOST = "localhost";
    private static int PORT = 9991;
    private static MLLPMetaData metaData = new MLLPMetaData(HOST, PORT);

    /**
     * @param args
     */
    public static void main(String[] args) {
        MLLPTransport transport = new MLLPTransport();
        MLLPTransportable transportable = null;
        for (int i = 0; i < 10; i++) {
            try {
                transportable = transport.receiveMessageReconnectOnError(metaData);
                HL7Message msg = transportable.message;
                System.out.println(msg);
                HL7Message ack = HL7Parser.parseMessage("MSH|^~\\&|GC APP|GC FAC|ACME APP|ACME FAC|20071016055244||ACK^A01|20071016055244131|P|2.3.1|\r\n" + "MSA|AA|13463136|MSG Received Successfully|", true);
                MLLPTransportable response = new MLLPTransportable();
                response.message = ack;
                response.metaData = new MLLPMetaData(HOST, PORT);
                transport.sendMessage(response);
            }
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

jL7: How to receive an HL7 message

Let’s assume we have a remote system which sends us HL7 messages on port xxx. We first need to create an MLLPMetaData object which will contain the information concerning host and port we’ll be listening to:

        private MLLPMetaData metaData = new MLLPMetaData(host, port);

The receiving of a message is done using an MLLPTransport object and invoking it’s receiveMessage method:

                MLLPTransport transport = new MLLPTransport();
                MLLPTransportable transportable = null;
                try {
                        transportable = transport.receiveMessage(metaData);
                } catch (IOException e) {
                        fail("IOException: " + e);
                }
                HL7Message msg = transportable.message;

The receiveMessage method returns an MLLPTransportable object which contains a reveived message in its Message member variable.

Since we usually do not want to close the listening socket after every message, the receiveMessage keeps an open connection which needs to be closed:

                try {
                        transport.disconnect();
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }

That’s it !

jL7: How to send an HL7 message

We’ll now see how we can send an HL7 message using jL7.
Let’s assume we already have an HL7 message object:

                HL7Message msg = HL7Parser.parseMessage("...", true);

Now we need to create an MLLPTransportable object. This object basically encapsulates the information need to send the message: the message itself the hostname and port number where it should be sent to

                MLLPTransportable transportable = new MLLPTransportable();
                transportable.message = msg;
                transportable.metaData = new MLLPMetaData(host, port);

Now we use an MLLPTransport object in order to actually send this newly created transportable object:

                MLLPTransport transport = new MLLPTransport();
                try {
                        transport.sendMessage(transportable);
                } catch (IOException e) {
                        fail("IOException: " + e);
                }

Since the transport object retains the connection to the remote host after sending the message (we do not want to create x connections in order to send x messages in a row), we should close the connection when we’re done with the transport object:

                try {
                        transport.disconnect();
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }

That’s it !

jL7: How to use the jl7 domain specific language

Introduction

The jl7 DSL (Domain Specific Language) is a scripting language which can be used to process and convert HL7 messages. It allows accessing HL7 entities (like message, segments, fields…) with a more natural language than using the Java API.

The DSL is implemented in the package org.jl7.dsl. The entry point to the DSL capabilities is the HL7DSL class. It provides two static methods to work with HL7 messages:

  • processMessage
  • convertMessage

The only difference between these two methods is that convertMessage additionally creates an output message which is returned after the processing.

In the processMessage method, the provided message is to be referred to as “message”. In the convertMessage method, the input message is called “message_in” and the output message is called “message_out”.

General syntax

The DSL is based on Groovy (see http://groovy.codehaus.org for more information). All Groovy and Java libraries can be used.

Here are a few examples of how to use the DSL.

Accessing message content

The following example obtains the PID segment:

println "message.PID:\n"+message.PID

If the message contains multiple PID segments, it will return the first one. In order to get the second PID segment the following syntax shall be used:

println "message.PID[2]:\n"+message.PID[2]

Note: The first segment has the index number 1 (not 0).

To obtain the PID-5 field from a message we can write:

println "message.PID(5):\n"+message.PID(5)

If we have a repetition of this field, the second instance of the field can be accessed by using:

println "message.PID(5)[2]:\n"+message.PID(5)[2]

This is actually the same syntax as for segments.

If the field contains multiple components we access the second component with:

println "message.PID(5)(2):\n"+message.PID(5)(2)

The same kind of syntax can be used to access subcomponents:

println "message.PID(5)[2](3)(2):\n"+message.PID(5)[2](3)(2)

So in the DSL a message has the following structure:

  1. Message
  2. Segment
  3. Field
  4. Component
  5. Subcomponent

To go from one level to the next one, round brackets are used: xxx(n).
To access a repetition of a segment or a field, square brackets are used: xxx[n].
In both cases, the first index is 1.

Modifying message content

A message can be assigned the value of another message using the operator: <<.

The following example copies the content of message_in to message_out.

message_out << message_in

A segment of one message can be added to another message. The following example copies the MSH segment from message_in to message_out.

message_out << message_in.MSH

The operator = can be used instead of >> in order to replace a segment rather than add it.

message_out.PV1 = "PV1|1|O"

The example above replaces the first PV1 segment in message_out by the new segment.

message_out.PV1 = message_in.PV1

Note: If there is no PV1 segment already available in message_out, the two operators = and << have the same effect.

Message groups

The jl7 DSL also supports segments groups. These are groups of segment semantically related e.g. the PD1, ARV, ROL, NK1, NTE segments following a PID segment belong together with the PID segment to a PATIENT group.

The following groups exist:

  • PATIENT: PID or MRG, PD1, ARV, ROL, NK1, NTE
  • VISIT: PV1, PV2, ARV, ROL, DB1
  • ORDER: ORC, OBR, NTE
  • PROCEDURE: PR1, ROL
  • INSURANCE: IN1, IN2, IN3, ROL

Segment groups can be extracted from a message:

message_in.PATIENTS

This returns a list of all PATIENT segment groups. In order to get one of these group, the following can be used:

message_in.PATIENTS(1)

The first group in the list is indexed with 1.

Segments group can be added to a message just like segments.

message_out.MSH = message_in.MSH
message_out.EVN = message_in.EVN
message_out << message_in.PATIENTS(1)
message_out << message_in.VISITS(1)

Mapping message content

In order to map values in a HL7 message, a mapping table can be define.

//Define mapping table
def map = ["s":"I", "a":"O"]
//Change the visit type
message_out.PV1(2) << map[message_in.PV1(2)]

A default value can also be defined for the map.

def map = ['s':'I', 'a':'O'].withDefault{ 'U' }

This means that if the value of attribute being mapped is not ‘s’ or ‘a’, ‘U’ will be used as mapped value.

Note: jL7 can be downloaded here.

Leistungserfassung – Activity recording for billing in Germany

General workflow

Capture

The capture of data regarding delivered services is done at the time of delivery by the person responsible for the delivery (e.g. a nurse, a radiologist or medical therapist).

Coding

Depending on the usage, the provisioning of services in the hospital is coded in different ways:
DRG coding: For operational and other procedures which are carried out for inpatients or outpatients in the hospital, OPS codes are usually used;
Billing of single services: Depending on the health insurance type different tariff systems are used e.g. uniform assessment standard (EBM) or the tariff system for physicians (GOÄ);
In-house accounting for services: Here both OPS and tariff systems (GOÄ or EBM) can be used. The advantage of the tariff systems is that a provisioning is already assigned to a cost equivalent (point values). Thus tariff systems are also used with the DRG calculation to the capture of the expenses.
Ideally, a uniform mechanism based on an internal catalog should be made available for the coding of delivered services. Nevertheless, because of the complicated rules involved (e.g. guidelines for DRG-coding) it is only possible in limited cases.

Grouping

At the time of the coding, all data relevant for DRG are usually not available yet or not yet provided by the software interfaces.
Example: When a patient is discharged, the completeness and correctness of the documentation is checked. The data required for the determination of a DRG, among other things the diagnoses of the patient (coded using ICD-10) as well as the procedures (coded with OPS), are displayed and valued.

Communication

Message overview

To support billing, two HL7 message types are used in Germany:
  • BAR messages to transfer information regarding diagnosis codes and procedures: used for DRG i.e. procedure based fees.
  • DFT messages are used for lump compensation and additional/extra fees, to transfer:
    • either EBM/GOÄ codes if the sending system supports a rule based generation of EBM and GOÄ codes
    • or neutral charge codes if not

DFT Messages

Statutory basis

Vertragsarztrechtsänderungsgesetz
The Vertragsarztrechtsänderungsgesetz (German law regarding physicians involved in contractual medical care) was considerably liberalized the activities of physicians on January, 1st 2007). It considerably changed how physicians can be contracted (e.g. part time) and how practices can be organized.
Therefore, all active physicians involved in contractual medical care received on July, 1st 2008 a new lifelong doctor’s number and a company site number, so that their performed services can be associated unambiguously to an insurance company.
Starting July, 1st 2008, every physicians involved in contractual medical care must mark all bills with the LANR (lifelong doctor’s number) and the BSNR (company site number). Thus all DFT messages should contain this information.

LANR

 The lifelong doctor’s number (LANR) refers to the person. All active physicians involved in contractual medical care country-wide have been given such a 9 digit number.
The first 7 digits of the LANR are assigned to a physician once and never change.
The 8th and 9th digits of the LANR encode the specialty of the physician. They can change if the doctor changes his main activity focus.

BSNR

The company site number (BSNR) indicates whether the doctor has performed an activity at his main site or at satellite site. Every hospital/practice main or satellite site as well as every medical care center receives its own number.
The company site number is necessary for all bills instead of the previously used doctor’s number. The physicians involved in contractual medical care can present a common bill for all activities to the insurance company. Then the bill must contain the different company site numbers.

BAR Messages

Diagnosis and procedures

Diagnosis codes are to be coded using ICD-10.
Procedure codes are to be coded using OPS (formerly known as OPS-301).

Both catalogs of codes are updated every year. The new catalogs have to be made available in all systems (HIS and departmental systems) at the same time. It must be possible to import the published list of procedure/diagnosis codes in the OIS.
Additionally all codes sent in BAR messages have to be assigned a code representing the catalog used for the coding (e.g. I10-2009 or O301-2009).

Each diagnosis code or procedure code must be uniquely identified in BAR messages.

When diagnosis codes and/or procedure codes are cancelled, a cancellation BAR message is to be sent with all codes having been cancelled.

When diagnosis codes and/or procedure codes are updated, an update BAR message is to be sent with all codes having been updated.

A short introduction to HL7

HL7 (Health Level Seven) is an international healthcare standard used for the integration of medical systems and the exchange of health integration.  HL7 has been developed in order to reduce the cost of interfacing medical systems.

Organization

This standard is developed by a volunteer-based non-profit organization. From 1993 national HL7 organization have existed in addition to the global HL7 organization (HL7 Inc.).

History

It was founded in 1987 and became an ANSI (American National Standards Institute) accredited standard in 1994.
The name HL7 refers to the seventh layer (application layer) of the ISO OSI reference model.

Version 1.0 was published in 1987 and only supported admission-discharge-transfer, order-entry and display-oriented queries.
Version 2.0 was published in 1988. The backward compatible revisions 2.1 to 2.6 were developed between 1988 and 2007.

Version 2.x is currently supported by all major medical information systems vendors.

In 1995, work was started on HL7 version 3. An initial standard publication in 2005 was published in 2005. HL7 version 3 is based on object oriented principles and on a Reference Information Model (RIM) used to express the data contents.

Although being able to provide a more exact and more powerful definition of the contents of messages, HL7 v3 adoption has been much slower than expected. A few reasons for that are:

  • Most vendors already use an HL7 v2 interface to communicate with other systems so you cannot only have a v3 interface but always also need a v2 interface in order to interoperate.
  • It’s much easier to write a basic v2 interface than using v3 which introduces considerable new conceptual complexity.