Intervals of Integer, Decimal and Time are returned as CQL text instead of FHIR.Range / FHIR.Period
Repository: cqframework/clinical_quality_language
Related: child of #1430 (Correctly support CQL to FHIR (and vice versa) type mapping).
Overlaps #1744 and clinical-reasoning #1090, both of which show this in their example
responses but are filed as wrong-value bugs. See "Relationship to existing issues" below.
Summary
Evaluating an expression that returns Interval<System.Integer>, Interval<System.Decimal> or
Interval<System.Time> via $cql produces a valueString containing CQL source text, marked with
a cqf-cqlText extension:
{
"name": "return",
"valueString": "Interval[1, 10]",
"_valueString": {
"extension": [
{ "url": "http://hl7.org/fhir/StructureDefinition/cqf-cqlText", "valueBoolean": true }
]
}
}
Per Conformance Requirement 4.3 — FHIR Type Mapping these should be returned as
FHIR.Range and FHIR.Period respectively. Two things are wrong:
- The wrong FHIR type is used. A rendered CQL string is returned instead of the mapped type.
- The required
cqf-cqlType extension is absent. The IG states that all CQL-valued
parameters and results SHALL include a cqf-cqlType extension to unambiguously specify the
type, and that in the absence of a cqf-cqlType extension, values are assumed to be the FHIR
type. So a conformant consumer must read the above as a genuine FHIR.string whose value
happens to be "Interval[1, 10]".
Additionally, http://hl7.org/fhir/StructureDefinition/cqf-cqlText is not a registered
extension — the canonical url returns HTTP 404, unlike the registered cqf-cqlType. Nothing can
be expected to interpret it.
Expected mapping
| CQL type |
Expected FHIR type |
Interval<System.Integer> |
FHIR.Range |
Interval<System.Long> |
FHIR.Range |
Interval<System.Decimal> |
FHIR.Range (with quantity-precision extension) |
Interval<System.Quantity> |
FHIR.Range |
Interval<System.Date> |
FHIR.Period |
Interval<System.DateTime> |
FHIR.Period |
Interval<System.Time> |
FHIR.Period (time values as dateTime with an @0001-01-01 date) |
The engine already does this correctly for two of the seven
This is an incomplete implementation rather than a representation gap — the engine emits the
mapped FHIR type for Date and Quantity intervals, and falls back to CQL text for the rest.
| Expression |
Returned |
Conformant |
Interval[1, 10] |
valueString + cqf-cqlText |
no — expected Range |
Interval[1.0, 10.0] |
valueString + cqf-cqlText |
no — expected Range |
Interval[@T01:00:00.000, @T02:00:00.000] |
valueString + cqf-cqlText |
no — expected Period |
Interval[@2012-01-01, @2012-12-31] |
valuePeriod |
yes |
Interval[1 'mg', 10 'mg'] |
valueRange |
yes |
Verbatim, from HAPI FHIR 8.10.0 hosting the CQF engine (cqlEngineVersion 4.1.0):
// Interval[@2012-01-01, @2012-12-31] -- correct
{ "name": "return",
"valuePeriod": { "start": "2012-01-01T00:00:00-07:00", "end": "2012-12-31T00:00:00-07:00" } }
// Interval[1 'mg', 10 'mg'] -- correct
{ "name": "return",
"valueRange": { "low": { "value": 1, "unit": "mg", "system": "http://unitsofmeasure.org", "code": "mg" },
"high": { "value": 10, "unit": "mg", "system": "http://unitsofmeasure.org", "code": "mg" } } }
// Interval[1, 10] -- incorrect
{ "name": "return",
"valueString": "Interval[1, 10]",
"_valueString": { "extension": [
{ "url": "http://hl7.org/fhir/StructureDefinition/cqf-cqlText", "valueBoolean": true } ] } }
Note that Range.low/Range.high are Quantity, and a Quantity may carry a bare value with no
unit — so an integer or decimal interval is representable without inventing anything.
Impact
Running the cql-tests suite against this engine, 47 test results are returned this way. Every
one of them is an Interval, and all 47 currently fail:
| Test file |
Count |
CqlIntervalOperatorsTest |
40 |
CqlDateTimeOperatorsTest |
5 |
CqlTypesTest |
2 |
cqf-cqlType was present on 0 of 47.
Of the 47, 41 fail only because of the representation — the value itself is correct, but a
consumer comparing a Range/Period against a returned string cannot match it. The remaining 6
have a genuine value defect as well (the uncertainty low-bound issue in #1724, and expand/
collapse returning only the first interval, #1744), so they would still fail after this is fixed.
To reproduce
Run the following against cql-tests-runner. The first three tests fail; the last two pass,
demonstrating that the mapping is implemented for some interval point types and not others.
<?xml version="1.0" encoding="utf-8"?>
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://hl7.org/fhirpath/tests" xsi:schemaLocation="http://hl7.org/fhirpath/tests ../../testSchema/testSchema.xsd"
name="CqlIntervalOperatorsTest" reference="https://hl7.org/fhir/uv/cql/3.0.0-202609-ballot/en/conformance.html#fhir-type-mapping" version="1.0">
<capability code="interval-operators"/>
<group name="IntervalFhirTypeMapping" version="1.0">
<!-- Interval<System.Integer> SHALL be returned as FHIR.Range. Returns a valueString
of "Interval[1, 10]" carrying the unregistered cqf-cqlText extension. -->
<test name="IntegerIntervalAsRange" version="1.0">
<expression>Interval[1, 10]</expression>
<output>Interval[1, 10]</output>
</test>
<!-- Interval<System.Decimal> SHALL be returned as FHIR.Range. -->
<test name="DecimalIntervalAsRange" version="1.0">
<expression>Interval[1.0, 10.0]</expression>
<output>Interval[1.0, 10.0]</output>
</test>
<!-- Interval<System.Time> SHALL be returned as FHIR.Period. -->
<test name="TimeIntervalAsPeriod" version="1.0">
<expression>Interval[@T01:00:00.000, @T02:00:00.000]</expression>
<output>Interval[@T01:00:00.000, @T02:00:00.000]</output>
</test>
<!-- Already correct: returned as valuePeriod. -->
<test name="DateIntervalAsPeriod" version="1.0">
<expression>Interval[@2012-01-01, @2012-12-31]</expression>
<output>Interval[@2012-01-01, @2012-12-31]</output>
</test>
<!-- Already correct: returned as valueRange. -->
<test name="QuantityIntervalAsRange" version="1.0">
<expression>Interval[1 'mg', 10 'mg']</expression>
<output>Interval[1 'mg', 10 'mg']</output>
</test>
</group>
</tests>
Or directly, without the runner:
curl -s -X POST http://localhost:8080/fhir/'$cql' \
-H 'Content-Type: application/json' \
-d '{"resourceType":"Parameters","parameter":[{"name":"expression","valueString":"Interval[1, 10]"}]}'
Suggested fix
In FhirTypeConverter (Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/FhirTypeConverter.java),
extend interval conversion to cover the Integer, Long, Decimal and Time point types alongside the
existing Date/DateTime and Quantity handling, and attach cqf-cqlType to every CQL-valued result.
Once intervals are returned as Range/Period, cqf-cqlText should no longer be emitted for them.
#1430 notes this converter predates the current IG, which is consistent with what is observed here.
Relationship to existing issues
Environment
- CQL engine: Java CQFramework Engine 4.1.0, hosted by HAPI FHIR Server 8.10.0 (FHIR 4.0.1)
- Operation: system-level
$cql
- CQL version: 1.5
- Observed with
cql-tests-runner against the full cql-tests suite (1809 tests executed)
Intervals of Integer, Decimal and Time are returned as CQL text instead of FHIR.Range / FHIR.Period
Repository:
cqframework/clinical_quality_languageRelated: child of #1430 (Correctly support CQL to FHIR (and vice versa) type mapping).
Overlaps #1744 and clinical-reasoning #1090, both of which show this in their example
responses but are filed as wrong-value bugs. See "Relationship to existing issues" below.
Summary
Evaluating an expression that returns
Interval<System.Integer>,Interval<System.Decimal>orInterval<System.Time>via$cqlproduces avalueStringcontaining CQL source text, marked witha
cqf-cqlTextextension:{ "name": "return", "valueString": "Interval[1, 10]", "_valueString": { "extension": [ { "url": "http://hl7.org/fhir/StructureDefinition/cqf-cqlText", "valueBoolean": true } ] } }Per Conformance Requirement 4.3 — FHIR Type Mapping these should be returned as
FHIR.RangeandFHIR.Periodrespectively. Two things are wrong:cqf-cqlTypeextension is absent. The IG states that all CQL-valuedparameters and results SHALL include a
cqf-cqlTypeextension to unambiguously specify thetype, and that in the absence of a
cqf-cqlTypeextension, values are assumed to be the FHIRtype. So a conformant consumer must read the above as a genuine
FHIR.stringwhose valuehappens to be
"Interval[1, 10]".Additionally,
http://hl7.org/fhir/StructureDefinition/cqf-cqlTextis not a registeredextension — the canonical url returns HTTP 404, unlike the registered
cqf-cqlType. Nothing canbe expected to interpret it.
Expected mapping
Interval<System.Integer>FHIR.RangeInterval<System.Long>FHIR.RangeInterval<System.Decimal>FHIR.Range(withquantity-precisionextension)Interval<System.Quantity>FHIR.RangeInterval<System.Date>FHIR.PeriodInterval<System.DateTime>FHIR.PeriodInterval<System.Time>FHIR.Period(time values as dateTime with an@0001-01-01date)The engine already does this correctly for two of the seven
This is an incomplete implementation rather than a representation gap — the engine emits the
mapped FHIR type for Date and Quantity intervals, and falls back to CQL text for the rest.
Interval[1, 10]valueString+cqf-cqlTextRangeInterval[1.0, 10.0]valueString+cqf-cqlTextRangeInterval[@T01:00:00.000, @T02:00:00.000]valueString+cqf-cqlTextPeriodInterval[@2012-01-01, @2012-12-31]valuePeriodInterval[1 'mg', 10 'mg']valueRangeVerbatim, from HAPI FHIR 8.10.0 hosting the CQF engine (
cqlEngineVersion4.1.0):Note that
Range.low/Range.highareQuantity, and aQuantitymay carry a barevaluewith nounit — so an integer or decimal interval is representable without inventing anything.
Impact
Running the
cql-testssuite against this engine, 47 test results are returned this way. Everyone of them is an Interval, and all 47 currently fail:
CqlIntervalOperatorsTestCqlDateTimeOperatorsTestCqlTypesTestcqf-cqlTypewas present on 0 of 47.Of the 47, 41 fail only because of the representation — the value itself is correct, but a
consumer comparing a
Range/Periodagainst a returned string cannot match it. The remaining 6have a genuine value defect as well (the uncertainty low-bound issue in #1724, and
expand/collapsereturning only the first interval, #1744), so they would still fail after this is fixed.To reproduce
Run the following against
cql-tests-runner. The first three tests fail; the last two pass,demonstrating that the mapping is implemented for some interval point types and not others.
Or directly, without the runner:
Suggested fix
In
FhirTypeConverter(Src/java/engine-fhir/src/main/java/org/opencds/cqf/cql/engine/fhir/converter/FhirTypeConverter.java),extend interval conversion to cover the Integer, Long, Decimal and Time point types alongside the
existing Date/DateTime and Quantity handling, and attach
cqf-cqlTypeto every CQL-valued result.Once intervals are returned as
Range/Period,cqf-cqlTextshould no longer be emitted for them.#1430 notes this converter predates the current IG, which is consistent with what is observed here.
Relationship to existing issues
concrete, testable instance of it.
collapsereturns an array instead of a list) and clinical-reasoning FileBasedFhirProvider logic error when including/excluding resource during code validation #1090(
exceptreturns the wrong response) both includecqf-cqlTextresponses in their examples, butare scoped to the returned value. Fixing those as written would not necessarily change the
representation.
of interval) was the one issue squarely about this representation. It was closed 2026-04-02 as
"moved to clinical_quality_language Uncertainty interval with "months between" operation returns wrong low interval #1724", but Uncertainty interval with "months between" operation returns wrong low interval #1724 covers only the uncertainty low-bound
arithmetic. The representation half appears to have been dropped in that move — worth confirming
whether that was intended.
Environment
$cqlcql-tests-runneragainst the fullcql-testssuite (1809 tests executed)