E-Prescription

Introduction

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.

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:

  1. Get the message, parse it using xml.parse{}
  2. Extract out the base64 piece.
  3. Decode the base64 to get the EDIFACT message.
  4. Since the delimiters used in the EDIFACT are not readable translate them into readable characters.
  5. Strip off the header bit.
  6. 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

Next: Parsing formatted text