Stream: implementers
Topic: Observation.referenceRange unit constraint?
David Simons (Oct 15 2021 at 10:45):
Any best practices on ensuring that the units of referenceRange high/low match those of the valueQuantity, on Observation?
To my surprise the base spec nor for example the vitalSigns profiles constrain anything on this...
One approach is to constrain the referenceRange to only hold a value, without an explicit unit. (UnitlessQuantity).
https://www.hl7.org/fhir/observation-definitions.html#Observation.referenceRange.low
https://www.hl7.org/fhir/observation-definitions.html#Observation.referenceRange.high
Example that should get flagged because of the kg
vs nmol/L
units
{
"resourceType": "Observation",
"code": {
"coding": [
{
"system": "http://loinc.org",
"code": "76485-2",
}
]
},
"valueQuantity": {
"value": 45,
"system": "http://unitsofmeasure.org",
"code": "nnmol/L"
},
"referenceRange": [
{
"low": {
"value": 40,
"system": "http://unitsofmeasure.org",
"code": "kg"
},
"high": {
"value": 140,
"system": "http://unitsofmeasure.org",
"code": "kg"
},
"text": "40-140"
}
]
...
}
Lloyd McKenzie (Oct 15 2021 at 13:22):
No expectation they'll match. The reference ranges are just that - ranges assigned by the equipment manufacturer, lab procedures manual or something else. If the reference range for body weight for a given age and gender is 5-7kg, that doesn't mean a clinician can't capture the weight in pounds or ounces. Some organizations might have best practices that require this - or at least require a translation to the reference range units, but it's not something we can enforce across all types of measurements across all countries.
David Simons (Oct 15 2021 at 14:37):
Lloyd McKenzie said:
No expectation they'll match. The reference ranges are just that - ranges assigned by the equipment manufacturer, lab procedures manual or something else. If the reference range for body weight for a given age and gender is 5-7kg, that doesn't mean a clinician can't capture the weight in pounds or ounces. Some organizations might have best practices that require this - or at least require a translation to the reference range units, but it's not something we can enforce across all types of measurements across all countries.
Thanks, your example of an alternate unit that _is_ clinically relevant (lbs vs kg) indeed helps to understand that such constraints would be very context specific. Hence, we'll have to create solution-specific profiles with corresponding valueset bindings on the units for example, in the context of use.
David Simons (Oct 27 2021 at 14:35):
Trying to add a warning using FHIRPath expressions - crazy how tricky it is to debug/tweak these expressions to give the correct outcome:
<element id="Observation.referenceRange">
<path value="Observation.referenceRange" />
<constraint>
<key value="Observation.referenceRange.low.code" />
<severity value="warning" />
<human value="SHOULD have a match in unit code between valueQuantity and referenceRange.low" />
<expression value="Observation.referenceRange.where( low.code.toString() != Observation.value.ofType(Quantity).code.toString() ).empty()" />
</constraint>
<constraint>
<key value="Observation.referenceRange.high.code" />
<severity value="warning" />
<human value="SHOULD have a match in unit code between valueQuantity and referenceRange.high" />
<expression value="Observation.referenceRange.where( high.code.toString() != Observation.value.ofType(Quantity).code.toString() ).empty()" />
</constraint>
<mustSupport value="true" />
<isModifier value="false" />
</element>
Unfortunately the where - to handle the multiple referenceRanges on the Observation - does not work as expected... Any tips?
David Simons (Oct 27 2021 at 14:45):
This works $this.referenceRange.where(low.code.toString() != 'Cel').empty()
with hardcoded String,
, but I want to use the actual Observation.valueQuantity.code from the same Resource being validated.
Lloyd McKenzie (Oct 27 2021 at 14:48):
If your context is Observation.referenceRange, you can't refer to Observation.value unless you use %resource. (It's not in scope.)
Lloyd McKenzie (Oct 27 2021 at 14:49):
Also, you want to check for a match, not a non- match. (The expression asserts what is desired to be true)
Lloyd McKenzie (Oct 27 2021 at 14:49):
Finally, if you're already in Observation.referenceRange, you shouldn't be prefixing with Observation.referenceRange.
Lloyd McKenzie (Oct 27 2021 at 14:59):
I think what you want is:
high.code.empty() or %resource.value.empty or (high.code=%resource.value.ofType(Quantity).code and high.system=%resource.value.ofType(Quantity).system)
David Simons (Oct 27 2021 at 15:06):
Thanks!
The challenge is the multiplicity of referenceRange... or does the PATH iterate over all of those?
So yeah, I had tried the root Observation context also, to have the valueQuantity in scope - good to know about %resource
!
<element id="Observation">
<path value="Observation" />
<constraint>
<key value="Observation.referenceRange.low.code" />
<severity value="warning" />
<human value="SHOULD have a match in unit code between valueQuantity and referenceRange.low" />
<expression value="referenceRange.where( low.code.toString() != %resource.value.ofType(Quantity).code.toString() ).empty()" />
</constraint>
<mustSupport value="true" />
<isModifier value="false" />
</element>
Note that I am searching for mismatches: the !=
is used to ensure there are no (empty()
) mismatches, across the multiple referenceRange.low attributes (via the where
).
Lastly, I used the toString(), to compare codes, I sensed that comparing codes directly did not work?
Will try your suggestions
Lloyd McKenzie (Oct 27 2021 at 15:29):
Comparing codes directly should be fine. The invariant will trigger on each element repetition at the element path the invariant is declared on. You don't want to search for mismatches, you want to assert that all should be matches. The warning will trigger if the specified expression evaluates to 'false'.
David Simons (Oct 27 2021 at 15:56):
Lloyd McKenzie said:
The invariant will trigger on each element repetition at the element path the invariant is declared on.
Clear - thank you - if indeed the constraint is evaluated separately for each instance of the path, then I don't need the where and the negation approach, as you say, and can keep it a simple comparison.
Really appreciate the help - this kind of detail is hard to find in the docs.
Lloyd McKenzie (Oct 27 2021 at 16:37):
If you want to propose enhanced wording in the spec, change requests are always welcome :)
David Simons (Oct 27 2021 at 17:29):
Yes, will try to . Helping to build a knowledge base here on Zulip also should help others.
Particularly the following I could not find on <https://hl7.org/fhirpath/>
- %resource
- the element repetition handling of constraint under a given element path
As an update, the following seems to work - but will also try to further simplify tomorrow.
The %resources
and the precondition of the code.exists() seemed to have helped me.
<element id="Observation">
<path value="Observation" />
<constraint>
<key value="Observation.referenceRange.low.code" />
<severity value="warning" />
<human value="SHOULD have a match in unit code between valueQuantity and referenceRange.low" />
<expression value="referenceRange.where( low.code.exists() and (low.code != %resource.value.ofType(Quantity).code) ).empty()" />
</constraint>
<constraint>
<key value="Observation.referenceRange.high.code" />
<severity value="warning" />
<human value="SHOULD have a match in unit code between valueQuantity and referenceRange.high" />
<expression value="referenceRange.where( high.code.exists() and (high.code != %resource.value.ofType(Quantity).code) ).empty()" />
</constraint>
<mustSupport value="true" />
<isModifier value="false" />
</element>
Lloyd McKenzie (Oct 27 2021 at 17:56):
You won't find anything about %resource in the base fhirpath spec because that's a FHIR-specific extension. (It wouldn't make any sense for FHIRPath exercised against v2, for example.
Grahame Grieve (Oct 27 2021 at 21:50):
http://hl7.org/fhir/fhirpath.html
David Simons (Oct 28 2021 at 15:05):
Thank you both
An for completeness, the following is more elegant indeed, also works, and gives the warnings more precisely on the line items of the referenceRange:
<element id="Observation.referenceRange">
<path value="Observation.referenceRange" />
<constraint>
<key value="Observation.referenceRange.low.code" />
<severity value="warning" />
<human value="SHOULD have a match in unit code between valueQuantity and all referenceRange.low" />
<expression value="(%resource.value.ofType(Quantity).code.exists() and low.code.exists()) implies (%resource.value.ofType(Quantity).code = low.code)" />
</constraint>
<constraint>
<key value="Observation.referenceRange.high.code" />
<severity value="warning" />
<human value="SHOULD have a match in unit code between valueQuantity and all referenceRange.high" />
<expression value="(%resource.value.ofType(Quantity).code.exists() and high.code.exists()) implies (%resource.value.ofType(Quantity).code = high.code)" />
</constraint>
<mustSupport value="true" />
<isModifier value="false" />
</element>
Last updated: Apr 12 2022 at 19:14 UTC