Custom ACKs

Introduction

It is possible to customize the LLP listener component to invoke a Translator instance. This allows using the Translator instance to customize the response of the LLP listener component to allow applications like:

  • Custom ACK formats including XML formatted ACKs.
  • Application ACKs/NACKs as opposed to just protocol ACKs, i.e, “not only did I get your data but my application said it was okay!”
  • Responding to queries (i.e., query/response logic)

You can also send messages via LLP with the Translator. This allows you to send queries and apply sophisticated logic to responses and ACKs/NACKs.

Configuring the LLP listener channel [top]

By default an LLP listener will be created with the Fast setting, which generates a generic HL7 ACK message.

To customize the ACK message:

  1. Edit the channel:
  2. Change the ACKnowlegement Settings to Translator and Save Changes:
  3. After the channel has been saved you will be able to click on the Edit Script… link to edit the code used to generate the ACK message:

    Note: The “No commit is selected. The channel cannot be run.” error is expected it simply means you need to save your first commit before you can run the channel.

Generic ACK example [top]

With the Translator you have the freedom to send any message you like. Simply call ack.send() with the message (a string), and you’re done. We also provide ack.generate() to create Fast-ACK messages inside the Translator. The following example sends ACKs exactly like the Fast-ACK would:

It’s very clear to see the relationship between the incoming sample message and the resulting ACK message.

You can get the Create a Generic ACK code from our code repository.

Custom ACK Example [top]

In this example we create a complete ACK message from scratch, mapping each of the fields ourselves using Lua:

Here’s a generated ACK message:

MSH|^~\&|Main HIS|St. Micheals|iNTERFACEWARE|Lab|20160915003015||ACK|9B38584D9903051F0D2B52CC0148965775D2D23FE4C51BE060B33B6ED27DA820|P|2.6.1| 
MSA|AA|9B38584D9903051F0D2B52CC0148965775D2D23FE4C51BE060B33B6ED27DA820|Everything was okay dokay!|

You can get the Create a custom ACK code from our code repository.

Retrying a Custom ACK [top]

This expands on the example from the previous page by using the retry module to retry ack.send() if it fails. The retry.call() function is designed to stop the channel after the specified number of retries. if you wish to continue processing the next message you can use pcall() to catch the error and continue processing.

Both options are demonstrated (and commented) in this example:

You can get the Retry a Custom ACK code from our code repository.

Tip: To customize error handling use the errorfunc argument with an error function, see Customize the retry logic using an error function.

Query Response Example [top]

This is a great technique for implementing a query response interface. Say you have an incoming message that looks like:

MSH|^~&|Acme||||20100927160935||QRY^A19|2010927169956|P|2.4|
QRD|20100927160935|R|I|92716934|||1^RD|4525285|DEM|

For which you need to send back an ADR^A19 message giving patients matching the given ID.

Here’s an example. As you can see the principle is simple. The query comes in. It’s parsed and used to generate a SQL query against the database. The data from the SQL query is used to populate the HL7 message that is returned. The logic can be made more elaborate but the principle is the same.

Note: We set live = true to execute SQL queries interactively in the Iguana script editor.

Tip: For a production version it is recommended best practice to escape the Id to avoid a SQL injection attack, see escaping values correctly for SQL.

Here’s the vmd query.vmd and you can get the Query Response ACK code from our code repository.

Failover Solution [top]

One interesting scenario we came across with a client was this one they had third party EHR systems sending messages which could sometimes be incorrect. They would want to send a negative acknowledgment instead of forwarding the messages to their pharmacy system.

So the algorithm to follow was:

  1. Get the message.
  2. Send it to a web service to validate and in some case modify the message.
  3. If it was bad send back a negative ACK, this would be rendered inside the EHR systems allowing the end user or IT team to resolve the problem.
  4. If good, send it to the Pharmacy System, get the ACK and then send that back to the EHR system (since it could be negative).

The problem is what to do when the Pharmacy system is down. Obviously the NACK mechanism cannot be maintained but data still needs to flow.

So here’s a failover solution. The solution works by having one channel set up with LLP–>To Channel. The above script goes into the LLP Translator script. A second channel is set up with LLP listener –> LLP client which just acts as a queue when the pharmacy system is not able to receive the messages.

local llp_connect = require 'llp'

local function SendMessage(Data)
   -- optionally validate data here - if message is bad send NACK
   local C = llp.connect{host='localhost', port=5145, live=true, timeout=1}
   C:send(Data)
   local A = C:recv()
   C:close()
   ack.send(A)
end

local function SendMessageToQueue(Data)
   local C = llp_connect{host='localhost', port=5362, live=true}
   C:send(Data)
   local A = C:recv()
   C:close()
end

function main(Data)
   local Success = pcall(SendMessage, Data)
   if not Success then
      SendMessageToQueue(Data)
      ack.send(ack.generate(Data))
   end
end

Suppressing ACKs altogether [top]

Sometimes there are systems that you will encounter which want no ACKs whatsoever.

This isn’t recommended practice but Iguana can support this model. Simply use a custom Translator script but pass in nil i.e.:

ack.send(nil)

ACK vs. filter Translator instance [top]

Confusion often arises about which API calls are available (and which are not), within the Acknowledgment Translator (LLP) and Filter Translator components. In summary, queue.push() is not available in an Acknowledgment Translator (LLP) component and ack.send() is not available in a Filter.

Common mistakes are:

  • Trying to call queue.push{data=Message} from an Acknowledgment Translator (LLP) component
    Note: This is not possible in an Acknowledgment Translator (LLP) component because the message is automatically passed to the Filter component.
  • Trying to send acknowledgments from a Filter Translator component
    Note: This is not possible in a Filter component, you can only send ACKs in the Acknowledgment Translator (LLP) component.

If you need to follow a logic flow something like this:

  1. If message is good ACK it and enqueue.
  2. Else if message is bad send a NACK and do not queue the message.

Then you can use this solution:

  1. Make a shared lua module that is invoked from two Translator components.
  2. Have a clean function defined in that module that:
    • Takes a message and returns true/false if the message is accepted/rejected
    • Gives a reason why the message is rejected

    Note: This function only tests the validity of the message, it cannot have any side-effects (i.e., it cannot change any data).

  3. The ACKnowledgment script can then use that function to determine when to make a NACK and what to put into it.
  4. The Filter script can use the function to determine whether or not to enqueue the message.

In pseudo code, the shared module might look something like this:

-- Source code of myfilter shared module

function IsGoodMessage(Msg)
   -- Do logic
   if Bad then
       return false, Reason
   else
       return true
   end
end

And the Translator instance used for the Acknowledgment could look like this:

require 'myfilter'
-- main() is given the original HL7 message.
function main(Data)
   local Msg = hl7.parse{vmd='demo.vmd', data=Data}
   local Ack = hl7.message{vmd='ack.vmd', name='Ack'}
   Ack.MSH[3][1] = Msg.MSH[5][1]
   Ack.MSH[4][1] = Msg.MSH[6][1]
   Ack.MSH[5][1] = Msg.MSH[3][1]
   Ack.MSH[6][1] = Msg.MSH[4][1]
   Ack.MSH[10] = Msg.MSH[10]
   Ack.MSH[9][1] = 'ACK'
   Ack.MSH[11][1] = 'P'
   Ack.MSH[12][1] = '2.6.1'
   Ack.MSH[7][1] = os.date('%Y%M%d%h%m%S')
   Ack.MSA[2] = Msg.MSH[10]

   local Good, Reason = IsGoodMessage(Msg)

   if Good then
      Ack.MSA[1] = 'AA'
      Ack.MSA[3] = "Everything was okay dokay!"
   else
      Ack.MSA[1] = 'AR'
      Ack.MSA[3] = Reason 
   end

   ack.send(Ack:S())
end

And the Filter Translator code would look like this:

require 'myfilter'

function main(Data)
   if IsGoodMessage(Data) then
      queue.push{data=Data}
   end
end

Leave A Comment?