openehr-rails 0.6.0 → 0.7.0
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 +75 -0
- data/CLAUDE.md +34 -2
- data/Gemfile +7 -0
- data/docs/backlog.md +22 -0
- data/docs/design/multi-leaf-non-observation-plan.md +188 -1
- data/docs/reports/fsh-generator-log.md +608 -0
- data/lib/generators/openehr/fhir_profile/fhir_profile_generator.rb +5 -1
- data/lib/generators/openehr/scaffold/scaffold_generator.rb +5 -3
- data/lib/openehr_rails/fhir/fsh_generator.rb +56 -7
- data/lib/openehr_rails/fhir/profile_generator.rb +71 -2
- data/lib/openehr_rails/fhir/type_map.rb +72 -7
- data/lib/openehr_rails/fhir/unsupported_profile_error.rb +27 -0
- data/lib/openehr_rails/opt/field_extractor.rb +1 -2
- data/lib/openehr_rails/opt/parser.rb +4 -0
- data/lib/openehr_rails/release_check.rb +47 -4
- data/lib/openehr_rails/version.rb +1 -1
- data/lib/openehr_rails.rb +1 -0
- data/script/build_demo.sh +6 -0
- data/spec/fixtures/fsh/openehr-evaluation-problem-diagnosis-v1.fsh +22 -0
- data/spec/openehr_rails/fhir/fsh_generator_spec.rb +135 -2
- data/spec/openehr_rails/fhir/profile_generator_spec.rb +147 -9
- data/spec/openehr_rails/opt/field_extractor_binding_spec.rb +1 -2
- data/spec/openehr_rails/release_check_spec.rb +39 -0
- data/templates/openehr_template.rb +6 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 065a9ba44f893adaea9e6c0ccf6f3c5f8a0af8c8c3897379cd008e5642e99a7a
|
|
4
|
+
data.tar.gz: 98f81ad0baf2b086e2193102c25804b3affcd2fa1ffb4c0730952d6a916e3a58
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 42353942db8a02b901e3727a85446fe1995f3c953fd33f3da2dfea68d7018b533de95b97494db856593df77ccbacd8d57f0f4bb4cdcedee086da6c4e44035a04
|
|
7
|
+
data.tar.gz: 4fd7e793d2d77230e585dcf06204147926d3b36a86a576525614d19395930f80b4fe38bc0b534de5a0f6b26e98e0859d5c8275ce865a1efedf3aa942dc1d272e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,8 +7,83 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-09-10
|
|
11
|
+
|
|
12
|
+
> **Upgrade note.** Regenerate `app/fhir/profiles/*.json`. `Condition.component`
|
|
13
|
+
> slices are gone; anything reading them must move to the mapped elements
|
|
14
|
+
> (`category` slice `ckm`, `code`, `onsetDateTime`, `recordedDate`,
|
|
15
|
+
> `abatementDateTime`, `verificationStatus`), and an entry with no mapping onto
|
|
16
|
+
> a non-`Observation` resource now appears in the generator's `#skipped` list
|
|
17
|
+
> instead of producing a profile.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- **Breaking for consumers of generated FHIR profiles.** `EVALUATION` entries no
|
|
21
|
+
longer produce `Condition.component` slices, which FHIR R5 `Condition` does not
|
|
22
|
+
have. Each leaf now maps onto the real `Condition` element that means the same
|
|
23
|
+
thing — `code` (bound to the leaf's value set), `onsetDateTime`,
|
|
24
|
+
`recordedDate`, `abatementDateTime`, `verificationStatus` — and the archetype
|
|
25
|
+
anchor moves from `Condition.code` to a `Condition.category` slice (`ckm`,
|
|
26
|
+
`1..1`, pattern-discriminated on `$this`, so an instance keeps room for its
|
|
27
|
+
other categories), since `code` is now the diagnosis itself. `recordedDate`
|
|
28
|
+
carries an `ElementDefinition.comment` stating that it approximates at0003
|
|
29
|
+
(date/time clinically recognised). `ProfileGenerator` (JSON) and
|
|
30
|
+
`FshGenerator` generate from one table in `TypeMap`, so the two outputs cannot
|
|
31
|
+
disagree. Generated FSH for `problem_list.opt` goes from 29 Sushi errors to 0.
|
|
32
|
+
Host apps must regenerate cached `app/fhir/profiles/*.json`; anything reading
|
|
33
|
+
the old `Condition.component` slices must be updated (#33). Currently mapped:
|
|
34
|
+
`openEHR-EHR-EVALUATION.problem_diagnosis.v1`. A multi-leaf entry mapped to
|
|
35
|
+
`ServiceRequest`/`Procedure`/`Encounter` without a mapping-table row is now
|
|
36
|
+
skipped and reported (see Added) instead of producing `component` constraints
|
|
37
|
+
those resources do not have; its mapping table remains reserved as #35.
|
|
38
|
+
- Value-set canonicals on mapped leaves no longer carry OPT's `terminology:`
|
|
39
|
+
prefix, in either output.
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- `ProfileGenerator#skipped` and `FshGenerator#skipped`: the entries left out of
|
|
43
|
+
`#profiles` / `#to_fsh_files`, each as an
|
|
44
|
+
`OpenehrRails::Fhir::UnsupportedProfileError` (`archetype_id`,
|
|
45
|
+
`resource_type`, `leaf_count`) in template order. An entry is skipped when it
|
|
46
|
+
has more than one leaf, maps to a resource without `component`, and has no row
|
|
47
|
+
in `TypeMap::ENTRY_ELEMENT_MAPS`; the rest of the template still generates.
|
|
48
|
+
`rails g openehr:fhir_profile` and `openehr:scaffold --fhir` print each skip
|
|
49
|
+
(#33).
|
|
50
|
+
|
|
51
|
+
### Removed
|
|
52
|
+
- `TypeMap.value_element`, dead since it was added — its ternary returned the
|
|
53
|
+
same string on both branches and nothing called it. Its intended
|
|
54
|
+
resource-type branch is now the mapping table (#33).
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
- `templates/openehr_template.rb` pins `json < 3` in the generated app's
|
|
58
|
+
`Gemfile`. `json` 3.0 (2026-09-08) changed `JSON.parse`'s arity and
|
|
59
|
+
`ActiveSupport::JSON.decode` as of activesupport 8.1.3.1 still passes options
|
|
60
|
+
positionally, so a freshly generated host app failed at `db:seed` on its first
|
|
61
|
+
json-column write (`ArgumentError: wrong number of arguments (given 2, expected
|
|
62
|
+
1)`). Temporary: remove once a Rails patch release carries the fix. This
|
|
63
|
+
repo's own `Gemfile` and `script/build_demo.sh` carry the same dev-only pin.
|
|
64
|
+
- `release:check` now fails when a tag matching the gemspec version exists and
|
|
65
|
+
`HEAD` is not that tag's commit. `gem.files` comes from `git ls-files`, so a
|
|
66
|
+
gem built at a later commit ships different bytes under the same version
|
|
67
|
+
number — the defect that let 0.6.0 be published as a `master`-HEAD build
|
|
68
|
+
(#34). The check stays silent when the version has no tag yet, so ordinary
|
|
69
|
+
pre-release development is unaffected.
|
|
70
|
+
|
|
10
71
|
## [0.6.0] - 2026-08-26
|
|
11
72
|
|
|
73
|
+
> **Published artifact note.** The gem published to RubyGems for this version
|
|
74
|
+
> was built from `master` HEAD rather than the `v0.6.0` tag, so it contains the
|
|
75
|
+
> tagged tree plus two documentation files added after the tag
|
|
76
|
+
> (`docs/design/multi-leaf-non-observation-plan.md`, and this project's
|
|
77
|
+
> `docs/reports/fsh-generator-log.md` carrying its R6 entry). `lib/` is
|
|
78
|
+
> byte-identical to the tag artifact and version, runtime dependencies and
|
|
79
|
+
> `required_ruby_version` are unchanged, so the published gem is functionally
|
|
80
|
+
> identical to what CI built and verified at the tag; it was accepted rather
|
|
81
|
+
> than yanked. Published sha256
|
|
82
|
+
> `223e3b3897f85c38eaffe7ea39bd7c2acf4c5de9cab7d50fe38f754ff9d65db6`; tag-build
|
|
83
|
+
> sha256 `08cdd14ab1f3890b0c6b5f0ae0d5ca55615f0b4874ed5efb0cd4d7bda9e573ca`.
|
|
84
|
+
> Full measurements: `docs/reports/fsh-generator-log.md` R8. Prevention:
|
|
85
|
+
> `skoba/openehr-rails#34`.
|
|
86
|
+
|
|
12
87
|
### Added
|
|
13
88
|
- `OpenehrRails::Fhir::FshGenerator` renders one FHIR Shorthand profile per
|
|
14
89
|
openEHR entry, including metadata, archetype codes, value constraints,
|
data/CLAUDE.md
CHANGED
|
@@ -46,6 +46,37 @@ Before tagging, make the final semver determination from the actual content of
|
|
|
46
46
|
`[Unreleased]`, not from a pre-assigned version number. If the instructed version number
|
|
47
47
|
contradicts the actual content, stop instead of tagging and ask for re-arbitration.
|
|
48
48
|
|
|
49
|
+
**Publish only the CI artifact itself.** `gem push` takes the `.gem` downloaded
|
|
50
|
+
from the tag's Release run (`gh run download <run-id> -n gem`), after its sha256
|
|
51
|
+
has been compared against the value recorded for that run - never a locally built
|
|
52
|
+
`pkg/*.gem`. `gem.files` comes from `git ls-files`, so a build made at any commit
|
|
53
|
+
other than the tag ships different bytes under the same version number, and a
|
|
54
|
+
stale `pkg/` artifact from unrelated local work is indistinguishable by filename
|
|
55
|
+
from a release build.
|
|
56
|
+
|
|
57
|
+
(Added 2026-08-27, from the 0.6.0 publish: the gem that reached RubyGems was a
|
|
58
|
+
`master`-HEAD build left in `pkg/` by an unrelated verification run, not the
|
|
59
|
+
CI-verified `v0.6.0` artifact. Accepted rather than yanked - the delta was two
|
|
60
|
+
documentation files and `lib/` was byte-identical - but nothing in the release
|
|
61
|
+
path caught it. Measurements in `docs/reports/fsh-generator-log.md` R8; the
|
|
62
|
+
matching `release:check` guard is `skoba/openehr-rails#34`.)
|
|
63
|
+
|
|
64
|
+
**Confirm publication by checksum, not by version number.** RubyGems publishes
|
|
65
|
+
the sha256 it recorded at push time (`/api/v1/gems/openehr-rails.json` `sha`, and
|
|
66
|
+
the `checksum:` field in `https://index.rubygems.org/info/openehr-rails`). The
|
|
67
|
+
post-push confirmation compares that value against the sha256 the tag's Release
|
|
68
|
+
run recorded for its artifact - the same value already compared before the push.
|
|
69
|
+
Seeing the version number appear is not the check; it only shows that *a* gem
|
|
70
|
+
landed under that number. (Ported 2026-09-10 from openehr-ruby's 2.4.3 release,
|
|
71
|
+
where CI run, downloaded artifact and published gem all agreed on one sha256.)
|
|
72
|
+
|
|
73
|
+
**RubyGems API propagation lag.** `/api/v1/versions/openehr-rails.json` can lag
|
|
74
|
+
the push by minutes; do not read the new version's absence there as a failed
|
|
75
|
+
publish, and do not re-push. Confirm with
|
|
76
|
+
`/api/v1/versions/openehr-rails/latest.json`, `/api/v1/gems/openehr-rails.json`,
|
|
77
|
+
or the compact index `https://index.rubygems.org/info/openehr-rails`, which
|
|
78
|
+
updated first in that same 2.4.3 run.
|
|
79
|
+
|
|
49
80
|
## Verification
|
|
50
81
|
|
|
51
82
|
- **Verify against the repo before recording a fact in it**, even when a prompt or an
|
|
@@ -119,7 +150,8 @@ name explicitly a distinction that the existing repository-context rule left
|
|
|
119
150
|
implicit: it governs *how* to target a cross-repo command correctly, not *whether*
|
|
120
151
|
crossing into implementation work on another repository has actually been
|
|
121
152
|
authorized for the task at hand. A matching line is planned for `anlage`'s own
|
|
122
|
-
`CLAUDE.md` in a later batch - not yet added there as of this entry.
|
|
153
|
+
`CLAUDE.md` in a later batch - not yet added there as of this entry. Update
|
|
154
|
+
2026-09-04: added there as 絶対規律 item 9.)
|
|
123
155
|
|
|
124
156
|
## Project Overview
|
|
125
157
|
|
|
@@ -200,4 +232,4 @@ This is `openehr-rails`, a Rails engine gem that turns an openEHR Operational Te
|
|
|
200
232
|
- Guard setup available for TDD workflow
|
|
201
233
|
- RuboCop Rails linting configured
|
|
202
234
|
- SimpleCov for test coverage
|
|
203
|
-
- `openehr` gem dependency; see the gem's own README/CHANGELOG for OPT-parser and AQL-engine capabilities and known gaps before relying on either.
|
|
235
|
+
- `openehr` gem dependency; see the gem's own README/CHANGELOG for OPT-parser and AQL-engine capabilities and known gaps before relying on either.
|
data/Gemfile
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
source 'https://rubygems.org'
|
|
2
2
|
|
|
3
3
|
gemspec
|
|
4
|
+
|
|
5
|
+
# json 3.0 (released 2026-09-09) changed JSON.parse's arity; ActiveSupport::JSON.decode
|
|
6
|
+
# as of activesupport 8.1.3.1 still passes its options positionally, so reading any
|
|
7
|
+
# json column raises ArgumentError (wrong number of arguments (given 2, expected 1)).
|
|
8
|
+
# Dev/test-only pin -- the gemspec is untouched, host apps resolve their own json.
|
|
9
|
+
# Drop once a Rails patch release carries the fix (docs/backlog.md, "Dependencies").
|
|
10
|
+
gem 'json', '< 3'
|
data/docs/backlog.md
CHANGED
|
@@ -90,6 +90,13 @@ status entry above for verification details.
|
|
|
90
90
|
docs-only pass — implementing the workflow change goes through an Issue (once
|
|
91
91
|
ticket-driven work applies to it) plus the normal explore → plan → approval gate,
|
|
92
92
|
not a direct docs commit.
|
|
93
|
+
- A point in Trusted Publishing's favour, measured 2026-08-27: the gem build is
|
|
94
|
+
byte-identical across machines and Ruby installs for a given commit — `4080053`
|
|
95
|
+
(the `v0.6.0` tag) produced sha256 `08cdd14a…` from CI, from the old machine
|
|
96
|
+
(R5), and from a rebuild on the replacement machine under local ruby 4.0.6 vs
|
|
97
|
+
CI's `ruby/setup-ruby@v1` ruby 4.0. So a CI- or Trusted-Publishing-published
|
|
98
|
+
artifact stays independently verifiable against a local rebuild
|
|
99
|
+
(`docs/reports/fsh-generator-log.md` R8).
|
|
93
100
|
|
|
94
101
|
## From #25 / PR #26 (FieldExtractor terminology scope fix, 2026-08-22)
|
|
95
102
|
|
|
@@ -155,3 +162,18 @@ status entry above for verification details.
|
|
|
155
162
|
duplicate-method inventory being produced first.
|
|
156
163
|
- **#2** (constraint -> HTML attribute mapping extraction): gated on a generality decision
|
|
157
164
|
after Anlage Slice 4; whether to even start is undecided.
|
|
165
|
+
|
|
166
|
+
## Dependencies
|
|
167
|
+
|
|
168
|
+
- **Remove the `json < 3` pins once Rails ships the fix.** Added 2026-09-10 in
|
|
169
|
+
three places: `Gemfile` (dev/test), `script/build_demo.sh` (the demo app's
|
|
170
|
+
Gemfile), `templates/openehr_template.rb` (the generated host app's Gemfile).
|
|
171
|
+
Cause: `json` 3.0 (3.0.1 on 2026-09-08, 3.0.2 on 2026-09-09) changed
|
|
172
|
+
`JSON.parse`'s arity and `ActiveSupport::JSON.decode` as of activesupport
|
|
173
|
+
8.1.3.1 still passes options positionally, so every json-column read raises
|
|
174
|
+
`ArgumentError: wrong number of arguments (given 2, expected 1)`. Measured on
|
|
175
|
+
PR #39's CI run `34423912397` (62 failures, all RM-graph/AQL specs) and
|
|
176
|
+
reproduced locally with `bundle update` on `master`; with the pin and a fresh
|
|
177
|
+
resolve, 295 examples / 0 failures. Removal condition: an activesupport release
|
|
178
|
+
whose `JSON.decode` accepts json 3 (check the Rails CHANGELOG), then delete the
|
|
179
|
+
three pins in one docs+tooling commit and let CI resolve fresh.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Fix: multi-leaf non-Observation entries constrain a nonexistent `component`
|
|
2
2
|
|
|
3
|
-
- Status:
|
|
3
|
+
- Status: **ruled 2026-08-27 — option (d), proper mapping, adopted. §§2-6 below are superseded; §8 is the normative spec, as corrected by §9 (the ruling's four corrections, applied 2026-09-10).**
|
|
4
4
|
- Target: `openehr-rails` (this repo). No cross-repo work.
|
|
5
5
|
- Issue: [#33](https://github.com/skoba/openehr-rails/issues/33)
|
|
6
6
|
- Log: `docs/reports/fsh-generator-log.md` (continuing R1-R5)
|
|
@@ -178,3 +178,190 @@ this repo's division of labor, or directly if the change is judged small enough
|
|
|
178
178
|
skip that split — decide at approval time), Claude Code review, full
|
|
179
179
|
`bundle exec rspec` + full-repo `rubocop` + `sushi` re-verification for
|
|
180
180
|
`bmi_calculation.opt`, commit(s), `docs/reports/fsh-generator-log.md` entry.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
# 8. RULING (2026-08-27): option (d), proper mapping to `Condition`
|
|
186
|
+
|
|
187
|
+
The recommendation in §3 — option (c), restrict multi-leaf non-`Observation`
|
|
188
|
+
entries and raise — **was not adopted**. The ruling directs a *proper mapping*:
|
|
189
|
+
`problem_diagnosis`'s leaves land on the real `Condition` elements that mean the
|
|
190
|
+
same thing. §§2-6 are kept for the record but are superseded by this section.
|
|
191
|
+
|
|
192
|
+
**This table is the single specification for both outputs.** `ProfileGenerator`
|
|
193
|
+
(the JSON facade) and `FshGenerator` both generate from it; neither may carry a
|
|
194
|
+
mapping decision the other doesn't.
|
|
195
|
+
|
|
196
|
+
## 8.1 Mapping table — `openEHR-EHR-EVALUATION.problem_diagnosis.v1` → `Condition` (FHIR R5)
|
|
197
|
+
|
|
198
|
+
Measured, not assumed: leaves are `FieldExtractor#entries` output for
|
|
199
|
+
`spec/templates/problem_list.opt`; every target element was compiled against
|
|
200
|
+
`hl7.fhir.r5.core#5.0.0` with `sushi` 3.16.0 (**0 Errors**) before this table was
|
|
201
|
+
written.
|
|
202
|
+
|
|
203
|
+
| openEHR leaf | Label (fixture, ja) | RM type | → `Condition` element | Rationale |
|
|
204
|
+
|---|---|---|---|---|
|
|
205
|
+
| *(archetype anchor)* | — | — | `category` — fixed coding `CKM#openEHR-EHR-EVALUATION.problem_diagnosis.v1` | The anchor cannot stay on `code`: under a proper mapping `code` is claimed by at0002, the diagnosis itself. `category` is R5's 0..* CodeableConcept for "what kind of Condition record is this", with an *example* binding, so a fixed archetype coding is legal there. |
|
|
206
|
+
| `at0002` | プロブレム・診断名 | `DV_CODED_TEXT`, value set `http://id.who.int/icd/release/11/mms` | `code` 0..1, `only CodeableConcept`, `from <ICD-11 MMS> (required)` | `Condition.code` is "identification of the condition, problem or diagnosis" — the direct counterpart. Its base binding is *example*, so a profile may tighten it to *required*. |
|
|
207
|
+
| `at0077` | 発症日時 | `DV_DATE_TIME` | `onsetDateTime` 0..1, `only dateTime` | `onset[x]` is the date/time the condition began; the `dateTime` choice matches `DV_DATE_TIME` exactly. (`sushi` normalises the path to `Condition.onset[x]` with `type: [dateTime]` — that is the shape the JSON facade emits.) |
|
|
208
|
+
| `at0003` | 臨床的に認識された日時 | `DV_DATE_TIME` | `recordedDate` 0..1, `only dateTime` | Nearest R5 element. **Approximation, recorded as such**: `recordedDate` is "when this Condition record was created in the system", which is not a synonym for "clinically recognised". No closer element exists in R5; the gap is written down here rather than implied by the mapping. |
|
|
209
|
+
| `at0030` | 治癒日時 | `DV_DATE_TIME` | `abatementDateTime` 0..1, `only dateTime` | `abatement[x]` is "the date the condition resolved or went into remission" — the direct counterpart. |
|
|
210
|
+
| `at0073` | 診断確度 (`at0074` 疑い / `at0075` 推定 / `at0076` 確定) | `DV_CODED_TEXT`, `terminology_id = "local"` | `verificationStatus` 0..1, `only CodeableConcept`, **no value-set binding emitted** | `verificationStatus` (unconfirmed \| provisional \| differential \| confirmed \| refuted \| entered-in-error) is the semantic counterpart of 診断確度. See 8.2 for why the local codes are deliberately *not* bound. |
|
|
211
|
+
|
|
212
|
+
**Nothing in this archetype is unmappable** — all five leaves land. What is
|
|
213
|
+
deliberately *not* emitted is in 8.2.
|
|
214
|
+
|
|
215
|
+
## 8.2 Deliberate omissions
|
|
216
|
+
|
|
217
|
+
- **`at0073`'s local code list (`at0074`/`at0075`/`at0076`) is not bound.**
|
|
218
|
+
`Condition.verificationStatus` has a **required** binding to
|
|
219
|
+
`http://hl7.org/fhir/ValueSet/condition-ver-status`; binding an archetype's
|
|
220
|
+
local at-codes there would be invalid. Translating 疑い/推定/確定 into
|
|
221
|
+
`provisional`/`confirmed`/etc. is a `ConceptMap` concern, outside what a
|
|
222
|
+
`StructureDefinition` can express. The profile therefore constrains the
|
|
223
|
+
element's cardinality and type only. This means the current
|
|
224
|
+
`apply_value_constraints` behaviour — emitting
|
|
225
|
+
`binding: { strength: 'required' }` for any `DV_CODED_TEXT` carrying a local
|
|
226
|
+
`code_list` — must **not** apply to a mapped leaf.
|
|
227
|
+
- **Multi-leaf non-`Observation` entries with no mapping table entry keep their
|
|
228
|
+
current behaviour.** The ruling scopes this fix to `problem_diagnosis`; the
|
|
229
|
+
`INSTRUCTION`→`ServiceRequest` case (`request-referral`, arriving with
|
|
230
|
+
referral v2) is reserved as its own Issue rather than generalised here.
|
|
231
|
+
|
|
232
|
+
## 8.3 Where the table lives
|
|
233
|
+
|
|
234
|
+
In `TypeMap`, next to `ENTRY_RESOURCES`, keyed by archetype id — the existing
|
|
235
|
+
RM-type→FHIR-resource mechanism, not a new conditional scattered across the two
|
|
236
|
+
generators. Both generators ask `TypeMap` the same question. The dead
|
|
237
|
+
`TypeMap.value_element` (§1) is removed as part of this: it was a stub for
|
|
238
|
+
exactly this resource-type branch and never implemented it.
|
|
239
|
+
|
|
240
|
+
## 8.4 TDD
|
|
241
|
+
|
|
242
|
+
- **Red**: `problem_list.opt`'s generated FSH under `sushi` 3.16.0 — measured
|
|
243
|
+
**29 Errors** today (all `No element found at path component…`). Pinned as the
|
|
244
|
+
starting measurement.
|
|
245
|
+
- **Green**: the same fixture compiles with **0 Errors**. The exact rule set the
|
|
246
|
+
implementation must emit was pre-verified against
|
|
247
|
+
`hl7.fhir.r5.core#5.0.0` before implementation began.
|
|
248
|
+
- **JSON facade**: `profile_generator_spec.rb` gains expectations for the mapped
|
|
249
|
+
`Condition` elements, and asserts no `Condition.component` element is produced
|
|
250
|
+
— the original complaint in #33.
|
|
251
|
+
- **Regression pin**: `bmi_calculation.opt` (multi-leaf `Observation`) is
|
|
252
|
+
untouched by the new branch and must stay green, `component` slicing intact.
|
|
253
|
+
|
|
254
|
+
## 8.5 Semver
|
|
255
|
+
|
|
256
|
+
**Minor.** The JSON facade's output shape changes for `EVALUATION` entries
|
|
257
|
+
(`Condition.component` slices disappear, real `Condition` elements appear), which
|
|
258
|
+
is observable to any host app consuming `app/fhir/profiles/*.json`. Ships with
|
|
259
|
+
`#34`'s `release:check` change; version finalised at release inventory, 0.7.0
|
|
260
|
+
expected.
|
|
261
|
+
|
|
262
|
+
# 9. RULING FOLLOW-UP (2026-09-10): the four corrections to §8, applied
|
|
263
|
+
|
|
264
|
+
The 2026-08-27 ruling approved §8 **with four corrections**: (1) the archetype
|
|
265
|
+
anchor is a *slice* of `category`, (2) `at0003`'s approximation is stated on the
|
|
266
|
+
element as a `^comment`, (3) unmapped multi-leaf non-`Observation` entries are
|
|
267
|
+
*skipped and reported* per entry, (4) through a named exception class. The
|
|
268
|
+
implementation commit `01f31f3` (2026-08-27 12:38 JST) landed 21 minutes after §8
|
|
269
|
+
itself (`07767cc`, 12:17 JST) and carries none of them; this repository held no
|
|
270
|
+
record of the four points until this section. Applied under the reopened #33.
|
|
271
|
+
Everything below was measured against `hl7.fhir.r5.core#5.0.0` with `sushi`
|
|
272
|
+
3.16.0 before the code was written, as §8 was.
|
|
273
|
+
|
|
274
|
+
## 9.1 Correction 1 — the anchor is a slice of `category`
|
|
275
|
+
|
|
276
|
+
- **Before**: `* category.coding.system = "…"` / `* category.coding.code = #…`
|
|
277
|
+
(JSON: one `Condition.category` element with `patternCodeableConcept`).
|
|
278
|
+
- **Why that was wrong**: `Condition.category` is `0..*`. Fixing the coding on the
|
|
279
|
+
element constrains *every* repetition, so a conforming instance could not also
|
|
280
|
+
carry e.g. `problem-list-item` beside the archetype coding.
|
|
281
|
+
- **After**: pattern slicing on `$this`, `rules = #open`, `contains ckm 1..1`,
|
|
282
|
+
`category[ckm] = http://openehr.org/ckm/archetypes#<archetype id>` (FSH
|
|
283
|
+
assignment to a `CodeableConcept` is a `patternCodeableConcept`, the same shape
|
|
284
|
+
the JSON facade emits). JSON: two `Condition.category` elements — the slicing
|
|
285
|
+
root (`discriminator: [{type: pattern, path: $this}]`, `rules: open`) and the
|
|
286
|
+
`ckm` slice (`min 1`, `max "1"`, `patternCodeableConcept`). The slice name
|
|
287
|
+
reuses the name the FSH output already gives the CKM coding slice on
|
|
288
|
+
`code.coding`; it is `TypeMap::ANCHOR_SLICE` so both generators share it.
|
|
289
|
+
- **Measured**: the candidate rule set compiled to **0 Errors** before
|
|
290
|
+
implementation; the generator's own output afterwards byte-matches the golden
|
|
291
|
+
and compiles to **0 Errors** together with `bmi_calculation.opt`.
|
|
292
|
+
|
|
293
|
+
## 9.2 Correction 2 — `at0003` carries a `^comment`
|
|
294
|
+
|
|
295
|
+
§8.1 records `at0003` → `recordedDate` as an approximation in this document only;
|
|
296
|
+
the profile now says so itself. `ENTRY_ELEMENT_MAPS` gains a `:comment` key on
|
|
297
|
+
the leaf, emitted as `* recordedDate ^comment = "…"` (FSH) and
|
|
298
|
+
`ElementDefinition.comment` (JSON) — table-driven, so the two outputs cannot
|
|
299
|
+
differ on the wording. Text: *Approximation: openEHR at0003 (Date/time clinically
|
|
300
|
+
recognised) has no exact counterpart in FHIR R5 Condition; recordedDate is when
|
|
301
|
+
this Condition record was created in the system. See
|
|
302
|
+
docs/design/multi-leaf-non-observation-plan.md section 8.1.*
|
|
303
|
+
|
|
304
|
+
## 9.3 Corrections 3 and 4 — skip-and-report, `UnsupportedProfileError`
|
|
305
|
+
|
|
306
|
+
These answer §6's open questions 2 and 3.
|
|
307
|
+
|
|
308
|
+
- **Condition**: an entry with **more than one leaf**, whose base resource is
|
|
309
|
+
**not `Observation`**, and which has **no row** in `ENTRY_ELEMENT_MAPS`. Before
|
|
310
|
+
this section such an entry silently took the `component` path — the original
|
|
311
|
+
#33 defect, still live for `ServiceRequest`/`Procedure`/`Encounter` (the
|
|
312
|
+
`[Unreleased]` CHANGELOG said as much).
|
|
313
|
+
- **One decision point** (§3): `TypeMap.assert_supported!(entry)` raises
|
|
314
|
+
`OpenehrRails::Fhir::UnsupportedProfileError` (`archetype_id`, `resource_type`,
|
|
315
|
+
`leaf_count`, and a message naming all three plus #33/#35). Both generators
|
|
316
|
+
call it; neither carries its own conditional.
|
|
317
|
+
- **Skip-and-report per entry, not raise-through** (Q2): `ProfileGenerator` and
|
|
318
|
+
`FshGenerator` partition entries at construction, generate for the supported
|
|
319
|
+
ones, and expose the errors in template order through `#skipped`. The batch
|
|
320
|
+
never aborts on one bad entry. The Rails generators (`openehr:fhir_profile`,
|
|
321
|
+
`openehr:scaffold --fhir`) print each skip as `say_status :skip, …, :yellow` —
|
|
322
|
+
the report reaches the person running the generator, and a library caller that
|
|
323
|
+
wants a hard failure re-raises from `#skipped`.
|
|
324
|
+
- **Class** (Q3): `OpenehrRails::Fhir::UnsupportedProfileError < StandardError`,
|
|
325
|
+
in `lib/openehr_rails/fhir/unsupported_profile_error.rb`, required before
|
|
326
|
+
`type_map`.
|
|
327
|
+
- **Not covered, deliberately**: single-leaf entries. The ruling's wording is
|
|
328
|
+
多葉 (multi-leaf); the single-leaf non-`Observation` path has its own defect,
|
|
329
|
+
recorded in 9.5 rather than folded in here.
|
|
330
|
+
|
|
331
|
+
## 9.4 TDD (resolution shape (b) enhancement for all four)
|
|
332
|
+
|
|
333
|
+
- **Red**: specs written first in `fsh_generator_spec.rb` and
|
|
334
|
+
`profile_generator_spec.rb` (slice, `^comment`/`comment`, skip-and-report,
|
|
335
|
+
`#skipped` empty for an all-`Observation` template) plus the regenerated
|
|
336
|
+
golden: **36 examples, 12 failures** on the pre-correction code.
|
|
337
|
+
- **Green**: `spec/openehr_rails/fhir/`: **48 examples, 0 failures**; full suite
|
|
338
|
+
**304 examples, 0 failures** (295 before).
|
|
339
|
+
- **Synthetic entry, spec-level, not a fixture file**: no real multi-leaf
|
|
340
|
+
`INSTRUCTION` OPT exists in this repo (#35 is blocked on exactly that), so the
|
|
341
|
+
skip-and-report specs stub `FieldExtractor` to return `problem_list.opt`'s real
|
|
342
|
+
entry plus one invented `INSTRUCTION` entry
|
|
343
|
+
(`openEHR-EHR-INSTRUCTION.synthetic_unmapped_test.v1`, two of the real leaves
|
|
344
|
+
relabelled). The id is self-evidently invented; the spec comment says so and
|
|
345
|
+
points here.
|
|
346
|
+
- **Golden**: regenerated from the corrected generator, diffed against the
|
|
347
|
+
pre-verified candidate (identical), then compiled again: **0 Errors**.
|
|
348
|
+
|
|
349
|
+
## 9.5 Residual found while measuring 9.1 — not fixed here
|
|
350
|
+
|
|
351
|
+
A single-leaf non-`Observation` entry still takes the legacy path and emits
|
|
352
|
+
`* value[x] 0..1` (FSH) / `<Resource>.value[x]` (JSON). `Condition` has no
|
|
353
|
+
`value[x]` either: a hand-written probe (`Parent: Condition`, `* value[x] 0..1`)
|
|
354
|
+
compiled under the same `sushi` run to **1 Error**, `No element found at path
|
|
355
|
+
value[x] for CardRule`. No fixture in this repository reaches that path
|
|
356
|
+
(`problem_list.opt` is 5-leaf; every single-leaf fixture entry is
|
|
357
|
+
`OBSERVATION`), so nothing observable regressed. Outside the ruling's scope
|
|
358
|
+
(多葉); filed as its own bug Issue rather than widened into #33 — the natural fix
|
|
359
|
+
is to extend `assert_supported!` to it, but that is a second contract change and
|
|
360
|
+
gets its own red spec.
|
|
361
|
+
|
|
362
|
+
## 9.6 Semver
|
|
363
|
+
|
|
364
|
+
Still **minor**, on top of §8.5: `#skipped` and `UnsupportedProfileError` are
|
|
365
|
+
new public API; the `category` anchor and the `comment` change the JSON facade's
|
|
366
|
+
shape again for `EVALUATION` entries. `CHANGELOG.md` `[Unreleased]` updated in
|
|
367
|
+
the same change.
|