Smart Container Technical Documentation for OpenMRS Developers

Primary objective of this module is to convert OpenMRS into a SMART container. After installing this module, OpenMRS users will be able to install and launch SMART Apps easily. Before going deep into the detail of the module architecture we have to understand some terminologies related to SMART Apps and SMART Containers.

What is a SMART App?

A SMART app is a substitutable medical app built using reusable techniques. It can be a web app built using reusable technologies such as HTML and JavaScript. It uses the SMART API to retrieve data from data sources. The data source may be a EMR like OpenMRS, a PHR like Indivo, or a data analytic platform such as i2b2. There are two types of SMART apps: SMART Connect app and SMART REST apps.

SMART Connect app
These are embedded in to the SMART container using HTML iframes and uses interframe communication (using javaScript callbacks) to get the data from the container. The API that SMART connect apps use is called the SMART Connect API.

SMART REST apps
These are standalone applications(clients) that are running separately as a back-end server to a SMART connect app and uses a set of REST calls to get its data from the container. The API used by SMART REST apps is called as SMART REST API.

More information is available on the following page http://wiki.chip.org/smart-project/index.php/Main_Page#What_is_SMART.3F.

What is SMART Container?

A SMART container is a data source that exposes its data through the SMART API and provides a UI for SMART connect apps. Every EMR can be a SMART container when it is providing the SMART API, UI and secure way of accessing the API. Additionally, it can provide its own way of installing and managing of the SMART apps.

For more information refer to this page: http://wiki.chip.org/smartproject/index.php/Developers_Documentation:_Terminology#SMART_Container.

How to convert OpenMRS into SMART Container

In order to convert OpenMRS into a SMART container the module should provide at least the following to the SMART Apps:

  1. UI: where the SMART App is launched

  2. Expose data: which are going to consumed by SMART Apps

  3. Authentication: Enables secure way of transferring data.

Providing UI for SMART Apps

Almost all SMART Apps are centered around patient data. Therefore the apps are displayed as a tab on the patient dashboard.

.

SMART Apps are displayed at patient dashboard under the SMART tab. The SMART Apps which are selected by the user on the Manage User Apps admin page will be displayed at the side pane. When its icon is clicked the app will be launched within the HTML iframe. Using the available extension point (PatientDashboardTabExt) at the patient dashboard the SMART app tab was created.

Exposing Data

The idea the smartcontainer module adopts is initially expose SMART REST API and convert SMART connect javaScript callback into a ajax call to SMART REST API. So the smartcontainer module is proxying SMART connect API with SMART REST API. This proxying mechanism is done at smartAppForm.jsp as following:

$j.ajax({ beforeSend : function(xhr) { xhr.setRequestHeader("Authorization", " "); }, dataType : "text", url : "${pageContext.request.contextPath}" + "/ws/smartcontainer/api" + api_call.func, contentType : activity.contentType, data : activity.params, type : activity.method, success : callback, error : function(data) { alert("error"); } });

SMART REST API

We created our own controllers which process the REST because of some technical difficulties in generating RDF payloads. Each SMART data types smartcontainer has a controller. For example there is a ProblemController to serve the requests which are asking problems. Those requests are in following form:

GET http://localhost:8080/openmrs/ws/smartcontainer/api/records/{patientId}/problems

In a nutshell
ProblemController asks the SmartProblemHandler to convert Problems (openmrs objects) to SmartProblem objects.

The SmartProblems are then passed to the ProblemRDFSource to be converted to RDF XML form.

In a bigger nutshell
Smartcontainer uses OpenRDF library to generate RDF payloads. The SmartDataService is responsible for generating appropriate SMART domain objects such as SmartProblem from a selected patient. Appropriate handlers are supplied to the SmartDataService class through the moduleApplicationContext.xml which are used to convert the OpenMRS domain object into SMART domain object.Currently the module has handlers and RDFSources for following SMART data types.

Patient demographics

SMART field

Description

Data format

OpenMRS terminology

SMART field

Description

Data format

OpenMRS terminology

Given name

A person's given, or first, name

Free text

patient.getFamilyName()

Family name

A person's family, or last, name

Free text

patient.getGivenName()

Gender

A person's gender

'male' or 'female'

patient.getGender().equals("M") ? "male" : "female"

Zip code

A person's zip code

Free text

patient.getPersonAddress().getPostalCode()

Birth day

A person's birth date

ISO-8601 string

patient.getBirthdate()

Medication (mapped to RxNorm)

SMART field

Description

Data format

OpenMRS terminology

SMART field

Description

Data format

OpenMRS terminology

Drug name

RxNorm Concept ID for this medication

RDF CodedValue node with code drawn from
http://rxnav.nlm.nih.gov/REST/rxcui/{rxcui}

DrugOder.getDrug().getConcept() is used to retrieve the mapped RxNorm concept code, if the code available it is used to set the code of the drug name concept and the title of the coded value is still filled with OpenMRS concept name.

Instruction

Clinician-supplied instructions from the prescription signature

Free text

DrugOder.getFrequency()

Quantity

For a medication with a simple dosing schedule, record the amount to take with each administration.

RDF ValueAndUnit node

value:DrugOder.getQuantity()
 unit:DrugOrder.getunits()

Frequency

For a medication with a simple dosing schedule, record how often to take the medication

RDF ValueAndUnit node

DrugOder.getFrequency() is first split-ed for '/' and then first part is converted into number,it gives the value .The second part is mapped from Openmrs format("daily") to SMART format("/d") or if the OpenMRS format is different it gives unstructured unit as "\'{my units}".

Start date

when the patient started taking a medication

ISO-8601 string

DrugOrder.getStartDate()

End date

when the patient stopped taking a medication

ISO-8601 string

First DrugOder.getDiscontinued() is checked if it so the end date is set to that otherwise  DruOrder.getAutoExpireDate() is set .

Fulfilment

 

RDF Fulfillment node

NOT IMPLEMENTED YET

Problems (Mapped to SNOMED CT)

OpenMRS data model represents problem in two way. First is using problem table for which the mapping to SMART problem is done directly as follows.

SMART field

Description

Data format

OpenMRS terminology

SMART field

Description

Data format

OpenMRS terminology

Notes

Additional notes about the problem

Free text

Problem.getComments();

Problem name

SNOMED-CT Concept for the problem

CodedValue node referencinghttp://www.ihtsdo.org/snomed-ct/concepts/{concept_id}

Problem.getProblem() is used to retrieve related mapped SNOMED-CT concept code and this value is used to set the code of the coded value node and the OpenMRS concept name is used to fill the title of the coded value node.

Resolution

Date of problem resolution

ISO-8601 string

Problem.getEndDate()

Title

Human-readable problem name

Free text

It is already included within the problem name

Onset

Date of problem onset

ISO-8601 string

Problem.getStartDate()

Second way is using problem added/problem removed obs to represent the problems.

SMART field 

Description 

Data format 

OpenMRS terminology 

SMART field 

Description 

Data format 

OpenMRS terminology 

Notes

Additional notes about the problem 

Free text 

Obs.getComments()

Problem name

SNOMED-CT Concept for the problem 

CodedValue node referencing[[http://www.ihtsdo.org/snomed-ct/concepts/]{concept_id}

Obs.getValueCoded().getName()

Resolution

Date of problem resolution 

ISO-8601 string 

The obs table is searched for another obs with the same obs.valued_coded but with obs.concept_id == user-chosen-PROBLEM_RESOLVED-concept-id.  If found, the obs.obsdatetime is used, otherwise null.

Title

Human-readable problem name 

Free text 

 

Onset

Date of problem onset 

ISO-8601 string 

Obs.getObsDatetime()

From both method user have to select one method at Administration->Set up problem object page

Lab result

The Obs related to lab result are separated by only taking the obs whose concept.conceptClass is "Test".

SMART field

Description

Data format

OpenMRS terminology

SMART field

Description

Data format

OpenMRS terminology

Lab name

LOINC Coded Value for result

RDF CodedValue node *drawn from LOINC

Obs.getConcept() is used to retrieve related mapped LOINC concept code and this value is used to set the code of the coded value node and the OpenMRS concept name is used to fill the title of the coded value node.

Quantitative result

Qualitative result, if any

RDF QuantitativeResult node

If the obs.concept is numeric then
quantity.valueAndUnit=(Obs.ValueNumeric,ConceptNumeric.units)
quantity.normalRange=(ConceptNumeric.hiNormal,ConceptNumeric.lowNormal,ConceptNum)
quantity.nonCriticalRange=NOT IMPLEMENTED

else if Obs.concept is coded value then
quantity.valueAndUnit=(Obs.ValueCoded.Name,null)

Nominal result

Nominal result, if any

RDF NominalResult node

NOT IMPLEMENTED

 

Narrative result

Narrative result, if any

RDF NarrativeResult node

NOT IMPLEMENTED

Ordinal result

Ordinal result, if any

RDF OrdinalResult node

NOT IMPLEMENTED

Accession number

External accession number for a lab result

Free Text

Obs.accessionNumber

Status

Status of this lab

RDF CodedValue node, drawn fromhttp://smartplatforms.org/terms/code/LabResultStatus#

NOT IMPLEMENTED

Abnormal interpretation

Abnormal interpretation status for this lab

RDF CodedValue node, drawn fromhttp://smartplatforms.org/terms/code/LabResultInterpretation#

NOT IMPLEMENTED

Specimen collected

Attribution for who collected the specimen for this result, and when

RDF Attribution node

Start date:Obs.ObsDateTime
End Date:NOT IMPLEMENTED
Participant:NOT IMPLEMENTED

Specimen received

Attribution for who received the specimen for this result, and when

RDF Attribution node

NOT IMPLEMENTED

Resulted

Attribution for who determined this result from the specimen, and when

RDF Attribution node

NOT IMPLEMENTED

Comments

Narrative comments for this result

Free Text

NOT IMPLEMENTED

Vital Signs

Obs whose concepts match a fixed list of LOINC codes

SMART field

Description

Data format

OpenMRS terminology

SMART field

Description

Data format

OpenMRS terminology

Encounter

Encounter during which vital signs were taken

RDF Encounter node

Currently typical OpenMRS installation has following encounter types ADULTINITIAL,ADULTRETURN,PEDSINITIAL and PEDSRETURN which are actually
ambulatory encounter type.
encouner.type="ambulatory"
encounter.startDate=Encounter.visit.startDateTime
encounter.endDate=Encounter.visit.endDateTime

Date

Date + time when vital signs were recorded

ISO-8601 string

Encounter.encounterDateTime

height

Patient's height in meters

RDF ValueAndUnit node where unit is: "m"

if Obs.concept's mapped conceptCode.equals("8302-2")
vitalSign.valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)

weight

Patient's weight in kg

RDF ValueAndUnit node where unit is: "kg"

if Obs.concept's mapped conceptCode.equals("3141-9")
vitalSign.valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)

Body Mass Index

Patient's Body Mass Index

RDF ValueAndUnit node where unit is: "{BMI}"

if Obs.concept's mapped conceptCode.equals("39156-5")
vitalSign.valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)

Respiratory Rate

Patient's Respiratory Rate per minute

RDF ValueAndUnit node where unit is: "{breaths}/min"

if Obs.concept's mapped conceptCode.equals("9279-1")
vitalSign.valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)

Heart Rate

Patient's Heart Rate per minute

RDF ValueAndUnit node where unit is: "{beats}/min"

if Obs.concept's mapped conceptCode.equals("8867-4")
vitalSign.valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)

 

Temperature

Patient's Temperature in Celcius

RDF ValueAndUnit node where unit is: "Cel"

if Obs.concept's mapped conceptCode.equals("8310-5")
vitalSign.valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)

Oxygen Saturation

Patient's Oxygen Saturation in percent hemoglobin

RDF ValueAndUnit node where unit is: "%{HemoglobinSaturation}"

if Obs.concept's mapped conceptCode.equals("2710-2")
vitalSign.valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)

Blood Pressure

Patient's systolic + diastolic Blood Pressure in mmHg, with optinal position coding

RDF BloodPressure Node

if Obs.concept's mapped conceptCode.equals("8462-4")
vitalSign(diastolic).valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)
else if Obs.concept's mapped conceptCode.equals("8480-6")
vitalSign(systolic).valueAndUnit=(Obs.valueNumeric,ConceptNumeric.units)

 

Authentication

Because the apps are running inside of openmrs, the standard openmrs authentication and privilege checking is used.

Managing Apps

OpenMRS administrators can add or remove SMART Apps. The actual hosting of the SMART apps is outside of this module. The container gets all information necessary to launch the remotely hosted SMART app from the uploaded manifest file of the SMART app. The ManifestParser is responsible for parsing the manifest file and the AppFactory generates the App object which can be saved in database.

The added Apps are only available on the patient dashboard after user adds SMART apps at Administration->Manage User Apps.

Detailed presentation: http://vimeo.com/23265477