openehr-rails 0.5.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: 49c5536ccdf6abee6fb03aea4a4ed6002fc6402e6551d6f234b0e6a1a7b9200c
4
- data.tar.gz: c36b2b1b58f60d22c2cbbbcb5754108bb3235f12737bd573103d2a3463a70618
3
+ metadata.gz: 065a9ba44f893adaea9e6c0ccf6f3c5f8a0af8c8c3897379cd008e5642e99a7a
4
+ data.tar.gz: 98f81ad0baf2b086e2193102c25804b3affcd2fa1ffb4c0730952d6a916e3a58
5
5
  SHA512:
6
- metadata.gz: 0651aa4386933ccff02d230ab06d1eda996ca102da9b99ba2ebf09d3fde233ce7f08cea00b26644b744b92b647f917fe0e80dd6169fa158d9af19759c7db7f1b
7
- data.tar.gz: 4358f499a80546004d5ca80b1ec542f2fe2d977a674461be36ee1afb288322717b71507978a50b1f7560edf1248835783f29e60b0bc874f164d8c4141bcc06a8
6
+ metadata.gz: 42353942db8a02b901e3727a85446fe1995f3c953fd33f3da2dfea68d7018b533de95b97494db856593df77ccbacd8d57f0f4bb4cdcedee086da6c4e44035a04
7
+ data.tar.gz: 4fd7e793d2d77230e585dcf06204147926d3b36a86a576525614d19395930f80b4fe38bc0b534de5a0f6b26e98e0859d5c8275ce865a1efedf3aa942dc1d272e
@@ -16,6 +16,9 @@ assignees: ''
16
16
  - `openehr` version: <!-- e.g. ~> 2.3 -->
17
17
  - Rails version: <!-- e.g. 8.1 -->
18
18
  - Ruby version: <!-- e.g. 3.3, 3.4, 4.0 -->
19
+ - If the report involves an OPT/ADL file: the generating tool and its
20
+ version (e.g. Better Archetype Designer / Ocean Template Designer 2.6 /
21
+ ADL Workbench / LinkEHR / HMC).
19
22
 
20
23
  ## Reproduction
21
24
 
data/CHANGELOG.md CHANGED
@@ -7,6 +7,97 @@ 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
+
71
+ ## [0.6.0] - 2026-08-26
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
+
87
+ ### Added
88
+ - `OpenehrRails::Fhir::FshGenerator` renders one FHIR Shorthand profile per
89
+ openEHR entry, including metadata, archetype codes, value constraints,
90
+ component slicing, quantity units, fixed terminology codes, and required
91
+ external value-set bindings. Observation-mapped output is Sushi-verified;
92
+ multi-leaf non-Observation entries have a known structural gap tracked as #33.
93
+
94
+ ### Changed
95
+ - `FshGenerator` no longer depends on `active_support/core_ext/string`; the
96
+ three String helpers it used are now small plain-Ruby private methods,
97
+ verified byte-identical output. Restores the Rails-independence this class
98
+ was designed for (see `anlage`'s `docs/design/fsh-plan.md`), so it can move
99
+ wholesale to a future satellite gem without a Rails dependency riding along.
100
+
10
101
  ## [0.5.0] - 2026-08-25
11
102
 
12
103
  ### Added
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
@@ -102,6 +133,26 @@ lasting effect, but the near-miss is what prompted this generalization. See
102
133
  logging the underlying structural fix under consideration - one
103
134
  worktree/session per repo instead of per-command vigilance.)
104
135
 
136
+ ## Cross-repository implementation work needs its own authorization gate
137
+
138
+ The moment it becomes clear that the actual implementation target for a piece of
139
+ work is a repository other than this one, stop before starting that work and insert
140
+ a gate report confirming the implementation target, rather than sliding from
141
+ read-only cross-repo reference (already permitted for design/evidence-gathering)
142
+ into cross-repo write work on the strength of that same permission. Correctly
143
+ targeting a command once you've decided to cross the boundary (the previous
144
+ section's `cd`/`-C`/`-R` discipline) is a different concern from being authorized to
145
+ cross it at all - following the operational convention precisely does not retroactively
146
+ authorize the crossing.
147
+
148
+ (Added 2026-08-26, alongside the `#32`/`#33` FSH-generator work continuation, to
149
+ name explicitly a distinction that the existing repository-context rule left
150
+ implicit: it governs *how* to target a cross-repo command correctly, not *whether*
151
+ crossing into implementation work on another repository has actually been
152
+ authorized for the task at hand. A matching line is planned for `anlage`'s own
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.)
155
+
105
156
  ## Project Overview
106
157
 
107
158
  This is `openehr-rails`, a Rails engine gem that turns an openEHR Operational Template (`.opt`, ADL2/XML) into a working Rails resource in one command: `rails generate openehr:scaffold path/to/template.opt --fhir` emits a model, migration, controller, views, i18n locale, and (with `--fhir`) HL7 FHIR R5 `StructureDefinition` profiles. Generated models persist both as typed columns (for forms/search) and as full openEHR RM data (canonical JSON + a typed node graph with immutable-append versioning), and are queryable via a growing AQL surface. A mountable admin engine (`/openehr`) provides template upload/management, runtime scaffolding, and a FHIR R5 facade. Legacy ADL-archetype-only generators (model/controller/migration/helper/assets/i18n/template/template_model, based on `Openehr::Generators::ArchetypedBase`) have been removed — OPT is the only supported input format for scaffolding.
@@ -181,4 +232,4 @@ This is `openehr-rails`, a Rails engine gem that turns an openEHR Operational Te
181
232
  - Guard setup available for TDD workflow
182
233
  - RuboCop Rails linting configured
183
234
  - SimpleCov for test coverage
184
- - `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
@@ -34,6 +34,23 @@ actually running, not just present:
34
34
  push will reproduce the same red run. See "Release automation" below.
35
35
  - Most recent `master` push at the time of this record (`4055ec3`): CI run
36
36
  `32569774700`, success.
37
+ - **Tag `v0.5.0` (2026-08-25, #30): `release.yml` run
38
+ [32831263811](https://github.com/skoba/openehr-rails/actions/runs/32831263811) --
39
+ overall conclusion **success**, all 12 jobs green (9 reused `ci.yml` spec-matrix
40
+ jobs, `demo smoke`, `application template smoke test`, `Build gem artifact`).**
41
+ First fully-green tag-push run on record -- confirms the "Release automation fix
42
+ landed (2026-08-23, PR #28)" note below actually fixed the structural red
43
+ described above, not just that the workflow file changed (`v0.4.1`/`v0.4.0`/`v0.3.0`
44
+ were all red at the now-removed RubyGems-publish step; `v0.5.0` has no such step to
45
+ fail at). Downloaded the run's `gem` artifact and independently built
46
+ `pkg/openehr-rails-0.5.0.gem` locally at the same tagged commit (`69db63f`): both
47
+ 197632 bytes, identical sha256
48
+ `e07815bd1c86736403cbb558fec869fbe04666f695e6cc12a41dad9be77230e6`. RubyGems
49
+ publish (human `gem push`, per "Release automation" below) confirmed complete via
50
+ `https://rubygems.org/api/v1/versions/openehr-rails.json`: version `0.5.0`,
51
+ `created_at: 2026-08-25T09:26:15.342Z`, published `sha` field matches the same
52
+ sha256 above exactly -- local build, CI artifact, and the published gem are all
53
+ byte-identical.
37
54
 
38
55
  Recorded here after this file previously claimed "this repo currently has no CI
39
56
  workflow gating pull requests" — wrong on both existence and execution. This file is
@@ -73,6 +90,13 @@ status entry above for verification details.
73
90
  docs-only pass — implementing the workflow change goes through an Issue (once
74
91
  ticket-driven work applies to it) plus the normal explore → plan → approval gate,
75
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).
76
100
 
77
101
  ## From #25 / PR #26 (FieldExtractor terminology scope fix, 2026-08-22)
78
102
 
@@ -138,3 +162,18 @@ status entry above for verification details.
138
162
  duplicate-method inventory being produced first.
139
163
  - **#2** (constraint -> HTML attribute mapping extraction): gated on a generality decision
140
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.
@@ -0,0 +1,113 @@
1
+ # Enhancement: FshGenerator (FSH output alongside ProfileGenerator)
2
+
3
+ - Status: draft, approved-by-citation (design decisions made upstream in
4
+ `skoba/anlage#17`, not re-litigated here)
5
+ - Target: `openehr-rails` (this repo)
6
+ - Issue: [#32](https://github.com/skoba/openehr-rails/issues/32)
7
+ - Log: `docs/reports/fsh-generator-log.md`
8
+ - Design authority: `skoba/anlage`'s `docs/design/fsh-plan.md` (2026-08-26
9
+ ruling, all 4 judgments approved) and `docs/reports/fsh-log.md` R1-R4.
10
+ This doc translates that already-approved plan into this repo's
11
+ file:line reality; it does not re-decide source/scope/output-form.
12
+
13
+ ## 1. Summary
14
+
15
+ Add `OpenehrRails::Fhir::FshGenerator`, a sibling to
16
+ `OpenehrRails::Fhir::ProfileGenerator` that renders FSH (FHIR Shorthand)
17
+ text instead of a JSON StructureDefinition Hash, from the exact same
18
+ input: `OpenehrRails::Opt::FieldExtractor#entries`. No independent OPT
19
+ walk — this is the "share the intermediate representation" design
20
+ anlage's plan settled on (rejecting a pathcards-based or
21
+ independently-walking alternative as violating the no-double-derivation
22
+ constraint).
23
+
24
+ ## 2. Input contract (already shipped, `skoba/openehr-rails#30` / 0.5.0)
25
+
26
+ `FieldExtractor#entries[].fields[]` now always carries (verified live
27
+ in `field_extractor.rb:160-177`, doc comment `field_extractor.rb:1-23`):
28
+
29
+ - `value_set_uri`: `C_CODE_REFERENCE#reference_set_uri`, else `nil`
30
+ - `code_bindings`: `[{system_uri:, code:}]`, else `[]` — not limited to
31
+ `DV_CODED_TEXT` (BMI's LOINC binding sits on a `DV_QUANTITY` element,
32
+ `CHANGELOG.md` [0.5.0] Added note)
33
+
34
+ No further gem work is a prerequisite for this Issue.
35
+
36
+ ## 3. v1 scope (per the approved anlage plan, §"v1サブセットの範囲確定")
37
+
38
+ Mirror exactly what `ProfileGenerator` already emits, in FSH syntax,
39
+ plus binding writes (now unblocked):
40
+
41
+ | JSON (`profile_generator.rb`) | FSH form |
42
+ |---|---|
43
+ | `build_profile` metadata (`:33-50`) | `Profile:`/`Parent:`/`Id:`/`Title:` header lines |
44
+ | `code_element` (`:62-69`) | `* code.coding.system = ...` / `* code.coding.code = ...` (or a `patternCodeableConcept`-equivalent assignment — pick whichever Sushi compiles identically to the JSON shape; verify by comparing compiled output, not by assumption) |
45
+ | `value_elements` (`:71-80`) | `* value[x] <min>..<max>` + `* value[x] only <Type>` |
46
+ | `component_elements`/`component_slice` (`:82-113`) | slicing declaration (`^slicing.discriminator...`) + `* component contains <slice> <card>` + per-slice `code`/`value[x]` assignment (see anlage's `docs/reports/fsh-log.md` R1 for a working Sushi-verified example of this exact shape) |
47
+ | `DV_QUANTITY` unit (`apply_value_constraints`, `:117-124`) | `* value[x].unit = "..."` (or equivalent fixed-value rule) |
48
+ | `DV_CODED_TEXT` binding (`:125-131`, now includes `valueSet`) | `field[:value_set_uri]` present → `* value[x] from <uri> (required)`. `field[:code_bindings]` present (any rm_type) → fixed `patternCodeableConcept`-equivalent assignment per binding entry |
49
+
50
+ Explicitly out of scope for v1 (matches the JSON facade's own gaps, not
51
+ new ones introduced here): `magnitude_range` (extracted but never
52
+ emitted by `ProfileGenerator` either — a separate, pre-existing gap).
53
+ `mml_referral`-scale templates are out of v1's verification scope per
54
+ the approved plan (size cap); this Issue's fixtures stay to the 3 small
55
+ ones listed below.
56
+
57
+ ## 4. Fixtures (no new SNOMED literal budget — reuse what's already here)
58
+
59
+ - `spec/generators/templates/bmi_calculation.opt` — single-leaf,
60
+ `DV_QUANTITY`, real SNOMED-CT (`60621009`) + LOINC `code_binding` on
61
+ `at0004` (`spec/templates/bmi_calculation_without_uid.opt:1683-1697`
62
+ for the byte-identical sibling with visible XML)
63
+ - `spec/templates/problem_list.opt` — `C_CODE_REFERENCE`/ICD-11
64
+ `value_set_binding` on `at0002`, local `code_list` contrast on
65
+ `at0073` (byte-identical copy of anlage's `ProblemList.opt`,
66
+ provenance comment already in the file per `0bbbc47`)
67
+
68
+ Both are already indexed by existing specs
69
+ (`spec/openehr_rails/fhir/profile_generator_spec.rb`,
70
+ `spec/openehr_rails/opt/field_extractor_binding_spec.rb`) — reuse the
71
+ same `template` `let` pattern.
72
+
73
+ ## 5. TDD (t-wada, per this repo's CLAUDE.md)
74
+
75
+ 1. Red/Green: `FshGenerator` non-binding v1 subset against
76
+ `bmi_calculation.opt` — metadata, single-leaf `value[x]`, unit. Fix
77
+ the exact FSH text with a golden spec (string equality or a small
78
+ set of `include` assertions on stable substrings — match whichever
79
+ style `profile_generator_spec.rb` already uses for the JSON side)
80
+ 2. Red/Green: `code_binding` write against `bmi_calculation.opt`'s
81
+ `at0004` (SNOMED-CT `60621009` and/or the LOINC binding — pick
82
+ whichever demonstrates the fixed-value FSH form most clearly)
83
+ 3. Red/Green: `value_set_binding` write against `problem_list.opt`'s
84
+ `at0002` (ICD-11) — fix the `from <uri> (required)` line
85
+ 4. Red/Green: multi-leaf `component` slicing — `bmi_calculation.opt`'s
86
+ `openEHR-EHR-OBSERVATION.body_mass_index.v2` entry already has 2 leaf
87
+ fields (verified: `bundle exec ruby` with `FieldExtractor#entries`,
88
+ 2026-08-26), and `at0004`'s SNOMED/LOINC `code_binding` sits inside
89
+ this same multi-leaf entry — one fixture covers both slicing and
90
+ binding for this case. No new fixture needed (`problem_list.opt` also
91
+ has 5 leaf fields, `lab_result_report_reduced.opt` has 3, for
92
+ reference)
93
+ 5. Manual verification (not part of this Issue's automated suite,
94
+ done once locally before closing): pipe generated FSH through
95
+ `fsh-sushi` and confirm 0 Errors, matching the shapes anlage already
96
+ verified by hand (`docs/reports/fsh-log.md` R1 in `skoba/anlage`)
97
+
98
+ ## 6. Structural note
99
+
100
+ `FshGenerator` takes only `FieldExtractor#entries` (or a `template` it
101
+ extracts from, same as `ProfileGenerator.new(template)`) — no
102
+ `ActiveRecord::Base`/`Rails` dependency. This keeps it relocatable if a
103
+ future `openehr-fhirbridge` satellite gem is split out (anlage
104
+ `docs/backlog.md` item 5, not a decision made in this Issue).
105
+
106
+ ## 7. Out of scope here
107
+
108
+ - CI wiring for Sushi verification (anlage-side decision, ruled out for
109
+ v1 — `docs/design/fsh-plan.md`'s 判断3 in `skoba/anlage`)
110
+ - The `rake fsh:export` output path (anlage-side, `skoba/anlage`'s own
111
+ Issue #17 scope)
112
+ - Any RubyGems release/publish step (human-gated per this repo's
113
+ `docs/backlog.md` "Release automation" / `skoba/openehr-rails#27`)