Database to HL7 migrating a legacy system

<strong>Test</strong>: Final Code Tweaks

So in the vmd file the OBR.18 field was defined as a string – i.e. a ST composite type. There was a fragment of python script that appended ^NHCCVIS to the value.

Effectively this made the OBR.18.2 set to this value.

The difference showed up when comparing the strings directly. The other little difference is the the original interface appended \r to the last segment whereas the Translator interface did not.

This was easy to resolve by tweaking the vmd file and the editing the script. I also modified the script to tell me if the generated message was identical or not. The other trivial difference was a trailing \r at the end of the message. I made it so that test code will only run within the editor in test mode.

So here’s the final code. The CVISOutbound.vmd vmd file and the filter module:

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

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'}

   if #R == 0 then
      print("Odd - unable to find message matching this one.")
      return
   end 

   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])
   if iguana.isTest() then
      Compare(Msg)
   end
   queue.push{data=Msg:S()}
end

function Compare(Msg)
   local Out = Msg:S()
   local Orig, Different = dbfill.CompareMessage(Out)
   if Different then
      local R = diff.Compare(Orig, Msg)   
      trace('There were '..#R..' differences.')
   else
      trace('Messages match!')
   end
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] = T.MSHDateTimeofMessage:HT()
   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%S', 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][1] = T.OBRPlacerField
   OBR[18][2] = 'NHCCVIS'
   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][5][1] = T.OBXObservationValue1
   OBX[2][3][1] = T.OBXObservationIdentifierIdentifier1
   OBX[2][3][3] = 'NHCCVIS'
   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

The dbfill module:

dbfill = {}

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

local function RandomDate(Modifier)
   local T = 40700 - Modifier
   trace(T)
   return os.ts.date("%Y-%m-%dT%H:%M:%S", T)
end

local function RandomString(V, RowIndex)
   return V:nodeName()..RowIndex
end

local function FillValue(R, i, RowIndex)
   local T = R[i]:nodeType()
   if T == 'datetime' then
      R[i] = RandomDate(RowIndex*i)   
   elseif T == 'string' then 
      R[i] = RandomString(R[i], RowIndex)
   else
      error('Need to handle integers and doubles')
   end
end

local function MakeRow(T,RowIndex)
   for i =1,#T do
      FillValue(T, i, RowIndex)   
   end
   return T
end

local function makeDummyDatabase(Vmd, Name)
   local T = db.tables{vmd=Vmd, name=Name}
   for i =1,20 do
      MakeRow(T.NHCCVISReport[i], i)
   end
   return T
end

local function ColumnList(Row)
   local L = ''
   for i =1, #Row-1 do
      L = L..Row[i]:nodeName()..',n'
   end
   L = L..Row[#Row]:nodeName()
   return L
end

local function generateSql(T, DB)
   for TableIndex=1,#T do
      local C = ColumnList(T[TableIndex][1])
         ..", MESSAGE_ID, PARENT_ID"
      for RowIndex = 1, #T[TableIndex] do
         local Row = T[TableIndex][RowIndex]
         local S = 'REPLACE INTO '..T[TableIndex]:nodeName()
         ..'('..C..')n VALUES('
         for ColumnIndex =1,#Row-1 do
            S = S..'"'..Row[ColumnIndex]..'",n'
         end
         S = S..'"'..Row[#Row]..'"n'
         S = S..',"'..RowIndex..'"n'
         S = S..',"'..RowIndex..'")'
         trace(S)
         DB:execute{sql=S, live=true}
      end
   end
end

local function InsertMessageRow(Count, MessageName, DB) 
   for i=1, Count do
      local S = 'REPLACE INTO '..MessageName..'(MESSAGE_ID, STATUS)'..' VALUES('..i..",'W')"
      DB:execute{sql=S, live=true}
   end
end

function dbfill.generateDummySourceData(Vmd, MessageName, D)
   local T = makeDummyDatabase(Vmd, MessageName)
   generateSql(T, D)
   InsertMessageRow(#T.NHCCVISReport, MessageName, D)
end

function dbfill.SaveMessage(Base, Msg, Data)
   local FileName = 'D:temp'..Base
                 ..Msg.MSH[10]..'.txt'
   trace(FileName)
   local F = io.open(FileName, "w")
   F:write(Data)
   F:close()
end

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

-- gets equivalent old message
-- the name is a blind...
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

Here’s the source code for the diff module.

And the code for the “From Translator” channel:

require('dbfill')

 --dbfill.generateDummySourceData('CVISOutbound.vmd', 'NHCCVIS',D)

And the code for the “To Translator” channel we used to record the original messages from the old interface:

require 'dbfill'

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

function main()
 --dbfill.generateDummySourceData('CVISOutbound.vmd', 'NHCCVIS',D)
 
 local R = conn:query(
 "SELECT MESSAGE_ID FROM NHCCVIS WHERE STATUS = 'W'"
 )
 for i=1, #R do
 PushId(R[i].MESSAGE_ID:nodeValue())
 conn:execute(
 "UPDATE NHCCVIS SET STATUS='R' WHERE MESSAGE_ID = "
 ..R[i].MESSAGE_ID
 )
 end
end

function PushId(Id)
 queue.push{data=Id}
end

Next Step?

Now we have completed the regression test it only remains to review what we learned and consider the next steps.

Leave A Comment?

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.