Repository navigation
Correct uncertainty-arithmetic test fixtures per CQL 1.5.2 §5.5.2 - #176
Open
zacharywhitley wants to merge 1 commit into
Open
zacharywhitley wants to merge 1 commit into
zacharywhitley wants to merge 1 commit into
Conversation
Five test-fixture <output> values currently encode a 'widest-§15' interpretation inconsistent with the strict-less-than rule in CQL 1.5.2 §5.5.2 'Determining Difference and Duration'. Affected cases: - CqlDateTimeOperatorsTest::DateTimeDurationBetweenUncertainAdd - CqlDateTimeOperatorsTest::DateTimeDurationBetweenUncertainInterval2 - CqlDateTimeOperatorsTest::DateTimeDurationBetweenUncertainMultiply - CqlDateTimeOperatorsTest::DateTimeDurationBetweenUncertainSubtract - CqlTypesTest::DateTime::DateTimeUncertain See PR description for CQL 1.5.2 §5.5.2 worked-example derivation, §5.5.3 Subtract-case reading disclosure, and before/after differential evidence. Corroboration: CQF first-party Kotlin reference engine, CQF engine-fhir SKIP list, fixture-author TODOs in each affected block.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Correct five uncertain-arithmetic fixture oracles per CQL §5.5.2
Summary
Five test fixtures in
tests/cql/CqlDateTimeOperatorsTest.xmlandtests/cql/CqlTypesTest.xmlencode<output>values that diverge fromCQL 1.5.2 §5.5.2 "Determining Difference and Duration". All five test
blocks carry an author-supplied TODO comment acknowledging normative
uncertainty about the oracle. CQFramework's own Kotlin first-party
unit-test oracles (in
engine/src/jvmTest/kotlin/...ofcqframework/clinical_quality_language) assert different, spec-correctvalues for semantically equivalent expressions, and CQFramework's
engine-fhirtest-runner SKIP list formally records that four of thefive fixture oracles are not treated as authoritative.
This PR aligns the published fixture
<output>values with CQL 1.5.2§5.5.2 uncertainty semantics and with CQFramework's own engine first-
party oracles. No test expressions are changed; only the expected
<output>values are corrected.Normative framework — CQL 1.5.2 §5.5.2
Specification reference: CQL 1.5.2 §5.5.2 "Determining Difference
and Duration" (
https://cql.hl7.org/05-languagesemantics.html#determining-difference-and-duration).Verbatim passages (unchanged between CQL 1.5.2 and CQL 1.5.3 Errata 2):
Worked example from the same section:
Two normative rules follow directly from this text.
Rule R1 — uncertainty predicate. An operand is "uncertain at the
requested precision" iff its stored precision is coarser than the
requested precision. The spec phrase "finer precision than what is
specified in the input values" defines a strict less-than relation:
stored_precision < requested_precision. Same-precision operands(
stored == requested) are not uncertain; the worked example makes thisexplicit when it says "its maximum and minimum are the same date".
Rule R2 — uncertainty bounds. The duration-uncertainty interval's
lower bound uses the first comparand's maximum against the second's
minimum; the upper bound uses the first's minimum against the second's
maximum. A non-uncertain operand's maximum and minimum are the same
point.
The five fixture oracles corrected below all derive from a "widest"
interpretation that additionally expands DAY- or MONTH-precision
operands at their own precision to a sub-precision millisecond range.
This contradicts Rule R1 (strict less-than) and the §5.5.2 worked
example (DAY-LHS treated as a fixed point).
A separate, orthogonal spec question appears for the Subtract case
alone, discussed in that case's section below (§Changes/
DateTimeDurationBetweenUncertainSubtract): §5.5.3 "UncertaintyPropagation" is silent on whether
Interval<Integer> - Interval<Integer>uses endpoint-pairwise or full-interval arithmetic. We adopt
CQFramework's reference-engine choice (endpoint-pairwise) for the
proposed corrected oracle and note the standards-clarification
candidate for future HL7 action.
Changes
tests/cql/CqlTypesTest.xml—DateTimeUncertain(lines 125-133)Expression (unchanged):
Correction:
Derivation from §5.5.2 primitives:
DateTime(2015, 2, 10)— stored precision = DAY. Requestedprecision = DAY. By Rule R1,
DAY < DAYis false; LHS is notuncertain; fixed point
2015-02-10.DateTime(2015, 3)— stored precision = MONTH. Requestedprecision = DAY. By Rule R1,
MONTH < DAYis true; RHS uncertainover its month-boundary window
[2015-03-01, 2015-03-31](March 2015has 31 days).
low = whole_days(max(LHS), min(RHS)) = whole_days(2015-02-10, 2015-03-01) = 19.high = whole_days(min(LHS), max(RHS)) = whole_days(2015-02-10, 2015-03-31) = 49.Interval[19, 49].CQFramework first-party Kotlin oracle (confirming):
cqframework/clinical_quality_language→engine/src/jvmTest/kotlin/org/opencds/cqf/cql/engine/execution/CqlTypesTest.kt:163-165asserts
The fixture's current
[18, 49]requires treating the DAY-precisionLHS as a sub-day millisecond range, which contradicts Rule R1 and the
§5.5.2 worked example's "its maximum and minimum are the same date".
tests/cql/CqlDateTimeOperatorsTest.xml—DateTimeDurationBetweenUncertainInterval2(lines 876-885)Expression (unchanged):
Correction:
Derivation from §5.5.2 primitives:
DateTime(2005)— stored YEAR; requested MONTH. By R1,YEAR < MONTHis true; LHS uncertain over[2005-01-01, 2005-12-01].DateTime(2006, 5)— stored MONTH; requested MONTH. By R1,MONTH < MONTHis false; RHS not uncertain; fixed point2006-05-01.low = whole_months(2005-12-01, 2006-05-01) = 5.high = whole_months(2005-01-01, 2006-05-01) = 16.Interval[5, 16].CQFramework Kotlin oracle (confirming, semantically equivalent
expression):
cqframework/clinical_quality_language→engine/src/jvmTest/kotlin/.../CqlDateTimeOperatorsTest.kt:45-48asserts
.start = 5,.end = 16onDateTimeDurationBetweenUncertainInterval2.tests/cql/CqlDateTimeOperatorsTest.xml—DateTimeDurationBetweenUncertainAdd(lines 886-896)Expression (unchanged):
Correction:
Derivation:
days between DateTime(2014, 1, 15) and DateTime(2014, 2)=Interval[17, 44]— this is the explicit §5.5.2 worked example (usingFeb 1 and Feb 28 as the RHS's uncertainty window).
Interval[17, 44] + Interval[17, 44]. For non-null, non-negative inputs Add is commutative, so endpoint-pairwise
[a+c, b+d]and full-interval arithmetic
[low, high]both give[17+17, 44+44] = [34, 88]. No §5.5.3 ambiguity on this input.Interval[34, 88].CQFramework Kotlin oracle (confirming, semantically equivalent
expression):
cqframework/clinical_quality_language→engine/src/jvmTest/kotlin/.../CqlDateTimeOperatorsTest.kt:52-55asserts
.start = 34,.end = 88onDateTimeDurationBetweenUncertainAdd.tests/cql/CqlDateTimeOperatorsTest.xml—DateTimeDurationBetweenUncertainSubtract(lines 897-907)Expression (unchanged):
Correction (adopting CQFramework's endpoint-pairwise §5.5.3 reading as
the de-facto reference-engine behavior; see note below):
Derivation:
Interval[17, 44](§5.5.2 worked example).Interval[5, 16](derivation perDateTimeDurationBetweenUncertainInterval2above).Interval[17, 44] - Interval[5, 16]:[start-start, end-end]:[17-5, 44-16] = [12, 28]. Source:cqframework/clinical_quality_language→engine/src/commonMain/kotlin/.../elm/executing/SubtractEvaluator.kt:140-148:[low-high, high-low]:[17-16, 44-5] = [1, 39].Normative note on §5.5.3 ambiguity: CQL 1.5.2 §5.5.3 "Uncertainty
Propagation" is silent on which specific interval-arithmetic rule
applies to
Interval<Integer> - Interval<Integer>. CQFramework'sreference engine applies endpoint-pairwise; classical numerical-
analysis convention applies full-interval. The two rules coincide on
Add (commutative) and Multiply (monotonic on non-negative inputs) but
differ on Subtract. This PR adopts CQFramework's endpoint-pairwise
reading because it is the only existing first-party reference-engine
behavior for this operator shape. If HL7 subsequently clarifies §5.5.3
in favor of full-interval arithmetic, the corrected value here would
become
Interval[1, 39]; we recommend this fixture value be revisitedif the §5.5.3 ambiguity is resolved.
Crucially, the fixture's current
Interval[0, 40]matches neither§5.5.3 reading applied to spec-correct §5.5.2 inner intervals. It is
only consistent with endpoint-pairwise Subtract applied to the "widest-
§15" (R1-violating) inner intervals
[16, 44] - [4, 16] = [16-16, 44-4] = [0, 40], which fails R1 at both inner operators. The§5.5.3 underdetermination does not rehabilitate the fixture value.
CQFramework Kotlin oracle (under endpoint-pairwise reading,
confirming):
cqframework/clinical_quality_language→engine/src/jvmTest/kotlin/.../CqlDateTimeOperatorsTest.kt:57-60asserts
.start = 12,.end = 28onDateTimeDurationBetweenUncertainSubtract.tests/cql/CqlDateTimeOperatorsTest.xml—DateTimeDurationBetweenUncertainMultiply(lines 908-918)Expression (unchanged):
Correction:
Derivation:
Interval[17, 44](§5.5.2 worked example).Interval[17, 44] * Interval[17, 44]. For non-negative,non-null intervals Multiply is monotonic on both endpoints:
endpoint-pairwise
[a*c, b*d]and full-interval[min(a*c, a*d, b*c, b*d), max(…)]give the same result[17*17, 44*44] = [289, 1936]. No §5.5.3 ambiguity on this input.Interval[289, 1936].CQFramework Kotlin oracle (confirming, semantically equivalent
expression):
cqframework/clinical_quality_language→engine/src/jvmTest/kotlin/.../CqlDateTimeOperatorsTest.kt:62-65asserts
.start = 289,.end = 1936onDateTimeDurationBetweenUncertainMultiply.Cross-engine cross-check
DateTimeUncertain[18, 49][19, 49][19, 49][19, 49]DateTimeDurationBetweenUncertainInterval2[4, 16][5, 16][5, 16][5, 16]DateTimeDurationBetweenUncertainAdd[32, 88][34, 88][34, 88][34, 88]DateTimeDurationBetweenUncertainSubtract[0, 40][12, 28](CQF §5.5.3 reading) /[1, 39](full-interval §5.5.3 reading)[12, 28][12, 28]DateTimeDurationBetweenUncertainMultiply[256, 1936][289, 1936][289, 1936][289, 1936]All five proposed values match the §5.5.2-derived spec-compelled value.
Further corroboration — CQFramework
engine-fhirSKIP listCQFramework's
engine-fhirtest runner explicitly records that thefixture oracles for four of the five cases above are not treated as
authoritative. Source:
cqframework/clinical_quality_language→engine-fhir/src/jvmTest/kotlin/org/hl7/fhirpath/CQLOperationsR4Test.kt:72-99:DateTimeUncertainis not on this SKIP list only because it lives inCqlTypesTest.xml(notCqlDateTimeOperatorsTest.xml); the identicalfixture-author TODO comment (next section) is a fourth-source author
acknowledgement equivalent to a SKIP-list entry.
Fixture-author's own acknowledgement — identical TODO on all five
Each of the five test blocks carries this verbatim author comment:
This PR optionally allows upstream maintainers to either (a) clear
these TODO comments once the corrected oracles land, or (b) rewrite
them to point to the §5.5.2 derivation above. We do not prescribe
either choice; this PR leaves the comments in place.
CQFramework internal source —
isUncertainstrict-less-thanThe
isUncertainpredicate in the CQFramework runtime implementsexactly the strict-less-than rule derived from §5.5.2 above. Source:
cqframework/clinical_quality_language→engine/src/commonMain/kotlin/org/opencds/cqf/cql/engine/runtime/DateTime.kt:197-205:This confirms that CQFramework's reference engine's own author
implements R1 as strict less-than, and that the engine's observable
behavior on the five cases follows from this implementation.
Downstream impact
engine-fhirCQLOperationsR4TestSKIP list(quoted above) currently excludes four of the five cases. Once these
fixture corrections merge, the SKIP-list entries can be removed in a
follow-up PR against
cqframework/clinical_quality_language—the FHIRPath runner's
Equivalent()comparison will then produce anon-null result matching the engine's
Intervaloutput.(CQFramework itself; Cobalt / cql-wasm) will see these fixtures flip
from FAIL to PASS without any implementation change.
match the current fixture values will need to re-examine their
isUncertainsemantics against §5.5.2. The direction of thecorrection aligns the published fixtures with the specification text
and with the first-party reference engine.
Additional recommendation (non-blocking)
The CQL §5.5.3 "Uncertainty Propagation" ambiguity on interval-
arithmetic semantics (noted above under the Subtract case) warrants a
future HL7 specification clarification: should
Interval<Integer> OP Interval<Integer>use endpoint-pairwise[start OP start, end OP end]or full-interval arithmetic? CQFramework's reference engine uses
endpoint-pairwise; numerical-analysis convention uses full-interval.
The two coincide on Add and Multiply but differ on Subtract. This PR
does not require the clarification to land first; it adopts CQF's
endpoint-pairwise reading for the Subtract case to maintain consistency
with the reference engine.
If HL7 subsequently clarifies §5.5.3 in favor of full-interval
arithmetic, the corrected
DateTimeDurationBetweenUncertainSubtractoracle in this PR would move from
Interval[12, 28]toInterval[1, 39]. Either way, the currentInterval[0, 40]isincorrect under any reading of §5.5.3 applied to §5.5.2-correct inner
intervals.
Testing
No test code changes are required for this PR; only
<output>valuesin the XML fixtures are modified. Implementations that previously
asserted against the old fixture values will need to re-run the
affected tests after this PR merges. CQFramework's own Kotlin unit
tests at
engine/src/jvmTest/kotlin/.../CqlDateTimeOperatorsTest.ktand
.../CqlTypesTest.ktare unaffected; their expected values matchthe proposed corrected fixture oracles.
Related upstream work (non-blocking)
A follow-up PR against
cqframework/clinical_quality_languagewouldremove the four SKIP-list entries at
engine-fhir/src/jvmTest/kotlin/org/hl7/fhirpath/CQLOperationsR4Test.kt:79-83once this fixture PR merges.