Skip to content

Add mixed-unit Quantity tests for div and mod - #167

Open
bryantaustin13 wants to merge 2 commits into
cqframework:mainfrom
bryantaustin13:mixed-unit-quantity-div-mod
Open

bryantaustin13 wants to merge 2 commits into
cqframework:mainfrom
bryantaustin13:mixed-unit-quantity-div-mod

Conversation

@bryantaustin13

@bryantaustin13 bryantaustin13 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Every Quantity test for div and mod uses the same unit on both sides ('g' div 'g', 'cm' mod 'cm', …). So the suite can't tell whether an engine converts units at all. On the Java engine, for example, 250.0 'cm' div 1.0 'm' returns 250 'cm' and 1.0 'm' mod 30.0 'cm' returns 1 'm' (cqframework/clinical_quality_language#1860), but every existing Quantity div/mod test only checks the unit label.

The expected units follow FHIR-59659 ("Specify result unit determination for div and mod operators"), which asks for examples based on these tests:

Div should be unitless after converting arguments to common units in the same way as for division (because it is only a count: How many times does this quantity fit into that one)

Modulo should have the same units as it's operands after converting to common units in the same way as for division (because it is how many are left over of the original quantity, so it has the same units)

"In the same way as for division" refers to the FHIR-58137 rule (#161): convert to the most granular unit, which for a positive exponent is the smaller one.

Change

Seven tests in CqlArithmeticFunctionsTest.xml. They are marked version="1.5", since the Quantity overloads were introduced in 1.5, and they carry the same capabilities as their sibling Quantity tests. Each set has a comment quoting FHIR-59659 and linking the spec entry.

Truncated Divide (end of the group):

Test Expression Output
TruncatedDivide250CMBy1MQuantity 250.0 'cm' div 1.0 'm' 2.0 '1'
TruncatedDivideNeg250CMBy1MQuantity -250.0 'cm' div 1.0 'm' -2.0 '1'
TruncatedDivide10CMBy1MQuantity 10.0 'cm' div 1.0 'm' 0.0 '1'
TruncatedDivide7KGBy2GQuantity 7.0 'kg' div 2.0 'g' 3500.0 '1'

"The div operator performs truncated division of its arguments", so 250 cm ÷ 1 m = 2.5 truncates to 2, and −2.5 truncates to −2. The result is unitless ('1'), per FHIR-59659.

Modulo (end of the group):

Test Expression Output
Modulo1MBy30CMQuantity 1.0 'm' mod 30.0 'cm' 10.0 'cm'
Modulo1KGBy300GQuantity 1.0 'kg' mod 300.0 'g' 100.0 'g'
Modulo10GBy3Quantity 10.0 'g' mod 3 1.0 'g'
  • Mixed units: the operands convert to the smaller unit, and the remainder stays in it: 100 cm mod 30 cm = 10 cm.
  • An Integer divisor: 3 converts implicitly to 3 '1'. As in division (10.0 'g' / 3 is 3.33 'g'), '1' has no dimension to convert, so the remainder keeps the left operand's unit. FHIR-59659 doesn't spell this case out; Bryn confirmed the tests should be built on that ticket.

An earlier version of this PR compared the mixed-unit Modulo results with =, because the result unit wasn't specified. FHIR-59659 settles it, so they now expect exact units.

Verification

  • CqlArithmeticFunctionsTest.xml validates against tests/testSchema.xsd.
  • Run through cql-tests-runner:
Test cql-execution 4.0.0-beta.1 Java engine 5.4.0 (clinical-reasoning 4.13.0, HAPI FHIR 8.12.0)
TruncatedDivide250CMBy1MQuantity pass fail (250 'cm')
TruncatedDivideNeg250CMBy1MQuantity pass fail (-250 'cm')
TruncatedDivide10CMBy1MQuantity pass fail (10 'cm')
TruncatedDivide7KGBy2GQuantity pass fail (3 'kg')
Modulo1MBy30CMQuantity fail (null) fail (1 'm')
Modulo1KGBy300GQuantity fail (null) fail (1 'kg')
Modulo10GBy3Quantity not run; null expected (see below) pass (1 'g')

No other test changes.

🤖 Generated with Claude Code

Every Quantity test for div and mod used the same unit on both sides, so
the suite could not tell whether an engine converts units at all.

Four Truncated Divide tests divide operands of the same dimension in
different units: "The div operator performs truncated division of its
arguments", so 250.0 'cm' div 1.0 'm' is 2.5 truncated to 2.0 '1'
(the dimensionless unit, as in the existing tests since cqframework#148), and
likewise for a negative, a zero and a kg/g case.

Two Modulo tests do the same for mod: 100 cm mod 30 cm = 10 cm. The
spec does not say which unit a mixed-unit result is in (10 'cm' or
0.1 'm'), so they compare with =, which only requires the same
dimension.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FHIR-59659 specifies that mod keeps the operands' unit after converting
them to common units as division does, so the two mixed-unit Modulo
tests now expect 10.0 'cm' and 100.0 'g' instead of comparing with =.
Adds Modulo10GBy3Quantity (10.0 'g' mod 3 is 1.0 'g') and cites the
ticket's div rule on the mixed-unit Truncated Divide tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant