Support numeric interval results mapped as Range per FHIR-56226 - #110
Open
alexzautke wants to merge 7 commits into
Open
Support numeric interval results mapped as Range per FHIR-56226#110alexzautke wants to merge 7 commits into
alexzautke wants to merge 7 commits into
Conversation
Plan for adjusting Interval<Integer|Decimal|Long> extraction and comparison to the proposed Range + quantity-precision mapping (FHIR-56226, spun out of issue #85). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUJ7RDTBnqwBwFFwGwrR1b
…apping Groundwork for FHIR-56226: a Symbol-keyed, non-enumerable metadata slot for interval precision/point type shared between the extractor and the comparison logic, plus README notes on the Range + quantity-precision mapping. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUJ7RDTBnqwBwFFwGwrR1b
…-56226) Extracts Interval<Integer|Long|Decimal> results sent as FHIR Range with unity-coded Quantity boundaries into plain numeric interval objects, recording the quantity-precision extension (from either the Quantity or its primitive value element) and the cqf-cqlType point type as interval metadata for the comparison step. Registered ahead of the quantity interval extractor so unity ranges are claimed first; real quantity ranges are unaffected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUJ7RDTBnqwBwFFwGwrR1b
resultsEqual now detects interval-shaped operands and compares them via intervalsEqual, which normalizes open boundaries to their closed equivalent one step inwards before comparing values (FHIR-56226 / issue #85). The step comes from the actual interval's declared quantity-precision metadata when present, then the declared point type (1 for Integer/Long, 10^-8 for Decimal), then an integrality heuristic for untyped integer intervals, defaulting to the CQL decimal step. Quantity boundaries never use the integer heuristic (Quantity values are Decimals), BigInt Long boundaries compare safely, and date/time intervals keep the pre-existing structural comparison. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUJ7RDTBnqwBwFFwGwrR1b
Detection is now strictly declaration-driven: a valueRange is extracted as a numeric interval only when the parameter carries a cqf-cqlType extension naming Interval<Integer|Long|Decimal>. The unity-quantity fallback (code '1', UCUM, no unit) was removed after review: FHIR-56226 defines only the forward mapping, not an inverse detection rule, and unity coding is ambiguous with a dimensionless Interval<Quantity>. Unity-coded ranges without the extension fall through to the quantity interval extractor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUJ7RDTBnqwBwFFwGwrR1b
…al-checks-plan-yw4gjv # Conflicts: # test/results-utils.test.ts
There was a problem hiding this comment.
Pull request overview
Adds support in the runner for the proposed FHIR-56226 representation of numeric CQL intervals (Integer/Long/Decimal) as FHIR Range, including precision metadata, and updates result comparison so open/closed boundary forms can match (fixing the numeric open-vs-closed mismatch reported in #85).
Changes:
- Introduces
NumericIntervalExtractorto extract declared numericvalueRangeresults into plain{lowClosed, low, highClosed, high}intervals and attach non-enumerable metadata (point type + precision). - Updates
resultsEqualto detect interval-shaped operands and delegate to interval-aware comparison that normalizes open boundaries inward by an appropriate step. - Adds comprehensive unit tests and documents the mapping/detection/normalization behavior.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/results-utils.test.ts | Adds interval comparison unit tests (FHIR-56226 and #85 scenarios, including metadata-based precision handling). |
| test/extractResults-library_evaluate_operations.test.ts | Adds $evaluate extraction fixtures for numeric Range intervals and metadata assertions. |
| test/extractResults-cql_operations.test.ts | Adds $cql extraction fixtures for numeric Range intervals and metadata assertions. |
| src/shared/results-utils.ts | Implements interval-aware equality with boundary normalization and precision/type-based step selection. |
| src/shared/interval-utils.ts | Adds shared Symbol-keyed interval metadata utilities and interval-shape detection. |
| src/server/extractor-builder.ts | Registers NumericIntervalExtractor ahead of QuantityIntervalExtractor. |
| src/extractors/value-type-extractors/numeric-interval-extractor.ts | New extractor for strictly-declared numeric interval Range results, including precision extraction. |
| README.md | Documents FHIR-56226 numeric interval mapping and the strict detection rule via cqf-cqlType. |
| docs/plan-fhir-56226-numeric-interval-checks.md | Design/decision record for numeric interval extraction and comparison. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… precisions A Range boundary whose quantity value is missing or not numeric is now treated as absent instead of producing a closed boundary with an unusable value. Step selection ignores negative or fractional quantity-precision values, and an Integer/Long point type always steps by one regardless of any declared precision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WUJ7RDTBnqwBwFFwGwrR1b
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.
Summary
Implements runner support for the proposed FHIR-56226 mapping of
Interval<Integer>,Interval<Long>, andInterval<Decimal>results, and fixes the open-vs-closed boundary comparison mismatch for numeric intervals reported in #85.Under FHIR-56226, a numeric interval is returned as a FHIR
Rangewhose boundaries are unity quantities (code: "1",system: http://unitsofmeasure.org), each optionally carrying aquantity-precisionextension. SinceRangecannot express open boundaries, an open boundary is sent as its closed equivalent at the stated precision — e.g.Interval[1.0, 1.4)arrives as[1.0, 1.3]with precision 1. The runner previously extracted such ranges as quantity intervals and compared boundary flags literally, so these results could never match the expected values.Changes
Extraction
NumericIntervalExtractor(registered ahead ofQuantityIntervalExtractor) extracts a declared numericvalueRangeinto the same plain-number{lowClosed, low, highClosed, high}shape the CVL parser produces for expected values.cqf-cqlTypeextension namingInterval<Integer|Long|Decimal>(optionalSystem.prefix). A unity-coded fallback without the extension was implemented and then removed in review — FHIR-56226 only defines the forward mapping, not an inverse detection rule, and unity coding alone is ambiguous with a dimensionlessInterval<Quantity>. Ranges without the extension keep their existing quantity-interval behavior.quantity-precisionextension is read from both plausible placements (low.extensionas shown in the Jira example, andlow._value.extensionper FHIR primitive-extension rules) and recorded — together with the point type — as non-enumerableSymbol-keyed metadata (src/shared/interval-utils.ts), so it rides along without disturbing structural comparison or JSON output. String-encoded decimal values (value: "1.40") are supported, with digits-after-the-dot as an implicit precision fallback.Comparison
resultsEqualnow detects interval-shaped operands and delegates tointervalsEqual, which normalizes open boundaries on both sides to their closed equivalent one step inwards before comparing values, so open and closed forms of the same interval are equal. This also fixes thepart-based interval representation from Test Runner Not Detecting Equal Intervals (Open vs Closed Boundaries) #85 ([1.0, 4.0)vs[1.0, 3.99999999]).quantity-precision(10^-p) → declared point type (1for Integer/Long,10^-8for Decimal) → an integrality heuristic for untyped integer intervals → the CQL decimal step (10^-8). Quantity boundaries never use the integer heuristic (Quantity values are Decimals). The comparison is deliberately not over-lenient: an engine that truncates a predecessor at an undeclared precision still fails.Number.MAX_SAFE_INTEGER); Date/DateTime/Time intervals keep the pre-existing structural semantics byte-for-byte (Test Runner Not Detecting Equal Lists of Interval<DateTime>/Period #80/Test Runner Not Detecting Equal DateTimes (Offset vs No Offset) #84 remain out of scope).Docs
docs/plan-fhir-56226-numeric-interval-checks.mdrecords the full design, the detection decision, and open spec questions (extension URL typo in the ticket, extension placement, string-encoded values).Testing
npx vitest run), including ~30 new tests: extractor fixtures for both$cqland$evaluateshapes, the FHIR-56226 ticket example, the Test Runner Not Detecting Equal Intervals (Open vs Closed Boundaries) #85 cases (DecimalIntervalExcept1to3,IntegerIntervalExcept1to3shapes), strictness regressions, and negative precision-truncation cases.tsc --noEmitclean.conf/cql-execution-local.jsonciting Test Runner Not Detecting Equal Intervals (Open vs Closed Boundaries) #85 are intentionally left in place; removing them should follow a verified run against a live engine.Notes for reviewers
cqf-cqlTypewill have numeric-interval results extracted as quantity intervals and fail those tests. It may be worth feeding back on FHIR-56226 that the mapping should require the cqlType extension to be invertible.🤖 Generated with Claude Code
https://claude.ai/code/session_01WUJ7RDTBnqwBwFFwGwrR1b