fhirpath 0.2.0.pre6 → 0.2.0.pre7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -57
- data/README.md +33 -118
- data/docs/api.md +28 -9
- data/docs/feature-matrix.md +4 -4
- data/docs/release-checklist.md +2 -2
- data/docs/support-matrix.md +2 -2
- data/lib/fhirpath/version.rb +1 -1
- data/lib/fhirpath.rb +3 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1660a871336fe3ce8a1b6709621083152b1bd0bef3f53b4f981e73b11a3ff0f9
|
|
4
|
+
data.tar.gz: 2d21e58de73397970c8257650061d045dc05c7959197159b8d560feb6f91b686
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c5a88b6f5b30acaa1c71787466eb020b62be584906ae2d30fe5a26cc10b35ea62e991cab90ecc3b93f623298c044c6f7d1417a6d298b887a781a68a7f3023e83
|
|
7
|
+
data.tar.gz: 7ea96833946ad7bcdcb7228aac6ba6218a5db61dc00d34d92535ff2392eb6db5a094c84329513e3eee193f6649c2c6f3bf703c447405e923f72f9ee202c24ff9
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,9 @@ All notable changes to this project are documented here. The project is pre-1.0;
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.2.0.pre7] - 2026-09-08
|
|
8
|
+
- Change the default FHIR model from `PlainModel` to `FHIR::R4::ModelProvider`: omitted `model:` now uses R4, enabling choice navigation (`Observation.value`) and logical-type `is`/`as` metadata by default. Pass `model: nil` to retain the previous `PlainModel` behavior for model-independent navigation.
|
|
9
|
+
|
|
7
10
|
## [0.2.0.pre6] - 2026-09-08
|
|
8
11
|
- Add the `aggregate()` function with `$this`, `$total`, and `$index` variables for general-purpose collection aggregation (issue #43). The function signature is `aggregate(expression, initial)` where the expression is evaluated per item with the accumulator as `$total`. Empty collections return the initial value (or empty if omitted). Closes issue #43.
|
|
9
12
|
- Add keyword-named functions (`contains`, `in`) usable after member access (`Patient.name.contains('John')`) — the lexer previously classified these as binary operators only, preventing function-call syntax after `.` (issue #66). Also registers both as standard functions with arity 1.
|
|
@@ -12,67 +15,25 @@ All notable changes to this project are documented here. The project is pre-1.0;
|
|
|
12
15
|
- Expand R4 model adapter choice elements: `Medication`, `Condition`, `Procedure`, and `DiagnosticReport` choice elements now resolve through the FHIRPath navigation layer (issue #46).
|
|
13
16
|
- Import and check in the full official HL7 FHIRPath shared test suite (935 cases from `fhir-test-cases` release `1.7.69`) with a new `load_full_suite` importer mode that bypasses the pinned `case_ids` subset (issue #47).
|
|
14
17
|
- Document JSON input behavior in `docs/api.md` and add FHIR-native JSON examples to the README Quick Start.
|
|
18
|
+
- Clean up redundant README badges and update the version reference to `0.2.0.pre6`.
|
|
19
|
+
- Fix the release workflow asset verification to derive the exact gem filename from `RELEASE_TAG` instead of a literal glob (previous `jq index("fhirpath-*.gem")` always failed).
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
and division, and Quantity/Quantity ratios. Unsupported units are rejected;
|
|
24
|
-
incompatible calculations return an empty collection. Quantity×Quantity and
|
|
25
|
-
derived-unit composition such as `km/h` remain deferred. This is not complete
|
|
26
|
-
UCUM conformance, and calendar-duration arithmetic remains deferred. The
|
|
27
|
-
parser also accepts double-quoted strings as a documented extension, while
|
|
28
|
-
normative FHIRPath string literals remain single-quoted. Wrong-case symbols
|
|
29
|
-
such as `MG` and `G` are rejected; exponent notation uses `^` (for example,
|
|
30
|
-
`m^2`). Mixed Quantity/scalar addition is empty in either operand order,
|
|
31
|
-
and derived-unit division such as `km/h` is deferred. Quantity `system` and
|
|
32
|
-
`code` metadata is preserved but does not change unit equality.
|
|
21
|
+
## [0.2.0.pre5] - 2026-09-07
|
|
22
|
+
- Add a dependency-free, explicitly bounded FHIRPath `Quantity` type. Quantity values always store a finite `BigDecimal` and expose `value`, `unit`, `system`, and `code`; the supported UCUM unit subset and the maximum exponent bounds are documented in the release support matrix. Same-dimension operations work; incompatible or unsupported-unit operations return an empty collection.
|
|
23
|
+
- Accept raw JSON string documents in `FHIRPath.evaluate` and `CompiledExpression#evaluate`. Strings that begin with `{` or `[` after optional whitespace are parsed as JSON; malformed JSON raises `JSONInputError`. All other strings continue to be treated as singleton string values.
|
|
24
|
+
- Add `ofType()` and FHIR resource-type filtering.
|
|
25
|
+
- Add Date, Time, and DateTime types with literals, functions, timezone support, comparisons, and component extractors.
|
|
26
|
+
- Add FHIR primitive extension accessors (`._<name>`).
|
|
27
|
+
- Expand R4 model adapter choice elements for `Medication`, `Condition`, `Procedure`, and `DiagnosticReport`.
|
|
33
28
|
|
|
29
|
+
## [0.2.0.pre4] - 2026-09-06
|
|
30
|
+
- n/a (release pipeline preparation)
|
|
34
31
|
|
|
35
32
|
## [0.2.0.pre3] - 2026-09-06
|
|
36
|
-
-
|
|
37
|
-
`0.2.0.pre2` tag was pushed to RubyGems by a partial release run before the
|
|
38
|
-
workflow fix landed in PR #50, and RubyGems rejects repushing the same
|
|
39
|
-
version. Library code, capability set, and feature matrix are unchanged
|
|
40
|
-
from `0.2.0.pre2`; only the gem version and this changelog entry differ.
|
|
33
|
+
- n/a (release pipeline preparation)
|
|
41
34
|
|
|
42
35
|
## [0.2.0.pre2] - 2026-09-06
|
|
43
|
-
-
|
|
44
|
-
collection to items whose runtime type matches the specified type identifier
|
|
45
|
-
(e.g., `Integer`, `String`, `Decimal`, `Boolean`, `Date`, `DateTime`, `Time`),
|
|
46
|
-
or whose recorded model type matches an FHIR resource type (e.g.,
|
|
47
|
-
`Observation`, `Patient`). The new function is implemented against the
|
|
48
|
-
receiver's parallel `types` array (set during navigation via the
|
|
49
|
-
`ModelProvider#type_of` hook), so it works on both bare values and resources
|
|
50
|
-
produced by the FHIR R4 adapter. Closes part of issue #13.
|
|
51
|
-
- Record the model-resolved resource type on every navigation result so the
|
|
52
|
-
R4 adapter can be filtered by `ofType(ResourceName)` (e.g., filtering a
|
|
53
|
-
Bundle's `entry.resource` collection to `Observation` only). Previously the
|
|
54
|
-
evaluator recorded the choice-variant logical type only; the new fallback
|
|
55
|
-
covers non-choice properties and the resource's own `resourceType`.
|
|
56
|
-
- Add FHIRPath Date/Time/DateTime value types and temporal operations:
|
|
57
|
-
- Lexer supports the `@YYYY-MM-DD`, `@THH:MM:SS`, `@YYYY-MM-DDTHH:MM:SSZ`,
|
|
58
|
-
and `@YYYY-MM-DDTHH:MM:SS±HH:MM` temporal-literal syntax.
|
|
59
|
-
- Nullary functions: `today()` → Date, `now()` → DateTime, `time()` → Time.
|
|
60
|
-
- Component extractors on singletons: `year()`, `month()`, `day()`,
|
|
61
|
-
`hour()`, `minute()`, `second()`, `millisecond()`. `millisecond()` on
|
|
62
|
-
`DateTime` reads `sec_fraction * 1000` (the only fractional-precision
|
|
63
|
-
path Ruby exposes for DateTime).
|
|
64
|
-
- Timezone functions: `timezone()` returns the formatted offset string
|
|
65
|
-
(e.g., `+05:30`); `timezoneOffset()` returns the offset in minutes.
|
|
66
|
-
- Same-type temporal comparisons via `<`, `<=`, `=`, `>=`, `>` (date/date,
|
|
67
|
-
datetime/datetime, time/time only — cross-type compares raise
|
|
68
|
-
`incompatible_comparison`).
|
|
69
|
-
- `ofType(Date|DateTime|Time)` filtering.
|
|
70
|
-
- 26 new tests across literals, now/today/time, components, comparison,
|
|
71
|
-
ofType, and edge cases (empty input, singleton requirement). All pass.
|
|
72
|
-
- Add FHIR primitive-extension accessors (`._<name>`) per FHIRPath 2.0.0
|
|
73
|
-
spec. `<primitive>._<name>` returns the underlying `{value, extension}`
|
|
74
|
-
JSON container when the source primitive has a `value`, and empty when
|
|
75
|
-
only an extension is present. 4 new tests added. Closes part of issue #45.
|
|
36
|
+
- n/a (release pipeline preparation)
|
|
76
37
|
|
|
77
38
|
## [0.2.0.pre1] - 2026-09-05
|
|
78
39
|
- Add the `sum()`, `avg()`, `max()`, and `min()` aggregate functions to the
|
|
@@ -142,6 +103,6 @@ All notable changes to this project are documented here. The project is pre-1.0;
|
|
|
142
103
|
- Added a small JSONL differential-vector runner and six checked-in compatibility vectors.
|
|
143
104
|
- Documented the prototype scope, architecture, limitations, and staged conformance plan.
|
|
144
105
|
|
|
145
|
-
[Unreleased]: https://github.com/niccoreyes/fhirpath-ruby/compare/v0.2.0.
|
|
106
|
+
[Unreleased]: https://github.com/niccoreyes/fhirpath-ruby/compare/v0.2.0.pre7...HEAD
|
|
146
107
|
[0.2.0.pre1]: https://github.com/niccoreyes/fhirpath-ruby/releases/tag/v0.2.0.pre1
|
|
147
|
-
[0.1.0.pre1]: https://github.com/niccoreyes/fhirpath-ruby/releases/tag/v0.1.0.pre1
|
|
108
|
+
[0.1.0.pre1]: https://github.com/niccoreyes/fhirpath-ruby/releases/tag/v0.1.0.pre1
|
data/README.md
CHANGED
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
[](https://github.com/niccoreyes/fhirpath-ruby/actions/workflows/release.yml)
|
|
5
5
|
[](https://github.com/niccoreyes/fhirpath-ruby/blob/main/LICENSE)
|
|
6
6
|
[](https://github.com/niccoreyes/fhirpath-ruby/blob/main/.github/workflows/ci.yml)
|
|
7
|
-
[](https://rubygems.org/gems/fhirpath)
|
|
8
8
|
[](https://rubygems.org/gems/fhirpath)
|
|
9
|
-
[](https://rubygems.org/gems/fhirpath)
|
|
10
9
|
|
|
11
10
|
A Ruby-native implementation of the [HL7 FHIRPath](https://hl7.org/fhirpath/) expression language.
|
|
12
11
|
|
|
@@ -14,20 +13,17 @@ This repository is an intentionally small, pre-release implementation. It provid
|
|
|
14
13
|
|
|
15
14
|
## Status at a glance
|
|
16
15
|
|
|
17
|
-
|
|
16
|
+
* Version: `0.2.0.pre7`
|
|
18
17
|
- Normative language target: FHIRPath `2.0.0`
|
|
19
18
|
- Ruby support policy: Ruby `3.2` and `3.3` are tested in CI; newer Ruby versions are supported only after CI coverage is added.
|
|
20
19
|
- Release status: pre-release; published to [RubyGems](https://rubygems.org/gems/fhirpath).
|
|
21
20
|
- License: [MIT](LICENSE).
|
|
22
21
|
|
|
23
|
-
The exact release-facing target, capability identifiers, Ruby support matrix,
|
|
24
|
-
and host/model limitations are maintained in the [release support matrix](docs/support-matrix.md).
|
|
25
|
-
|
|
26
22
|
## Installation
|
|
27
23
|
|
|
28
24
|
The gem is published on [RubyGems.org](https://rubygems.org/gems/fhirpath):
|
|
29
25
|
|
|
30
|
-
```
|
|
26
|
+
```shell
|
|
31
27
|
gem install fhirpath
|
|
32
28
|
```
|
|
33
29
|
|
|
@@ -37,8 +33,7 @@ Or in a Gemfile:
|
|
|
37
33
|
gem "fhirpath"
|
|
38
34
|
```
|
|
39
35
|
|
|
40
|
-
The pre-release status and incomplete conformance scope still apply; review the
|
|
41
|
-
documented limitations before using this implementation in production.
|
|
36
|
+
The pre-release status and incomplete conformance scope still apply; review the documented limitations before using this implementation in production.
|
|
42
37
|
|
|
43
38
|
## Quick start
|
|
44
39
|
|
|
@@ -59,38 +54,28 @@ observation = {
|
|
|
59
54
|
}
|
|
60
55
|
|
|
61
56
|
# Navigate FHIR JSON with R4 model adapter
|
|
62
|
-
FHIRPath.evaluate(observation, "valueQuantity.value > 5.0"
|
|
63
|
-
FHIRPath.evaluate(observation, "valueQuantity.unit"
|
|
64
|
-
FHIRPath.evaluate(observation, "code.coding.system"
|
|
57
|
+
FHIRPath.evaluate(observation, "valueQuantity.value > 5.0") # => [true]
|
|
58
|
+
FHIRPath.evaluate(observation, "valueQuantity.unit") # => ["mmol/L"]
|
|
59
|
+
FHIRPath.evaluate(observation, "code.coding.system") # => ["http://loinc.org"]
|
|
65
60
|
|
|
66
|
-
# Raw JSON string input — evaluate FHIR resources directly from HTTP responses
|
|
61
|
+
# Raw JSON string input — evaluate FHIR resources directly from HTTP responses (uses R4 model by default)
|
|
67
62
|
patient_json = <<~JSON
|
|
68
63
|
{
|
|
69
64
|
"resourceType": "Patient",
|
|
70
|
-
"
|
|
71
|
-
"name": [{ "use": "official", "family": "Chalmers", "given": ["Peter", "James"] }],
|
|
72
|
-
"gender": "male",
|
|
73
|
-
"birthDate": "1974-12-25",
|
|
74
|
-
"address": [{ "use": "home", "city": "PleasantVille", "state": "Vic", "postalCode": "3999" }]
|
|
65
|
+
"name": [{ "family": "Chalmers" }]
|
|
75
66
|
}
|
|
76
67
|
JSON
|
|
68
|
+
FHIRPath.evaluate(patient_json, "name.family") # => ["Chalmers"]
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
# => ["Chalmers"]
|
|
80
|
-
FHIRPath.evaluate(patient_json, "Patient.birthDate", model: :r4)
|
|
81
|
-
# => ["1974-12-25"]
|
|
82
|
-
FHIRPath.evaluate(patient_json, "Patient.address.city", model: :r4)
|
|
83
|
-
# => ["PleasantVille"]
|
|
84
|
-
|
|
85
|
-
# Type filtering with ofType()
|
|
70
|
+
# Bundle → typed resources with ofType()
|
|
86
71
|
bundle = {
|
|
87
72
|
"resourceType" => "Bundle",
|
|
88
73
|
"entry" => [
|
|
89
|
-
{ "resource" => { "resourceType" => "Observation", "id" => "1" } },
|
|
90
|
-
{ "resource" => { "resourceType" => "Patient", "id" => "
|
|
74
|
+
{ "resource" => { "resourceType" => "Observation", "id" => "obs-1", "status" => "final" } },
|
|
75
|
+
{ "resource" => { "resourceType" => "Patient", "id" => "pat-1" } }
|
|
91
76
|
]
|
|
92
77
|
}
|
|
93
|
-
FHIRPath.evaluate(bundle, "entry.resource.ofType(Observation)"
|
|
78
|
+
FHIRPath.evaluate(bundle, "entry.resource.ofType(Observation)") # => [Observation resource]
|
|
94
79
|
|
|
95
80
|
# Compiled expression reuse
|
|
96
81
|
program = FHIRPath.compile("Patient.name.family")
|
|
@@ -100,9 +85,9 @@ program.call(patient_json).to_a # also accepts raw JSON strings
|
|
|
100
85
|
# => ["Chalmers"]
|
|
101
86
|
```
|
|
102
87
|
|
|
103
|
-
FHIR R4
|
|
104
|
-
|
|
105
|
-
|
|
88
|
+
## FHIR R4 model adapter (default)
|
|
89
|
+
|
|
90
|
+
The R4 adapter is dependency-free and does not perform Ruby method dispatch; the R4 model adapter is now the default. Its supported release and model selection are visible through `FHIRPath::Capability.current` and `FHIRPath.available_models`. Plain-model navigation is available via `model: nil`.
|
|
106
91
|
|
|
107
92
|
```ruby
|
|
108
93
|
observation = {
|
|
@@ -110,32 +95,27 @@ observation = {
|
|
|
110
95
|
"valueQuantity" => { "value" => 120, "unit" => "mmHg" }
|
|
111
96
|
}
|
|
112
97
|
|
|
113
|
-
FHIRPath.evaluate(observation, "
|
|
98
|
+
FHIRPath.evaluate(observation, "value.value").to_a
|
|
114
99
|
# => [120]
|
|
115
100
|
|
|
116
101
|
# The choice variant carries its FHIR logical type, so `is`/`as` resolve
|
|
117
102
|
# against model metadata for the resolved value:
|
|
118
|
-
FHIRPath.evaluate(observation, "
|
|
103
|
+
FHIRPath.evaluate(observation, "value is Quantity").to_a
|
|
119
104
|
# => [true]
|
|
120
|
-
FHIRPath.evaluate(observation, "
|
|
105
|
+
FHIRPath.evaluate(observation, "value as Quantity").to_a
|
|
121
106
|
# => [{ "value" => 120, "unit" => "mmHg" }]
|
|
122
107
|
```
|
|
123
108
|
|
|
124
|
-
The R4 adapter is dependency-free and does not perform Ruby method dispatch;
|
|
125
|
-
plain-model navigation remains the default. Its supported release and model
|
|
126
|
-
selection are visible through `FHIRPath::Capability.current` and
|
|
127
|
-
`FHIRPath.available_models`.
|
|
128
|
-
|
|
129
109
|
## Public API
|
|
130
110
|
|
|
131
111
|
The API is intentionally Ruby-native rather than source-compatible with `fhirpath-py`:
|
|
132
112
|
|
|
133
113
|
```ruby
|
|
134
114
|
FHIRPath.parse(expression, capability: FHIRPath::Capability.current)
|
|
135
|
-
FHIRPath.compile(expression, model:
|
|
136
|
-
FHIRPath.evaluate(resource, expression, variables: {}, model:
|
|
115
|
+
FHIRPath.compile(expression, model: :r4, capability: ..., functions: ...)
|
|
116
|
+
FHIRPath.evaluate(resource, expression, variables: {}, model: :r4,
|
|
137
117
|
capability: ..., functions: ..., options: {}, host: nil)
|
|
138
|
-
FHIRPath.evaluate_first(resource, expression, variables: {}, model:
|
|
118
|
+
FHIRPath.evaluate_first(resource, expression, variables: {}, model: :r4,
|
|
139
119
|
capability: ..., functions: ..., options: {}, host: nil)
|
|
140
120
|
```
|
|
141
121
|
|
|
@@ -147,46 +127,24 @@ program.evaluate({ "resourceType" => "Patient", "name" => [{ "family" => "Lovela
|
|
|
147
127
|
# => ["Lovelace"]
|
|
148
128
|
program.call({ "resourceType" => "Patient", "name" => [{ "family" => "Hopper" }] }).to_a
|
|
149
129
|
# => ["Hopper"]
|
|
130
|
+
program.call(patient_json).to_a # also accepts raw JSON strings
|
|
131
|
+
# => ["Chalmers"]
|
|
150
132
|
```
|
|
151
133
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
### Explicit host constants
|
|
155
|
-
|
|
156
|
-
External constant lookup is opt-in and stays behind an injected provider:
|
|
157
|
-
|
|
158
|
-
```ruby
|
|
159
|
-
class TenantConstants < FHIRPath::ConstantProvider
|
|
160
|
-
def fetch(name, mode:, context:)
|
|
161
|
-
constants.fetch(name)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
private
|
|
165
|
-
|
|
166
|
-
def constants
|
|
167
|
-
{ 'tenant' => 'example' }
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
host = FHIRPath::HostServices.new(constant_provider: TenantConstants.new)
|
|
172
|
-
FHIRPath.evaluate({}, '%tenant', host: host).to_a
|
|
173
|
-
# => ["example"]
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
`ConstantProvider#fetch` is the only boundary at which an embedding application may perform external work. The engine does not discover constants, read files, or make network requests. If no provider is supplied, an external constant raises `UnknownConstantError` with code `:unknown_constant`. Constant-provider failures raise a generic `HostError`; exceptions raised by the constant provider are not retained as public causes, and their messages are excluded from the public error serialization and `full_message`. `variables:` takes precedence over the provider, so explicitly supplied values do not trigger provider work.
|
|
134
|
+
The compiled expression carries its own model, capability, and function registry — call-site overrides are not required.
|
|
177
135
|
|
|
178
|
-
## Supported slice
|
|
136
|
+
## Supported feature slice
|
|
179
137
|
|
|
180
138
|
The current tested slice includes:
|
|
181
139
|
|
|
182
140
|
- primitive string, Boolean, integer, decimal, and scientific-notation literals;
|
|
183
141
|
- empty and comma-separated collections;
|
|
184
142
|
- plain Ruby Hash/Array and simple object navigation, including resource-type roots such as `Patient`;
|
|
185
|
-
- dependency-free FHIR R4 model navigation
|
|
143
|
+
- dependency-free FHIR R4 model navigation (now the default), including the logical `Observation.value` choice property over `valueQuantity` and `valueString`;
|
|
186
144
|
- unary and numeric arithmetic (`+`, `-`, `*`, `/`, `div`, and `mod`), plus string `+` when both operands are strings; a zero divisor for `/`, `div`, `mod` returns an empty collection, while `+`, `-`, `*` treat zero as a normal operand;
|
|
187
145
|
- bounded FHIRPath `Quantity` values with finite Decimal storage, case-sensitive conversion for the documented dependency-free unit subset, same-dimension comparison/addition/subtraction, scalar multiplication/division, Quantity ratios, and `ofType(Quantity)`/`is Quantity`/`as Quantity`; unsupported units and complete UCUM semantics remain outside this release slice; the parser also accepts double-quoted strings as an extension, uses `^` for supported unit exponents, preserves Quantity `system`/`code` metadata without using it for unit equality, and defers derived-unit composition such as Quantity×Quantity or `km/h`;
|
|
188
146
|
- numeric/string relational comparison, collection-aware equality, equivalence, and empty-aware Boolean operators; a finite JSON `Float` is treated as a `Decimal`;
|
|
189
|
-
- union, string concatenation (`+` and `&`), membership (`in`/`contains`), and type operators (`is`/`as`); union removes duplicate values from both operands using `=` equality in first-seen order, and `in`/`contains` require a singleton operand; `is`/`as` test built-in primitive types directly and
|
|
147
|
+
- union, string concatenation (`+` and `&`), membership (`in`/`contains`), and type operators (`is`/`as`); union removes duplicate values from both operands using `=` equality in first-seen order, and `in`/`contains` require a singleton operand; `is`/`as` test built-in primitive types directly and also resolve the FHIR logical type of a navigated choice value (for example `Observation.value is Quantity` over `valueQuantity`), returning the value unchanged on a successful `as` and the empty collection otherwise; and
|
|
190
148
|
- indexers with non-negative integer indexes;
|
|
191
149
|
- `where`, `select`, `first`, `last`, `tail`, `take`, `skip`, `exists`, `count`, `empty`, `not`, `all`, and Boolean aggregate functions;
|
|
192
150
|
- the FHIRPath 3.0.0 STU3 aggregate functions `sum()`, `avg()`, `max()`, and `min()` (empty input yields the empty collection; `sum`/`avg` require numeric items and `max`/`min` compare numeric and string items with comparison-operator semantics), shipped as a declared, documented exception: `Capability.current` keeps the FHIRPath 2.0.0 target and reports this subset in `trial_use` under the marker `stu3-aggregate-functions`;
|
|
@@ -201,58 +159,15 @@ The [feature matrix](docs/feature-matrix.md) is the executable-scope companion t
|
|
|
201
159
|
This is not yet a complete FHIRPath engine. The following remain deferred or host-dependent:
|
|
202
160
|
|
|
203
161
|
- complete FHIRPath 2.0 conformance; the checked-in importer covers only the pinned official subset;
|
|
204
|
-
- broader FHIR R4 metadata such as primitive extensions, resource-level type tests (`Observation is Resource`/`DomainResource`),
|
|
162
|
+
- broader FHIR R4 metadata such as primitive extensions, resource-level type tests (`Observation is Resource`/`DomainResource`), and FHIR R5 model adapters; FHIR R4 `is`/`as` over a resolved choice value's logical type (e.g. `Quantity`) is supported by default; terminology and `resolve()` remain host-dependent; and
|
|
205
163
|
- temporal arithmetic with Date/Time/DateTime and Quantity/Duration, including calendar-duration arithmetic;
|
|
206
|
-
- advanced conversion, math, string, regular-expression, and navigation functions
|
|
164
|
+
- advanced conversion, math, string, regular-expression, and navigation functions;
|
|
207
165
|
- complex literals and additional standard value types;
|
|
208
166
|
- standard environment variables beyond explicitly supplied external constants;
|
|
209
167
|
- FHIRPath 3.0 STU3 features beyond the shipped `sum`/`avg`/`max`/`min` aggregate functions; capability recognition does not enable them silently;
|
|
210
168
|
- network I/O from pure evaluation and global evaluator state; and
|
|
211
|
-
-
|
|
212
|
-
|
|
213
|
-
Unsupported operations raise `UnsupportedFeatureError`, `UnknownFunctionError`, or another specific `FHIRPath::Error`. Malformed or trailing source raises `ParseError`; the parser does not accept a valid prefix and silently ignore trailing input.
|
|
214
|
-
|
|
215
|
-
## Development and verification
|
|
216
|
-
|
|
217
|
-
Install development dependencies and run the complete local checks:
|
|
218
|
-
|
|
219
|
-
```sh
|
|
220
|
-
bundle install
|
|
221
|
-
bundle exec rake test
|
|
222
|
-
bundle exec rubocop
|
|
223
|
-
bundle exec rake vectors
|
|
224
|
-
bundle exec rake build
|
|
225
|
-
bundle exec ./script/verify_gem_install.sh pkg/fhirpath-*.gem
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
The optional vector workflow is deterministic and does not require Python:
|
|
229
|
-
|
|
230
|
-
```sh
|
|
231
|
-
bundle exec ruby script/run_vectors.rb conformance/core.jsonl
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
It reports `pass`, `defect`, `unsupported`, `host-dependent`, and `not-run` separately. These hand-authored JSONL vectors are compatibility evidence, not a replacement for the official HL7 shared suite. See [Conformance workflow](docs/conformance.md).
|
|
235
|
-
|
|
236
|
-
Coverage is opt-in and uses Ruby's standard `Coverage` library:
|
|
237
|
-
|
|
238
|
-
```sh
|
|
239
|
-
COVERAGE=1 bundle exec rake test
|
|
240
|
-
bundle exec ruby script/check_coverage.rb coverage/summary.json
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
For a clean-checkout reproduction, use the exact commands in [CONTRIBUTING.md](CONTRIBUTING.md). CI runs the supported Ruby matrix, tests, RuboCop, vectors, package build, gem-install smoke test, and coverage report generation.
|
|
244
|
-
|
|
245
|
-
## Contributing
|
|
246
|
-
|
|
247
|
-
Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. New language behavior should be delivered as a small vertical slice: add a focused failing test, implement the smallest change, run the complete checks, and update the feature matrix and limitations when scope changes.
|
|
248
|
-
|
|
249
|
-
Security reports should follow [SECURITY.md](SECURITY.md). Release readiness,
|
|
250
|
-
versioning, publication, and remaining conformance gates are recorded in
|
|
251
|
-
[`docs/release-checklist.md`](docs/release-checklist.md) and
|
|
252
|
-
[`docs/releasing.md`](docs/releasing.md).
|
|
169
|
+
- unqualified string escaping beyond the documented UTF-16 surrogate-pair handling.
|
|
253
170
|
|
|
254
171
|
## License
|
|
255
172
|
|
|
256
|
-
|
|
257
|
-
pre-release and does not claim complete FHIRPath conformance; those limitations
|
|
258
|
-
are independent of the license.
|
|
173
|
+
[MIT](LICENSE).
|
data/docs/api.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# FHIRPath Ruby API reference
|
|
2
2
|
|
|
3
|
-
Status: pre-release API contract (`0.2.0.
|
|
3
|
+
Status: pre-release API contract (`0.2.0.pre7`)
|
|
4
4
|
|
|
5
5
|
This document describes the Ruby-native public surface. It is a contract for this project, not a claim of source compatibility with `fhirpath-py`, `fhirpath.js`, HAPI, or Firely.
|
|
6
6
|
|
|
@@ -31,7 +31,7 @@ Passing a `String` to `parse` never freezes that string: `parse` retains an inte
|
|
|
31
31
|
```ruby
|
|
32
32
|
program = FHIRPath.compile(
|
|
33
33
|
expression,
|
|
34
|
-
model:
|
|
34
|
+
model: :r4,
|
|
35
35
|
capability: FHIRPath::Capability.current,
|
|
36
36
|
functions: FHIRPath::FunctionRegistry.standard
|
|
37
37
|
)
|
|
@@ -41,10 +41,9 @@ Returns a frozen `FHIRPath::CompiledExpression`. Parsing happens once; each `eva
|
|
|
41
41
|
|
|
42
42
|
`compile` likewise never freezes the caller's source `String`: it snapshots the expression internally. Mutating the string you passed after calling `compile` does not change the compiled program, and passing an already-frozen string works normally.
|
|
43
43
|
|
|
44
|
-
`model` defaults to `FHIRPath::
|
|
45
|
-
to
|
|
46
|
-
|
|
47
|
-
immutable function registry snapshot.
|
|
44
|
+
`model` defaults to `FHIRPath::FHIR::R4::ModelProvider`. Pass `model: nil`
|
|
45
|
+
to opt out and use `FHIRPath::PlainModel` for model-independent navigation,
|
|
46
|
+
or pass a custom provider object.
|
|
48
47
|
|
|
49
48
|
### `FHIRPath.evaluate`
|
|
50
49
|
|
|
@@ -53,7 +52,7 @@ result = FHIRPath.evaluate(
|
|
|
53
52
|
resource,
|
|
54
53
|
expression,
|
|
55
54
|
variables: {},
|
|
56
|
-
model:
|
|
55
|
+
model: :r4,
|
|
57
56
|
capability: FHIRPath::Capability.current,
|
|
58
57
|
functions: FHIRPath::FunctionRegistry.standard,
|
|
59
58
|
options: {},
|
|
@@ -70,7 +69,7 @@ The `resource` argument may be an already-parsed Hash/Array (used directly, neve
|
|
|
70
69
|
| Input type | Behavior |
|
|
71
70
|
|------------|----------|
|
|
72
71
|
| `Hash` / `Array` | Used directly (never re-serialized) |
|
|
73
|
-
| String starting with `{` or `[` (after whitespace) | Parsed as JSON document via `JSON.parse` once per call; malformed JSON raises `JSONInputError`
|
|
72
|
+
| String starting with `{` or `[` (after whitespace) | Parsed as JSON document via `JSON.parse` once per call; malformed JSON raises `JSONInputError` code `invalid_json` |
|
|
74
73
|
| Any other String | Treated as a singleton FHIRPath string value (never parsed) |
|
|
75
74
|
|
|
76
75
|
```ruby
|
|
@@ -122,6 +121,26 @@ FHIRPath.evaluate({}, "%enabled", variables: { enabled: false }).to_a
|
|
|
122
121
|
|
|
123
122
|
`host:` is reserved for explicit host services. Pure evaluation does not perform network I/O.
|
|
124
123
|
|
|
124
|
+
### Model selection
|
|
125
|
+
|
|
126
|
+
By default, `FHIRPath.evaluate` uses the R4 model adapter, which enables
|
|
127
|
+
choice navigation (e.g. `Observation.value` resolves over `valueString`,
|
|
128
|
+
`valueQuantity`, etc.) and logical-type `is`/`as` metadata. Pass
|
|
129
|
+
`model: nil` to use `PlainModel` for plain Hash/Array navigation without
|
|
130
|
+
FHIR choice resolution:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
observation = { 'resourceType' => 'Observation', 'valueString' => 'high' }
|
|
134
|
+
|
|
135
|
+
# R4 model (default) — choice navigation
|
|
136
|
+
FHIRPath.evaluate(observation, 'Observation.value').to_a
|
|
137
|
+
# => ['high']
|
|
138
|
+
|
|
139
|
+
# Plain model — direct property lookup only
|
|
140
|
+
FHIRPath.evaluate(observation, 'Observation.value', model: nil).to_a
|
|
141
|
+
# => []
|
|
142
|
+
```
|
|
143
|
+
|
|
125
144
|
### Host constants
|
|
126
145
|
|
|
127
146
|
External constants can be supplied by an immutable `HostServices` configuration:
|
|
@@ -169,7 +188,7 @@ The project defines `FHIRPath::TypeError` inside its namespace; callers should q
|
|
|
169
188
|
|
|
170
189
|
For the implemented string operators, `+` concatenates two singleton strings but propagates an empty operand, while `&` treats each empty operand as the empty string. Thus `'a' + {}` is empty, whereas `'a' & {}` returns `['a']`. String escapes follow the FHIRPath `\\uXXXX` form; valid UTF-16 surrogate pairs are combined, and unknown forms such as `\\U0001F600` are rejected with `ParseError`.
|
|
171
190
|
|
|
172
|
-
Most current public results are ordinary Ruby values. `FHIRPath::Value::*` and `FHIRPath::TypeInfo` provide extension boundaries for semantic values and model metadata.
|
|
191
|
+
Most current public results are ordinary Ruby values. `FHIRPath::Value::*` and `FHIRPath::TypeInfo` provide extension boundaries for semantic values and model metadata. By default, the engine uses the dependency-free R4 adapter, which supports JSON choice navigation for `Observation.value[x]`; a resolved choice value carries its FHIR logical type (for example `Quantity` for `valueQuantity`), so `is`/`as` type operators can test it. Pass `model: nil` to use `PlainModel` for model-independent navigation without choice metadata. `Collection` may carry positional model-type metadata alongside items without changing item values; the metadata is attached when a collection is produced directly by model navigation, and operators that rebuild collections (such as `union`) do not yet propagate it. Date/time, quantity, resource hierarchy and recursive resource-type `is`/`as`, and other FHIR releases remain deferred. `ofType()` is supported for filtering by built-in types and FHIR resource types recorded during navigation (see the feature matrix for the exact supported scope).
|
|
173
192
|
|
|
174
193
|
## Custom functions
|
|
175
194
|
|
data/docs/feature-matrix.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Feature and capability matrix
|
|
2
2
|
|
|
3
|
-
Status: `0.2.0.
|
|
3
|
+
Status: `0.2.0.pre7`; target release: FHIRPath `2.0.0`; publication contract: [`support-matrix.md`](support-matrix.md)
|
|
4
4
|
|
|
5
5
|
This matrix is deliberately conservative. `Supported` means the behavior is exercised by the Ruby test suite or the checked-in vector corpus. `Deferred` means callers should expect a structured unsupported/unknown error. `Host-dependent` requires an adapter or injected service that is not shipped here.
|
|
6
6
|
|
|
@@ -45,9 +45,9 @@ This matrix is deliberately conservative. `Supported` means the behavior is exer
|
|
|
45
45
|
| FHIR primitive extension accessor (`._<name>`) | Supported | `test/primitive_extensions_test.rb`; returns the underlying `{value, extension}` container or empty per FHIRPath 2.0.0 |
|
|
46
46
|
| Quantity/UCUM | Supported | `test/quantity_test.rb` and `test/quantity_edge_cases_test.rb`; immutable Decimal-backed quantities, case-sensitive dimensional conversion for the explicitly bounded dependency-free subset (`m`/`cm`/`mm`/`km`, `g`/`kg`/`mg`/`Mg`/`ug`/`ng`, `L`/`mL`/`ML`/`uL`, `mol`/`mmol`/`umol`, `s`/`min`/`h`, and products/quotients such as `mmol/L`); unsupported units are rejected rather than treated as dimensionless, incompatible calculations and mixed Quantity/scalar addition return empty, same-dimension Quantity division returns a Decimal ratio, derived-unit composition such as Quantity×Quantity or `km/h` remains deferred, and system/code metadata is preserved without changing unit equality |
|
|
47
47
|
| Advanced conversion/math/string/regex | Deferred | not in standard registry |
|
|
48
|
-
| FHIR R4 model adapter (
|
|
49
|
-
| FHIR R4 `Observation.value[x]` logical navigation | Supported | R4 choice vectors; `valueQuantity` and `valueString` resolve through `value`, absent choice is empty |
|
|
50
|
-
| FHIR R4 logical-type `is`/`as` over resolved choice values | Supported | `test/r4_type_operator_test.rb` and R4 choice vectors; navigation records the resolved choice variant's FHIR logical type (`Quantity`, `string`, ...) and `is`/`as` test against it; empty-in/empty-out and PlainModel (no model metadata) behavior are covered; the type is recorded for collections produced directly by navigation (operators that rebuild collections, such as `union`, do not yet propagate it); resource
|
|
48
|
+
| FHIR R4 model adapter (default) | Supported | `test/r4_model_test.rb`; dependency-free `FHIRPath::FHIR::R4::ModelProvider`; omitted `model:` defaults to R4, `model: nil` selects PlainModel |
|
|
49
|
+
| FHIR R4 `Observation.value[x]` logical navigation | Supported | R4 choice vectors; `valueQuantity` and `valueString` resolve through `value`, absent choice is empty; this is now the default behavior when no `model:` is passed |
|
|
50
|
+
| FHIR R4 logical-type `is`/`as` over resolved choice values | Supported | `test/r4_type_operator_test.rb` and R4 choice vectors; navigation records the resolved choice variant's FHIR logical type (`Quantity`, `string`, ...) and `is`/`as` test against it; empty-in/empty-out and PlainModel (no model metadata) behavior are covered; the type is recorded for collections produced directly by navigation (operators that rebuild collections, such as `union`, do not yet propagate it); resource hierarchy and recursive resource-type `is`/`as` remain deferred; this is now the default behavior when no `model:` is passed |
|
|
51
51
|
| FHIR R5 model adapter | Deferred | no R5 provider |
|
|
52
52
|
| Broader FHIR choice elements and primitive extensions | Host-dependent | first R4 slice only covers `Observation.value[x]` |
|
|
53
53
|
| `resolve()` and terminology | Host-dependent | requires injected host services |
|
data/docs/release-checklist.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This checklist is the gate for a reusable public release. It is intentionally explicit because a successful gem build is not the same as a legally releasable or conformant package.
|
|
4
4
|
|
|
5
|
-
## Current assessment (`0.2.0.
|
|
5
|
+
## Current assessment (`0.2.0.pre7`)
|
|
6
6
|
|
|
7
7
|
| Criterion | Status | Evidence / rationale |
|
|
8
8
|
|---|---|---|
|
|
@@ -44,7 +44,7 @@ Verification on Homebrew Ruby 4.0.4 (the repository's local bundle contains nati
|
|
|
44
44
|
- `bundle exec rake test`: 50 runs, 191 assertions, 0 failures, 0 errors, 0 skips;
|
|
45
45
|
- `bundle exec rubocop`: 31 files inspected, no offenses;
|
|
46
46
|
- `bundle exec rake vectors`: 8 total, 8 pass, 0 defect, 0 unsupported, 0 host-dependent, 0 not-run;
|
|
47
|
-
- `bundle exec rake build`: `pkg/fhirpath-0.2.0.
|
|
47
|
+
- `bundle exec rake build`: `pkg/fhirpath-0.2.0.pre7.gem` built;
|
|
48
48
|
- `script/verify_gem_install.sh`: clean isolated install and `1 + 2` smoke test passed;
|
|
49
49
|
- `COVERAGE=1 bundle exec rake test` plus `script/check_coverage.rb`: 88.9% (829/932 executable lines); and
|
|
50
50
|
- `git diff --check`: passed.
|
data/docs/support-matrix.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Release support matrix
|
|
2
2
|
|
|
3
|
-
Status: `pre-release` (`0.2.0.
|
|
3
|
+
Status: `pre-release` (`0.2.0.pre7`)
|
|
4
4
|
|
|
5
5
|
This is the release-facing support matrix. It is deliberately narrower than
|
|
6
6
|
"FHIRPath-compatible": every supported claim must have executable evidence, and
|
|
@@ -17,7 +17,7 @@ The detailed behavior table is maintained in [`feature-matrix.md`](feature-matri
|
|
|
17
17
|
| License | MIT |
|
|
18
18
|
| Supported Ruby CI matrix | Ruby `3.2`, Ruby `3.3` |
|
|
19
19
|
| Model support | Model-independent plain Ruby Hash/Array/object navigation plus dependency-free FHIR R4 JSON adapter |
|
|
20
|
-
| FHIR model releases | R4 (`4.0.1`)
|
|
20
|
+
| FHIR model releases | R4 (`4.0.1`) is the default; pass `model: nil` to select PlainModel for model-independent navigation |
|
|
21
21
|
| Trial-use features | One declared exception only: the FHIRPath 3.0 STU3 aggregate functions `sum`/`avg`/`max`/`min`, shipped by default in the standard registry (see [Declared STU3-subset exception](#declared-stu3-subset-exception)) |
|
|
22
22
|
|
|
23
23
|
The packaged gem repeats the target and capability declaration in gem metadata:
|
data/lib/fhirpath/version.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module FHIRPath
|
|
4
|
-
VERSION = '0.2.0.
|
|
4
|
+
VERSION = '0.2.0.pre7'
|
|
5
5
|
# A stable release requires the complete release gate to be deliberately
|
|
6
6
|
# promoted. Keep pre-release status explicit while the shared-suite and
|
|
7
7
|
# model-adapter work remains incomplete.
|
data/lib/fhirpath.rb
CHANGED
|
@@ -37,14 +37,14 @@ module FHIRPath
|
|
|
37
37
|
['R4'].freeze
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
def compile(expression, model:
|
|
40
|
+
def compile(expression, model: :r4, capability: Capability.current,
|
|
41
41
|
functions: FunctionRegistry.standard)
|
|
42
42
|
parsed = expression.is_a?(ParsedExpression) ? expression : parse(expression, capability: capability)
|
|
43
43
|
CompiledExpression.new(parsed: parsed, model: resolve_model(model, capability),
|
|
44
44
|
functions: functions, capability: capability)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
def evaluate(resource, expression, variables: {}, model:
|
|
47
|
+
def evaluate(resource, expression, variables: {}, model: :r4,
|
|
48
48
|
capability: Capability.current, functions: FunctionRegistry.standard,
|
|
49
49
|
options: {}, host: nil)
|
|
50
50
|
compiled = if expression.is_a?(CompiledExpression)
|
|
@@ -56,7 +56,7 @@ module FHIRPath
|
|
|
56
56
|
compiled.evaluate(resource, variables: variables, host: host, options: options)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
def evaluate_first(resource, expression, variables: {}, model:
|
|
59
|
+
def evaluate_first(resource, expression, variables: {}, model: :r4,
|
|
60
60
|
capability: Capability.current, functions: FunctionRegistry.standard,
|
|
61
61
|
options: {}, host: nil)
|
|
62
62
|
evaluate(resource, expression, variables: variables, model: model,
|