Database to HL7 migrating a legacy system

<strong>Test</strong>: Retrieve Old data

So I added ‘dbfill’ to the filter routine and added a new routine called dbfill with this code:

function dbfill.CompareMessage(Data)
   local Out = hl7.parse{vmd='CVISOutbound.vmd', data=Data}
end

And then tweaked the main routine to call this code:

local Out = Msg:S()
dbfill.CompareMessage(Out)
queue.push{data=Out}

That told me my first problem with the new mapping code. It was missing the message type! Oops.

Easy enough to solve with two additional lines of script (on lines 23 and 24) in the MapMSH function:

Then I altered the dbfill routine to:

function dbfill.LoadMessage(Base, Msg)
   local FileName = 'D:temp'..Base
                 ..Msg.MSH[10]..'.txt'
   trace(FileName)
   local F = io.open(FileName, "r")
   return F:read("*a")
end

function dbfill.CompareMessage(Data)
   local Out = hl7.parse{vmd='CVISOutbound.vmd', data=Data}
   local Orig = dbfill.LoadMessage('orig', Out)
   return hl7.parse{vmd='CVISOutbound.vmd', data=Orig}
end

This is where the real power of the Translator shows through because we are returning a parsed node tree from the loading routine we can open up parallel dialogs for the original message and the new output message and compare them along side each other. This screen shot shows exactly that with the left hand side showing the new tree and the right hand side the original output:

It’s easy to see the gaps that we have to fill in the new interface. I went through and added in the missing MSH fields:

function MapMSH(MSH, T)
   MSH[9][1]='ORU'
   MSH[9][2]='R01'
   MSH[3][1] = 'NHCCVIS'
   MSH[4][1] = 'NHC'
   MSH[5][1] = 'PCS'
   MSH[6][1] = 'EMR'
   MSH[12] = '2.3'
   MSH[10] = T.MSHMessageControlID
   MSH[7] = os.date('%Y%M%d%H%m')
   MSH[11][1] = 'T'
   return MSH
end

I went through the script putting in the mappings. Some observations:

  1. Having very regularly named source data made it quite easy see if data was set as a constant or came from the database.
  2. Deep auto-completion was very helpful in locating source data in the database table to map it.

To give an example see:

You can see in the above I was searching for the key phrase “219” in order to find the OBXObservationIdentifierText219 value.

Doing things this way makes for a very efficient work flow. It took me about 1/2 hour to flesh out the rest of the mappings. My script looked like this now:

require 'dateparse'
require 'node'
require 'dbfill'

local conn = db.connect{
   api=db.SQLITE,
   name='test', 
   live=true
}

local function trace(a,b,c,d,) return end

function main(Data)
   local R = conn:query{
      live=true,
      sql="SELECT * FROM NHCCVISREPORT WHERE MESSAGE_ID = "..Data
   }

   local Msg = hl7.message{vmd='CVISOutbound.vmd', name='NHCCVIS'}

   MapMSH(Msg.MSH, R[1])
   MapPID(Msg.PID, R[1])
   MapPV1(Msg.PV1, R[1])
   MapOBR(Msg.OBR, R[1])
   MapOBX(Msg.OBX, R[1])
   trace(Msg)
   local Out = Msg:S()
   dbfill.CompareMessage(Out)
   queue.push{data=Out}
end

function MapMSH(MSH, T)
   MSH[9][1]='ORU'
   MSH[9][2]='R01'
   MSH[3][1] = 'NHCCVIS'
   MSH[4][1] = 'NHC'
   MSH[5][1] = 'PCS'
   MSH[6][1] = 'EMR'
   MSH[12] = '2.3'
   MSH[10] = T.MSHMessageControlID
   MSH[7] = os.date('%Y%M%d%H%m')
   MSH[11][1] = 'T'
   return MSH
end

function node.HD(N)
   return os.date('%Y%m%d', dateparse.parse(N:S()))
end

function node.HT(N)
   return os.date('%Y%m%d%H%M', dateparse.parse(N:S()))
end

function MapPID(PID, T)
   PID[2][1] = T.PIDPatientIDExternalIDID
   PID[2][3] = '11'
   PID[2][4][1] = 'EMI Primary'

   PID[3][1] = T.PIDPatientIDInternalIDID
   PID[3][2] = T.PIDPatientIDInternalIDCheckDigit
   PID[3][3] = '11'
   PID[3][4][1] = 'MRN'

   PID[5][1] = T.PIDPatientNameFamilyName
   PID[5][2] = T.PIDPatientNameGivenName
   PID[7] = T.PIDDateTimeofBirth:HD()
   PID[8] = T.PIDSex
   PID[18][1] = T.PIDPatientAccountNumberID
   PID[18][4][1] = "Visit"

   return PID
end

function MapPV1(PV1, T)
   PV1[2] = 'OUTPATIENT'
   PV1[3][1] = 'NHCCVIS Assign'
   PV1[3][4][1] = 'NHC'
   PV1[18] = 'NA'
   PV1[19][1] = T.PVVisitNumberID
   PV1[19][4][1] = 'Visit'
   PV1[44] = T.PVAdmitDateTime:HT()
end

function MapOBR(OBR, T)
   OBR[2][2] = 'PCS'
   OBR[3][1] = T.OBRFillerOrderNumberEntityIdentifier
   OBR[3][2] = 'NHCCVIS'
   OBR[4][1] = T.OBRUniversalServiceIDIdentifier
   OBR[4][2] = T.OBRUniversalServiceIDText
   OBR[4][3] = 'NHCCVIS'
   OBR[7] = T.OBRObservationDateTime:HT()
   OBR[18] = T.OBRPlacerField
   OBR[25] = 'F'
   OBR[27][6] = 'ROUTINE'
   return OBR
end

function MapOBX(OBX, T)
   OBX[1][1] = '1'
   OBX[1][2] = 'FT'
   OBX[1][3][1] = T.OBXObservationIdentifierIdentifier
   OBX[1][3][3] = 'NHCCVIS'
   OBX[1][5][1] = T.OBXObservationValue
   OBX[1][11] = 'F'
   OBX[2][1] = '2'
   OBX[2][2] = 'RP'
   OBX[2][3][1] = T.OBXObservationIdentifierIdentifier1
   OBX[2][11] = 'F'

   OBX[3][1] = '3'
   OBX[3][2] = 'FT'
   OBX[3][3][1] = T.OBXObservationIdentifierIdentifier2
   OBX[3][3][2] = T.OBXObservationIdentifierText2
   OBX[3][3][3] = 'NHCCVIS'
   OBX[3][5][1] = T.OBXObservationValue2
   OBX[3][11] = 'F'
end

This screen shot shows the changes as we can see inside the editor, you can really see the power of having integrated version control in the Translator:

Next Step?

At this point our equivalent mapping is fairly complete. What is required now is to use the power of the Translator to programmatically identify the last few remaining differences between the old implementation of the interface and the new one.

Leave A Comment?