Software Development: Exploring OpenMRS

THESE NOTES ARE PRELIMINARY---THERE MAY BE INCONSISTENCIES AND INACCURACIES.

Read the OpenMRS Technical Overview The OpenMRS Technical Overview gives a quick introduction to a number of the technologies used by OpenMRS. You may not understand everything on this page, but read through it anyway.

Adding some data to the database The sample database that is provided with OpenMRS has many patients and concepts entered (see below for more on the latter), but no observations. Here we'll set things up so you can add encounters and observations; you should see the changes you make in the application reflected in the  database.

 

Add a provider. A provider is someone who can add observations to patients; without any providers, we cannot add encounters and observations. We could make the OpenMRS administrator a provider, but that seems unreasonable, so instead we'll make a new account.   From the Administration page, select "Manage Users."   On the "User Management" page, select "Add User."   On the "Adding a User" page, enter a name (maybe your name), birthdate, and sex, and press "Create Person."   On the "User Form" page, fill in the fields, and be sure to select the "Provider" role. Press "Save User."   Stay logged in as  (I'm not sure why, yet).

  

 Add an encounter and an observation. </li>   From the "Administration" page, select "Manage Encounters." </li>  On the "Encounter" page, select "Add encounter." </li>  Press the "Select" for the Patient. In the text box, enter "oloo". This will automatically bring up all patients such that "oloo" matches some part of the name (in my version of the database, there is only one such patient, Anet Wanza Oloo). Select such a patient. </li>

 Press the "Select" button for Provider. Select the user you created in the previous step. </li>  Choose "Unknown location" for the location, enter a date, and choose "Basic form" for the Form. </li>  Press "Save encounter." This redisplays the current page with the same data that you just entered. Now select "Add observation." </li>  Press the "Select" button for Concept. Type "chest" into the text box and select "X-RAY, CHEST." Press the "Select" button for Value and select "NORMAL." </li>  Press "Save observation." </li> </ol> </li>

</ol>

The Data Model This explanation of the data model is cribbed from a presentation that Darius gave us at the beginning of the semester.

The most straightforward approach to the data model for an EMR system is to have tables that correspond to events; the fields of these tables correspond to data collected or observations made. For example, there might be an  table in which each row represents a visit by a patient: The data recorded here is data that might change visit by visit. Presumably there would be a  table in which patient identifiers are associated with unchanging data (or at least data for which only the current value is ever of interest) associated to that patient. The problem with this approach is that it is not easily extensible. What happens if there is new information that should be collected after the application is deployed? This involves changes to the database (inconvenient), making up values for entries already present in the database (possibly misleading), and probably changing application and presentation logic (which may make assumptions about what fields are present in the table).

Instead, OpenMRS makes use of a concept dictionary; this is a listing of all the concepts that might be used in other parts of OpenMRS. For example, "weight" and "home type" might be concepts. A concept is specified in the  table by a name, a data type (e.g., integer or string), and other information. The  table itself gives numeric identifiers to the concepts; other parts of the specification (e.g., the data type) are specified as identifiers in other tables (e.g., ). Let's look at a particular example. In the  table we see the following row:

There are other fields in this table, mostly having to do with administrative information such as who created the concept, whether it is a concept that is currently used, etc. From  we find the following:

So this concept refers to a chest x-ray. From  we find the following:

So it looks like this concept refers to a test that is performed on the patient. From  we find

which indicates that the possible values that can be recorded for an observation of this concept come from a fixed list of values. We can find out what the possible values are by looking at the table for rows with

equal to 12: Notice that each  value is a  in the   table! Chasing this through, we see that concept 1115 has name NORMAL and no associated datatype (the name says it all...).

So far all we have done is to define concepts. How do they get used? One of the other main tables in the OpenMRS data model is the  table. Each row in this table represents an observation of a person. One of the fields is , identifying which concept is associated to this observation. The values for this particular observation are stored in other fields for the row. For example, if the associated concept is of Coded datatype, then the actual code (value) for the observation is recorded as the value of the field (which, of course, will be a concept identifier!). Notice that there are many  fields in the

table---the only field to receive a non-null value will be the field that corresponds to the concept's datatype. If you have added an encounter and an observation as in the previous section, you should see a single row in this table.

Adding a concept to OpenMRS What happens when we add a concept to OpenMRS? Here's how we find out.   If we go to the Concept Dictionary Maintenance page, we see that the "Add new Concept" link requests the URL (relative to the webapp). </li>  We look at the web application context file to see how the URL is mapped. We find

&lt;bean id="urlMapping" class="...SimpleUrlHandlerMapping"&gt; &lt;property name="interceptors"&gt; &lt;list&gt; &lt;ref bean="localeChangeInterceptor" /&gt; &lt;!-- &lt;ref bean="openSessionInViewInterceptor"/&gt; --&gt;

&lt;/list&gt; &lt;/property&gt; &lt;property name="order"&gt;&lt;value&gt;99&lt;/value&gt;&lt;/property&gt; &lt;property name="mappings"&gt; &lt;props&gt;

...           &lt;prop key="dictionary/concept.form"&gt;conceptForm&lt;/prop&gt; &lt;/props&gt; &lt;/property&gt; &lt;/bean&gt; This tells us that the request for

is handled by the bean named. We now look for that bean definition in : &lt;bean id="conceptForm" class="org.openmrs.web.controller.ConceptFormController"&gt; &lt;property name="commandName"&gt;&lt;value&gt;concept&lt;/value&gt;&lt;/property&gt; &lt;property name="validator"&gt;

&lt;ref bean="conceptValidator" /&gt; &lt;/property&gt; &lt;property name="formView"&gt;&lt; value&gt;/dictionary/conceptForm&lt;/value&gt; &lt;/property&gt;

&lt;property name="successView"&gt; &lt;value&gt;concept.form&lt;/value&gt; &lt;/property&gt; &lt;/bean&gt; So now we know that "Add new Concept" will be handled by.

</li>  We now look at  and see that it extends Spring's. That's good, because this is well-documented. Reading about , we learn: <ul>  The page used to display the form is ; the  bean will map this to

, so that is where we look to find the JSP for this form. </li>  On success, we will request  again, presumably to add another concept. </li>  The validator for the data that is submitted on this form is defined by the  bean; look through to find this bean definition.

</li> <li> The command name for the form view is, so we expect the form view to refer to  , an object that is likely to be populated by the method of, which in turn is probably overridden by. </li> </ul> </li>

<li> Now looking at, we see that it overrides the following methods from  : ,, ,  , and. It now behooves us to read about  to understand what each of these methods does, and how they are used in the  workflow. Noticing that

seems focused on examining the submission to look for certain kinds of errors, we focus on. <ol> <li> : we get a  object that presumably allows us to manipulate concepts in the database. </li> <li> Next we see a main conditional that tests ; we assume (we should eventually verify) that this is true if the user has the necessary credentials to use this context.

</li> <li> : cast the command object from the form to a  object. </li> <li> The next main conditional is of the form if (action.equals(msg.getMessage("Concept.delete"))) { ... } else { ... } Again, we make some educated guesses, which we should eventually verify: this tests whether the action on the form was to delete a concept or not. We're considering the latter case, so we pay attention only to the  block.

</li> <li> . Here we can make an educated guess that this gets the value of some attribute of the request. But if that's what is happening, then we need to look at the form to see how that attribute (probably ) gets set, and in that case we had better make sure we know what this call does instead of making an educated guess. We look at the  statements and see that  is a Spring class. The API documentation is good first place to look. We notice that the description of this class indicates that this is a good class to use for simple submissions where the use of a command object would be overkill and that yes,  does just what we expect (along with supplying a default value to use as the return value if the parameter was not set in the form). </li> <li>

Now we look at  and search for . We find it as the name of an attribute in the following context: &lt;spring:bind path="concept.set"&gt; &lt;input type="checkbox" name="conceptSet" id="conceptSet" &lt;c:if test="${status.value}"&gt; checked="checked" &lt;/c:if&gt; onClick="changeSetStatus(this)" /&gt;

&lt;c:if test="${status.errorMessage != ''}"&gt; &lt;span class="error"&gt; ${status.errorMessage}&lt;/span&gt;&lt;/c:if&gt; &lt;/spring:bind&gt; Reading about the  element in HTML, we see that

will presumably be set if this checkbox is selected. To be certain, we should be running OpenMRS and see if there is a corresponding checkbox on the "Add new concept" form. There is, and selecting the checkbox immediately produces another collection of controls. In our current analysis, we have to decide whether we want to consider that situation or not; it seems simplest to assume that it is not checked. Then the question is what the value of this control will be. There is no  attribute, so this is probably handled by some sort of default. What is strange is that the documentation for the  control says that the  attribute is mandatory for checkboxes. I'm not sure what is going on here. However, we do know that if the checkbox is not selected, then this control is not successful, and so  will not be one of the parameters in the form submission. So in this case,  will get the value.

<p style="font-size:smaller"> However, since this element occurs inside a  element, we need to read up on those to see what else is happening here. For that, we probably look at the Spring Reference manual, Chapter 14. Unfortunately, we don't find too much helpful there; the only reference to something like  is in the section on Velocity and Freemarker, and that seems to be alternative syntaxes. The Appendix of the Spring Reference manual does give a definition of the  element, but it is not very helpful. However, looking at the discussion for Velocity and Freemarker, it seems that this element probably arranges so that the value of the  element that is contained by the  element is bound to the   path in the command object. We can at least see if this is reasonable by checking whether there is a  attribute of the command object for this controller. And indeed, when we look at

(the type of the command object), there is a field , which matches up with the input type being a checkbox. It is odd, though, that if  gets any value other than  , then  "manually" sets the   property of the concept; it seems that this is what the

tag ought to be doing. </li> <li> . It seems reasonable that the concept ID is not set, as we are just now adding a new concept. But, we should be sure. First we look at the command object created by to see whether it is set or not there, and we see that if the  parameter of the request for this form is not set, then the property of the command object will be left unset. Again, this seems reasonable, but really we should do some debugging and verify this; we could either add some log messages or we could use Eclipse's debugging facilities. For now, we'll make it an assumption.

</li> <li> . Hopefully this is self-explanatory. </li> <li> . If the guard is true, we will make this concept into a Numeric concept, one of the special-case concepts in OpenMRS. </li> <li> . Finally save the concept to the database. We could pursue this by examining ; eventually we should see the data being saved. But we'll stop here... </li> </ol> </li>

</ol>