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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69d9f917d3eb32a92e7d47aeb3f43bf8992a6a44f8ceb49320a1e5a6130ecebf
4
- data.tar.gz: 1f39015dbd51241ea4dca624bd89e988a82bf62ef3a02964e54a343b6e8ccbf1
3
+ metadata.gz: 065a9ba44f893adaea9e6c0ccf6f3c5f8a0af8c8c3897379cd008e5642e99a7a
4
+ data.tar.gz: 98f81ad0baf2b086e2193102c25804b3affcd2fa1ffb4c0730952d6a916e3a58
5
5
  SHA512:
6
- metadata.gz: 5acec88b6d3dee1ab32167080639a81052911eee90043ae09474e083fa0c92e081da69dade909848da04743cffc96603e887765a8efaff29d6172f873e93a074
7
- data.tar.gz: 1c134c5a1147f54ee8628b62efdcfcb867f257f634a5094a4f0aacb2af460ac28cfcaa011457017610262686437041ec85045186f9c05acb034ed4080e6ec793
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: draft, awaiting approval
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.