Adding support for population-based reference ranges in OpenMRS

OpenMRS supports simple reference ranges for numeric observations to determine if a numeric result’s value is normal, abnormal, critical, or out of range. As we know, reference ranges aren’t always that simple. Reference ranges (i.e., “normal” ranges) can vary by a number of factors, including age, sex, genetics, pregnancy, and time of day. It’s also possible to have a normal value for non-numeric test results (e.g., “non-reactive”).

To date, we’ve deferred introducing this complexity; however, implementations are increasingly looking for ways to define age- or gender-specific normal ranges in some cases.

I’d like to see OpenMRS make a fundamental shift (aligning with HL7, including FHIR) to interpret results as they are being recorded rather than (as OpenMRS currently does) every time a result is read. In other words, we should expect the lab reporting a result or the application storing a vital sign to include abnormal flag ± reference ranges along with the result’s value at the time the result is recorded. This doesn’t answer all the questions around implementing age- or gender-specific reference ranges, but it does shift how we approach the problem. Let’s evolve away from interpreting results (e.g., applying reference ranges) on every read and toward the assumption that it’s the job of any application/code reporting (recording) a result to include the interpretation.

Fully embracing reference ranges and aligning with FHIR would involve:

  1. Migrating obs.interpretation to a concept and populating this interpretation (with an observation interpretation value) whenever we are recording observations.

  2. Define reference ranges (or normal values for coded results) within OpenMRS as part of the concept definition. FHIR observations can include one or multiple reference ranges and each can apply to a specific age range and/or target population (via an appliesTo property of a reference range). Modeling reference ranges within OpenMRS could be done with new tables like this:

      concept_id (Concept)
      hi_value (double)
      low_value (double)
      normal_value (Concept)
      type (Concept)
      hi_age (double)
      low_age (double)
      reference_range_text (text)
      population (Concept)

    I’m not sure how critical ranges and absolute ranges would be handled with this model, but (although not mentioned in the FHIR spec), it would seem most natural to define these as reference range types (i.e., absolute and non-critical to align with normal).

  3. Then deprecate the reference ranges used in concept_numeric in favor of adopting concept_reference_range.

  4. Add reference range to observation. This aligns with the ultimate goal of not only recording interpretation but also reference ranges used with each observation, putting the job of interpretation and reporting reference ranges used on the system reporting the result, which might not always be OpenMRS (e.g., may come from a separate lab system) – i.e., adding tables obs_reference_range and obs_reference_range_population mirroring the concept reference range tables.

Sound too complicated? Small, incremental, and slightly hacky steps in this direction could be:

  • Add obs.interpretation_coded (Concept) and add interpretation values to our dictionaries (starting with CIEL) to begin assigning FHIR-compatible interpretation to observations.
  • Add concept.reference_range_json to hold FHIR-compatible reference range definition(s) until we get around to normalizing it into new tables.
  • Use obs.comments to report reference range used for now

Thoughts? Are there implementations needing age- and gender-specific reference ranges within OpenMRS?

/cc @grace @ibacher @mseaton @akanter


This seems like a good idea to better capture the ranges… I am not sure about how to capture the interpretation as a separate value. It seems that in some cases LOINC has separate LOINC codes for the interpretation. However, if we are simply recording the Normal, abnormal, HIGH, LOW, etc. as compared to the lab’s reference range for the patient, then I agree that it makes sense to add this as a structured field attached to the numeric result.

1 Like

Yes. This is adopting the approach that has been used to communicate clinical observations for decades (i.e., HL7), where the interpretation is typically included with the result, since the lab (i.e., system reporting a result) is better-suited to provide an interpretation.

@caseynth2 from OpenELIS is also going to weigh in here when he gets a chance to call out any concerns or thoughts based on their experience maintaining multiple ref ranges for things in OpenELIS.

We talked about a strategy for supporting population-based reference ranges on today’s TAC call. The takeaway was to target the following changes for the next Platform release:

  • Decide on whether we can use obs.interpretation in its current state (an Enum) vs deprecating the Enum approach and introduce a concept-based obs.interpretation_coded (Concept) to the data model.
    • While I initially suggested the latter (moving interpretation from Enum to Concept), looking again at obs.interpretation, it is already pretty closely aligned with FHIR’s obs.interpretation. So, maybe we just need to start using it.
  • Add tables to support reference ranges (aligned with FHIR) for both Concept and Obs
    • On further inspection, FHIR’s ObservationDefinition has a slightly different approach to reference ranges, using qualifiedValue that is similar to the Obs reference range model, but not exactly the same (e.g., hardcoded gender into model and a range category (reference, critical, absolute) instead of type.

tl;dr – during the call, we decided to add obs.interpretation_coded and tables (_reference_range and _reference_range_population) for Concept & Obs. As I was writing this summary of that decision and looked more closely at our existing obs.interpretation and FHIR’s ObservationDefinition, we might be able to simply start using our obs.interpretation enum and should consider FHIR’s ObsDefinition.qualifiedValue before creating a Concept_reference_range table.

In case you want to review the conversation on the TAC call…

Also, here’s an example of the type of reference ranges we want to support (provided by @michaelbontyes):

This is a fantastic idea. In OpenELIS we’ve had to support pediatric normal levels measured in days. Which makes reading the result in real-time not work very well. I’d suggest adding a critical high and critical low flag to the at-that-time results.

In the interop piece, would you want to define the interpretation directly in OMRS, or would you want the lab system to send those flags from the LIS system as a part of the diagnostic report? We could make this harmonize with OpenELIS.

1 Like

While adding interpretations to observations received without an interpretation at the time they are received would be better than trying to perform the interpretation every time the observation is read (as we do now) would be an improvement, the ideal scenario would be the fulfiller (the lab reporting a result, the app recording vitals, etc.) including the interpretation within observation.

So, yes. Please include interpretation (and reference range information if you have it) with observations and we’ll work toward being able to receive & persist it within OpenMRS. :slight_smile:

1 Like