-
Notifications
You must be signed in to change notification settings - Fork 36
Concepts and their UI strings
In Buendia there is a need to display strings for medical concepts like "Temperature in degrees Celsius" in the UI. OpenMRS has a good database schema design for doing this, but it can be confusing and it isn’t perfect. The aim of this document is:
- to give some technical background into the domain
- explain some of the issues encountered in this area
- to explain the design choices we have made
One of the core concepts in OpenMRS is that of the concept dictionary. This maps a concept to a unique id, and allows things to be associated with that unique id, like type, localized names and so on.
Obviously medical record systems have existed for a while, and so many different concept dictionaries exist. In true xkcd 927 fashion, OpenMRS has resolved this by creating the MVP-CIEL concept dictionary. The nice thing about this dictionary is it already has:
- a lot of concepts predefined by proper medics and epidemiologists
- mapping to many other standard dictionaries
- some synonyms for English
This dictionary is maintained by Andrew Kanter, and we are trying to contribute back anything we find it is missing, and keep synchronized with it.
The key problem is how do we get the strings that we want displayed for questions and answers.
The issue here is the same issue that has been puzzling philosophers since Plato: what is a concept? Take for example a requirement we have from MSF: "ask for Pulse Oximetry as a number". This is fairly simple. It involves a single concept, "Pulse Oximetry". If you search the maternal concepts lab site for this all you get is "Pulse" which is basically "pulse rate measured by a pulse oximeter". Sound right? But it isn’t, because we have a separate requirement for pulse (rate) from MSF. So looking up Pulse Oximetry on Wikipedia we see "Pulse oximetry is a non-invasive method for monitoring a person's "O2 saturation](http://en.wikipedia.org/wiki/Oxygenation_(medical)#Measurements)". So we go and search the MVP-CIEL dictionary again for saturation and we find Blood oxygen saturation which is the correct concept here.
That wasn’t too bad. But things get more complicated when it comes to something like “Diarrhea”. Firstly it is a nightmare to spell, and is written differently in US English ("diarrhea") and UK English ("diarrhoea"). Secondly, people think they know what it is. But is it a symptom, or a diagnosis? OpenMRS say on their wiki page: "Symptom, Finding, and Diagnosis: A finding is something a healthcare practitioner notes on examination, a symptom is something a patient complains of, and a diagnosis is a label made after findings, symptoms, and tests are considered. The differences are subtle, but important to distinguish between. Chest pain is not a finding for example, it's a symptom. Anemia is not a finding or a symptom, it's a diagnosis."
Yet when you ask clinicians, some admit that the lines are blurry and ambiguous, while others insist the difference is clear between the classes, yet disagree what the clear differences are.
Back to Diarrhea. We want a symptom for Ebola, as it is something we are asking the patient, not finding or diagnosing. MVP-CIEL has 1 symptom, Chronic Diarrhea, but clearly defines it as “For greater than 2 weeks duration” which we don’t want. It has four Diagnoses (Diarrhea, Protozoal Diarrhea, Campylobacter Diarrhea, and Chronic Diarrhea of Unknown Origin). The last 3 are not what we want. The first looks good, but says it is a diagnosis. However, it is coded as a question with answers "no", "unknown" and "yes". This looks good and like a symptom, so we decide to use it.
The question we have been asked to ask by MSF has answers "None", "Mild", "Moderate", and "Severe". So now we have a dilemma. We have one question for one conceptual symptom with two coded answer sets, with one being boolean and one having degrees of severity. This is hard to solve. OpenMRS really emphasises NOT duplicating concepts. In fact, it is even forbidden for two concepts to have the same name string in the same locale.
Which means we either need to:
- introduce a concept of “Diarrhea severity” and say it is a new concept,
- or keep the existing concept, but change the answer set for the it, and lose our consistency with MVP-CIEL.
Neither is good, but we chose the latter.
So back to Pulse Oximetry. We know we have the following requirements:
- Use concept MVP-CIEL 5092 Blood oxygen saturation
- Display as “Pulse Oximetry” in English
- Have the ability to localize to other languages
- Display both in Xforms and in RPCs
The question we are left with is where to store the string “Pulse Oximetry” associated with concept 5092, and English.
OpenMRS has the 15 different methods in the Java API for getting Strings to represent concepts:
- getName()
- getName(locale [and optionally other arguments])
- getPreferredName
- getShortestName
- getFullySpecifiedName
- [deprecated] getBestName
- [deprecated] getBestShortName
- getShortNameInLocale
- [deprecated] getShortNameIn[Country/Language]
- getCompatibleNames(locale)
- getDescription(locale) and various related methods
- getDisplayString()
- getNames([optionally specifying locale])
- getShortNames()
- getSynonyms([optionally specifiying locale])
The source code doesn’t clarify much. So what about the data model? This makes it clear all names are stored in the concept_name table. Description is clearly something different. And names appear to have a concept_name_type. The Javadoc makes this clearer: null means it is a synonym. FULLY_SPECIFIED is allowed for one name per locale and is the default. SHORT is allowed for one name per locale. INDEX_TERM should not be displayed and is used for searching; it might be a misspelling. There is also a boolean locale_preferred which means it is preferred for that locale.
So realistically given the OpenMRS data model there seem to be 3 options:
- Anything we want to use our own name for, and it to the synonyms, and set the “Preferred name” boolean flag on it. This is obvious as it seems to match the OpenMRS design best. However, it makes merging in the MVP-CIEL dictionary harder.
- Add our own custom locale, eg en_buendia, and munge the locale for lookups, giving us easy dictionary editing. It makes dictionary merging easy, but we will have to write our own custom strings for everything.
- Go the route the original Xforms module, and OpenMRS HTML forms module went, and store labels in a completely separate place.
Storing in the client might seem natural, and is normally the right decision. But since the xform sends a label through, we need it server side. We also need it server side for user editability. Storing in the form text table is wrong as there is no ability to localize.
I am reluctant to add yet another table, as we seem to be doing exactly the same job as the concept_name table (localization of concepts to strings). We’d have no admin interface pre-written. So I will reject option 3.
Option 1 seems like the right way to go, but the OpenMRS antiduplicate error checking is getting in the way. For example, the fully specified name for concept 1067 is "UNKNOWN". If we would like this displayed as "Unknown" we can’t add a synonym of this string, as the validation code declares the two strings duplicates.
So to take option 1, we would need to: a. modify validation, b. or edit in place in the case our custom String is a duplicate, c. or add some post hoc modifications to fix case.
I’m not keen on post hoc modifications, as then it is not user-editable. Also if the user added an acronym (like AVPU) we would edit the case wrong. Modifying validation it would be very easy to miss something or break something and be error prone. So I think it would have to be the edit in place solution. And this is hard to clarify in instructions for the users.
I’m nervous about Option 2, as I don’t know how well OpenMRS handles locale extensions, and it seems very, very hacky. But at the moment it seems like the best option, so we will take it.
We will add fallback logic, eg if asked for locale es_419 (apparently there are a lot of nice Cuban doctors):
- try “es_419_buendia”
- then try “es_419”
- then try “es” (locale.getLanguage()?)
- then try “en”
There is a global property named locale.allowed.list.
We will add a locale en_GB_client, with the name "English Client" to our database.
About the software
System Overview
Client Application
Server Application
Server Platform
Development practices
GitHub Usage
Java Style
Testing
Releases
For field users and testers
Software Install and Configuration
Upon Receiving Your Gear
Setting Up a Tablet
Setting Up a Server
Setting Up an Access Point
Reference Configuration