# Existing numeric obs with no obs\_reference\_range

**URL:** <https://talk.openmrs.org/t/existing-numeric-obs-with-no-obs-reference-range/50080>\
**Category:** Development\
**Created:** [September 11, 2026, 6:40pm UTC](https://talk.openmrs.org/t/existing-numeric-obs-with-no-obs-reference-range/50080 "2026-09-11T18:40:52Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![mseaton](https://talk.openmrs.org/user_avatar/talk.openmrs.org/mseaton/32/8088_2.png) [@mseaton](https://talk.openmrs.org/u/mseaton)\
**Post date:** [September 11, 2026, 6:40pm UTC](https://talk.openmrs.org/t/existing-numeric-obs-with-no-obs-reference-range/50080/1 "2026-09-11T18:40:52Z")

</div>

In OpenMRS 2.7.0 (see TRUNK-6245), `concept_reference_range` and `obs_reference_range` were introduced.

Prior to running this version, reference ranges for numeric observations were only associated with the concept\_numeric itself, in absolute, critical, and normal range values that only had a single possible value. Displaying or reporting on reference ranges associated with an Obs involved looking up this associated metadata on the Concept Numeric.

With the advent of the new feature in 2.7, a change was made to directly associate reference ranges with observations _ **at the time those obs were created** _ . From the point in which an existing implementation upgraded to 2.7+, any new Obs associated with a concept numeric would also have an entry in obs\_reference\_range. And due to a quirk in the way this was implemented, there are also situations in which these obs\_reference\_ranges would not have been created even after a 2.7 upgrade (eg. if the implementation had `validation.disable` = `true` set in their global properties), but that’s a discussion for another thread and ticket.

As far as I can tell, no migration was ever created to retroactively create reference ranges for existing obs. Is this true and if so, what was the thinking at the time?

The upshot is that implementations find themselves in a situation where newly created obs have obs reference ranges, and previously existing obs created before this feature do not.

Have others run into this and how have they handled it? Does a migration script exist in the community (eg. maybe in the 2.7 release notes as an optional step to execute due to the size of the obs table and how long it would take to run)? Some thoughts:

- My guess is that 99% of implementations have no concept\_reference\_ranges defined at all, and a gated liquibase changeset _could_ retroactively create obs\_reference\_ranges for these under those conditions (though this might be prohibitively slow)
- We could also have a liquibase changeset that migrates any obs whose concept does not have a row in concept\_reference\_range, since that would always be assumed to use the values defined in concept\_numeric up to that point (same slowness concern)
- Or we could _not_ migrate the data, but fall back to the values defined in concept\_numeric when retrieving this data from the API, if no specific obs\_reference\_range exists (easiest to implement, but does this introduce possibilities of showing incorrect ranges)

Looking for input from those who built these features in, or who have expertise and/or opinions about what would be the best approach with data quality, data completeness, and patient safety in mind. I’m sure there are other options than the one’s I have initially laid out.

@ibacher / @burke / @akanter / @dkayiwa

Thanks,

Mike

---

<div class="post-metadata">

**Author:** ![ibacher](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ibacher/32/19751_2.png) [@ibacher](https://talk.openmrs.org/u/ibacher)\
**Post date:** [September 11, 2026, 7:47pm UTC](https://talk.openmrs.org/t/existing-numeric-obs-with-no-obs-reference-range/50080/2 "2026-09-11T19:47:24Z")

</div>

> [@mseaton](#):
>
> Is this true and if so, what was the thinking at the time?

Yes. For the most part, though, we’ve tried to make the APIs responsible for returning anything to do with reference ranges fallback to the concept reference range.

There are a few considerations here:

- Obs tables are large and a migration to create a reference range for every numeric obs would be massive task; we generally don’t want to create changesets that cause upgrades to take hours by default.
- We didn’t change any existing semantics; reference ranges prior to 2.7.x were provided by the `concept_reference_range` and for obs generated before the `obs_reference_range` existed, that’s presumably the controlling reference range.
- Running the obs reference ranges, especially for years-old obs, might actually attach the wrong reference range to an obs where guidelines and thresholds have changed (rare, but it does happen).
- Sometimes reference ranges vary a little bit depending on the equipment used to test them. I don’t think we have enough data to determine if that’s the case or not retroactively.
- We didn’t (at the time) have a good community database with a large variety of observations, so writing such a migration with the change would’ve meant shipping something that may or may not be fit-for-purpose.

---

<div class="post-metadata">

**Author:** ![mseaton](https://talk.openmrs.org/user_avatar/talk.openmrs.org/mseaton/32/8088_2.png) [@mseaton](https://talk.openmrs.org/u/mseaton)\
**Post date:** [September 12, 2026, 1:03pm UTC](https://talk.openmrs.org/t/existing-numeric-obs-with-no-obs-reference-range/50080/3 "2026-09-12T13:03:09Z")

</div>

> [@ibacher](#):
>
> Yes. For the most part, though, we’ve tried to make the APIs responsible for returning anything to do with reference ranges fallback to the concept reference range.

Thanks @ian . In our app, we are just retrieving Obs and then displaying their associated reference range, which is only populated for newly created obs (and only then if validation is not disabled), afaict.

Are you saying that you intended obs.getReferenceRange() to fallback to a reference range created on the fly from the underlying `obs.getConcept()`'s associated reference range? Or is this the responsibility of the all independent apps to handle and figure out on their own? How do we ensure this is done consistently across the app and module ecosystem?
