OpenMRS Manual
Preface
Introduction
Our world continues to be ravaged by pandemics of epic proportions, as over 40 million people are infected with or diseases such as HIV/AIDS, multi-drug resistant tuberculosis, and malaria — most (up to 95%) of these afflictions are present in developing countries. Prevention and treatment interventions on this scale require efficient information management, which is critical as clinical care must increasingly be entrusted to less skilled providers. Whether for lack of time, developers, or money, most health care programs in developing countries manage their information with simple spreadsheets or small, poorly designed databases...if anything at all. To help them, we need to find a way not only to improve management tools, but also to reduce unnecessary, duplicative efforts.
As a response to these challenges, the Open Medical Record System (OpenMRS®) formed in 2004 as a open source medical record system platform for developing countries — a tide which rises all ships. OpenMRS is a multi-institution, nonprofit collaborative led by Regenstrief Institute, Inc. (http://regenstrief.org), a world-renowned leader in medical informatics research, and Partners In Health (http://pih.org), a Boston-based philanthropic organization with a focus on improving the lives of underprivileged people worldwide through health care service and advocacy. These teams nurture a growing worldwide network of individuals and organizations all focused on creating medical record systems and a corresponding implementation network to allow system development self reliance within resource constrained environments. To date, OpenMRS has been implemented in twenty countries throughout the world, including South Africa, Kenya, Rwanda, Lesotho, Zimbabwe, Mozambique, Uganda, Tanzania, Haiti, India, China, United States, Pakistan, and the Phillipines. This work is supported in part by organizations such as the World Health Organization (WHO), the Centers for Disease Control (CDC), The Rockefeller Foundation, and the President's Emergency Plan for AIDS Relief (PEPFAR).
OpenMRS is a software platform and a reference application which enables design of a customized medical records system with no programming knowledge (although medical and systems analysis knowledge is required). It is a common platform upon which medical informatics efforts in developing countries can be built. The system is based on a conceptual database structure which is not dependent on the actual types of medical information required to be collected or on particular data collection forms and so can be customized for different uses.
OpenMRS is based on the principle that information should be stored in a way which makes it easy to summarize and analyze, i.e. minimal use of free text and maximum use of coded information. At its core is a concept dictionary which stores all diagnosis, tests, procedures, drugs and other general questions and potential answers. OpenMRS is a client-server application which means it is designed to work in an environment where many client computers access the same information on a server.
At the moment much of the information on these pages is aimed at programmers/developers; however an aim is to provide comprehensive information for:
- Programmers/developers: The people who do the actual programming of the application and who design and modify the table structure.
- Implementers: The people who decide what sort of medical records need to be collected at a particular health facility. They use the application to adjust the concept dictionary as required and to design forms to collect this information. Ideally the designers for a particular health facility should be a team of systems analysts who understand the conceptual structure of the system and clinicians who understand medicine and their own health facility's medical record information requirements.
- Users: The people use the system to enter, retrieve and analyze information in the system in their particular health facility.
There are several layers to the system:
- The OpenMRS data model borrows heavily from the Regenstrief model, which has over a 30-year history of proven scalability and is also based on a concept dictionary
- The API (application programming interface) provides a programmatic wrapper around the data model, allowing developers to program against more simplified method calls rather than having to understand the intricacies of the data model
- The Web Application includes web front-ends and modules that extend the core functions — these are the user interfaces and applications themselves built upon the lower levels
Community
OpenMRS is also a community of developers, implementers, and users working toward a shared and open foundation for managing health information in developing countries.
Learn more about the OpenMRS Community
Contact Us
For general questions:
- There are several channels to reach the OpenMRS Community, such as mailing lists, our forum, or the OpenMRS IRC channel.
For media inquiries:
- Please contact press@openmrs.org
The goal of this page is to help you understand what it takes to implement OpenMRS. If you are already beginning an implementation and would like more detailed advice, see the Field_Guide.
Staff Requirements
Primary Implementer
Skills needed
- One dedicated IT person for at least two or three months, usually full time. They need to know the whole stack (server maintenance, mysql, tomcat, java etc.). The level of expertise you need on site depends in part on whether or not you have the Internet to get help from online resources or remote developers.
- Tomcat expertise
- Install and manage Apache Tomcat
- upload and install new WAR files
- troubleshoot, read log files
- Database expertise
- Install and manage MySQL environment
- understand the OpenMRS data model
- perform SQL queries and run SQL scripts
- Clinical form design
- understanding of how to create meaningful, useful, and non-ambiguous questions/answers
- Medical expertise -- to understand what questions/answers make sense, what's clinically relevant
- Technical expertise -- to understand how questions/answers can be interpreted by a computer
- Data Management expertise -- to understand how questions/answers will be used for reporting, research, etc.
- understanding of how to create meaningful, useful, and non-ambiguous questions/answers
- Dictionary design
- ability to infer dictionary concepts from a form (both coded questions and answers), modeling expertise -- e.g., do you create
CHEST PAIN code> as a boolean (true/false) or do you create aCARDIAC REVIEW OF SYSTEMS code> as a coded concept withCHEST PAIN code> as a possible answer?
- ability to infer dictionary concepts from a form (both coded questions and answers), modeling expertise -- e.g., do you create
- Ability to install and configure Apache + SSL (if extending network beyond a single LAN)
Long term IT support
OpenMRS relies on some basic infrastructure. After this infrastructure is in place, long term IT support will be needed to ensure it continues working properly.
- Electricity
- Connectivity to the internet and/or a local area network (for implementations with multiple locations)
- Computers, virus protection and basic computer maintenance(e.g. a mouse stops working)
Many organizations lack the internal skills to deal with these issues, so a combination of staff training and external support can be critical.
Data Entry Clerks
Many health facilities have information management staff who can be trained as data entry clerks. Some implementers train cleaners to enter data.
Data Managers
If you have more than 4 ish data entry clerks, it is often useful to train data managers to oversee the data entry clerks and ensure that they are maintaining high data quality.
Infrastructure Requirements
Power Infrastructure
OpenMRS is only as reliable as the power system that supports it. Unless electricity is almost 100% stable in your area, you will probably want at primary power source and a backup. Backup power systems usually involve some kind of battery. Using grid power with a diesel generator as a back up is probably not sufficient because there is a short lag inbetween loosing grid power and turning on the generator, and the suddden loss of power can damage your server or data quality. Hybrid systems are ideal, where you have a primary power source (such as grid power or solar), and a secondary source (such as a generator or solar), both of which feed into short term backup batteries.
Machines for entering, storing, and accessing data
- A server, recommended one or two processors 1.5+ GHz, 2 GB of memory, and 150+ GB of disk space with RAID and appropriate backup. See Field_Guide for more info on selecting a server.
- Work stations for retrospective data entry: If you intend for clinicians to enter data on paper forms that are later entered into OpenMRS by data entry clerks, you estimate the number of work stations you will need based on the amount of data you will enter. As a general rule of thumb, it will take a data clerk about one day to enter 80-100 forms with 20 observations per form (this general rule may vary greatly from site to site). For retrospective data entry you may use regular computers that range in cost from $300-$1,000
- Work stations for point of care: If you intend to build a point of care system, you should estimate the number of work stations needed based on the number of clinicians or clinic rooms. You might want to consider using thin clients or touch screens, and may want to use mobile devices if for remote clinics. Such devices range from $100 (for a j2me smart phone capable of running OpenMRS) to $1,000 for some touch screen appliances.
- Mobile devices: If you would like submit forms from the field, and do not need rich access to patient data, you can do so with mobile devices that cost as little as $20. Forms can be sent via SMS, GPRS or wifi.
Connectivity
You can connect your server (which stores all OpenMRS data) to the work stations where people enter data within your facility using:
- ethernet cable
- a local area wireless network
- wireless Internet
If your implementation involves multiple facilities, you may want some means of communicating among sites. Options include:
- Bringing all paper forms to a central location and entering them into the electronic system (requires transportation, but no connectivity equipment)
- Setting up a local area network using long range wifi
- Installing VSAT or otherwise accessing Internet if you wish to communicate with very remote sites (such as a distant office in the country's capital or in another country).
Security:
Some projects require significant investment to protect equipment, potentially including installing doors, locks, bars on windows, or metal equipment storage cabinets. At other sites security is less of an issue, so consider the local context. Only local knowledge can guide this decision.
Implementation Process
- Build power infrastructure or strengthen as needed
- Improve security to support safely storing electronic equipment
- Build connectivity infrastructure
- Ethernet cables, hubs, etc. as needed for local area network
- Potential install VSAT or other means of accessing Internet for remote support.
- Design paper encounter forms (getting input from clinical and IT teams)
- Install server
- Server w/ power backup -- UPS for server and power backup (?solar)
- Install Windows Server 2003 on server along with supporting software (e.g., antivirus, firewall)
- Install OpenMRS system (mysql, apache, tomcat, etc. — see Setting up an OpenMRS Server)
- Configure server to serve OpenMRS web application through Apache over HTTPS
- Setup OpenMRS core data set
- Install core data set (using SQL)
- Make user account and define privileges
- Define locations
- Define tribes
- Define encounter types
- Build dictionary concepts around forms
- Design electronic form(s) within OpenMRS
- Select a form entry tool from among HTMLFormEntry Module, XForms Module, FormEntry (uses Infopath), or Remote FormEntry (also uses Infopath).
- Define forms within OpenMRS
- Test form(s)
- Setup client workstations for data entry
- Firefox
- If using FormEntry or RemoteFormEntry Modules, install InfoPath 2003 with Service Pack 2 or later
Ongoing Support
- Review new concept proposals
- Make changes to forms over time (both paper and electronic versions)
- Building reports from data exports
- Managing user accounts
- Server maintenance
- Client maintenance
- VSAT maintenance
Installation
Overview
As of OpenMRS 1.5, there is an auto-install process that makes installation easier than it was in earlier versions.
Steps Involved
- Install Firefox
- Install Java 5+ runtime environment
- Install Tomcat 5.5+
- Install MySQL 5+
- Deploy OpenMRS
- Go to http://localhost:8080/openmrs and follow the installation steps (note: if you are not installing on your computer, then use the domain of your server instead of localhost, port 8080 is the standard Tomcat port)
* Note: Java must be installed before Apache Tomcat
Minimum Requirements
1 GHz processor or better, 256 MB of memory or more, 40 GB hard drive or larger. You can set up the server on a laptop for demonstration or testing purposes. For production usage, we recommend one or two processors 1.5+ GHz, 2 GB of memory, and 150+ GB of disk space with RAID and appropriate backup facilities.
Example Installation Presentations
- OpenMRS Installation
- MySQL and Firefox ( Google Doc link | Direct to Presentation )
- Tomcat and OpenMRS Install (Based on OpenMRS v1.2, links may be out of date) Google Doc link | Direct to Presentation )
Install Firefox
- Download the latest stable release of Firefox and run installation program
- Accept the license agreement
- Select Standard or Custom installation to install to c:\Program Files\Mozilla Firefox
Install Java
- Download the latest stable release of the Java Runtime Environment (JRE)
- Run the install program (e.g., jre-1_5_0_12-windows-i586-p.exe)
- Accept the license agreement and default installation directories
Install Tomcat
- Download latest stable release of Tomcat — e.g., [1] and execute file
- Accept the license agreement
- Accept Component defaults (Tomcat, Start Menu Items, Documentation)
- Accept default destination folder: C:\Program Files\Apache Software Foundation\Tomcat 5.5
- Accept HTTP/1.1 Connector Port (8080)
- Set Administrator login (admin/password)
- Accept path for the JRE (C:\Program Files\Java\jre1.5.0_12) or navigate to the actual path and select
- Select "Install Tomcat"
- Open the Tomcat users file (c:\Program Files\Apache Software Foundation\Tomcat 5.5\conf\tomcat-users.xml) in a text editor and add the roles admin and manager for the user "tomcat"
-
<user name="tomcat" password="tomcat" roles="tomcat,admin,manager"/>
-
- (Ubuntu Only) Turn off tomcat security flag in "/etc/init.d/tomcat6" file:
- Use the Java security manager? (yes/no)
- TOMCAT6_SECURITY=no
- (Ubuntu Only) Fix application data directory
- sudo mkdir /usr/share/tomcat6/.OpenMRS
- sudo chown -R tomcat6:root /usr/share/tomcat6/.OpenMRS
- Optional (Windows) Set Tomcat to start automatically
- Start → Settings → Control Panel → Administrative Tools → Services
- Right Click "Apache Tomcat" → Properties → Set "Startup Type" to Automatic
Install MySQL
- Download the latest stable (recommended, generally available) release of MySQL — e.g., for "Windows (x86) ZIP/Setup.EXE" → mysql-5.0.41-win32.zip
- Extract with Winzip
- Run the MySQL install program (setup.exe) and run Custom Setup. Accept all defaults except the installation directory and the data directory, recommend installing into a directory without spaces (eg. c:\MySQL5\, c:\MySQL5\data). The default directory usually includes spaces which seems to cause problems with MySQL.
- Accept the license agreement
- Accept to Configure Instance → Select `Detailed Configuration’ → Developer Machine → Multifunctional Database → InnoDB Settings (c: and Installation Path) → DSS/OLAP → Enable TCP/IP Networking (Port 3306) → Select "Manual Selected Default Character Set / Collation" and set the character set to utf8 → Check "Installed as Windows Service" → Root password (password) (note: do NOT forget this root password for MySQL! You will need it later)
- Execute the configuration, the installer should step through each step and give you green check marks at each stage
- If you are stopped with a message saying that the server could not be started, your Windows Firewall may be blocking the MySQL port (default port is 3306). For problems related to error 1067 check the MySQL forum here
- Open Windows Firewall (Start → Settings → Control Panel → Windows Firewall)
- Under the "Exceptions" tab, click the "Add Port" button
- Name: MySQL
- Port number: 3306
- (optional) for added security, click the "Change scope" button and limit to "My network (subnet) only" — this will prevent computers outside of your local area network from being able to access your database directly
Deploy OpenMRS
- Ensure that Tomcat is started by checking to see if icon in the tray is green (Windows install).
- Download the latest stable release of OpenMRS.
- Navigate to http://localhost:8080/manager/html and enter your Tomcat administrator creditials (username and password chosen when installing Tomcat)
- In the Tomcat Web Application Manager, enter the location of the downloaded OpenMRS WAR file (openmrs.war) to deploy.
- The deployment could take some time while the file is copied to the folder c:\Program Files\Apache Software Foundation\Tomcat 5.5\webapps and decompressed.
- Note that the OpenMRS.WAR file is most easily downloaded with Mozilla FireFox. Internet Explorer tries to open the file as a Zip file.
- At the end of this process, the web page will refresh and /openmrs should be displayed under Applications. Apache Tomcat should also start the application (Running = True; and in Commands, Stop is underlined)
Starting your OpenMRS program
After you have finished deploying OpenMRS in Tomcat, and it is being displayed under applications, you can click on /OpenMRS tab (on the left side of the Tomcat Manager window) to start your application. The first time you do this, you will need to complete the initial configuration steps. After OpenMRS is configured, you will see a login page. You will need to login initially using Username: admin Password: test (both are in lowercase). Alternatively, while Tomcat is running you can start OpenMRS by entering http://localhost:8080/openmrs/login.htm (assuming 8080 is your port number for Tomcat - insert the appropriate port number if it is not 8080).
Troubleshooting
- When uploading the war file, Tomcat hangs and stops responding
- This typically occurs when you have not defined a MySQL user account that OpenMRS can use to access the database or you have not granted this user full access to the openmrs database. The default username is test with password test. The default username/password can be overridden in the OPENMRS_RUNTIME_PROPERTIES.PROPERTIES configuration file. To fix this: use Navicat or the MySQL administration tool (available from mysql.com) to verify that you have a user with username "test" and password "test" and this user has full access to the openmrs database.
- Cannot connect to Tomcat on port 8080
- This port is sometimes used by other programs, such as Popfile and TivoServer. You can use the Windows "netstat -ao" command to discover if another process is using port 8080. Also, a case was found where software for a HP Laserjet 2840 printer was trying to use port 8005, which conflicts with Tomcat startup.
- War file deploys in Tomcat but it won't start OpenMRS
- Try this:
- Go to MySQL command line (accessible from Start menu)
- type the following lines and press enter after each one:
- use OpenMRS;
- create user openmrs identified by 'openmrs';
- GRANT ALL ON openmrs.* TO 'openmrs'@'%';
- Go to C:\Documents and Settings\LocalService\Application Data\OpenMRS
- There should be a file there called OPENMRS-runtime.properties
- In this file, ensure it says the following:
### Database connection properties connection.username=openmrs connection.password=openmrs
- Also copy the file into C:\Documents and Settings\YOURUSERNAME\Application Data\OpenMRS where YOURUSERNAME is the name you use to log in to windows - this is in case tomcat looks there instead of in LocalService
User Guide
All OpenMRS users must authenticate to the system using a username and password. If you do not know your username, you should contact your system administrator. If you have forgotten your password, the system may allow you to reset your password by using a secret question mechanism.
FormEntry
just a test
[[Image:Example.jpgMedia:Bold text]] Completing a FormEntry Form
Concept Dictionary
Administrator Guide
Adding a User
To add a user, log into OpenMRS as an administrator and click on the "Administration" menu:
From there, you will see see a list of options. One of these will be "Add User". Click it.
You'll be prompted to enter a name, gender, and birthday or age. This information is required because OpenMRS creates a unique "Person" object for each user. Person objects can be both a patient and a user, so OpenMRS needs to ensure that the user you are about to create doesn't exist.
After clicking "Create Person" OpenMRS displays a form where you can fill in a username and password.
Roles and Privileges
Controlling User Access: Roles and Privileges
Resetting User Passwords
If a user is locked out, it is possible to reset their password using the "Edit User" administration site.
However, if said user is actually the administrator, you have to reset things manually.
- Option #1: Go into the "users" table in the database and set the "secret_question" and "secret_answer" columns in plain text. Now use the "I forgot my password" link in the OpenMRS web application to reset your password
- Option #2: Change the "password" and "salt" columns to known hashes:
- password: 4a1750c8607d0fa237de36c6305715c223415189
- salt: c788c6ad82a157b712392ca695dfcf2eed193d7f
- This sets the username password to "test" for all 1.5.0+ installations.
Managing User Lockout
Starting with 1.5, authorization controls were added:
- Users are locked out of OpenMRS for 30 minutes after 7 incorrect password attempts.
- Number of attempts and last attempted time are stored as a user property. The Edit User administration page will allow you to edit the current user's properties. (Alternatively, you can clear the rows in the user_property table for that user.
- IP addresses are locked out after 10 username/password attempts.
- The number of attempts per IP are left in memory on the server in the LoginServlet. Restart OpenMRS to clear this variable.
At least the second one is configurable as a global properties: security.loginAttemptsAllowedPerIP testing Identifiers are used uniquely identifier patients within the system. Different types of identifiers are distributed by various health care systems. Some of these systems will be within your control, so you will be able to control how identifiers are created and distributed; however, there will likely be identifiers that are not within your control but are useful to record within your system to aid in patient identification.
- Go to Administration → Manage Patients → Manage Identifier Types
- Click on "Add Patient Identifier Type"
- Provide a name the new identifier type
- The name should be specific to the authority providing the identifiers — e.g., "Wilson Hospital Medical Record Number"
- Provide a clear description of the identifier types, including identifying the authority responsible for distributing the identifiers
- Optionally provide a format for the identifiers
- The "Regex Format" allows you to enforce a pattern for identifiers using a Regular Expression (describes the identifier pattern in a way the computer can understand) — e.g., "\d{1,8}-\d" would allow 1 to 8 digits followed by a dash and a single digit
- The "Description of Format" allows you to describe the required pattern in a way that users of the system can understand — e.g. "Must follow the pattern NNNNNNNN-N (up to 8 digits followed by a dash and a final digit)"
- Specify whether the identifier is required
- Specify whether check digits should be enforced for this identifier type
- Click on the button to save your new identifier type
Administering Encounters Administering Observations Administering Orders NOTE: if you are running your server on Linux, please see the documentation about Installing An OpenMRS Server On Linux.
Form Design Process
Define/Locate Concepts
The concept dictionary is a central part of OpenMRS. Concepts can commonly be thought of as questions and possible answers which are on forms (although they have other uses too).
The first step in designing forms is working out what data needs to be collected and how the form will look on paper. From there, building forms in OpenMRS is a gradual process that involves identifying/constructing concepts within your concept dictionary corresponding to the questions and possible answers on your form.
To identify concepts
- Review your existing paper form.
- Log into OpenMRS.
- Go to the concept dictionary
- For each field on the paper form, search the Concept Dictionary to locate an existing concept.
- If a concept already exists, it is helpful to mark the concept ID on the paper form or keep a list of form questions and corresponding concept IDs for your future reference.
- If a corresponding concept does not exist, add it to the concept dictionary.
Concepts have a number of possible data types. The most common are:
- Number. Choose this if the concept you are setting up is a question with a numeric answer, for example, weight. (Another example commonly used in OpenMRS demos is nasal hair length :-) ) If the value could be entered as a floating number (with decimals) as an answer to the question, check the "precise" box. If the user is limited to entering whole numbers, uncheck the "precise" box.
- Coded. Choose this if the concept you are setting up is a question with another concept as an answer. This is what you use if you want the user to choose the answer on the form using a drop down list / combo box / set of option buttons or set of tick boxes. After you choose the "coded" data type, you are given an option of choosing the possible answers to the question. (If you haven't yet set up the possible answers as concepts, you have to do this first before linking them to the question)
- N/A. This is used if the concept you are setting up is not actually a question at all, but a possible answer.
- Text. Choose this if the concept you are setting up is a question with a textual answer. Note that textual answers are not so easy to analyse later.
- Boolean. Choose this if the concept you are setting up is a question with a yes/no answer.
When setting up a concept, you will notice there is a tick box "is set". This is useful for grouping observations together, by putting concepts together in sets. For example, one question may be "what are the reasons for poor adherence". This can be a concept with a coded datatype, and several possible reasons can be set up as answers. However, the form can also have an "other, specify" space which is a concept with a text answer. In order to gather information correctly, you should set up a third concept "combined reasons for poor adherence" which includes both the coded concept "reasons for poor adherence" and the "other specify" concept. When the user enters text for "other specify" the system will know that this particular "other specify" is referring to the "combined reasons for poor adherence" as it is in that set.
Create Form MetaData
After creating concepts, you must now set up your form in OpenMRS. This process involves using the OpenMRS application to populate the metadata related to your form. You can create a new form or duplicate an existing form (which allows you to reuse an existing form's metadata and, more importantly, its schema).
To view all forms in the system
- Log into OpenMRS as an administrator.
- Select the Administration link in the top navigation menu.
- Select Manage Forms under the Forms link section.
To create a new form, follow these instructions:
- Select the Add Form link at the top of the page
- NOTE: currently, the form submission engine requires encounter-based forms with a specific hierarchy (contained in the Basic Form definition); therefore, you should start new forms by copying the Basic Form and adding observations to that foundation.
To duplicate an existing form , follow these instructions:
- Select an existing form from the Duplicate Form field — e.g., the Basic Form contains all of the currently required fields in the proper hierarchy to be handled properly by the FormEntry engine.
- Click the Duplicate button.
- Update the form metadata, including the form name.
NOTE: While editing the form schema, ensure that the Published checkbox is unchecked. When we are ready to make the form available through the Form Entry module, we will need to check this checkbox.
Design Schema
This process involves using the OpenMRS application to create a schema for your form. This means telling OpenMRS which concepts are on your form.
To add new concepts to a form schema:
- Choose the Design Schema use case.
- Type the desired concept into the Find Field Elements search box (consult Define Concept section above). The search results should automatically display in the area below the search box.
- Press the Enter key. The search box and search result index (i.e. the number to the left of the search result) should now be highlighted in gold.
- Type the search result index in the search box.
- Press the Enter key.
NOTE: You can also drag n' drop the desired field element from the search result box to the form schema. However, when starting with a blank form schema it is difficult to locate the place to "drop" the field element. You can also double-click the desired field element, but this requires you to "update" the its metadata.
To move form elements within the form schema:
- Click the concept in the form schema
- Move the mouse to the desired location.
- Drop the form element into the desired location in the form element tree.
To update form element metadata:
- Right-click the desired form element in the schema.
- Click 'Edit Field'.
- Select 'Edit for this form only' to edit the form element for the current form.
- Enter the appropriate information (in most cases you will only need to change Multi?, Field #, Field Part, Page #, Min, Max, and Required).
- Multi - Indicates whether the schema should allow multiple values for a single form element (i.e. multiple answers to a single question like "What medications are you currently taking?").
- Field # - The field number corresponding to the actual form element in your form. This value may be left blank.
- Field Part - The sub field number/letter corresponding to the actual form element in your form (i.e. 'a' in 1a). This value may be left blank.
- Page # - The page number where this form element appears. This value may be left blank.
- Min - The minimum number of times that this form element should appear on the form. This value may be left blank. Default = 0.
- Max - The maximum number of times that this form element should appear on the form. This value may be left blank. Default = 1. Use -1 to allow for an arbitrary number of values, for example, if you intend to use a repeating table in the form which gathers many observations for this field.
- Required - Indicates whether the form element should be required.
To delete the form element from the schema:
- Select the X icon next to the form element name.
Default value
- The default value field is a velocity expression that is rendered every time the form is loaded
- If using the FormEntry Module, these variables are available to you:
- enterer = User object for the person filling out the form (e.g. ${enterer.userId})
- dateEntered = Date object for the current date+time
- patient = Patient object (e.g. ${patient.personName})
- timestamp = date format: yyyyMMdd'T'HH:mm:ss.SSSZ
- date = date format: yyyyMMdd
- time = time format: HH:mm:ss
- sessionId = current user's java session id
- uid = a unique identifier for the form being filled out that will carry through to the hl7 message
- patientEncounters = the current patient's past encounters. Most recent is first in the list
- relationships = all relationships the person currently has (as of version 4.2 of formentry)
Form Schema Requirements
At this point, the form schema is fairly constrained by the XSLT that translates the submitted form data (within FormEntry) into HL7. So, if you are designing forms for FormEntry (for use with InfoPath), you must follow these guidelines. Initially, we thought we'd be making up a separate XSLT for every form; however, a single XSLT has gotten us much further than anticipated.
First of all, you want to start with the basic form schema. Here are some guides...
- Form schemas should have top level nodes: PATIENT, ENCOUNTER, OBS, PROBLEM LIST, and ORDERS
- Most of the children in the PATIENT section (in the starter schema) are necessary, but I think only patient.patient_id is required
- While the XSLT can handle alternate identifiers (there's an example of our MTCT-PLUS identifier in the default XSLT), InfoPath is not the desired way to add identifiers to the system. Rather, we'd encourage you to edit patient demographics through the patient administration functions of the web application.
- In the ENCOUNTER section, encounter_datetime, location_id, and provider_id are required for things to work.
- The OBS section should linked to the concept MEDICAL RECORD OBSERVATIONS (in the schema designer, you should see the concept id for MEDICAL RECORD OBSERVATIONS in parentheses after "OBS" -- e.g., on the demo site, you'll see "OBS (1238)" for the OBS section.
- Within the OBS section, you may place either simple observations (with coded, date, boolean, numeric, or text datatype) or sets. Sets should contain 1 or more chidren that are all simple observations. The XSLT does not support sets-within-sets, so your OBS section may contain elements and elements w/ children, but should not go any deeper.
- PROBLEM LIST (if you use it), should be just like the starter schema (problem list with two children: problem added and problem resolved). If you are not collecting diagnoses on your form, you could try deleting this section in the schema, but I'd probably just leave it there and not use it on the form.
- The ORDERS section should contain only sets (no simple observations directly under ORDERS) following the design in the starter schema. Again, only one level is supported -- no sets within sets. At this stage, we are converting all of these orders to observations and have not completed a path to actually generate entries in the order tables from an HL7 message (i.e., an InfoPath form submission).
Other Resources
Adding A New Patient Identifier Field
You can look at pre-existing identifier fields in your system or on the demo website for examples. If the patient identifier type has already been defined within a field, then you should try to re-use the existing field if possible; otherwise, follow these steps to create a new identifier field:
- Go to Administration → Manage Fields
- Click "Add New Field"
- Create your field with the following information
| Field Name | Enter a name for the field, e.g. "Medical Record Number" |
| Description | Describe the field to help other administrators and users — e.g., "Unique patient identifier for Wilson Hospital" |
| Field Type | Database element |
| Database | Table = patient_identifier, Attribute = identifier |
| Default value | $!{patient.getPatientIdentifier(1).getIdentifier()}, replacing the 1 with the internal identifier type id (you can find this under "Manage Identifier Types", hovering your mouse over the identifier type and looking at the link's address) |
| Select Multi | Leave unchecked |
NOTE: typically, patient demographic fields (like an identifier) should be placed under the "PATIENT" section of a form schema. Administering Reports
Overview
The OpenMRS FormEntry module uses Microsoft® InfoPath™ to gather data for the repository.
- You must have dictionary concepts defined for each question and answer on your form.
- The form definition is created by defining a hierarchy of fields that will be used on the form.
- The layout of the form hierarchy currently has some restrictions imposed by how the forms are eventually translated into the database
- Form hierarchies should have the following sections (in order)
- PATIENT — as a section field
- ENCOUNTER — as a section field
- OBS — as a concept field with the concept MEDICAL OBSERVATIONS
- ORDERS — as a section field
- There are certain fields that are required for an encounter and these must be present within the form hierarchy under the appropriate section.
- We have created the "Basic Form" as a starting point. The basic form contains all of the required fields to get a form working. You should always start a new form by making a copy of the Basic Form and giving your copy a new name.
- Form hierarchies should have the following sections (in order)
- The layout of the form hierarchy currently has some restrictions imposed by how the forms are eventually translated into the database
Design Form
- If you have not just finished designing the schema...
- Log into OpenMRS as an administrator
- Navigate to Administration → Forms
- Click on the Edit Metadata link next to your form name
- Click on the Download XSN link
- Do not open the file with InfoPath (the default action); rather, save the file to disk
- Right mouse click the saved XSN file and click 'Design' to open it in Microsoft Infopath in design view
To add form controls:
- Select Data Source from the right navigation menu.
- Right-click a form element from the Data Source menu.
- Drag-and-drop the form element into the main content section.
- NOTE: for typical observations, you may want to drag the child "value" element instead of the entire observation element
- NOTE: For nicely-formatted form elements such as selection boxes and check boxes, use a coded item from your schema, and make sure that it is not set to Multi. Drag the value over to where you want. Afterwards, you can use the Properties dialog to give the form display values of your choosing.
- Select a form control from the context menu that is displayed.
To add layout elements:
- Move the blinking cursor to the content area where you would like to add a new layout element
- Select Layout from the right navigation menu.
- Select a layout option from the Layout menu.
To get just the concept name displayed you need to use some xpath scripting:
- Right click on the input box in design mode and select "Expression Box Properties"
- On the "General" tab, in the "Data Source" section, in the "XPath:" textbox, paste:
-
substring-before(substring-after(., "^"), "^")
Publish Form
To publish your form to the web (general instructions):
- Save XSN to filesystem (assuming you are still working within InfoPath).
- Close InfoPath.
- Log into OpenMRS as an administrator and go to Administration → Forms
- Click on the Upload XSN link
- Browse to your XSN and upload it
Editing a Form
DO NOT design the XSN that the server uses. The folder specified in the runtime properties for OpenMRS (via the formentry.infopath.output_dir property) is for use ONLY by OpenMRS. You should never manipulate these files directly. In order to get files into that folder, use the "Upload XSN" function. In order to get files out of that folder, use the "Download XSN" function.
Duplicating a Form
Duplication of form is now easily accomplished using the Manage Forms page. Simply select the form you wish to duplicate using the drop down box and then press duplicate. You will be taken to the meta data screen to allow you to change the name and the version. Of note, you have to change the build number in the database directly if you require (form table). It is important to note that XSNs have database form IDs incorporated, so one form cannot be used on another system unless you use the new Import/Export module (which requires identical Concept dictionaries). Occasionally it is necessary to change the ID of an XSN after it has been exported. This can be done using the old method of duplicating form (adapated from Andy's message):
- Open the XSN of the form in Design mode (right mouse click the file and choose design) and select File → Extract form files from the menu to save the form as separate files. Then close infopath.
- You will now see a folder with the name of your duplicate form. Inside this folder you will see some files. Edit the FormEntry.xsd file with a text editor (wordpad or notepad) and find the line:
-
<xs:attribute name="id" type="xs:positiveInteger" fixed="n" use="required" />
- where n is the id of the form.
-
- Change the n within fixed="n" to the form id of the form in the database with which you want to import the XSN(the new form id can be found by (a) examining the URL for the new form within OpenMRS' Admin → Form screen, e.g. hovering over the "metadata" link for the new form and looking at the end of the URL in the browser's status bar at the bottom) OR (b) looking up the form table in the MySQL back end. Save and close FormEntry.xsd
- In the same folder, right-click on the Manifest.xsf file and selected Design.
- Choose File, Save As and save the form to disk with another name to get the second version of the modified form. You can delete the first version of the form. Upload the second version of the form into OpenMRS.
OpenMRS uses the form id within the FormEntry.xsd file (compressed within the XSN) to determine the form to which the XSN belongs.
Moving Forms Between Servers
OpenMRS does not currently support the transfer of FormEntry forms between servers using different dictionaries or different form definitions
Assuming the two servers are using the same dictionary concepts and form and field definitions (all internal identifiers aligned), you need only upload the XSN to the destination server. If there are subtle differences (different answers to a concept on the desintation server or an additional field in the schema), you might be able to get the form working by uploading the XSN to the destination server, then downloading it and uploading it again -- we plan to add a "Rebuild XSN" link to simplify this process.
- Uploading an XSN (also known as "publishing an XSN")
- updates the URLs and advances the build number (the last number in the InfoPath solution version inside the XSN files). Uploading does *not* change the schema or templates.
- Downloading an XSN
- refreshes the schema and templates with the latest data from the form schema definition and the concept dictionary. Downloading does *not* change the build number and does not mess with the URLs since editing in InfoPath resets the URLs anyway.
- Editing with InfoPath
- changes the internal URLs anytime you save the file. Anytime an XSN is saved from InfoPath, it must be uploaded ("published") to correct the URLs before it can be used to collect data within an OpenMRS system.
Issues with long Infopath Forms
If your users are spending more than 15 minutes on a form, there is a possibility that their authenticated user session may time out. If this is the case, when the form is submitted, an undetected "403 Forbidden" error may occur and Infopath will close.
There are two global properties you can set to keep this from happening: formentry.infopath_taskpane_refresh_sec and formentry.infopath_taskpane_keepalive_min. The "refresh seconds" are "how often (in seconds) the taskpane should "ping" the server to keep the user's session alive". The "keepalive min" is "for how many minutes this should be repeated".
Workable values would be 300 seconds ("ping" server every five minutes) and 45 mins ("if they haven't submitted the form in 45 mins, stop pinging the server).
Other Resources
- InfoPath Resources
Administration Maintenance Backing up your data
OpenMRS Server Installation Overview
- Install Java 5
- Install Tomcat 5.5+
- Install MySQL 5+
- Install OpenMRS
Minimum Reqiurements
1 GHz processor or better, 512 MB of memory or more, 40 GB hard drive or larger. You can set up the server on a laptop for demonstration or testing purposes. For production usage, we recommend one or two processors 1.5+ GHz, 2 GB of memory, and 150+ GB of disk space with RAID and appropriate backup facilities.
OpenMRS Server Installation Step-By-Step
- Install Java SDK
- Set system environment variable JAVA_HOME = /path/to/javasdk
- Install Tomcat
- You will need to know the port used by tomcat (default is 8080)
- Install MySQL (must be 4.1 or higher, 5.x recommended)
- You will need to know the MySQL port (default is 3306)
- Within MySQL
- Create a database called "amrs" with default encoding of utf-8
-
CREATE DATABASE amrs /*!40100 DEFAULT CHARACTER SET utf8 */;
- Create a user account and grant full rights to the amrs database to this user
- Execute the OpenMRS Demo SQL Script to load the database into the "amrs" schema
- Create your runtime properties file (this file tells OpenMRS where to find the database and how to authenticate to MySQL). You must make a system variable called AMRS_PROPERTIES_FILE that points to this properties file.
-
Create a system variable for JAVA_HOME and another that points to your runtime properties file. JAVA_HOME should point to the top of your JAVA installation — e.g., C:\Program Files\Java\jds1.5.0_06. The name of the system variable pointing to your runtime properties should be AMRS_RUNTIME_PROPERTIES_FILE — where AMRS is the name of your OpenMRS implementation
-
- Install OpenMRS
- Log into tomcat manager at http://localhost:8080/manager/html
- Deploy war: amrs.war
- Set up Global Properties
- Certain properties should be configured right away. For example, scheduler.username and scheduler.password should be set to a valid admin user so that scheduled tasks can be performed.
At this stage, clients only need a browser (we're currently building for Firefox...if you use Internet Explorer . They need Microsoft InfoPath® (with Service Pack 2) to perform FormEntry functions.
Technical Overview- Documentation: Full documentation of the OpenMRS schemata, including specifics of every field and table — also an open forum for discussion of issues surrounding their design
- Change Log: History of all changes to the OpenMRS model
- WorkingGroup/DataModel: Community activity related to the data model.
Data Model Frequently Asked Questions (FAQ)
- What is the difference between retire and void columns?
- What are the conventions for changing the OpenMRS data model definition?
Past Entity Relationship Diagrams (ERDs):
- Version 1.10β:
- Openmrs_data_model_1.10.png: Full-size image version of the latest development build of the data model..
- Version 1.0β:
- openmrs_data_model_1.00.png: Full-size image version of the latest development build of the data model..
- openmrs_data_model_1.00.xml: XML version of model for use with DBDesigner4..
- Version 0.81:
- openmrs_data_model_0.81.png: Full-size image version of the latest release build of the data model..
- openmrs_data_model_0.81.xml: XML version of model for use with DBDesigner4..
- Version 0.80:
- openmrs_data_model_0.80.png: Full-size image version of this previous build of the data model..
- openmrs_data_model_0.80.xml: XML version of model for use with DBDesigner4..
- AMRS Version 0.7:
- amrs_data_model_0.7.png: Version 0.7 of the AMRS data model that eventually grew into the OpenMRS data model
- AMRS Version 0.1:
- amrs_data_model_0.01.png: Early image of data model that eventually grew into the OpenMRS data model
What's the difference between concept_set code> and concept_set_derived code> tables?
concept_set_derived code> tables? Humans (you and me) should only edit set relationships in the concept_set code> table. These relationships are then programmatically burst into the concept_set_derived code> table for "real world" use.
- Humans use
concept_set code> - Computers use
concept_set_derived code>
Understanding by example
Here's an example (using made-up concepts). Suppose we want to relate the following three concepts using concept sets:
- metoprolol
- Beta Blockers
- Anti-Hypertensive Medications
We (humans) use the dictionary editing tools to define two relationships:
| concept | set |
| metoprolol | Beta Blockers |
| Beta Blockers | Anti-Hypertensive Medications |
The above relationships, state that metoprolol is a Beta-Blocker and that all Beta-Blockers are Anti-Hypertensive Medications. The idea that metoprolol is an Anti-Hypertensive Medication is implied.
We then run a script (e.g., AdministratorService.updateConceptSetDerived(Concept concept)) to programmatically burst all of the implicit relationships and put these into the concept_set_derived code> table. Each time the script is run, the concept_set_derived code> table is cleared, and all entries in the concept_set code> table are copied into concept_set_derived code>, along with any implied relationships.
| concept | set |
| metoprolol | Beta Blockers |
| Beta Blockers | Anti-Hypertensive Medications |
| metoprolol | Anti-Hypertensive Medications |
The third item in the list above is derived from the manually defined relationships.
Why all this monkey business with concept_set_derived code>?
The concept_set code> table is displayed in the Data Model. The concept_set_derived code> is considered part of the business stuff that's needed to make OpenMRS work. The bottom line is that bursting out implicit relationships into a concept_set_derived code> table prevents the application from having to calculate all of the implicit relationships in real time. Likewise, we don't want to burden humans with having to explicitly define all of these implicit relationships (why create three relationships when two says it all — in the example above).
OpenMRS has been designed to have a tiered architecture. The real strength of OpenMRS is in its robust and flexible data model. However, not all users want to have to know this data model inside and out. The API layer allows a developer to only have to know Java objects and read/save to them. This layer can be used in a standalone application or, as most people use it, in a J2EE web application.
Context
The backbone of OpenMRS is the core API. This API has methods for all of the basic functions like adding/updating a patient, adding/updating a concept, etc. These methods are provided in services. There are classes named PatientService, ConceptService, EncounterService, ObsService, etc. The data model image groups the database tables into "domains." Each domain is a separate colored box. The breakdown of domains/tables is essentially a visual representation of the service separation.
The Context is a static class to allow the application to save on memory. Only one PatientService object, one ConceptService object, etc (and of course the associated DAOs) are instantiated. The Context's services are split into two categories, methods for the Services and for Users. The services are kept in the aptly named ServiceContext class. This is instantiated only once and is stored statically in the Context. The getter methods for the services simply pass through Context to the ServiceContext. The UserContext contains methods for acting on users: authentication and authorization, logging in, logging out, etc. A different UserContext is instantiated for every user accessing into the system. The "current" UserContext is stored on the current thread. When that user is done, the UserContext is taken off of the thread and put into the user's session variable (in the case of the webapp). When the user accesses the system again, the UserContext is taken off of the user's session and placed onto the thread again. In the webapp, this manipulation is done by the OpenmrsFilter class that wraps around every call to the server. Similar to the services, the methods on the Context class pass through to the current UserContext on the current thread.
Every access to the system must be defined within a "unit of work". This unit is bordered by calls to Context.openSession() and Context.closeSession(). In the webapp, these calls are done in OpenmrsFilter and most developers don't have to worry about making those calls. However, any developer of an external application or a thread spinoff (like Hl7InQueueProcessor and FormEntryProcessor) will need to be sure to include open/closeSession calls or risk leaking database connections.
Read more about the OpenMRS API
Spring DI and AOP
PatientService, ConceptService, etc are interfaces. PatientServiceImpl, ConceptServiceImpl, etc are the current default implementations of those interfaces. Which implementation Context serves up is determined by Spring's dependency injection. Each *Impl contains a reference to its DAO (PatientDAO, ConceptDAO, etc). The DAOs are also interfaces. The current default implementation of them are for Hibernate (HibernatePatientDAO, HibernateConceptDAO, etc). Which DAO implementation is used at runtime is also determined by Spring's DI.
All of these implementations are described in the Spring applicationContext-services.xml file. This file also controls the transaction and authorization AOP annotations (See next section).
Each *Service (not *DAO) is considered to be an AOP advice point. Because we instantiate and serve only one *Service class out of the Context, this is easy. When Spring starts, each service that has advice points around it gets wrapped with a Spring AOP class. If there are five modules/methods that wrap around a certain class, there will be five different wrappers. Any one of those could exit method execution early (if it's an "around" advice). Most AOP linking will be done by modules.
Also see OpenMRS AOP.
Authorization and Authentication
OpenMRS has a very granulated permissions system. Every action is associated with a Privilege. An action would be "Add Patient", "Update Patient", "Delete Patient", "Add Concept", "Update Concept", etc. A Role contains a collection of Privileges. A Role can also point to a list of inherited roles. The role inherits all privileges from that inherited role. In this way, hierarchies of roles are possible. A User contains only a collection of Roles, not Privileges.
AOP annotations are used to require a privilege for a service method. The @Authorized({"Add Patients"}) code> annotation is placed in the interface of the service. If the current user does not possess that privilege an APIAuthenticationException is thrown. (The webapp catches this exception and redirects to the login form).
Each page in the webapp is able to require a certain privilege for the page with the openmrs:require taglib. The openmrs:hasPrivilege taglib provides support for restricting only certain sections of a page.
While using the API, you may come to a point where you need to temporarily grant the current user a certain privilege in order to make an API call. This is accomplished using the Context.addProxyPrivilege(String priv) and removeProxyPrivilege(String priv) methods. Multiple priv objects defining the same string can be proxied. Subsequent calls to removePrivilege will only pop the first one off the stack. Best practice says that you should put your API method calls in a try code>/catch code> block and put the removeProxyPrivilege call in a finally code> block.
Hibernate
Hibernate is an excellent Object Relational Mapper. Using just xml files, we are able to describe the relationship between all of our tables and our domain (POJO) objects (like Patient.java, Concept.java, etc). Looking at the concept domain in the datamodel, we see that it consists of tables named concept, concept_answer, concept_set, concept_name. It would be very difficult to keep up with where to store each part of the concept object and the relations between them. Using Hibernate, we only need to concern ourselves with the Concept object, not the tables behind the object. The concept.hbm.xml mapping file does the hard work of knowing that the Concept object contains a collection of ConceptSet objects, a collection of ConceptName objects, etc. To add a new name to a concept:
ConceptService conceptService = Context.getConceptService();
Concept concept = conceptService.getConcept(1234);
ConceptName newConceptName = new ConceptName("some name", "some locale");
concept.addName(newConceptName);
conceptService.updateConcept(concept);
Hibernate knows what has changed and what needs to be saved into the database. (The long and short of it is that Hibernate wraps the Concept object in its own object and keeps track of what has been added, removed, etc).
Hibernate will not load all associated objects until they are needed -- this is called lazy loading. The concept object above never dove into the concept_answer table to get the list of answers for concept 1234. If we had called concept.getConceptAnswers() Hibernate at that point would have made a call to the database to retrieve the answers for us. For this reason, you must either fetch/save/manipulate your object in the same session (between one open/closeSession) or you must hydrate all object collections in the object by calling the getters (getConceptAnswers, getConceptNames, getSynonyms, etc).
Most Useful ANT Build.xml Targets
- clean
- Deletes the dist and build directories. Our custom build file overwrites certain @____@ strings in the code as described in the init method. If your IDE has compiled this file for you, the ANT file won't compile it and won't, therefore, overwrite those strings. Using the ANT clean target will delete those compiled files and a quick call to another ANT target that compiles code will be sure to clean those up.
- compile-api
- Usually just called by other targets. Compiles the api code into the /build folder
- package-api
- packages the api code up into a jar file. The jar file is placed in the /dist folder. This target will call compile-api.
- package-javadoc-api
- Compiles and packages the javadoc for the api. Places a javadoc.zip file into the dist folder.
- package-api-tests
- Compiles and packages the OpenMRS tests in /test into a tests-openmrs-api.jar file
- deploy-web
- very useful. Copies all .jsp, .js, .properties, .css, and images to the webapps folder in the defined tomcat home directory. When working solely in the webapp environment, this target provides rapid changing and testing of jsp and javascript. Assumes there is a war file deployed already.
- package-web
- Creates an openmrs.war file in the dist directory
- package-web-without-libs
- Creates an openmrs.war file in the dist directory that does not have the include all of the jar files in the /lib folder. This creates a much smaller war file. (Assumes the jars will be distributed separately. See next target).
- package-web-libs-only
- Creates a zip file of just the jars in the /lib folder.
- dist
- synonym for the package-web target. Creates an openmrs.war file in the /dist directory.
- compile-api-tests
- MUST be run before running any JUnit tests. Copies the right mapping and xml descriptor files to the folders that the tests assume have them.
- install
- Packages the war file and installs it into the tomcat defined by the tomcat.manager.url property. Assumes there is not a war file deployed named openmrs already.
- remove
- Removes (undeploys) the war file from tomcat
- update
- Essentially removes and then installs the openmrs war in tomcat.
- reload
- Overwrites the files in the tomcat webapps directory with the contents of openmrs.war. Does not work reliably in all situations. Update is preferred (but reload is faster)
- start
- Start the webapp in tomcat. Assumes the war has been deployed (installed) and is stopped already.
- stop
- Stops the webapp in tomcat. Assumes the war has been deployed (installed) and is started already.
- tomcat-start
- Starts the tomcat process and runs standard application output through the console in your IDE.
- tomcat-stop
- Stops the tomcat process.
Modules
Module documentation for developers.
Spring MVC
OpenMRS strongly subscribes to the Model-View-Controller pattern. We won't go into the depths of MVC, or even the basics of it. Mediawiki has a complete writeup about everything you may have wanted to know about the MVC programming pattern. Spring has also written a fair amount on how and why to use MVC. OpenMRS, for the most part, uses the domain objects as the model. Most controllers will be SimpleFormControllers and be placed in the org.openmrs.web.controller package. The model is set up in the controller's formBackingObject, and processed/saved in the processFormSubmission and onSubmit methods. The jsp views are placed in /web/WEB-INF/view.
Not all files served by the webapp are run through Spring. The /web/WEB-INF/web.xml file maps certain web page extensions to the SpringController. All *.form, *.htm, and *.list pages are mapped. The SpringController then uses the mappings in the openmrs-servlet.xml file to know which pages are mapping to which Controller.
There are no .jsp pages that are accessed directly. If a pages url is /admin/patients/index.htm, the jsp will actually reside in /web/WEB-INF/view/admin/patients/index.jsp. This is necessary so that we can do the redirect with the SpringController. Because the file being accessed ends with .htm, the SpringController is invoked by the web server. When the SpringController sees the url, it simply replaces .htm with .jsp and looks for the file in /web/WEB-INF/view/ according to the jspViewResolver bean in openmrs-servlet.xml. If the page being accessed was patient.form, the mapping in the urlMapping bean would have told spring to use the PatientFormController and the patientForm.jsp file.
That spring descriptor file also contains settings for the max form upload size, locale changing, message names, report filters (should be (re)moved), fieldGen handlers, and name/address templates.
DWR
Direct Web Remoting is a framework that allows us to translate java objects and methods to javascript objects and methods. Together with DOJO, DWR forms the basis of the AJAX in OpenMRS. The dwr.xml descriptor file describes which classes and methods are translated and made available to javascript calls. Most base DWR-able classes are placed into the org.openmrs.web.dwr package.
DOJO
DOJO is a javascript library and packaging system. All dojo packages are stored in /web/WEB-INF/view/scripts/dojo. The custom OpenMRS javascript "classes" are stored in /web/WEB-INF/view/scripts/dojo/src/widget/openmrs. The OpenMRS FormEntry module uses Microsoft® InfoPath™ to gather data for the repository.
Designing Forms
- The form hierarchy (or schema) created within the OpenMRS web application defines an XML Schema for data collection. Essentially, the form hierarchy defines all of the data points that will be (or could be) gathered on any particular form.
- When you download a form for the first time, the schema your have defined along with an XML template that follows the schema are injected into a starter form that contains the basic layout and plumbing for InfoPath. A downloaded XSN should be saved to disk and opened with InfoPath in design mode — e.g., right-click the XSN file and select "Design" from the context menu.
- During the design process, forms that are saved to disk are automatically altered internally by InfoPath. The URLs in the form are changed to point to the local file system.
- When you upload a form through the OpenMRS web application, the internal URLs are converted to the appropriate value based on the server's URL and the file's location on the server.
Submitting Forms
- When an InfoPath form is submitted to the server, an XML file containing the form data is actually posted to the server (via HTTP POST protocol, just as you were submitting a web-based form)
- The server places the XML file (unchanged) into the formentry_queue table
- A scheduled FormEntry queue processing task scans the formentry_queue table every 30 seconds and, when records are found, process queue entries. The XML is translated using the form's XSLT template to create an HL7 message to be placed in the hl7_in_queue (HL7 inbound message queue)
- If errors occur, the record is transferred to the formentry_error table and processing stops
- If no errors occur, the record is transferred to the formentry_archive table and an entry is made into the hl7_in_queue (HL7 inbound message queue)
- An HL7 processor task scans the hl7_in_queue table every 30 seconds and processes any entries that are found.
- The open source HL7API engine is used to parse the HL7 message and create the appropriate encounter and obs records.
- If any errors occur, they are logged in the hl7_in_error table; otherwise, the entry is moved to the hl7_in_archive table when processing is completed successfully.
In summary, form data are initially placed in the FormEntry queue, translated into HL7 messages in the HL7 inbound queue, and then parsed into the individual encounter and observational data points. Subsequently, all data operations (other than auditing FormEntry submissions) are performed on data from the encounter and obs tables.
By FormEntry 1.2, we should be translating order data into appropriate HL7 messages to follow the same general flow and generate data within the order tables.
Deployment Notes
- Be sure to set the formentry.infopath_server_url property at openmrs/admin/maintenance/globalProps.form
- As of Formentry version 2.6, the formentry_queue table is not used anymore, the default queue is done on the filesystem at $user/OpenMRS/formentry/queue
Glossary
- API
- application programmering interface
- EMR
- electronic medial record
- Hibernate
- An object-relational mapping tool (see hibernate.org)
- JDK
- Java Development Kit (allows you not only to run, but also to compile Java programs — used by developers to write Java programs)
- JRE
- Java Runtime Environment (allows you to run Java programs)
- MySQL
- an open-source database engine (see the MySQL website)
- ORM
- object-relational mapping, maps the world of Java objects to the relational data model of a database (see [http://www.hibernate.org Hibernate[)
- URL
- Uniform Resource Locator is essentially a reference to an address on the internet
Appendix A
Calculate a check digit
Why bother with check digits?
The purpose of check digits is simple. Any time identifiers (typically number +/- letters) are being manually entered via keyboard, there will be errors. Inadvertant keystrokes or fatigue can cause digits to be rearranged, dropped, or inserted. Have you ever misdialed a phone number? It happens.
Check digits help to reduce the likelihood of errors by introducing a final digit that is calculated from the prior digits. Using the proper algorithm, the final digit can always be calculated. Therefore, when a number is entered into the system (manually or otherwise), the computer can instantly verify that the final digit matches the digit predicted by the check digit algorithm. If the two do not match, the number is refused. The end result is fewer data entry errors.
What is the Luhn algorithm?
We use a variation of the Luhn algorithm. This algorithm, also known as the "modulus 10" or "mod 10" algorithm, is very common. For example, it's the algorithm used by credit card companies to generate the final digit of a credit card.
Given an identifier, let's say "139," you travel right to left. Every other digit is doubled and the other digits are taken unchanged. All resulting digits are summed and the check digit is the amount necessary to take this sum up to a number divisible by ten.
Got it? Alright, lets try the example.
Work right-to-left, using "139" and doubling every other digit.
9 x 2 = 18
3 = 3
1 x 2 = 2
Now sum all of the *digits* (note '18' is two digits, '1' and '8'). So the answer is '1 + 8 + 3 + 2 = 14' and the check digit is the amount needed to reach a number divisible by ten. For a sum of '14', the check digit is '6' since '20' is the next number divisible by ten.
Our variation on the Luhn algorithm
We have borrowed the variation on the Luhn algorithm used by Regenstrief Institute, Inc.. In this variation, we allow for letters as well as numbers in the identifier (i.e., alphanumeric identifiers). This allows for an identifier like "139MT" that the original Luhn algorithm cannot handle (it's limited to numeric digits only).
Allowing letters — even limited to capital letters — does not increase the accuracy of data entry. In fact, the potential for mistaking numbers and letters likely increases the chance for errors. In our case (Regenstrief with the AMRS), we were forced to come up with a simple method for generating identifiers in disparate, disconnected location without collision (giving out the same number twice). Adding a 2-3 letter suffix to the identifer was our solution.
To handle alphanumeric digits (numbers *and* letters), we actually use the ASCII value (the computer's internal code) for each character and subtract 48 to derive the "digit" used in the Luhn algorithm. We subtract 48 because the characters "0" through "9" are assigned values 48 to 57 in the ASCII table. Subtracting 48 lets the characters "0" to "9" assume the values 0 to 9 we'd expect. The letters "A" through "Z" are values 65 to 90 in the ASCII table (and become values 17 to 42 in our algorithm after subtracting 48). To keep life simple, we convert identifiers to uppercase and remove any spaces before applying the algorithm.
Here's how we handle non-numeric characters
For the second-to-last (2nd from the right) character and every other (even-positioned) character moving to the left, we just add 'ASCII value - 48' to the running total. Non-numeric characters will contribute values >10, but these digits are *not* added together; rather, the value 'ASCII value - 48' (even if over 10) is added to the running total. For example, '"M"' is ASCII 77. Since '77 - 48 = 29', we add 29 to the running total — *not* '2 + 9 = 11'.
For the rightmost character and every other (odd-positioned) character moving to the left, we use the formula '2 * n - 9 x INT(n/5)' (where INT() rounds off to the next lowest whole number) to calculate the contribution of every other character. If you use this formula on the numbers 0 to 9, you will see that it's the same as doubling the value and then adding the resulting digits together (e.g., using 8: '2 x 8 = 16' and '1 + 6 = 7'. Using the formula: '2 x 8 - 9 x INT(8/5) = 16 - 9 x 1 = 16 - 9 = 7') — identical to the Luhn algorithm. But using this formula allows us to handle non-numeric characters as well by simply plugging 'ASCII value - 48' into the formula. For example, '"Z"' is ASCII 90. '90 - 48 = 42' and '2 x 42 - 9 x INT(42/5) = 84 - 9 x 8 = 84 - 72 = 12'. So we add 12 (*not* '1 + 2 = 3') to the running total.
So, here's how we would use the Luhn algorithm for the identifier "139MT"
T (ASCII 84) -> 84 - 48 = 36 -> 2 x 36 - 9 x INT(36/5) = 72 - 9 x 7 = 72 - 63 = 9
M (ASCII 77) -> 77 - 48 = 29
9 x 2 = 18 -> 1 + 8 = 9 or 9 => 2 x 9 - 9 x INT(9/5) = 18 - 9 x 1 = 18 - 9 = 9
3 = 3
1 x 2 = 2 or 1 => 2 x 1 - 9 x INT(1/5) = 2 - 9 x 0 = 2
Summing the results we get '9 + 29 + 9 + 3 + 2 = 52'. The next number divisible by ten is 60. So, our check digit (the difference) is 8.
The modified mod10 algorithm implemented in Java:
public int checkdigit(String idWithoutCheckdigit) { // allowable characters within identifier String validChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVYWXZ_"; // remove leading or trailing whitespace, convert to uppercase idWithoutCheckdigit = idWithoutCheckdigit.trim().toUppercase(); // this will be a running total int sum = 0; // loop through digits from right to left for (int i = 0; i < idWithoutCheckdigit.length(); i++) { //set ch to "current" character to be processed char ch = idWithoutCheckdigit .charAt(idWithoutCheckdigit.length() - i - 1); // throw exception for invalid characters if (validChars.indexOf(ch) == -1) throw new InvalidIdentifierException( "\"" + ch + "\" is an invalid character"); // our "digit" is calculated using ASCII value - 48 int digit = (int)ch - 48; // weight will be the current digit's contribution to // the running total int weight; if (i % 2 == 0) { // for alternating digits starting with the rightmost, we // use our formula this is the same as multiplying x 2 and // adding digits together for values 0 to 9. Using the // following formula allows us to gracefully calculate a // weight for non-numeric "digits" as well (from their // ASCII value - 48). weight = (2 * digit) - (int) (digit / 5) * 9; } else { // even-positioned digits just contribute their ascii // value minus 48 weight = digit; } // keep a running total of weights sum += weight; } // avoid sum less than 10 (if characters below "0" allowed, // this could happen) sum = Math.abs(sum) + 10; // check digit is amount needed to reach next number // divisible by ten return (10 - (sum % 10)) % 10; }
The modified mod10 algorithm implemented in VBA:
Function checkdigit(idWithoutCheckDigit) ucIdWithoutCheckdigit = UCase(idWithoutCheckDigit) total = 0 For i = Len(ucIdWithoutCheckdigit) To 1 Step -2 digit = Asc(Mid(ucIdWithoutCheckdigit, i, 1)) - 48 total = total + (2 * digit) - Int(digit / 5) * 9 If (i > 1) Then digit = Asc(Mid(ucIdWithoutCheckdigit, i - 1, 1)) - 48 total = total + digit End If Next i total = Abs(total) + 10 checkdigit = (10 - (total Mod 10)) Mod 10 End Function
The modified mod10 algorithm implemented in Groovy:
def checkdigit(idWithoutCheckDigit) { idWithoutCheckDigit = idWithoutCheckDigit.trim().toUpperCase() sum = 0 (0..<idWithoutCheckDigit.length()).each { i -> ch = idWithoutCheckDigit[-(i+1)] if (!'0123456789ABCDEFGHIJKLMNOPQRSTUVYWXZ_'.contains(ch)) throw new Exception("$ch is an invalid character") digit = (int)ch - 48; sum += i % 2 == 0 ? 2*digit - (int)(digit/5)*9 : digit } (10 - ((Math.abs(sum)+10) % 10)) % 10 } // Validate our algorithm assert checkdigit('12') == 5 assert checkdigit('123') == 0 assert checkdigit('1245496594') == 3 assert checkdigit('TEST') == 4 assert checkdigit('Test123') == 7 assert checkdigit('00012') == 5 assert checkdigit('9') == 1 assert checkdigit('999') == 3 assert checkdigit('999999') == 6 assert checkdigit('CHECKDIGIT') == 7 assert checkdigit('EK8XO5V9T8') == 2 assert checkdigit('Y9IDV90NVK') == 1 assert checkdigit('RWRGBM8C5S') == 5 assert checkdigit('OBYY3LXR79') == 5 assert checkdigit('Z2N9Z3F0K3') == 2 assert checkdigit('ROBL3MPLSE') == 9 assert checkdigit('VQWEWFNY8U') == 9 assert checkdigit('45TPECUWKJ') == 1 assert checkdigit('6KWKDFD79A') == 8 assert checkdigit('HXNPKGY4EX') == 3 assert checkdigit('91BT') == 2 try { checkdigit ("12/3") assert false } catch(e) { }
Appendix B
OpenMRS allows you to indicate a required format for patient identifiers, using regular expressions. To specify a format, go to the Administration page, then click on Manage Identifier Types. From here, if you have created Identifier Types (at least one is required by OpenMRS), you can click on the name of the type to edit it. From the edit screen, you will find a field labeled "Regex Expression". Here you can enter a regular expression to specify what format an identifier of that type needs to be.
If you do not enter a regular expression, an identifier of any format can be entered for that identifier type.
If you do enter a regular expression, the system will verify that any identifier of this type conforms to this regular expression whenever an identifier is entered or changed.
The syntax of regular expressions is outside the scope of this page, but there are several sites/pages online that discuss them. Below is a link to a tutorial on regular expressions.
See also
Appendix C
This documentation is designed to give developers a basic description of how clinical observational data is written to the OpenMRS data repository. This model has been refined significantly since this early version was released. To see the latest version, feel free to view check out the Data Model section of our site.
To start, some basic definitions:
An observation is any clinical measurement acquired and observable in a clinical setting. If you use your imagination, just about anything can be an observation. For example, a hemoglobin test, a patient's weight, an answer to a question, or a physical exam finding. In short, any piece of data collected in a clinical setting (a patient's demographic information being a notable exception) is considered to be an observation. Note that if a clinician decides to order a hemoglobin, the order itself isn't observational: this is a clinician action, not something measured or acquired directly from the patient.
A concept is a means of describing observational information within our system. Inherent within the data model is a collection of definitions for any concept that's collected within the repository: the concept dictionary. Each concept is described with a term, which is represented within the term table (and is described in depth elsewhere).
Storing information within the data repository is pretty straightforward, if you understand the underlying database structure. Let's look at this portion of the data model.
Each observation noted during a clinical evaluation is stored as a row within this table. The obs_id is the unique identifier of the table, and should be a simple autonumbered field. Many of the subsequent attributes seen to the right are "meta data" related to the particular visit. patient_id is a foreign key to patient.patient_id, and should include the internal patient identifier number for the patient (not the medical record number). term_id is another foreign key which points to term.term_id and refers to the concept which describes what is being collected by the system. location_id describes where the observation was collected, it will typically be a clinical setting such as a clinic or a laboratory. It has a foreign key which points to location.location_id. More on location_id in a moment. Finally, encounter_id refers to the particular patient visit. As you might assume, many observations are collected during the course of a given clinical visit. Each visit's "meta data" is contained within this encounter table. You might wonder what the point of location_id is, after seeing what is stored within an encounter. In short, observations on a patient can be made outside of an encounter, or outside of the purview of our system. For example, a patient could bring copies of their medical record, or observations could be made during a home visit, etc. Therefore, encounter_id is not a required field.
When building an application, you'll have likely acquired all of this "meta data" before results are collected by your application. How you store the value of the observation, depends of the data type of the concept as defined by it's term definition. You'll notice that there are numerous different fields to store values. You'll only use one of these for each row. Let's walk through each major datatype to demonstrate.
Boolean: On one of the patient encounter forms used in the Eldoret clinics, the following question is asked: "Have you disclosed your HIV status to anyone?" This question has a simple yes or no answer, and is modeled in our dictionary within concept 1048. If you click through the provided link, you'll see that the datatype for this concept is boolean. Because of this, you will store a bit answer within value_boolean.
Coded: Another example, concept 1061. This is another question asked on an encounter form: "How do you think you were exposed to HIV?" There are six possible answers to this question, all of which are ALSO concepts within the system. Click through the link provided for more details. To store the answer for this question, you need to store the term_id for the corresponding concept. Say, in this case, the answer was "through a blood transfusion", which corresponds to concept 1063. You would store "1063" in value_coded. As you might have noticed, value_coded in fact has a foreign key to concept.concept_id to ensure this linkage occurs and maintain integrity.
Datetime: Concept 1113. In this particular example, the OpenMRS encounter form is capturing some historical information related to a previous date in which tuberculosis therapy was started. Note the "date" datatype. The answer to this question is a SQL compliant date, time or datetime. 1/1/2005, 1-1-2005, etc. depending on the database specifics within value_datetime.
Numeric: Let's look at a lab test result, a hematocrit: concept 1015. This test has a numeric answer, like 33 or 45. Store this answer within value_numeric. Most values will be pretty straightforward, like this. What if however, the result is a range, like 2-4, or a value that's "<1" or ">=400"? These more complex numeric answers can be described with the numeric_modifier field. numeric_modifier is an ordinal field, which has a list of all of the needed modifiers, such as ">", "<", "range", etc. To store a simple modifier, write the numeric value within value_numeric, and add the corresponding modifier into numeric_modifier. In the case of ranges, store the mean value of the range within value_numeric, store "range" in numeric_modifier, and the fully described value (for later display purposes) in value_text. This allows the system to use an approximation of the value in more computable ways (such as decision support, etc.), and still store the actual answer for display purposes. Contact us for more details on this.
Here are a few actual examples to provide a finer point.
|
Patient: Jenny D. Patient |
As you can see, this patient has a number of observations ready for storage. The name, and medical record number aren't important for the obs table, but you can see a patient_id, and a location. You also see a datetime for the visit. The third and fourth bits of information will give you enough information to build an encounter within the encounter table. Mosoriot has a corresponding location_id of "2". Following the demographics are four separate observations to be recorded: hemoglobin, a numeric value; weight, a numeric vital sign; date of TB treatment, a datetime; and gastrointestinal exam finding, a coded answer. If you look at this concept, hepatomegaly is one of the possible answers, and it's the corresponding concept 5008. Knowing all of this, filling in the table is pretty straightforward. Based on the data to the left, you would store 4 rows of data within the obs table with the following information...
| obs_id | pat_id | trm_id | lctn_id | encntr_id | sub_id | val_bool | val_cod | val_dt | val_num | num_mod | val_txt | d_e_t | enterer | comment |
| 1 | 123 | 21 | 2 | 345 | 11.5 | 1/15/2005 | 2 | |||||||
| 2 | 123 | 5089 | 2 | 345 | 45 | 1/15/2005 | 2 | |||||||
| 3 | 123 | 1113 | 2 | 345 | 1/1/2002 | 1/15/2005 | 2 | |||||||
| 4 | 123 | 1125 | 2 | 345 | 5008 | 1/15/2005 | 2 |
That should be enough info to get folks started. Feel free to send us mail if you'd like this to be explained in more detail!
-Paul
Appendix D
Two of the most critical characteristics of electronic medical record systems are complete flexibility and adaptability. Without an easy way of altering or adding to the system, its applicability may diminish extremely quickly. What follows is an introduction to the Concept Dictionary, OpenMRS’s unique foundation, and how it can provide the utmost flexibility for your organization.
The Basics – Definitions
Concept Dictionary (C.D.) – The fundamental building block of OpenMRS. Similar to a dictionary defining the function, meaning, and relationships of the words, the C.D. defines the name, code, and appropriate attributes for any observations or data collected (including medical tests, drugs, results, symptoms and conditions). To even further simplify the concept dictionary, one could compare it to an infinitely large Excel spreadsheet, where patients are represented as rows and concepts are represented by columns.
Concept – The basic element of flexibility in OpenMRS. Concepts are the individual data points collected from a population of patients. Concepts include both questions and answers; for example, the question of blood type is a possible concept, but the responses, “A”, “B,” & “O” would be considered concepts as well. The bottom line is, if you have a medical concept of any sort, and it’s needed within your records system, it needs to be defined within the dictionary.
Encounter - A single, specific interaction between the patient and the provider. An encounter can be any interaction, including doctor visits, home visits, counselor appointments, etc. Encounters are typically represented as a form, consisting of hundreds of observations.
Observations – Anything actively measured or observed during an encounter. As an example, patients’ weights, heights, blood pressures, and BMIs are observations, as well as qualitative facts including the number of years a patient smoked, the activities in which the patient experiences shortness of breath, and finding on an X-ray. Although typically an observable question, demographic s are an exception, and are recorded as separate concepts.
Demographics – Any descriptive characteristic of a person. This includes: name, address, date of birth, age, tribe name, employment and any other social construct involvement.
Primary Name – The most basic label for a concept. Each concept has one, and only one. The primary term should be the word(s) used most often by those who will have access to the records to prevent duplicate concept creation. Primary names should not include any abbreviations or acronyms.
Short Name – A shortened version of the primary name, possibly a word or phrase that people around the office typically use.
Description – A clear and concise descriptions of the concept, as agreed upon by the members of the organization or the most commonly referenced source.
Synonym – Any label or name that refers to a primary concept. This includes acronyms, abbreviations, and other names that reference the primary name. You can include your organization’s more informal names for concepts here.
Concept Class - The classification of a concept. This classification details how a concept will be represented (i.e. as a question or an answer). The current list of classes include:
- Test – lab tests(e.g. CD4 Count) or physical exam maneuver (e.g. Babinski)
- Procedure – spinal tap, lumbar puncture, etc.
- Drug – medications, prescriptions and over the counter
- Diagnosis – defined medical conclusion (usually in ICD), e.g. diabetes, AIDS
- Finding – physical or exam findings (shortness of breath, systolic murmur, LLL infiltrate)
- Anatomy – body part, e.g. right arm, frontal love, abdomen.
- Question - query to which there are either open-ended or coded responses
- LabSet – a group of several test concepts, e.g. I-Stat Chem8+
- MedSet – a group of several medications, e.g. cardiac medication
- ConvSet – a group of concepts, typically questions, assembled for convenience, e.g. vitals signs
- Misc – unclassifiable concepts, typically general descriptions of location or rankings, e.g. left, severe, positive
- Symptom – any sign or indication of a possible conclusion, e.g. chills, increased heart rate
- Symptom/Finding – any sign or indication, not specifically linked to one conclusion
- Specimen – a sample of any larger part, e.g. tissue, blood sample
- Misc Order – orders typically not utilized by the organization
- Program – a specific plan, or set of plans, that a patient may be enrolled in, e.g. first line TB treatment
- Workflow – a process, as described by the organization
- State – a general description of a patient or body’s status, e.g. comatose
Concept Datatype – The structured format you desired the data to be represented as. The current types are as follows:
- Numeric – any data represented numerically, also allows you to classify critical values and units, e.g. age, height, and liters consumed per day.
- Coded – allows answers to be only those provided, e.g. Blood type can only be “A,” “B,” and “O”
- Text – Open ended responses
- N/A – the standard datatype for any non-query-like concepts, e.g. symptoms, diagnoses, findings, anatomy, misc, etc.
- Document -
- Date – structured day, month, and year
- Time – structured time response
- DateTime – structured response including both the date and the time
- Boolean – checkbox response, e.g. yes or no queries
- Rule -
- Structured –
Version – A method to keep track of the number of updates applied to a specific concept.
I get all the definitions, now why does this apply to me?
Imagine attempting to graph the trend of a patient’s weight over time, and having several different concepts which refer to recorded weights - you’re looking at a lifetime of rummaging through non-standardized paperwork and measurements. If one properly uses the concept dictionary, they will be able to analyze any concept, no matter what encounter and form it was recorded in. The Concept Dictionary guarantees that all weights will be recorded as weights, and not under various headings.
As simple as it can be explained, OpenMRS is an infinitely large filing cabinet. Within that cabinet, each patient has a file. Within that file are a series of encounters, each consisting of hundreds of observations. As the patient continues to utilize the healthcare system, they will become associated with a limitless number of observations. Each of these observations consists of a question (what is the patient’s weight) and an answer (140lbs); the Concept Dictionary easily links these two concepts together. Because of this automatic correlation, there is a necessity for all concepts to be properly crafted.
Where do I start? What do I do?
So, you’re not exhausted from the descriptions, and you want to create a concept? Before you create your concept in OpenMRS, contemplate these three steps:
- Make sure the concept doesn’t already exist in the dictionary. When searching the dictionary, use partial names (e.g. KALE or KALET instead of KALETRA). Looking for partial names will help catch misspelled entries. Think about what your organization most generally refers to this concept as – consider all possible synonyms! You may be surprised what concepts already exist.
- Make sure that you can describe/understand the concept that you're getting ready to enter! Say for example, that you're asked to create a new term for the retroviral drug eliminatehivudine. Knowing that it's a retroviral drug is insufficient, as you're going to need to detail eliminatehivudine's differences from all other antiretrovirals within the terms description. Don’t be too sure of yourself - double check with the person requesting the new concept that you have the correct, specific definition.
- Make sure that you include and standardized representation of the concept, e.g. LOINC or ICD10. If you have no idea what this is – go to the internet or a coworker and find out!
Do you have all of the information ready? Then it's time to walk through a primary concept definition, and the basic attributes this includes.
- Primary Name
- The name should begin be completely specific. It is HEPATITIS B IMMUNIZATION, not IMMUNIZIATION, HEPATITIS B.
- Use all CAPITALS
- Use only alphanumeric characters! (If this was a concept, there would be no exclamation point.)
- NO ACRONYMS – Abbreviations and acronyms are only used as synonyms!!
- When necessary, always refer to the generic form, e.g. Ibuprofen, not Advil ©
- When referring to organism or virri, the full taxonomic name is used, e.g. HUMAN IMMUNODEFICIENCY VIRUS, not HIV
- Adhere to complete granularity! RIGHT UPPER QUADRANT ABDOMINAL PAIN refers to too many observations. This can be tricky in practice – if you’re unsure, refer to a geek or someone who can identify mini-clauses within your proposed primary name.
- Short Name
- Be smart – only use alphanumeric characters, avoid long phrases, and acronyms that may have several meanings
- Synonym
- Again, be smart! Use any other phrases or acronyms that people within your organization may search for when attempting to use this concept. If you’re at a loss, conduct a survey of possible end users.
- Description
- Without question, at the end of reading this statement, a lay person should have a decent idea of the concept’s meaning. This is always REQUIRED – no exceptions.
- Data Class
- Is the concept a question that requires responses? If so – label it as a question!
- Is it a list of questions that are related? If so – label it as a CONVSET!
- Is it a list of lab procedures that are related? If so - label it as LABSET!
- Is it a list of medications that are related (e.g. Antiretrovirals)? If so – label it as a MEDSET!
- If you’ve gotten through the above questions, be smart, and choose the best fitting of the remaining classes.
- Is it a set?
- If you answered yes to either question 2,3, or 4 above, check this box! Otherwise you won’t be getting much functionality out of the concept.
- Datatype
- Is your concept a question that requires responses?
- If yes, select the type that best represents the form that you wish to view your data as.
- If yes, and there are only a few possible answers, select CODED, and choose those possible answers.
- If yes, and the class is numeric, make sure you enter any critical values. Also, select PRECISE if you desire decimal answers.
- If no, select N/A.
- If yes, select the type that best represents the form that you wish to view your data as.
- Version
This is completely up to you.
- Use any method you think works for you.
- Use a method that will be easily recognized and utilized by all other OpenMRS users.
- Be consistent with your method across all concepts.
A few things to remember!
- AVOID ALL DUPLICATES – CONDUCT AMAZINGLY THOROUGH SEARCHES BEFORE CREATING CONCEPT
- ONLY USE ALPHANUMERIC CHARACTERS.
- You can use non-alphanumeric characters in the description only.
- ALWAYS WORK WITH THE END USERS OF OPENMRS TO GUARANTEE DATA IS BEING COLLECTED IN AN APPROPRIATE MANNER FOR YOUR NEEDS
Additional Assistance
Here are some notes/conversations related to concept modeling that may be helpful resources when confronting modeling issues.
Modeling diagnoses
Hamish wrote:
If I create a concept "malaria" say a boolean and I want to also have "Malaria diagnosis date" how do I link these two items so that it is clear that they refer to the same item?
Also as Darius and I discussed today what if Carole our epidemiologist friend says she wants all the diagnoses for her data collection form to have true, false, unknown and "no data available". Should we just create hundreds of coded concepts for this or formalize is as a standard construct? If we formalize it does that help us to handle such data efficiently in the same analysis and data extraction tools that normally take a boolean?
I would use DIAGNOSIS or PATIENT REPORTED DIAGNOSIS or similar generically and then record the diagnoses as answers (e.g. MALARIA) for most cases. That gives you the choice of recording the date of diagnosis into the obs date (probably simpler) or creating a DATE OF DIAGNOSIS observation to link dates to diagnoses through an obs_group_id.
If someone wants multiple choice answers for 1..n diagnois, then you are collecting two observations in each case: a multiple choice answer and the diagnosis for which the answer applies. There are a several ways to skin this.
1. Flat by diagnosis (add coded answers for each diagnosis)
MALARIA with coded answers TRUE, FALSE, UNKNOWN, NO DATA AVAILABLE
- PRO
- simple, MALARIA/etc could still be used as answers
- CON
- not scalable, ties you to one model of answers to diagnoses and these must be replicated/managed for *every* diagnosis in the system.
2. Fully abstract (one concept for diagnsosis and a 2nd for answer to multiple choice question)
INQUIRED DIAGNOSIS as coded answered by diagnosis DIAGNOSIS STATUS as coded with coded answers TRUE, FALSE, UNKNOWN, NO DATA AVAILABLE Tied together with obs_group_id
- PRO
- easily scalable
- CON
- needs tools/knowledge to convert back to one complex data point, diagnoses stored in a questionnaire-specific manner
3. Abstract, but use existing DIAGNOSIS concept
INQUIRED DIAGNOSIS as coded answered by diagnosis DIAGNOSIS STATUS as coded with coded answers FALSE, UNKNOWN, NO DATA AVAILABLE Linked with obs_group_id
Store TRUE answers as DIAGNOSIS with coded answer MALARIA, ASTHMA, etc.
- PRO
- re-use of DIAGNOSIS, so you can still search DIAGNOSIS concept for all known diagnoses
- CON
- application must know to treat TRUE answers differently
4. Flat by answer (one concept per answer for questionnaire)
DIAGNOSIS DIAGNOSIS DENIED DIAGNOSIS STATUS UNKNOWN DIAGNOSIS DATA NOT AVAILABLE each is coded and is answered with the diagnosis
CAROLE1 QUESTIONNAIRE DIAGNOSES OPTIONS as a concept_set of the above concepts
- PRO
- scalable, could re-use DIAGNOSIS concept or have a separate concept for diagnosis = true on questionnaire
- CON
- requires search for multiple concepts (could be facilitated by concept_set)



