Skip to content

Implement Modulo for Quantity - #394

Closed
bryantaustin13 wants to merge 1 commit into
cqframework:masterfrom
bryantaustin13:quantity-modulo
Closed

bryantaustin13 wants to merge 1 commit into
cqframework:masterfrom
bryantaustin13:quantity-modulo

Conversation

@bryantaustin13

@bryantaustin13 bryantaustin13 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #386.

Problem

mod on Quantity arguments always returns null, including same-unit cases like 10.0 'g' mod 3.0 'g'. Modulo.exec has no Quantity branch, so it computes x % y on two Quantity objects. TruncatedDivide doesn't have this problem because it goes through MathUtil.divide, which calls Quantity.dividedBy.

The spec defines mod(left Quantity, right Quantity) Quantity: "The mod operator computes the remainder of the division of its arguments. … For modulo operations involving quantities, the resulting quantity will have the appropriate unit. Attempting to operate on quantities with invalid or special units will result in null. … If the result of the modulo cannot be represented, or the right argument is 0, the result is null." (Modulo)

Change

  • Quantity.modulo (src/datatypes/quantity.ts), modelled on dividedBy:
    • It brings both operands to a common unit with normalizeUnitsWhenPossible, then takes Decimal.modulo.
    • It returns null for a null or zero divisor, for units that can't be brought to a common unit, and on overflow.
    • A non-Quantity right operand is treated as unit '1', as in the other Quantity arithmetic.
  • Modulo.exec (src/elm/arithmetic.ts) calls it when the left operand is a Quantity.
  • Unit tests in test/datatypes/quantity-test.ts cover: same unit, a negative dividend (-7 'cm' mod 3 'cm' → -1 'cm'), converted units (250 'cm' mod 1 'm' → 50 'cm'), units that can't be converted, and a zero or null divisor.
  • ModuloQuantity and Modulo10By3Quantity are removed from test/spec-tests/skip-list.txt, and the spec-test data is regenerated.

For mixed units, normalizeUnitsWhenPossible converts to the smaller unit, so 1.0 'm' mod 30.0 'cm' gives 10 'cm'. The spec says only "the appropriate unit". This matches the existing add/subtract behaviour and the "most granular" rule proposed in FHIR-58137.

Verification

On master (e8096c6) and on this branch:

master this branch
npm test 5450 passing, 99 pending 5456 passing, 97 pending, 0 failing

The six extra passing tests are the four new unit tests and the two un-skipped spec tests. npm run build, test:typecheck, lint and prettier are clean.

Through cql-tests-runner against cqframework/cql-tests, exactly four tests go from fail to pass: ModuloQuantity, Modulo10By3Quantity, and the mixed-unit Modulo1MBy30CMQuantity and Modulo1KGBy300GQuantity from cqframework/cql-tests#167. That runner check was done on b1a4c3e when filing #386, before this branch moved onto e8096c6.

🤖 Generated with Claude Code

Modulo on Quantities always returned null. Add Quantity.modulo, which
normalizes units the same way the other Quantity arithmetic does and
returns null for a zero divisor, null, or units that cannot be
converted. Remove ModuloQuantity and Modulo10By3Quantity from the
spec-test skip list and regenerate the spec-test data.

Fixes cqframework#386

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.33333% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.15%. Comparing base (e8096c6) to head (418c2a8).

Files with missing lines Patch % Lines
src/datatypes/quantity.ts 69.23% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #394      +/-   ##
==========================================
- Coverage   89.19%   89.15%   -0.05%     
==========================================
  Files          60       60              
  Lines        5064     5079      +15     
  Branches     1446     1452       +6     
==========================================
+ Hits         4517     4528      +11     
- Misses        315      319       +4     
  Partials      232      232              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bryantaustin13
bryantaustin13 marked this pull request as draft October 26, 2026 20:09
@bryantaustin13
bryantaustin13 marked this pull request as ready for review October 26, 2026 20:12
@bryantaustin13

Copy link
Copy Markdown
Contributor Author

Closing this in favour of #393, which fixes the same issue (#386) and was opened first.

The two PRs are functionally the same:

Quantity.modulo works the same way in both.
The changes to CqlArithmeticFunctionsTest.cql, the regenerated .json and skip-list.txt are byte-for-byte identical.
The unit tests cover the same cases.
The one difference is that #393's Modulo.exec also routes the call to Quantity.modulo when only the right operand is a Quantity. That covers slightly more than this PR did.

Two notes from this PR that also apply to #393:

Quantity mod Integer/Decimal: the number becomes a Quantity with unit '1', so 10 'g' mod 3 returns null. The Java engine keeps the left operand's unit and would return 1 'g' (cqframework/clinical_quality_language#1860). No cql-tests test covers this case.
#383: that PR splits skip-list.txt into separate files and still lists both Modulo entries. Whichever PR merges second needs to drop them.
For what it's worth, the same change ran green on all CI checks here (Node 20/22/24, lint and prettier, examples, test-server, npm audit).

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.

Modulo is not implemented for Quantity arguments (always returns null)

2 participants