HL7 to X12: Generating Billing Transactions
Contents
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:
- Download these files to your computer:
- Project zip file: x12_270_batch_example.zip
- Supporting VMD file: BARHL7.vmd
- From the Dashboard, click Add Channel. The Add Channel dialog appears.
- Select the following components:
- Source = LLP Listener
- Destination = To Translator
- Click the Configure Channel button. The Configure New Channel window appears.
- Click the Channel tab and enter the following information:
- Channel Name = X12 270 Batch
- Description = X12 270 batch example
- 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.
- In the Destination tab, click the Edit Script hyperlink to launch the Translator.
- In the dialog that appears, select Import project from zip file and enable Import sample data.
- Click the Choose File button and browse to select the project file you’ve just downloaded (x12_270_batch_example.zip).
- Click Import. Iguana loads the project (including code, modules, and the VMD file that you’ve also downloaded).
- 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.
- 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
- 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
- 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 thex12Message.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
- 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
- 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
- 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.