Professional Documents
Culture Documents
Version 1.0
Revision History
Date 04/17/2008 Version 1.0 Description Initial for version 0.70 Author Greg Caulton
Page 1 of 16
Contents
1. OVERVIEW........................................................................................................................................................................ 3 1.1 1.2 2. 2.1 2.2 2.3 3. 3.1 3.2 4. 4.1 4.2 5. 5.1 DEFAULT SETTINGS ............................................................................................ ERROR! BOOKMARK NOT DEFINED. DEFINITIONS ................................................................................................................................................................. 3 SETTINGS TOOL .................................................................................................. ERROR! BOOKMARK NOT DEFINED. CHANGING A SETTING ........................................................................................ ERROR! BOOKMARK NOT DEFINED. ADDING A SETTING ............................................................................................. ERROR! BOOKMARK NOT DEFINED. ADDING BATCH JOBS .......................................................................................... ERROR! BOOKMARK NOT DEFINED. SCHEDULING BATCH JOBS .................................................................................. ERROR! BOOKMARK NOT DEFINED. IDENTIFIER SOURCE TOOL .................................................................................. ERROR! BOOKMARK NOT DEFINED. ADDING AN IDENTFIER SOURCE ......................................................................... ERROR! BOOKMARK NOT DEFINED. LOGIN.................................................................................................................. ERROR! BOOKMARK NOT DEFINED.
Page 2 of 16
Overview
Integration
There are a number of inbound and outbound interfaces for PatientOS using Mirth for the majority of the integration points. The list is not limited to: a) Standard HL7 Interfaces. a. ADT Inbound b. ADT Outbound c. Lab Results Inbound d. Lab Orders Outbound b) XML or any other format supporting by Mirth a. XML Forms/Clinical Results Inbound b. XML Clinical c) Web development a. Patient Portal running within the JBoss container d) Batch processing a. Scheduled scripts which can directly reference the EJB services e) JDBC a. Direct SQL (not recommended except for read only access)
1.2 Definitions
POS
Page 3 of 16
2.
2.1
Mirth is an enterprise class integration engine with which PatientOS integrates. For inbound forms the basic flow is i) Mirth receives the XML file (from filesystem, JDBC, or port, etc) ii) Mirth maps the XML content into the PatientOS XML format for Records iii) Mirth inserts the XML into the interface_messages table iv) PatientOS interface controller (running as a JBoss service) picks up the file v) The appropriate Converter is called to map the XML to the PatientOS FormRecordModel vi) The model is stored writing to the records and record_details (if populated).
2.2 2.2.1 Mirth Setup Start Mirth
Program Files
PatientOS
version
Interface Engine
To setup the interface channels start the Mirth Manager using Start Manager. Login with admin/admin
Program Files
PatientOS
version
Interface
Figure 2 admin/admin
Page 4 of 16
2.2.3
Channels
Select the channels tab and view the existing channels (if the channels were not included download from http://www.patientos.org/documentation . Download the file oru_xml_file_pos_db.xml. The file name indicates it is a results file reading XML from a file and posting it into the PatientOS database. Select the channel ORU XML File POS DB and Edit Channel to edit the properties.
Figure 3 Channels
The first tab defines that XML is being read and this channel will auto start.
Page 5 of 16
2.2.4
Clicking on the Source tab will show which directory is being read to find the XML content and the file name format required to process. Note that the directory is defined as a relative path. It is relative from where the interface engine started (interfaces.bat or interfaces.sh). On windows this path would be C:\Program Files\PatientOS\<version>\server\interfaces\bin
In the figure above you can see that the channel will search for xml files in the directory C:\Program Files\PatientOS\<version>\server\interfaces\activity\oru_xml_in
every second.
Page 6 of 16
2.2.5
Destination
On the destinations tab you can see the SQL used to insert into the PatientOS interface_messages table. Take careful note of the URL and ensure the correct database is being accessed. In this case qapos is shown though the default should be demopos with the correct username and password.
Click on the Edit Transformer link to open the Transformer. Paste your XML into the top right frame and the XML elements will be parsed and displayed in a tree below. It helps if your XML is populated with sample data.
Page 7 of 16
2.2.6
Transformer
PatientOS is setup with Javascript to map the incoming XML elements to the PatientOS XML elements. The Outgoing tab serves to display a template XML which is populated by the Javascript. For this reason we want as little data as possible (most is not needed). However in order to see all the fields you might like to populate you can paste into the frame the RecordTransaction.xml file from http://www.patientos.org/documentation When have finished mapping replace the contents with the original XML You may need to tailor the XML (for example we will add RecordDetailModel rows.
The goal is to populate a) Enough of the PatientModel to match the patient name and MRN (matching is a large topic) unless you have the patient_id from the patients table. b) A single FormModel to reflect the form c) As many RecordModel has you have fields on this virtual form. d) As many RecordDetailModel if you have details for a specific Record. The concept of Record and RecordDetail is that the Record value is the significant value e.g. height, weight, lab results etc where as the RecordDetail are child details e.g. Clothing the patient had on when weighed or comments on the lab result. The script is written in Javascript and obviously you need some programming skills to understand the nuances of iterating through the XML elements and building up the target XML which is this tmp structure.
Page 8 of 16
We will work with this sample XML <ns1:PorcupineDataSet xmlns:ns1="http://www.cs.uit.no/inf3793/xml"> <ns1:patientId>4</ns1:patientId> <ns1:measureFrequency>300</ns1:measureFrequency> <ns1:sensorType>motion</ns1:sensorType> <ns1:timeStart>2008-01-18T00:00:00.000+01:00</ns1:timeStart> <ns1:timeStop>2008-01-18T00:15:00.000+01:00</ns1:timeStop> <ns1:commaSeparatedValues>89,22,0,0,0,0,0,0,0,2,22,24,4,12,99,140,120,94,2,0,0,0,4,5,32,4,0,0,0,0,0,0,0,0,0,0,32,12,1,0,0, 3,0,0,0,2,5,0,0,0,0,1,4,9,10,4,0,0,0,0</ns1:commaSeparatedValues> <ns1:comment>commaSeparatedValues and sensorType are real:)!</ns1:comment> <ns1:dataUnit>hammed motion</ns1:dataUnit> <ns1:intervalUnit>minutes</ns1:intervalUnit> </ns1:PorcupineDataSet> The patientId is actually the MRN and so in the javascript we first create the MRN mapping by dragging the XML patientID over to the javascript page. If you have setup a facility other than the default you would enter the reference key (your facility name uppercase with spaces removed).
On version 1.6 of Mith one way of creating the many iterations of RecordModel is to have the first defined in the template and then copy it each time to add to the XML. This section of Javascript will likely change to an easier to perform this in later version of Mirth. But essentially we know there are 8 records needed so we loop 8 times and within the loop duplicate the first RecordModel, map the values and where possible set common values outside each if statement. Not great but it works
Page 9 of 16
You must setup an Identifier Source (login to PatientOS as admin/admin and select Identifier Sources) specific to where this incoming data is coming from. PatientOS supports multiple interfaces and the source is a unique identifier for all external identifiers. In this example Porcupine Sensor was used as the identifier source.
2.2.9
Javascript
Ideally all the record_items have been created in PatientOS. The display must be unique in the database in order to reduce confusion with multiple items with the same name (it is possible to have dups but not recommended). The short display is typically used on controls, reports etc. The display being the formal name. The interface will add these items on the fly (writes to the record_items and record_item_details tables). Ideally preload using a reference spreadsheet, then import the record items. Note dates must be formatted YYYYMMDD and mapped to the date component or YYYYMMDDHHMMSS and mapped to the date/time format. Rather than parsing we will hard code it to get started with testing // map the MRN to match the patient tmp['PatientModel']['IdentifierModel']['sourceRef']['idvalue'] = "DEFAULTFACILITY"; tmp['PatientModel']['IdentifierModel']['idvalue'] = msg['ns1:patientId'].toString();
for (i=0; i<8; i++){ startTime = "20080118000000"; stopTime = "20080118001500"; if (i==0) { // measure frequency tmp['RecordModel'][i]['recordItemRef']['idvalue'] = "MOTIONMEASUREFREQUENCY"; tmp['RecordModel'][i]['recordItemRef']['display'] = "Motion Measure Frequency"; Page 10 of 16
tmp['RecordModel'][i]['dataTypeRef']['id'] = 56990; // DOUBLE tmp['RecordModel'][i]['valueDouble'] = msg['ns1:measureFrequency'].toString(); } else if (i==1) { // sensor type tmp['RecordModel'][i]=tmp['RecordModel'][i-i]; // create xml element tmp['RecordModel'][i]['recordItemRef']['idvalue'] = "MOTIONSENSORTYPE"; tmp['RecordModel'][i]['recordItemRef']['display'] = "Motion Sensor Type"; tmp['RecordModel'][i]['dataTypeRef']['id'] = 79128; // STRING tmp['RecordModel'][i]['valueString'] = msg['ns1:sensorType'].toString(); } else if (i==2) { // timestart tmp['RecordModel'][i]=tmp['RecordModel'][i-i]; // create xml element tmp['RecordModel'][i]['recordItemRef']['idvalue'] = "MOTIONTIMESTART"; tmp['RecordModel'][i]['recordItemRef']['display'] = "Motion Time Start"; tmp['RecordModel'][i]['dataTypeRef']['id'] = 56992; // DATE tmp['RecordModel'][i]['valueDate']['datetime'] = startTime; } else if (i==3) { // timestop tmp['RecordModel'][i]=tmp['RecordModel'][i-i]; // create xml element tmp['RecordModel'][i]['recordItemRef']['idvalue'] = "MOTIONTIMESTOP"; tmp['RecordModel'][i]['recordItemRef']['display'] = "Motion Time Stop"; tmp['RecordModel'][i]['dataTypeRef']['id'] = 56992; // DATE tmp['RecordModel'][i]['valueDate']['datetime'] = stopTime; } else if (i==4) { // commaseparatedvalues tmp['RecordModel'][i]=tmp['RecordModel'][i-i]; // create xml element tmp['RecordModel'][i]['recordItemRef']['idvalue'] = "MOTIONHAMMEDMOTION"; tmp['RecordModel'][i]['recordItemRef']['display'] = "Motion Hammed Motion"; tmp['RecordModel'][i]['dataTypeRef']['id'] = 79128; // STRING tmp['RecordModel'][i]['valueString'] = msg['ns1:commaSeparatedValues'].toString(); } else if (i==5) { // comment tmp['RecordModel'][i]=tmp['RecordModel'][i-i]; // create xml element tmp['RecordModel'][i]['recordItemRef']['idvalue'] = "MOTIONCOMMENT"; tmp['RecordModel'][i]['recordItemRef']['display'] = "Motion Comment"; tmp['RecordModel'][i]['dataTypeRef']['id'] = 79128; // STRING tmp['RecordModel'][i]['valueString'] = msg['ns1:comment'].toString(); } else if (i==6) { // dataunit tmp['RecordModel'][i]=tmp['RecordModel'][i-i]; // create xml element tmp['RecordModel'][i]['recordItemRef']['idvalue'] = "MOTIONDATAUNIT"; tmp['RecordModel'][i]['recordItemRef']['display'] = "Motion Data Unit"; tmp['RecordModel'][i]['dataTypeRef']['id'] = 79128; // STRING tmp['RecordModel'][i]['valueString'] = msg['ns1:dataUnit'].toString(); } else if (i==7) { // intervalunit tmp['RecordModel'][i]=tmp['RecordModel'][i-i]; // create xml element tmp['RecordModel'][i]['recordItemRef']['idvalue'] = "MOTIONINTERVALUNIT"; tmp['RecordModel'][i]['recordItemRef']['display'] = "Motion Interval Unit"; tmp['RecordModel'][i]['dataTypeRef']['id'] = 79128; // STRING tmp['RecordModel'][i]['valueString'] = msg['ns1:intervalUnit'].toString(); } // Common tmp['RecordModel'][i]['recordDt']['datetime'] = startTime; tmp['RecordModel'][i]['recordItemRef']['identifierSource'] = "PORCUPINESENSOR"; } Page 11 of 16
Make sure the Outgoing Data script is After saving the transformer changes the channel is deployed
Now drop a copy of your xml file into the oru_xml_in directory identified earlier. The channel will remove the file and any errors will be written to the log. On the dashboard we can see an error occurred:
Figure 12 Dashboard
Selecting Events will show the log, selecting the first entry and scrolling down the exception log we find the error relates to the ns1 prefix used in the XML
Page 12 of 16
After attempting some javascript tricks to remove or build the namespace the event log stopped filling with error messages. Instead by right clicking on the channel in the dashboard you can select View Messages You can select the message which errored and view the different states the message went through before and after the transformating Javascript. On the error tab we see the namespace is still an issue.
To resolve the issue on the Summary tab we deselected Strip namespace from messages and as show above the message was transformed and sent i.e. written to the interface_messages table.
Page 13 of 16
Logging into PatientOS as admin/admin and opening the Interface Manager shows that the ResultTransaction processing had already started but has not processed anything yet.
But we posted a RecordTransaction so that is why it was not picked up yet. This will be included in the next version but I can add one (using SQL to assign the poll script). Restarting the server and this interface processor will start.
Page 14 of 16
A snippet of the error message is written to the interface_messages table (select * from interface_messages) but currently you need to have JBoss running in console mode so you can see the exact error. e.g. This error shows the MRN was not passed and confirm in Mirth on the messages tab. 23:38:01,125 WARN [JDBCExceptionReporter] SQL Error: 0, SQLState: 42883 23:38:01,125 ERROR [JDBCExceptionReporter] ERROR: operator does not exist: character varying = bytea 23:38:01,125 ERROR [Log] data.common.BaseData.sqlQuery:select p.patient_id from identifiers i, patient_identifier pi, patients p where i.id value = :idvalue and i.source_ref_id = :identifierSourceRefId and i.identifier_id = pi.identifier_id and pi.patient_id = p.patient_id and p.active_ind = 1 idvalue = null identifierSourceRefId = 1000447(sqlQueryquery) 23:38:01,125 ERROR [Log] org.hibernate.exception.SQLGrammarException could not execute query...
After fixing the mapping in Mirth (namespaces were causing problems again now removed from the XML) the message gets this error in the interface_messages table: com.patientis.model.clinical.FormModel.validateDataModel(FormModel.java:55) JBoss showed the desired exception 00:05:38,078 ERROR [Log] com.patientis.model.clinical.NoRecordsInFormException null...
The validation failed for the FormModel due to all records being discarded likely to each each FormRecordModel being rejected due to failed validation. Checking the FormRecordModel the patient id is required and not populated in this version so an upgrade is required to fix that issue.
Page 15 of 16