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 +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +3 -0
- data/CHANGELOG.md +91 -0
- data/CLAUDE.md +52 -1
- data/Gemfile +7 -0
- data/docs/backlog.md +39 -0
- data/docs/design/fsh-generator-plan.md +113 -0
- data/docs/design/multi-leaf-non-observation-plan.md +367 -0
- data/docs/reports/binding-extraction-log.md +66 -0
- data/docs/reports/fsh-generator-log.md +925 -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 +215 -0
- 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 +2 -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 +235 -0
- 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 +8 -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
|
|
@@ -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`)
|