This topic contains 4 replies, has 4 voices, and was last updated by  Jeff Drumm 2 years, 9 months ago.

Acknowledgment messages?

  • Hi, I guess you are all quite busy with your user conference (a pity I can’t attend, but I hope you enjoy it) — so feel free to answer this later whenever you get around to it.

    We have a channel LLP -> Trans which receives a HL7 message, looks something up in a database, creates an XML and stores it in a file and in a database.

    Currently, the Ack script of the LLP listener always sends an “AA” ack, promising that the message will be processed. If the message is wrong, the database lookup fails, etc., the channel stops, but the LIMS will not notice it. I have a feeling that this is not the optimal way to do it.

    There are three possible acknowledgment codes: AA (accept), AE (error) and AR (reject). It seems that the difference between AE and AR is not clearly defined.

    I think it could be a good idea to return AE if the sending application has done something wrong (so it’s no use to resend this message) and AR if the receiving application (here: my Lua module) has done something wrong, so after some reconfiguration the same message can be resent. If there was an unexpected error (i.e. a bug in my code), I would just stop the channel with an error? Or should I send an “AR” ack before?

    This is a little bit different from what you propose: If the MSH fields 9, 11 or 12 are wrong, I feel it’s the sending application’s responsibility to send the correct message type with the correct processing ID and HL7 version, so I’d rather return AE there. Am I mistaken?

    Now, for the implementation, I can think of two possible ways:
    1. Check the incoming message in the Ack script, and if it is wrong, create an “AE” ack message. Also, look up my stuff in the database, and if something is wrong (database down, no data found, …), send an “AR” ack message. Then, in the Trans script, do it all again.
    2. Change the channel into LLP -> Queue, and move all the work into the Ack script. So, in the Ack, create the XML and save it everywhere. Catch unexpected errors to stop the channel (maybe return AR?)

    Number 1 has the drawback that I’m doing all the stuff twice (consult the database, etc.), once in Ack and once in the Trans script. And if for some reason the Trans script fails unforeseeably, I’m in the same dilemma as before.

    Number 2 sounds good, but is the Ack script really considered for doing all the work? What if the processing is slow? How long can the sending application wait for the Ack?

    I have found your nice failover solution, but this involves creating a new channel, and I don’t know whether we can afford this. Also, if the message is queued, it just returns AA, so the sending application will notice nothing of a problem.

    What is your experience regarding this? Do you have any hints or best practices?

    ACK support is all over the map in the medical informatics world. Very few applications have graceful support for issues here.

    If your application is fast enough there is nothing stopping you from avoiding queuing altogether and consuming the payload of the HL7 message within the ACK translator script – only send the ACK back when the message has been fully consumed.

    There are other strategies for handling bad messages – listing out exceptions in a dashboard etc.

    In my experience most upstream systems just don’t do anything with ACKs. More times than not, I’m getting messages from a an engine (not the original system) and they just don’t have the bandwidth (or need really) to build the paths required for that. I wouldn’t put a lot of effort into a full ACK system. I’d recommend doing something scaled down.

    I deploy one of two two ACK Modules on my installs. A fast acknowledgement (always AA) of messages or one that parses data for my required fields and then one that sends an AE with an error message. I mostly deploy the fast ACK response unless the client tells me they want verification or they have a pattern of sending me bad data. If I get enough junk data I deploy the AE messages to kind of cover myself should issues arise – “Well I told you I was rejecting these records”.

    function main(Data)
       local Msg, Name = hl7.parse{vmd='VMD_GLOBAL.vmd', data=Data}
       local Ack = hl7.message{vmd='VMD_GLOBAL.vmd', name='ACK'}
       -- Build MSH
       -- This segment is static regardless of whether 
       -- a message is acknowledged errored or accepted
       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] = Msg.MSH[12][1]
       Ack.MSH[7][1] = os.date('%Y%m%d%H%M%S')
       -- Build MSA
       -- This segment contains an an acknowledment 
       -- MSA 1 is AA if Message passes all validation
       -- MSA 1 is AE if Message has failed validation
       -- MSA 3 is the Reason
       -- MSA 4 is the Value or segment causing the issue
       Ack.MSA[2] = Msg.MSH[10]
       if Name =='Catch_All' then
          Ack.MSA[1] = 'AE'
          Ack.MSA[3] = 'Unsupported Message Type'
          Ack.MSA[4] = Msg.MSH[9][1]..' '..Msg.MSH[9][2]
          log.nack( Ack.MSA[3],Ack.MSA[4])
       elseif Msg.PID[2][1]:S() == '\"\"' 
          and Msg.PID[3][1]:S() == '\"\"'  then
          Ack.MSA[1] = 'AE'
          Ack.MSA[3] = 'Missing Patient Identifier'
          Ack.MSA[4] = 'PID-2.1,PID-3.1'
          log.nack( Ack.MSA[3],Ack.MSA[4])
       elseif Msg.PID[18][1]:S() == '\"\"' then
          Ack.MSA[1] = 'AE'
          Ack.MSA[3] = 'Missing Patient Account Inormation'
          Ack.MSA[4] = 'PID-18.1'
          log.nack( Ack.MSA[3],Ack.MSA[4])
       elseif Msg.PID[5][1]:S() == nil then
          Ack.MSA[1] = 'AE'
          Ack.MSA[3] = 'Missing Patient Last Name'
          Ack.MSA[4] = 'PID-5.1'
          log.nack( Ack.MSA[3],Ack.MSA[4])
       elseif Msg.PID[5][2]:S() == nil then
          Ack.MSA[1] = 'AE'
          Ack.MSA[3] = 'Missing Patient First Name'
          Ack.MSA[4] = 'PID-5.2'
          log.nack( Ack.MSA[3],Ack.MSA[4])
       elseif Msg.MSH[4]:S() == nil and Msg.PID[2][6]:S() == nil 
          and Msg.PID[3][6]:S() == nil and Msg.PID[4][6]:S() == nil then
          Ack.MSA[1] =  'AE'
          Ack.MSA[3] = 'Missing Facility Information'
          Ack.MSA[4] = 'MSH-4,PID-2.6,PID-3.6,PID-4.6'
          log.nack( Ack.MSA[3],Ack.MSA[4])
       elseif Msg.PID[7]:S() == nil then
          Ack.MSA[1] = 'AE'
          Ack.MSA[3] = 'Missing Date of Birth'
          Ack.MSA[4] = 'PID-7'
          log.nack( Ack.MSA[3],Ack.MSA[4])
       elseif Msg.PV1[44]:S() == nil then
          Ack.MSA[1] = 'AE'
          Ack.MSA[3] = 'Missing Date of Service'
          Ack.MSA[4] = 'PV1-44'
          log.nack( Ack.MSA[3],Ack.MSA[4])
          -- If no issues exist send a response code, AA
       else Ack.MSA[1] = 'AA'
          log.ack()
       end
       ack.send(Ack:S())
    end
    

    This code references a function I built in a shared module, I’m including that relevant code below.

    -- Canned log message for acknowledgments
    log={}
    function log.ack()
       iguana.logInfo('Message Acknowledged Accepted')
       return 'Message Acknowledged Accepted'
    end
    -- Canned error formated use in the full acknowledgement process
    function log.nack(A, B)
       iguana.logWarning('Validation failed, Acknowledged Error AE Response: '..A..' '..B)
       return 'Validation failed, Acknowledged Error AE Response: '..A..' '..B
    end
    

    Forgive any confusing syntax or inefficient ways of doing this. I wrote this shortly after I was first introduced to Iguana and I’m kind of cringing while reading it now. Maybe I’ll rewrite this if I ever find a real need.

    M.R. McBee

    Thank you for your helpful messages! So I see, if most systems do not handle ACK messages consistently or don’t even look at them, we can go for something rather simplistic.

    Considering this from the message producer’s (i.e. sending system’s) standpoint, my general rule of thumb has been to attempt to retry message delivery on an AE, and skip/log/alert on an AR. The assumption is that an AE represents a temporary error that may be resolved and redelivery should be attempted, whereas an AR is an indication that the message will never be accepted.

    Most interface engines and many HL7 user applications allow you to configure recourse actions based on ACK type; those actions can include resend, connection reset/resend, skip message and interface shutdown (with limits on the number of times resends/resets are attempted before skip or shutdown).

    You may want to consider this as you develop your ACK strategy.

    Jeff Drumm ◊ VP and COO ◊ HICG, LLC. ◊ http://www.hicgrp.com

You must be logged in to reply to this topic.