Working with X12

HL7 to X12: Generating Billing Transactions

To demonstrate how easy it is to convert HL7 data into usable X12 data, we are going to resolve a real-world scenario with code that you can later adapt for your own solution:

Quite often, HL7 patient data is collected over a specific time period, then processed all at once using a scheduler. In this scenario, a nightly feed of HL7 ‘BAR P01’ messages (“Add patient account”) is sent from an ADT/Registration system to a patient accounting system. We must determine each patient’s eligibility and benefits information, then produce a corresponding X12 270 transaction.

To implement our solution:

  1. Download these files to your computer:
  2. From the Dashboard, click Add Channel. The Add Channel dialog appears.
  3. Select the following components:
    • Source = LLP Listener
    • Destination = To Translator
  4. Click the Configure Channel button. The Configure New Channel window appears.
  5. Click the Channel tab and enter the following information:
    • Channel Name = X12 270 Batch
    • Description = X12 270 batch example
  6. We are going to leave the other channel component settings as their defaults, so simply click the Add Channel button. The channel is created and several warnings appear. Don’t worry! This is just Iguana reminding us that before we can run the channel, we’ll need to commit at least one milestone. We’ll perform this step shortly.
  7. In the Destination tab, click the Edit Script hyperlink to launch the Translator.
  8. In the dialog that appears, select Import project from zip file and enable Import sample data.
  9. Click the Choose File button and browse to select the project file you’ve just downloaded (x12_270_batch_example.zip).
  10. Click Import. Iguana loads the project (including code, modules, and the VMD file that you’ve also downloaded).
  11. Commit your first milestone.

Done!

How It Works

Let’s quickly walk through the code to give you a better idea of how this solution works. We’ve also included some comments in the script itself, as additional help.

  1. Starting at the top of the main() function, the first chunk of code converts any non-conforming terminators in the incoming data:
    function main(Data)
    
       -- Process batch file
       -- convert non-conformant terminators to "r"
       Data = convertTerminators(Data)

    Here is how we created the convertTerminators() function:

    function convertTerminators(Data)
       Data = Data:gsub('rn','r')
       return Data:gsub('n','r')
    end
  2. Next up in the main()function, we split the incoming batch file into an array of messages:
    -- split batch into array of messages
    local Msgs=splitBatch(Data)

    Here is how we created the splitBatch() function:

    function splitBatch(Data)
    
       -- strip batch info off Data
       local a = Data:split('r')  
       trace(a)
       Data=a[1]
       trace(Data)
       for i=2,#a do
          Data=Data..'r'..a[i]
          trace(Data)
       end
       trace(Data)
    
       -- split Data into messages
       local delimiter='MSH|^~&|'
       local b=Data:split(delimiter)
    
       -- add MSH segment info
       trace(b)
       for i=1,#b do 
          b[i-1]=delimiter..b[i]
       end
       b[#b]=nil -- delete dup msg
       trace(b)
    
       -- SOME IDEAS THAT COULD BE USEFUL
    
       -- global variable to count messages
       MsgCnt = #b
       trace('Messages Count '..MsgCnt)
    
       -- globals for batch segment info
       FHS=a[1]:split('|')
       BHS=a[2]:split('|')
       FTS=a[#a]:split('|') 
       BTS=a[#a-1]:split('|') 
    
       return b
    end
  3. Let’s continue walking through the main() function. Our next step is to create a blank X12 Eligibilty/Benefits transaction (4010 270). To do this, we will use the ‘x12_mapfactory’ module to call the x12Message.create(4010_270) function. This creates an X12 270 transaction template based on HIPAA version 4010. In the following code snippet, the new X12 transaction is assigned to a local variable ‘Outx12Msg’:
    local x12Message = require 'x12_mapfactory'
    local Outx12Msg = x12Message.create('4010-270')

    Here is how we created the create() function (found in the ‘x12_mapfactory’ module):

    function x12_mapfactory.create(x12MsgType)
       if x12MsgType == '4010-270' then
          local Outx12Msg =x12.message{vmd='270v3.vmd',name='x270'}
          --Populate x12 message
          create_4010_270(Outx12Msg) 
          return Outx12Msg
       end 
    end
  4. Next, let’s walk through and parse each of the BARP01 messages into the awaiting X12 template. First, we will call the populatePP() function and pass the parameters ‘Outx12Msg’ and ‘OutHl7’. This function will populate our blank X12 transactions with payer and provider information extracted from the BARP01 messages.
    for i=1,#Msgs do
        local OutHL7, name = hl7.parse{vmd='BARHL7.vmd', data= Msgs[i]}
        -- On first pass create the x12 doc and populate
        -- transactional header with payer and provider information
        if populated == false then 
           Outx12Msg = x12Message.populatePP(Outx12Msg,OutHL7)
           populated=true
        end

    Here is how we created the populatePP() function (also found in the ‘x12_mapfactory’ module):

    function x12_mapfactory.populatePP(x12Msg, hl7Msg)   
       template_4010_PAY_270(x12Msg,hl7Msg)
       template_4010_PRV_270(x12Msg,hl7Msg)
       return x12Msg 
    end

    This code leverages several additional template_4010() functions. Here is how we built those:

    local function template_4010_PAY_270(x12Msg,hl7Msg)
    
        -- HL Segment -- Payer Header Information 
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.HL[1]='1'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.HL[3]='20'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.HL[4]='1'
    
       -- Get payer information from payers module
       local P = require 'payers'
       local payerId = hl7Msg.IN1[3][1]:S()
       local payer = P.getPayerInfo(payerId) 
    
       -- NM1 Segment -- Payer Information
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.Loop2100A.NM1[1]=payer.EntityIdentifierCode
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.Loop2100A.NM1[2]=EntityTypeQualifier
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.Loop2100A.NM1[3]=payer.SourceName
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.Loop2100A.NM1[8]=payer.IdCodeQualifier
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.Loop2100A.NM1[9]=payer.InfoSourcePrimaryId
    
    end
    
    local function template_4010_PRV_270(x12Msg,hl7Msg)
    
       -- HL Segment -- Provider Information
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.HL[1]='2'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.HL[2]='1'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.HL[3]='21'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.HL[4]='1'
    
       -- NM1 Segment -- Provider Information
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.Loop2100B.NM1[1]='1P'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.Loop2100B.NM1[2]='1'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.Loop2100B.NM1[3]='WELBY'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.Loop2100B.NM1[4]='Marcus'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.Loop2100B.NM1[8]='SV'
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].Loop2000B.Loop2100B.NM1[9]='23452435'  
    
    end
  5. Our final big step is to iterate through each BARP01 message again, this time to add a subscriber entry to our X12 transaction. We do this by calling the populateSubrscr() function and passing, once again, ‘Outx12Msg, ‘OutHl7’, and ‘msgCounter’. The amount of BARP01 records in batch message will determine the amount of subscriber entries in the X12 transaction. In this scenario, there are three records.
    Outx12Msg=x12Message.populateSubscr(Outx12Msg,OutHL7,msgCounter)
    msgCounter = msgCounter + 1

    Here is how we created the populateSubscr() function (found in the ‘x12_mapfactory’ module):

    function x12_mapfactory.populateSubscr(x12Msg, hl7Msg,c)     
       template_4010_SUBR_270(x12Msg,hl7Msg,c)
       return x12Msg 
    end

    As you can see above, this function leverages another template_4010() function. Here is how we built that one out:

    local function template_4010_SUBR_270(x12Msg,hl7Msg,c)
    
       -- HL Segment -- Subcriber Information
    
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.HL[1]=c+2
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.HL[2]='2'
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.HL[3]='22'
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.HL[4]='0'
    
       -- NM1 Segment -- Subscriber Information
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.NM1[1]='IL'
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.NM1[2]='1' 
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.NM1[3]=hl7Msg.PID[5][1][1]
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.NM1[4]=hl7Msg.PID[5][2]
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.NM1[5]=hl7Msg.PID[5][3]
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.NM1[8]='MRN'
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.NM1[9]=hl7Msg.PID[3][1]
    
        -- DMG Segment -- Subscriber Demographic Information
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.DMG[2]=hl7Msg.PID[7][1]
      x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.DMG[3]=hl7Msg.PID[8]
    
       -- EQ Segment -- Subscriber Benefit Plan Information
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.Loop2110C[1].EQ[1]='30'
       x12Msg.InterchangeEnvelope.FunctionalGroup[1].TransactionSet[1]
       ["Position 010"].Grp010ABCD[1].Loop2000A.GrpBCD[1].GrpCD[c+2].Loop2000C.Loop2100C.Loop2110C[1].EQ[1]='FAM'  
    end
  6. Finally, let’s replace all ‘|’ HL7 character delimiters with the appropriate ‘*’ X12 delimiter:
    local Out = Outx12Msg:S()
    Out = Out:gsub("|","*")

Congratulations! You’ve taken a batch of HL7 messages and created a corresponding set of X12 transactions for their subscriber eligibility and benefits requests.

What’s Next?

You may come across a situation in which the patient facility sends an extremely large amount of HL7 data, all at once. Let’s examine how to handle this scenario without clogging your own system.