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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91c94659f15a0d3b56fadc1af248e1580ec706852f6b45bcdc3cd12201b90cc2
4
- data.tar.gz: 4e81e0c399f03c91963d58c0c8620d46a9326ddb13978ef569fdf406ee74a06b
3
+ metadata.gz: 1660a871336fe3ce8a1b6709621083152b1bd0bef3f53b4f981e73b11a3ff0f9
4
+ data.tar.gz: 2d21e58de73397970c8257650061d045dc05c7959197159b8d560feb6f91b686
5
5
  SHA512:
6
- metadata.gz: 90675210351495016dfdbeb09b8abdc00e116ee86129379667471e129395b006da7e6fd69fa7cc1013d41ce41dd0d4cf01b1ab8eaca84b9793a828586df2090e
7
- data.tar.gz: 482d0ca9d95920b32a451fbae77afe34e486c536c4617329ed3b4fe561e8e42975d0d2c60054379885f95fe683d859f21bfdd8b657b28f823e08a0f55014f34f
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
- - Add a dependency-free, explicitly bounded FHIRPath `Quantity` type. Quantity
17
- values always store a finite `BigDecimal` and expose `value`, `unit`,
18
- `system`, and `code`; integer quantity literals are promoted to Decimal.
19
- Single- and double-quoted unit literals are supported, with case-sensitive
20
- conversion for the tested subset of length, mass, volume, amount, and time
21
- units plus simple products/quotients such as `mmol/L`. Same-dimension
22
- quantities support comparison, addition, subtraction, scalar multiplication
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
- - Same library content as `0.2.0.pre2`. Version bumped because the earlier
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
- - Add the `ofType()` function to the standard registry. Filters the receiver
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.pre1...HEAD
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
  [![Release](https://github.com/niccoreyes/fhirpath-ruby/actions/workflows/release.yml/badge.svg)](https://github.com/niccoreyes/fhirpath-ruby/actions/workflows/release.yml)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/niccoreyes/fhirpath-ruby/blob/main/LICENSE)
6
6
  [![Ruby](https://img.shields.io/badge/Ruby-3.2%20%7C%203.3-blue.svg)](https://github.com/niccoreyes/fhirpath-ruby/blob/main/.github/workflows/ci.yml)
7
- [![Gem Version](https://badge.fury.io/rb/fhirpath.svg)](https://rubygems.org/gems/fhirpath)
7
+ [![Gem Version](https://img.shields.io/gem/v/fhirpath?logo=rubygems&logoColor=white)](https://rubygems.org/gems/fhirpath)
8
8
  [![Gem Downloads](https://img.shields.io/gem/dt/fhirpath)](https://rubygems.org/gems/fhirpath)
9
- [![RubyGems](https://img.shields.io/gem/v/fhirpath?logo=rubygems&logoColor=white)](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
- - Version: `0.2.0.pre3`
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
- ```sh
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", model: :r4) # => [true]
63
- FHIRPath.evaluate(observation, "valueQuantity.unit", model: :r4) # => ["mmol/L"]
64
- FHIRPath.evaluate(observation, "code.coding.system", model: :r4) # => ["http://loinc.org"]
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
- "id": "pat-1",
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
- FHIRPath.evaluate(patient_json, "Patient.name.where(use='official').family", model: :r4)
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" => "2" } }
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)", model: :r4) # => [Observation resource]
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 JSON can be selected explicitly through the versioned provider. The
104
- adapter exposes the logical `Observation.value` property over R4 choice keys
105
- such as `valueString` and `valueQuantity`:
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, "Observation.value.value", model: :r4).to_a
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, "Observation.value is Quantity", model: :r4).to_a
103
+ FHIRPath.evaluate(observation, "value is Quantity").to_a
119
104
  # => [true]
120
- FHIRPath.evaluate(observation, "Observation.value as Quantity", model: :r4).to_a
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: nil, capability: ..., functions: ...)
136
- FHIRPath.evaluate(resource, expression, variables: {}, model: nil,
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: nil,
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
- See [API reference](docs/api.md) for result, error, extension, and immutability contracts, and [architecture](docs/architecture.md) for the implementation boundaries.
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 selected with `model: :r4`, including the logical `Observation.value` choice property over `valueQuantity` and `valueString`;
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, with `model: :r4`, 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
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`), `ofType()`, and FHIR R5 model adapters; FHIR R4 `is`/`as` over a resolved choice value's logical type (e.g. `Quantity`) is supported, but terminology and `resolve()` remain host-dependent; and
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, and the general-purpose `aggregate()` function;
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
- - production support guarantees, until the support matrix and release gates are complete.
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
- This project is licensed under the [MIT License](LICENSE). The gem is still a
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.pre1`)
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: nil,
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::PlainModel`. Pass `model: :r4` (or `model: 'R4'`)
45
- to select the dependency-free `FHIRPath::FHIR::R4::ModelProvider`. Passing a
46
- provider object remains supported for custom model adapters. `functions` is an
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: nil,
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` (code `invalid_json`) |
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. The dependency-free R4 adapter 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. `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, broader type-aware model behavior (resource hierarchies, `ofType()`), and other FHIR releases remain deferred.
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
 
@@ -1,6 +1,6 @@
1
1
  # Feature and capability matrix
2
2
 
3
- Status: `0.2.0.pre3`; target release: FHIRPath `2.0.0`; publication contract: [`support-matrix.md`](support-matrix.md)
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 (`model: :r4`) | Supported | `test/r4_model_test.rb`; dependency-free `FHIRPath::FHIR::R4::ModelProvider` |
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-level type tests and `ofType()` remain deferred |
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 |
@@ -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.pre3`)
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.pre3.gem` built;
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.
@@ -1,6 +1,6 @@
1
1
  # Release support matrix
2
2
 
3
- Status: `pre-release` (`0.2.0.pre3`)
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`), selectable with `model: :r4` |
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:
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FHIRPath
4
- VERSION = '0.2.0.pre6'
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: nil, capability: Capability.current,
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: nil,
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: nil,
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,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fhirpath
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.pre6
4
+ version: 0.2.0.pre7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicco Reyes