- Introduction
- Example input message
- Algorithm to parse it
- Getting the NCDCP and XML Trees
- Small transcoding module
- How the edifact.vmd was created
- Mapping the XML Wrapper
- Mapping the NCDCP Portion
- Complete Source Code
- Building an NCPDP REFREQ Request
Introduction
This post is a copy of an article from our version 5 wiki, it provides a real world example of using XML to work with Surescripts. However as it is an older article it uses screenshots are from Iguana 5.
Surescripts run a network for handling e-Prescribing. The messaging format consists of XML with embedded NCPDP messages in base64 encoding.
The Translator makes light work of handling this combination.
This is a very common kind of pattern with healthcare interfaces. Surescripts have an older system in the back end which takes NCPDP and they use web service technology, namely HTTPS and XML to define the envelope. This happens all the time and it makes sense as a way to leverage existing technologies in the backend of these organizations. See the Quest Labs interface.
If you have any questions please contact us at support@interfaceware.com.
Example input message [top]
This is an example Surescripts message:
<Message xmlns="http://www.surescripts.com/messaging" version="1.0"> <Header> <To>mailto:6827255840004.spi@surescripts.com</To> <From>mailto:9998888.ncpdp@surescripts.com</From> <MessageID>6e953e603be4467a9dfd725a7765958d</MessageID> <SentTime>2006-03-13T22:56:30.3Z</SentTime> </Header> <Body> <EDIFACTMessage>VU5BHB0uIB8eVUlCHVVOT0EcMB0dNmU5NTNlNjAzYmU0NDY3YTlkZmQ3MjVhN zc2NTk1OGQdHR05OTk4ODg4HFAdNjgyNzI1NTg0MDAwNBxEHTIwMDYwMzEzHDIyNTYzMCwzHlVJSB 1TQ1JJUFQcMDA4HDAwMRxSRUZSRVEdNjAwNTEyHlBWRB1QMh05OTk4ODg4HEQzHR0dHR00WCBQaGF ybWFjeR0xMjM0NSBNb3VudGFpbiBSb2FkHEFsZXhhbmRyaWEcVkEcMjIzMTUdNzAzOTIxMjEyMRxU RR8yMTY0NDQ0MzgwHEZYHlBWRB1QQx02ODI3MjU1ODQwMDA0HFNQSR0dHVJlZmlsbENoYW5nZRxTd XJlU2NyaXB0cxxUZXN0HE1EHERSHR0dNDY5MCBQYXJrd2F5IERyLhxQUkVTQ1JJQkVSIENJVFkcT0 gcNDUwNDAdNTEzMjI5NTUwMBxURR81MTMyMjk1NTA1HEZYHlBUVB0dMTk2MjAxMjIdVHVja2VyHER lYnJhHBwcTXMuHUYdNjUzMjg2NRw5NB04MzMxIEV2ZXJ3b29kIERyLiBBcHQuIDM0MhxDbGV2ZWxh bmQcT0gcNDQxMDMdNDQwODQ1MDM5OBxURR5EUlUdUBxMaXBpdG9yIDQwbWcgVGFibGV0cxwwMDA3M TAxNTcyMxxORBwxMBw0MBxNRR1VMhw2MB0cVGFrZSBvbmUgdGFibGV0IGRhaWx5Lh1aRFMcNjAcOD A0Hzg1HDIwMDQwMzI3HDEwMh0xHVIcMR5VSVQeVUlaHR0xHg==</EDIFACTMessage> </Body> </Message>
It came from this specification: Sample Messages 4dot20.pdf.
The base64 piece is meant to be an NCPDP message like this:
UNA:+\*' UIB+UNOA:0++6e953e603be4467a9dfd725a7765958d+++9998888:P+6827255840004:D+2006 0313:225630,3' UIH+SCRIPT:008:001:REFREQ+600512' PVD+P2+9998888:D3+++++4X Pharmacy+12345 Mountain Road:Alexandria:VA:22315+7039212121:TE*2164444380:FX' PVD+PC+6827255840004:SPI+++RefillChange:SureScripts:Test:MD:DR+++4690 Parkway Dr.:PRESCRIBER CITY:OH:45040+5132295500:TE*5132295505:FX' PTT++19620122+Tucker:Debra:::Ms.+F+6532865:94+8331 Everwood Dr. Apt. 342:Cleveland:OH:44103+4408450398:TE' DRU+P:Lipitor 40mg Tablets:00071015723:ND:10:40:ME+U2:60+:Take one tablet daily.+ZDS:60:804*85:20040327:102+1+R:1' UIT' UIZ++1'
In fact the delimiter characters are slightly different, they are not printable but the general format is as above.
Algorithm to parse it [top]
The algorithm to parse it is:
- Get the message, parse it using xml.parse{}
- Extract out the base64 piece.
- Decode the base64 to get the EDIFACT message.
- Since the delimiters used in the EDIFACT are not readable translate them into readable characters.
- Strip off the header bit.
- Parse the resulting EDIFACT message using a specially configured vmd.
Tada!
Getting the NCDCP and XML Trees [top]
The code makes use of the base64 encoding in the filter module. It also uses a small transcoding module:
This code gives us the XML envelope in the XML node tree XmlEnv and the NCDCP message in the node tree Edi. The screen shots shown above are using the unstable 5.1 branch which has some nice functionality in the manner in which is shows the unprintable 28,29, 30 etc. characters.
Small transcoding module [top]
This transcoding module is helpful for translating unprintable characters into printable ones or any other kind of translation of characters you might wish to do.
For instance in the Surescript example the delimiters used were not printable by calling this module like this:
You can see the ASCII bytes 28, 29, 30, 31 and 46 being converted into printable characters.
transcode={} function transcode.convert(Data, Map) local j = 1 local p = {} for i=1, #Data do local C = Map[Data:byte(i)] if C then p[#p+1] = Data:sub(j,i-1) p[#p+1] = C j = i + 1 end end p[#p+1] = Data:sub(j,#Data) return table.concat(p) end
How the edifact.vmd was created [top]
The edifact.vmd was created by setting up these custom options:
Then setting up one default message and using the message browser to create the Composites and Segment definitions required to give labels to all the data found in the EDIFACT message.
Mapping the XML Wrapper [top]
One can really appreciate the power of using a scripting based mapper with this part of the map. I made use of the fuzzy date/time parser to parse the date in the XML wrapper. Had to strip off the timezone piece of the field to get it parsing. It shows up nicely in the annotations. I used the lua sub string function to remove the mailto: prefixes.
This next screen shot shows how the data is mapped out into the Envelope Row as you would see when double clicking on the Envelope Row:
And this shows the XML tree from which we mapped the data.
It’s incredibly powerful for mapping being able to so clearly see the inputs and outputs.
Mapping the NCDCP Portion [top]
This is straightforward. Just like mapping HL7 with the mapper.
The hardest part is figuring out the domain part of the problem, namely what data do you actually want to map. The documentation doesn’t really clearly spec out what the contents of the segments. The PVD segments look as though they have varying content based on the segment code. This fragment of mapping code shows how we can map out data:
Complete Source Code [top]
The code makes use of the base64 encoding in the filter module. It also uses a small transcoding module. This is the edifact.vmd.
Here’s the code:
require("node") require("transcode") require("dateparse") local function trace(a,b,c,d) return end function main(Msg) local XmlEnv = xml.parse{data=Msg} local Edi = GetEdiMessage(XmlEnv) local Out = db.tables{vmd='edifact.vmd', name='Message'} MapXmlEnv(Out.Envelope[1], XmlEnv) MapEdi(Out.Pharmacy[1], Edi) end CodeSet={ [28]=':', [30]='\n', [31]='', [29]='+', [46]='.' } function ConvertNonPrintable(Data) local Out = transcode.convert(Data, CodeSet) return Out end function GetEdiMessage(XmlMsg) local D = XmlMsg.Message.Body.EDIFACTMessage[1]:nodeValue() local E = filter.base64.dec(D) E = E:sub(10) E = ConvertNonPrintable(E) trace(E) return hl7.parse{data=E, vmd='edifact.vmd'} end function MapXmlEnv(T, In) T.Sent = In.Message.Header.SentTime[1]:nodeValue():sub(1,19) :D() T.To = In.Message.Header.To[1]:nodeValue() :sub(8) T.Id = In.Message.Header.MessageID[1] T.From = In.Message.Header.From[1]:nodeValue() :sub(8) return T end function MapEdi(T, In) for i=1,#In.PVD do if In.PVD[i][1]:nodeValue() == 'P2' then MapPharmacy(T, In.PVD[i]) end end return T end function MapPharmacy(T, PVD) T.Pharmacy = PVD[7] T.Street = PVD[8][1] T.Town = PVD[8][2] T.State = PVD[8][3] T.Zip = PVD[8][4] return T end
Building an NCPDP REFREQ Request [top]
Suppose we had the following NCPDP request.
UNA:+*‘ UIB+UNOA:0++6e953e603be4467a9dfd725a7765958d+++9998888:P+6827255840004:D+2006 0313:225630,3′UIH+SCRIPT:008:001:REFREQ+600512‘ PVD+P2+9998888:D3+++++4X Pharmacy+12345 Mountain Road:Alexandria:VA:22315+7039212121:TE*2164444380:FX’ PVD+PC+6827255840004:SPI+++RefillChange:SureScripts:Test:MD:DR+++4690ParkwayDr.:PRESCRIBER CITY:OH:45040+5132295500:TE*5132295505:FX‘ PTT++19620122+Tucker:Debra:::Ms.+F+6532865:94+8331 Everwood Dr. Apt. 342:Cleveland:OH:44103+4408450398:TE’ DRU+P:Lipitor40mgTablets:00071015723:ND:10:40:ME+U2:60+:Take one tablet daily.+ZDS:60:804*85:20040327:102+1+R:1‘ UIT’ UIZ++1‘
This is a NCPDP transaction containing one REFREQ message (i.e. the part enclosed within the UIH and UIT segments inclusive). We can use the EDIFACT modules provided in the sample project to build the request given above. The project provides modules that define a canonical model to which the user must map their inbound data. This is the first task in implementing a new interface. The canonical model to build REFREQ transactions is divided into two modules: the edifact.segment module that defines NCPDP segments and the edifact.message.refreq module that defines a REFREQ transaction. Here is the definition of a UIB segment which is one of many segment definitions found in the edifact.segment module.
function edifact.segment.createUIB() return { {complex='Syntax', mandatory='true', position='1', data={ {name='Syntax identifier', value='', length='4', fixed='true', mandatory='true'}, {name='Syntax version Number', value='', length='1', fixed='true'} } }, {complex='Transaction Control', mandatory='true', position='3', data={ {name='Transaction control reference', value='', mandatory='true'}, {name='Initiator reference identifier', value='', mandatory='false'}, {name='Controlling agency', value='', mandatory='false'}, } }, {complex='Interchange Sender', mandatory='true', position='6', data={ {name='Level one', value='', mandatory='true'}, {name='Level one identification code', value='', mandatory='true'}, {name='Level two', value='', mandatory='false'}, {name='Level three', value='', mandatory='false'} } }, {complex='Interchange Recipient', mandatory='true', position='7', data={ {name='Level one', value='', mandatory='true'}, {name='Level one identification code', value='', mandatory='true'}, {name='Level two', value='', mandatory='false'}, {name='Level three', value='', mandatory='false'} } }, {complex='Date/Time of Message', mandatory='false', position='8', data={ {name='Date of initiation', value='', length='8', fixed='true', mandatory='false'}, {name='Event Time', value='', length='8', fixed='true', mandatory='false'} } }, {name='Test Indicator', value='', mandatory='false', position='10'} } end
And here is the definition of a REFREQ transaction found in the edifact.message.refreq module.
local function createREFREQMessage() return { {segment='UIH', mandatory='true', data=edifact.segment.createUIH()}, {segment='REQ', mandatory='false', data=edifact.segment.createREQ()}, {segment='PVD', mandatory='true', data={create=edifact.segment.createPVD}}, {segment='PTT', mandatory='true', data=edifact.segment.createPTT()}, {segment='DRU', mandatory='true', data={create=edifact.segment.createDRU}}, {segment='OBS', mandatory='false', data=edifact.segment.createOBS()}, {segment='COO', mandatory='false', data=edifact.segment.createCOO()}, {segment='UIT', mandatory='true', data=edifact.segment.createUIT()} } end return { {segment='UNA', mandatory='true', data=edifact.segment.createUNA()}, {segment='UIB', mandatory='true', data=edifact.segment.createUIB()}, {message='REFREQ', mandatory='true', data={create=createREFREQMessage}}, {segment='UIZ', mandatory='true'} }
Since the inbound data can come from a variety of sources, the code that maps data to our model will be unique to each interface. Users of the EDIFACT modules are responsible for writing the data mapping code. As an example, we implemented a data map in our sample project where all values are hard coded. Instead, we could have used an HL7v2 ORM message as our source. Here is the data map used in our sample project.
function main(Data) FillUNA(REFREQ[1]) FillUIB(REFREQ[2]) FillMessage(REFREQ[3]) local Transaction = edifact.simpleapi.make(REFREQ) end function FillMessage(Data) Data.data[1] = Data.data.create() FillUIH(Data.data[1][1]) FillPVD(Data.data[1][3]) FillPTT(Data.data[1][4]) Data.data[1][5].data[1] = Data.data[1][5].data.create() FillDRU(Data.data[1][5].data[1]) FillUIT(Data.data[1][8]) end function FillPVD(Data) Data.data[1] = Data.data.create() FillPVDP2(Data.data[1]) Data.data[2] = Data.data.create() FillPVDPC(Data.data[2]) end function FillUNA(UNA) UNA.data[1].value = ':' UNA.data[2].value = '+' UNA.data[3].value = '' UNA.data[4].value = '*' UNA.data[5].value = ''' end function FillUIB(UIB) UIB.data[1].data[1].value = 'UNOA' UIB.data[1].data[2].value = '0' UIB.data[2].data[1].value = '6e953e603be4467a9dfd725a7765958d' UIB.data[3].data[1].value = '27255840004' UIB.data[3].data[2].value = 'D' UIB.data[4].data[1].value = '9998888' UIB.data[4].data[2].value = 'P' UIB.data[5].data[1].value = '2006 0313' UIB.data[5].data[2].value = '225630,3' end function FillUIH(UIH) UIH.data[1].data[1].value = 'SCRIPT' UIH.data[1].data[2].value = '008' UIH.data[1].data[3].value = '001' UIH.data[1].data[4].value = 'REFREQ' UIH.data[2].value = '600512' end function FillPVDP2(PVD) PVD[1].value = 'P2' PVD[2].data[1].value = '9998888' PVD[2].data[2].value = 'D3' PVD[4].value = '4X Pharmacy' PVD[5].data[1].value = '12345 Mountain Road' PVD[5].data[2].value = 'Alexandria' PVD[5].data[3].value = 'VA' PVD[5].data[4].value = '22315' PVD[6].data[1].value = '7039212121:TE*2164444380' PVD[6].data[2].value = 'FX' end function FillPVDPC(PVD) PVD[1].value = 'PC' PVD[2].data[1].value = '6827255840004' PVD[2].data[2].value = 'SPI' PVD[3].data[1].value = 'RefillChange' PVD[3].data[2].value = 'SureScripts' PVD[3].data[3].value = 'Test' PVD[3].data[4].value = 'MD' PVD[3].data[5].value = 'DR' PVD[5].data[1].value = '4690 Parkway Dr.' PVD[5].data[2].value = 'PRESCRIBER CITY' PVD[5].data[3].value = 'OH' PVD[5].data[4].value = '45040' PVD[6].data[1].value = '5132295500:TE*5132295505' PVD[6].data[2].value = 'FX' end function FillPTT(PTT) PTT.data[2].value = '19620122' PTT.data[3].data[1].value = 'Tucker' PTT.data[3].data[2].value = 'Debra' PTT.data[3].data[5].value = 'Ms.' PTT.data[4].value = 'F' PTT.data[5].data[1].value = '6532865' PTT.data[5].data[2].value = '94' PTT.data[6].data[1].value = '8331 Everwood Dr. Apt. 342' PTT.data[6].data[2].value = 'Cleveland' PTT.data[6].data[3].value = 'OH' PTT.data[6].data[4].value = '44103' PTT.data[7].data[1].value = '4408450398' PTT.data[7].data[2].value = 'TE' end function FillDRU(DRU) DRU[1].data[1].value = 'P' DRU[1].data[2].value = 'Lipitor 40mg Tablets' DRU[1].data[3].value = '00071015723' DRU[1].data[4].value = 'ND' DRU[1].data[5].value = '10' DRU[1].data[6].value = '40' DRU[1].data[7].value = 'ME' DRU[2].data[1].value = 'U2' DRU[2].data[2].value = '60' DRU[3].data[2].value = 'Take one tablet daily.' DRU[4].data[1].value = 'ZDS:60:804*85' DRU[4].data[2].value = '20040130' DRU[4].data[3].value = '102' DRU[5].value = '1' DRU[6].data[1].value = 'R' DRU[6].data[2].value = '1' end function FillUIT(UIT) end
The canonical model has been designed to allow users to take advantage of the Translator’s autocompletion functionality. Specifically, it allows users to distinguish between elements in the model when mapping their data.
For model elements that occur exactly once i.e. their cardinality = 1, we simply assign a value to the element. For example,
REFREQ[1].data[1].value = ‘:’
where REFREQ[1] represents the UNA segment.
We must take a different approach for elements with a cardinality of > 1. For example, all NCPDP transactions can contain one or more messages. To add a REFREQ message to a transaction in our model, we execute the following code
REFREQ[3].data[1] = REFREQ[3].data.create()
where REFREQ[3] represents the root table storing REFREQ messages within the transaction. In this case, the create function returns a table representing the REFREQ message. To add another REFREQ message, we could execute the following
REFREQ[3].data[2] = REFREQ[3].data.create()
storing the new table in the next available index. We can map data to the new tables as we create them in our model.
For segments in a message that have a cardinality of > 1, we use a similar approach. For example, a REFREQ message can have one or more PVD segments. Let us suppose REFREQ[3].data[1] represents our REFREQ message. To add a PVD segment to a REFREQ message, we would execute the following code
REFREQ[3].data[1][3].data[1] = REFREQ[3].data[1][3].data.create()
where REFREQ[3].data[1][3] represents the root table storing the PVD segments within the message. To add another, we do the following
REFREQ[3].data[1][3].data[2] = REFREQ[3].data[1][3].data.create()
Once the data is mapped, we the pass the model to edifact.simpleapi.make which generates the NCPDP transaction. And that’s it.
local Transaction = edifact.simpleapi.make(REFREQ)
Here is the sample project.
EDIFACT_Example_To_Translator.zip