openehr-rails 0.7.0 → 0.7.1
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 +24 -0
- data/docs/backlog.md +17 -0
- data/docs/design/multi-leaf-non-observation-plan.md +31 -0
- data/docs/design/rm-object-builder-section-instruction-plan.md +134 -0
- data/docs/reports/fsh-generator-log.md +76 -0
- data/docs/reports/referral-upstream-log.md +64 -0
- data/lib/openehr_rails/aql/dataset_adapter.rb +22 -4
- data/lib/openehr_rails/fhir/profile_generator.rb +1 -1
- data/lib/openehr_rails/fhir/type_map.rb +6 -6
- data/lib/openehr_rails/fhir/unsupported_profile_error.rb +12 -10
- data/lib/openehr_rails/rm/rm_object_builder.rb +25 -1
- data/lib/openehr_rails/version.rb +1 -1
- data/spec/openehr_rails/aql/dataset_adapter_spec.rb +47 -0
- data/spec/openehr_rails/aql/executor_spec.rb +28 -0
- data/spec/openehr_rails/fhir/fsh_generator_spec.rb +43 -0
- data/spec/openehr_rails/fhir/profile_generator_spec.rb +41 -0
- data/spec/openehr_rails/rm/rm_object_builder_spec.rb +21 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cf0122af26f620b3082c006c5d469fdf3710c5dd1647b856c9024c8e4e3f7c66
|
|
4
|
+
data.tar.gz: b54d925956180cd9baffab027d42564ecd55864f839698952ed2bfbfdfb9356d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 90fadabcf5f3215e6c24f4c75f5bef98e38b831286e0f0da33d6d67ae6743937d7958d286384c85042830452e47fe08e22eca6dce72541ab7bfb5395cc029ef2
|
|
7
|
+
data.tar.gz: 700d4afc6e60805cc76c478035131d0aa62b5f0decc09d44c67f0ac1bb4e8181500de65ba56b40f2974a0e24259945fb26285a6b3eac0ebe3ca394656cdc1c56
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.1] - 2026-09-25
|
|
11
|
+
|
|
12
|
+
> **Upgrade note.** AQL now skips a stored composition that holds an RM type
|
|
13
|
+
> `RmObjectBuilder` cannot rebuild, with a warning
|
|
14
|
+
> (`openehr-rails AQL: skipping composition uid=...`), instead of failing every
|
|
15
|
+
> query in the store. No host-app change is needed; if that line appears in
|
|
16
|
+
> your log, the composition is a #45 case (SECTION / INSTRUCTION / ACTIVITY
|
|
17
|
+
> read-back, next minor release).
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- A single-leaf entry mapped to a resource other than `Observation` and without a
|
|
21
|
+
`TypeMap::ENTRY_ELEMENT_MAPS` row no longer produces `<Resource>.value[x]`, which
|
|
22
|
+
`Condition`/`ServiceRequest`/`Procedure`/`Encounter` do not have; it is skipped and
|
|
23
|
+
reported through `#skipped` like the multi-leaf case since 0.7.0 (#38). The
|
|
24
|
+
`UnsupportedProfileError` message now names both missing elements.
|
|
25
|
+
- AQL no longer fails for the whole store when one stored composition cannot be
|
|
26
|
+
rebuilt as RM objects (a node type outside `RmObjectBuilder::TYPE_CLASSES`,
|
|
27
|
+
such as `SECTION`/`INSTRUCTION`, or an RM constructor rejecting stored data).
|
|
28
|
+
`DatasetAdapter` now warns (`Rails.logger` when present, else stderr), naming
|
|
29
|
+
the composition uid and the cause, and skips that composition; queries on the
|
|
30
|
+
rest of the store return their rows. `RmObjectBuilder` raises
|
|
31
|
+
`OpenehrRails::Rm::UnsupportedRmTypeError` (composition uid, node path,
|
|
32
|
+
rm_type) instead of an accidental `NoMethodError` (#44).
|
|
33
|
+
|
|
10
34
|
## [0.7.0] - 2026-09-10
|
|
11
35
|
|
|
12
36
|
> **Upgrade note.** Regenerate `app/fhir/profiles/*.json`. `Condition.component`
|
data/docs/backlog.md
CHANGED
|
@@ -74,6 +74,15 @@ that verification is deferred to the next real release (>= 0.5.0), not simulated
|
|
|
74
74
|
|
|
75
75
|
## Release automation
|
|
76
76
|
|
|
77
|
+
- **`release.yml` records no sha256 of the built `.gem`** (found at the 0.7.0
|
|
78
|
+
release, 2026-09-10, `docs/reports/fsh-generator-log.md` R14). The only digest in
|
|
79
|
+
the run log is `actions/upload-artifact`'s "SHA256 digest of uploaded artifact",
|
|
80
|
+
which hashes the uploaded archive, not the gem file, so `CLAUDE.md`'s "compare
|
|
81
|
+
against the value recorded for that run" cannot be followed literally; the
|
|
82
|
+
tag-rebuild cross-check is what pins the bytes today. Add a "Record sha256" step
|
|
83
|
+
after `rake build` (`sha256sum pkg/*.gem`), as openehr-ruby's `release.yml` has.
|
|
84
|
+
CI-only change; needs a PR, no Issue.
|
|
85
|
+
|
|
77
86
|
**Done (2026-08-23, PR #28)** — kept below for the original rationale; see the CI
|
|
78
87
|
status entry above for verification details.
|
|
79
88
|
|
|
@@ -122,6 +131,14 @@ status entry above for verification details.
|
|
|
122
131
|
|
|
123
132
|
## Fixture conventions
|
|
124
133
|
|
|
134
|
+
- **`spec/templates/sample_blood_pressure.opt` does not parse** -- `OpenehrRails::Opt.parse`
|
|
135
|
+
raises `ArgumentError: invalid archetype id form` from
|
|
136
|
+
`OpenEHR::RM::Support::Identification::ArchetypeID` under openehr 2.4.2 *and* 2.4.3
|
|
137
|
+
(measured 2026-09-10 while checking fixtures for #38), and no spec, script or
|
|
138
|
+
generator references the file. Orphan fixture: either find which archetype id in
|
|
139
|
+
it is malformed and decide whether that is an openehr-ruby parser gap worth an
|
|
140
|
+
upstream Issue, or delete the file. Not touched in 0.7.0.
|
|
141
|
+
|
|
125
142
|
- **Licensed terminology-code literals in spec expectations: keep minimal, cite the
|
|
126
143
|
source fixture's file:line.** Adopted from Anlage's C2 firewall precedent (SNOMED
|
|
127
144
|
CT is a licensed terminology; reproducing its codes verbatim in more places than
|
|
@@ -365,3 +365,34 @@ Still **minor**, on top of §8.5: `#skipped` and `UnsupportedProfileError` are
|
|
|
365
365
|
new public API; the `category` anchor and the `comment` change the JSON facade's
|
|
366
366
|
shape again for `EVALUATION` entries. `CHANGELOG.md` `[Unreleased]` updated in
|
|
367
367
|
the same change.
|
|
368
|
+
|
|
369
|
+
## 9.7 #38 (2026-09-10 ruling): the decision point widened to any leaf count
|
|
370
|
+
|
|
371
|
+
The ruling on 9.5 allowed #38 to ride in 0.7.0 if it stayed on the same
|
|
372
|
+
decision point, small, and green. It does: `TypeMap.assert_supported!` drops
|
|
373
|
+
its `fields.size <= 1` early return, so *any* entry whose base resource is not
|
|
374
|
+
`Observation` and which has no `ENTRY_ELEMENT_MAPS` row is skipped and
|
|
375
|
+
reported -- a single leaf would have produced `<Resource>.value[x]`, which
|
|
376
|
+
`Condition` / `ServiceRequest` / `Procedure` / `Encounter` lack just as they
|
|
377
|
+
lack `component` (measured: 1 sushi Error on the 9.5 probe). The error message
|
|
378
|
+
now names both missing elements and the leaf count in the singular where it is
|
|
379
|
+
one. Resolution shape (a) bug.
|
|
380
|
+
|
|
381
|
+
- **Fixture check before widening**: every OPT under `spec/templates/`,
|
|
382
|
+
`spec/generators/templates/` and `demo_assets/templates/` was listed with
|
|
383
|
+
`FieldExtractor#entries` -- all single-leaf entries are `OBSERVATION`
|
|
384
|
+
(`height.v2`, `body_weight.v2`, `heart_rate-pulse.v1`); the only
|
|
385
|
+
non-`Observation` entry is `problem_diagnosis.v1` (5 leaves, mapped). So no
|
|
386
|
+
fixture's output changes. (`spec/templates/sample_blood_pressure.opt` does not
|
|
387
|
+
parse at all -- `ArgumentError: invalid archetype id form` -- under openehr
|
|
388
|
+
2.4.2 *and* 2.4.3, and no spec references it; pre-existing, noted in
|
|
389
|
+
`docs/backlog.md`, not touched here.)
|
|
390
|
+
- **Red**: 4 new examples (two per generator, synthetic single-leaf
|
|
391
|
+
`EVALUATION` entry `openEHR-EHR-EVALUATION.synthetic_single_leaf_test.v1`):
|
|
392
|
+
**40 examples, 4 failures** across the two spec files.
|
|
393
|
+
- **Green**: the same files 40/0; `spec/openehr_rails/fhir/` 52/0; full suite
|
|
394
|
+
308/0. Regenerated FSH for `problem_list.opt` + `bmi_calculation.opt`
|
|
395
|
+
byte-identical to before and **0 Errors** under `sushi` 3.16.0 -- the pin.
|
|
396
|
+
- **Semver**: 0.7.1 (0.7.0 shipped before PR #43 was merged; see log R13); a bug fix (patch) that changes no
|
|
397
|
+
fixture output, only what an unmapped single-leaf non-`Observation` entry
|
|
398
|
+
yields (invalid FHIR before, a `#skipped` entry now).
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Plan: `RmObjectBuilder` reads back SECTION / INSTRUCTION / ACTIVITY
|
|
2
|
+
|
|
3
|
+
- Status: **explore + plan, awaiting approval** (2026-09-25). No code written.
|
|
4
|
+
- Issue: [#45](https://github.com/skoba/openehr-rails/issues/45) (anlage upstream candidate 15)
|
|
5
|
+
- Log: `docs/reports/referral-upstream-log.md` R1
|
|
6
|
+
- Prerequisite landed as its own fix: #44 / PR #50 (a failing `to_rm` no longer
|
|
7
|
+
fails the whole store; unknown types raise `UnsupportedRmTypeError`).
|
|
8
|
+
|
|
9
|
+
## 1. Current behaviour (measured, `file:line` on `master` after PR #50)
|
|
10
|
+
|
|
11
|
+
**Write side accepts the types.** `Rm::TypeMap::NODE_TYPES`
|
|
12
|
+
(`lib/openehr_rails/rm/type_map.rb:9-26`) lists SECTION, INSTRUCTION, ACTION,
|
|
13
|
+
ACTIVITY (and ITEM_SINGLE, ITEM_TABLE); the STI classes exist
|
|
14
|
+
(`lib/openehr_rails/rm/nodes.rb:10,14,15,18`). `GraphBuilder#infer_type`
|
|
15
|
+
(`lib/openehr_rails/rm/graph_builder.rb:111-122`) maps `activities` -> ACTIVITY and
|
|
16
|
+
`protocol` / `description` -> ITEM_TREE for `_type`-less hashes. `build_children`
|
|
17
|
+
(`:64-79`) persists Hash-valued attributes as child nodes or data-value rows and
|
|
18
|
+
Array-valued ones as ordered children; **String-valued attributes are dropped** --
|
|
19
|
+
so `ACTIVITY.action_archetype_id` never reaches the graph. `INSTRUCTION.narrative`
|
|
20
|
+
(DV_TEXT), `ACTIVITY.timing` (DV_PARSABLE: `text_value` + `formalism`, `:180-181`)
|
|
21
|
+
and `INSTRUCTION.expiry_time` (DV_DATE_TIME) are stored as data-value rows keyed by
|
|
22
|
+
`rm_attribute_name`.
|
|
23
|
+
|
|
24
|
+
**Read side does not.** `RmObjectBuilder::TYPE_CLASSES`
|
|
25
|
+
(`lib/openehr_rails/rm/rm_object_builder.rb`, 10 entries) has none of them;
|
|
26
|
+
`build_node` sets entry defaults only for Observation / Evaluation / AdminEntry,
|
|
27
|
+
`data` only for `EntryNode`, and never `protocol`, `narrative`, `activities`,
|
|
28
|
+
`description`. After PR #50 such a node raises `UnsupportedRmTypeError` and the
|
|
29
|
+
composition is skipped from AQL with a warning; before it, the whole store failed
|
|
30
|
+
(anlage referral-intake-log R6).
|
|
31
|
+
|
|
32
|
+
**What the openehr gem (2.4.3) demands of the objects** (`openehr-ruby`
|
|
33
|
+
`lib/openehr/rm/composition/content/`):
|
|
34
|
+
|
|
35
|
+
| class | mandatory | optional / notes |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `Navigation::Section` (`navigation.rb:12-27`) | -- | `items` must be nil or non-empty; `path_attribute :items` |
|
|
38
|
+
| `Entry::Instruction` (`entry.rb:112-138`) | Entry: `language`, `encoding`, `subject`; `narrative` | `activities` nil or non-empty; `expiry_time`, `wf_definition`; CareEntry `protocol`, `guideline_id` (`entry.rb:69-78`); `path_attribute :activities, :protocol` |
|
|
39
|
+
| `Entry::Activity` (`entry.rb:140-171`) | `description`, `action_archetype_id` (non-empty String) | `timing` optional since RM 1.1.0; `path_attribute :description` |
|
|
40
|
+
| `Entry::Action` (`entry.rb:173-204`) | Entry attrs; `time`, `description`, `ism_transition` (with `current_state` validated against the openEHR terminology, `:236-275`) | out of scope here, see section 5 |
|
|
41
|
+
|
|
42
|
+
`OpenEHR::RM::Composition::Composition` has `path_attribute :content, :context`,
|
|
43
|
+
so objects built with the attributes above are navigable by the AQL engine's
|
|
44
|
+
path evaluator without engine changes.
|
|
45
|
+
|
|
46
|
+
## 2. Design -- phase A, no schema change
|
|
47
|
+
|
|
48
|
+
1. `TYPE_CLASSES` gains `SECTION`, `INSTRUCTION`, `ACTIVITY`.
|
|
49
|
+
2. `build_node` gains, per type (each in its own small private helper, because
|
|
50
|
+
`build_node` already sits at rubocop's AbcSize/complexity limits -- PR #50 had to
|
|
51
|
+
extract `node_class` for a one-line change):
|
|
52
|
+
- **Section**: `items` = children built in position order; `nil` when there
|
|
53
|
+
are none (the constructor rejects `[]`).
|
|
54
|
+
- **Instruction**: the same entry defaults as the other entries
|
|
55
|
+
(`language` / `encoding` / `subject`; extend the `is_a?` list);
|
|
56
|
+
`narrative` = the `narrative` data-value row as DvText, **defaulting to the
|
|
57
|
+
node's name** when the row is absent (an injected default, documented like
|
|
58
|
+
`language`); `activities` = children with `rm_attribute_name == 'activities'`,
|
|
59
|
+
`nil` when none; `expiry_time` when its row exists.
|
|
60
|
+
- **Activity**: `description` = the `description` child (ITEM_TREE) -- no
|
|
61
|
+
default: a missing description is an RM-conformance failure and surfaces
|
|
62
|
+
through #44's warning; `timing` = the `timing` row as
|
|
63
|
+
`OpenEHR::RM::DataTypes::Encapsulated::DvParsable` when present;
|
|
64
|
+
`action_archetype_id` = the injected default `'/.*/'` (the RM's "any ACTION
|
|
65
|
+
archetype" pattern) because the graph cannot carry it yet (section 5).
|
|
66
|
+
- **`protocol` for every CareEntry** (Observation, Evaluation, Instruction):
|
|
67
|
+
`attrs[:protocol] = build_node(protocol child)` when present. Same
|
|
68
|
+
mechanism, and it is where `jp_referral` keeps 紹介先/紹介元
|
|
69
|
+
(`service_request` `protocol[at0008]`, anlage memory of 2026-09-25) -- without
|
|
70
|
+
it the INSTRUCTION would be readable but its most-queried data not.
|
|
71
|
+
3. `UnsupportedRmTypeError` keeps covering ACTION, ITEM_SINGLE, ITEM_TABLE.
|
|
72
|
+
|
|
73
|
+
Injected defaults are listed in the class comment next to the existing ones.
|
|
74
|
+
|
|
75
|
+
## 3. Spec plan (t-wada: red before green), resolution shape (b) enhancement
|
|
76
|
+
|
|
77
|
+
- **Fixture**: a spec-level synthetic canonical hash (invented ids, stated in the
|
|
78
|
+
comment with this section as design authority; no real canonical JSON with
|
|
79
|
+
SECTION/INSTRUCTION exists in this repo -- anlage's is hand-mapped and outside):
|
|
80
|
+
COMPOSITION whose `content` is `[SECTION { items: [EVALUATION { data: ITEM_TREE {
|
|
81
|
+
items: [ELEMENT DV_TEXT] } }] }, INSTRUCTION { narrative, protocol: ITEM_TREE {
|
|
82
|
+
items: [ELEMENT DV_TEXT] }, activities: [ACTIVITY { description: ITEM_TREE {
|
|
83
|
+
items: [ELEMENT DV_TEXT] }, timing: DV_PARSABLE }] }]`, committed with
|
|
84
|
+
`CompositionCommitter`.
|
|
85
|
+
- `rm_object_builder_spec.rb`: **red** today = `UnsupportedRmTypeError`; green =
|
|
86
|
+
`Section` with one `Evaluation` item; `Instruction` with the stored narrative,
|
|
87
|
+
one `Activity` whose `description.items.first.value` is the DvText,
|
|
88
|
+
`action_archetype_id == '/.*/'`, `protocol.items` present; entry defaults on the
|
|
89
|
+
Instruction.
|
|
90
|
+
- `executor_spec.rb`: AQL through the new objects --
|
|
91
|
+
`SELECT i/activities[at0001]/description[at0009]/items[at0121]/value/value ...
|
|
92
|
+
CONTAINS INSTRUCTION i[<synthetic id>]` and
|
|
93
|
+
`SELECT i/protocol[at0008]/items[at0010]/value/value`, plus
|
|
94
|
+
`... CONTAINS SECTION s[<id>] CONTAINS EVALUATION ev[<id>]`. Whether the
|
|
95
|
+
engine's CONTAINS chain accepts SECTION/ACTIVITY is **unknown until measured**;
|
|
96
|
+
if it does not, that becomes an openehr-ruby item and the spec pins the `i/...`
|
|
97
|
+
path form only.
|
|
98
|
+
- `dataset_adapter_spec.rb` / `executor_spec.rb` (#44): switch the "to_rm fails"
|
|
99
|
+
fixture from SECTION to ITEM_TABLE, which stays unsupported.
|
|
100
|
+
- Regression pins: `bmi_calculation` / `problem_list` specs unchanged; full suite
|
|
101
|
+
green; `CanonicalSerializer` round-trip of the new fixture is checked and, if it
|
|
102
|
+
does not survive (`narrative` / `timing` rows), recorded as its own issue rather
|
|
103
|
+
than fixed here.
|
|
104
|
+
|
|
105
|
+
## 4. Size and schedule
|
|
106
|
+
|
|
107
|
+
Estimate: +3 map entries; ~50 lines of new helpers plus splitting `build_node`'s
|
|
108
|
+
existing per-type `if` blocks into helpers (~80 lines moved, behaviour-neutral,
|
|
109
|
+
needed to stay inside the rubocop limits); ~130 lines of specs across three files;
|
|
110
|
+
no migration, no generator template change, no dependency change. **Small** by the
|
|
111
|
+
ruling's criterion (same builder, one file of runtime code, existing test
|
|
112
|
+
infrastructure). Recommendation: **implement before the freeze**, in one PR,
|
|
113
|
+
`Fixes #45`, after this plan is approved.
|
|
114
|
+
|
|
115
|
+
## 5. Out of scope (phase B, December unless pulled forward)
|
|
116
|
+
|
|
117
|
+
- **Persisting `action_archetype_id`**, `narrative`, `timing` as columns instead
|
|
118
|
+
of injected defaults / data-value rows: needs a migration in
|
|
119
|
+
`lib/generators/**/templates/` (shipped product, host apps migrate) -- its own
|
|
120
|
+
issue and plan.
|
|
121
|
+
- **ACTION**: `ism_transition` is persisted today as a generic CLUSTER node
|
|
122
|
+
(`infer_type` falls through), and rebuilding it needs codes valid against the
|
|
123
|
+
terminology; separate design.
|
|
124
|
+
- **ITEM_SINGLE / ITEM_TABLE**: no consumer yet.
|
|
125
|
+
|
|
126
|
+
## 6. Semver
|
|
127
|
+
|
|
128
|
+
New read-side capability, observable through AQL and `to_rm`: **minor**. If it
|
|
129
|
+
lands with #38 (PR #43) and #44 (PR #50), the next release is **0.8.0**, not 0.7.1
|
|
130
|
+
-- to be decided at the inventory.
|
|
131
|
+
|
|
132
|
+
## 7. Stop point
|
|
133
|
+
|
|
134
|
+
Explore + plan only. Implementation starts on approval.
|
|
@@ -923,3 +923,79 @@ retitled `[0.7.0] - 2026-09-10` with the Upgrade note, fresh empty
|
|
|
923
923
|
`[Unreleased]`) -> tag `v0.7.0` -> `release.yml` -> artifact sha256 (R14) ->
|
|
924
924
|
human `gem push` of that artifact -> confirmation by published checksum, the
|
|
925
925
|
first application of the rule PR #42 added.
|
|
926
|
+
|
|
927
|
+
## R14 -- v0.7.0 tagged, release.yml green, artifact sha256 recorded; gem push is the human's
|
|
928
|
+
|
|
929
|
+
- Release prep on `master` after `acb1e3f`: `b1bfaa3` (lint), `2212955` (R13),
|
|
930
|
+
`aafbd2c` (Release: bump version to 0.7.0). Before the release commit: full
|
|
931
|
+
suite **304 examples, 0 failures**, `rubocop` **109 files, no offenses**,
|
|
932
|
+
`release:check OK` (no tag yet, so the tag check was silent by design).
|
|
933
|
+
- Tag `v0.7.0` -> `aafbd2c`, pushed 2026-09-10. **Deviation noted**: this is a
|
|
934
|
+
lightweight tag; `v0.6.0` is an annotated tag object. Harmless for
|
|
935
|
+
`release.yml` (tag push) and `release:check` (`v0.7.0^{commit}`), but the
|
|
936
|
+
next release should use `git tag -a` to keep the series uniform.
|
|
937
|
+
- `release.yml` run **34438238454**: all 12 jobs success (9 spec matrix, demo
|
|
938
|
+
smoke, application template smoke, Build gem artifact). `release:check OK`
|
|
939
|
+
inside the run, i.e. HEAD was the tag commit.
|
|
940
|
+
- **Artifact**: downloaded with `gh run download 34438238454 -n gem -D pkg`:
|
|
941
|
+
`pkg/openehr-rails-0.7.0.gem`, 242176 bytes,
|
|
942
|
+
sha256 **`922ec940f15c8793ef6ee08568e4d7ea16f8a158da0d45936ff898e73cd1171a`**.
|
|
943
|
+
- **Cross-check**: a fresh `gem build` in a scratch worktree at `v0.7.0`
|
|
944
|
+
(local ruby 4.0.6, not `pkg/`) produced the **same sha256** -- CI artifact and
|
|
945
|
+
tag rebuild are byte-identical, as for 0.5.0 and 0.6.0.
|
|
946
|
+
- **Gap found in the comparison rule**: `CLAUDE.md` says to compare the
|
|
947
|
+
downloaded artifact "against the value recorded for that run", but
|
|
948
|
+
`release.yml` records no gem sha256 -- the only digest in the run log is
|
|
949
|
+
`actions/upload-artifact`'s `SHA256 digest of uploaded artifact`
|
|
950
|
+
(`671c8de1…238b`), which is the digest of the uploaded archive, **not of the
|
|
951
|
+
.gem**. The two are not comparable. The tag-rebuild cross-check above is what
|
|
952
|
+
actually pins the bytes today. Filed in `docs/backlog.md` "Release
|
|
953
|
+
automation": add a "Record sha256" step (as openehr-ruby's `release.yml`
|
|
954
|
+
has) so the rule can be followed literally.
|
|
955
|
+
- **Handed to the human**: `gem push pkg/openehr-rails-0.7.0.gem` (the
|
|
956
|
+
downloaded CI artifact, never a local build), then confirm by checksum per
|
|
957
|
+
the rule PR #42 added: `https://rubygems.org/api/v1/gems/openehr-rails.json`
|
|
958
|
+
`sha` (or the compact index `checksum:`) must equal `922ec940…171a`; use
|
|
959
|
+
`versions/openehr-rails/latest.json` rather than `versions/openehr-rails.json`
|
|
960
|
+
while the latter lags. Then delete `pkg/openehr-rails-0.7.0.gem`. Result goes
|
|
961
|
+
in R15.
|
|
962
|
+
- `master` is now past the tag by the docs commits that record this entry, so
|
|
963
|
+
`release:check` on `master` fails again by design until 0.7.1.
|
|
964
|
+
|
|
965
|
+
## R15 -- 0.7.0 published; confirmed by checksum (first application of the PR #42 rule)
|
|
966
|
+
|
|
967
|
+
Human `gem push` of the CI artifact `pkg/openehr-rails-0.7.0.gem` (R14). Confirmation
|
|
968
|
+
2026-09-10, every source compared against the CI artifact's sha256
|
|
969
|
+
`922ec940f15c8793ef6ee08568e4d7ea16f8a158da0d45936ff898e73cd1171a`:
|
|
970
|
+
|
|
971
|
+
| source | value |
|
|
972
|
+
|---|---|
|
|
973
|
+
| `/api/v1/gems/openehr-rails.json` `sha` | `922ec940…171a`, version 0.7.0 |
|
|
974
|
+
| compact index `https://index.rubygems.org/info/openehr-rails` | `checksum:922ec940…171a`, `created_at:2026-09-10T11:47:16Z` |
|
|
975
|
+
| `/api/v1/versions/openehr-rails/latest.json` | `{"version":"0.7.0"}` |
|
|
976
|
+
| `/api/v1/versions/openehr-rails.json` | 0.7.0, `sha` `922ec940…171a` (no lag observed this time) |
|
|
977
|
+
| `gem list -r -e openehr-rails` | 0.7.0 |
|
|
978
|
+
|
|
979
|
+
CI artifact, tag rebuild and published gem are one and the same bytes. The
|
|
980
|
+
`pkg/` copy was deleted after the confirmation, leaving `pkg/` empty.
|
|
981
|
+
|
|
982
|
+
### Tooling note (why two commits on the #43 branch fix single characters)
|
|
983
|
+
|
|
984
|
+
The PowerShell -> `wsl` wrapper used to run this session's scripts carried a
|
|
985
|
+
`sed -i "s/\r$//"`; the `\r` reached WSL as a plain `r`, so every script line
|
|
986
|
+
ending in "r" lost that letter. Effects found and handled: `git checkout master`
|
|
987
|
+
silently became `maste` (harmless, caught by the error), a backlog line on the
|
|
988
|
+
#43 branch was committed as "script o" (restored in `566cddb`), and the
|
|
989
|
+
`5fcc0ec` commit message reads "unde" for "under" (left as is). `master`'s
|
|
990
|
+
tracked files were grepped for the same pattern: clean. The wrapper no longer
|
|
991
|
+
runs sed.
|
|
992
|
+
|
|
993
|
+
### State after 0.7.0
|
|
994
|
+
|
|
995
|
+
- `master` = tag `v0.7.0` + docs (R14, R15). `release:check` fails on `master`
|
|
996
|
+
by design until the next release.
|
|
997
|
+
- PR #43 (`fix/38-single-leaf-non-observation`, Fixes #38): rebased for 0.7.1,
|
|
998
|
+
CHANGELOG line under the new `[Unreleased]`, awaiting the human's merge.
|
|
999
|
+
- Open follow-ups: `release.yml` "Record sha256" step (backlog, CI-only PR);
|
|
1000
|
+
next tag annotated (`git tag -a`); `json < 3` pins until Rails ships the
|
|
1001
|
+
`JSON.decode` fix (#40, backlog); reserves #35, #36, #37.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Referral-v2 upstream batch log (anlage candidates 15-17 and siblings)
|
|
2
|
+
|
|
3
|
+
Work on `openehr-rails` driven by `skoba/anlage`'s `jp_referral` v0.1 findings
|
|
4
|
+
(`docs/upstream-candidates.md` items 15-17, 2026-09-25). R1〜.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## R1 -- filing, #44 fixed, #45 planned, #46-#49 filed (2026-09-25)
|
|
9
|
+
|
|
10
|
+
### State on waking
|
|
11
|
+
|
|
12
|
+
`master` = `6b0e357` (v0.7.0 + R14/R15 docs). PR #43 (#38, 0.7.1) still open and
|
|
13
|
+
unmerged since 2026-09-10, CI green. Open issues before this batch: #35-#38.
|
|
14
|
+
|
|
15
|
+
### 1. #44 -- AQL store-wide failure isolated per composition (PR #50)
|
|
16
|
+
|
|
17
|
+
- Measured cause: `DatasetAdapter` materialises every head composition with
|
|
18
|
+
`to_rm` inside the lazy chain (`dataset_adapter.rb:35,37`); `RmObjectBuilder#build_node`
|
|
19
|
+
did `TYPE_CLASSES[rm_type]` -> nil -> `NoMethodError` for SECTION / INSTRUCTION /
|
|
20
|
+
ACTIVITY / ACTION / ITEM_SINGLE / ITEM_TABLE, all of which `GraphBuilder`
|
|
21
|
+
persists (`Rm::TypeMap::NODE_TYPES`).
|
|
22
|
+
- Resolution shape (a) bug. **Red**: 23 examples, 3 failures (DatasetAdapter walk,
|
|
23
|
+
Executor end to end, `to_rm` directly) against a synthetic SECTION composition
|
|
24
|
+
committed beside a BmiCalculation record. **Green**: 23/0, full suite **307/0**
|
|
25
|
+
(304 + 3), rubocop clean (one extraction, `node_class`, to stay under
|
|
26
|
+
`build_node`'s AbcSize/complexity limits).
|
|
27
|
+
- Behaviour: `DatasetAdapter#materialize` rescues, reports through `Rails.logger`
|
|
28
|
+
when present else `Kernel#warn` (spec env has no `Rails.logger`, so the specs
|
|
29
|
+
pin stderr), skips the composition. `OpenehrRails::Rm::UnsupportedRmTypeError`
|
|
30
|
+
(composition_uid, rm_type, path) replaces the accidental `NoMethodError`.
|
|
31
|
+
- PR #50 (`dd157b6`, Fixes #44), CI run 36115865528. Semver patch; CHANGELOG
|
|
32
|
+
`[Unreleased]` Fixed.
|
|
33
|
+
|
|
34
|
+
### 2. #45 -- SECTION / INSTRUCTION / ACTIVITY on the read side: explore + plan
|
|
35
|
+
|
|
36
|
+
`docs/design/rm-object-builder-section-instruction-plan.md`. Verdict: **small**
|
|
37
|
+
(phase A: 3 map entries, helper methods in one file, ~130 spec lines, no schema
|
|
38
|
+
change) -> recommended before the freeze, awaiting approval. Phase B (persist
|
|
39
|
+
`action_archetype_id`, ACTION, ITEM_SINGLE/TABLE) December. Note: minor, so a
|
|
40
|
+
release carrying it is 0.8.0.
|
|
41
|
+
|
|
42
|
+
### 3. Filed only
|
|
43
|
+
|
|
44
|
+
- **#46** context (EVENT_CONTEXT) not persisted from canonical JSON (upstream 16).
|
|
45
|
+
- **#47** FieldExtractor does not descend `activities` / `description` / `protocol`
|
|
46
|
+
(upstream 17; `DESCENDABLE_ATTRIBUTES = %w[data events items value]`,
|
|
47
|
+
`field_extractor.rb:47`).
|
|
48
|
+
- **#48** DV_TEXT leaf on a CodeableConcept target -> `code only string` (invalid);
|
|
49
|
+
mapping row should carry the FHIR type.
|
|
50
|
+
- **#49** 0-leaf entry gets an empty component-header profile instead of
|
|
51
|
+
skip-and-report (#38 sibling; #43 covers non-Observation 0-leaf once merged,
|
|
52
|
+
Observation 0-leaf not).
|
|
53
|
+
|
|
54
|
+
### 4. 0.7.1 inventory (candidates, not yet a gate)
|
|
55
|
+
|
|
56
|
+
| item | state | class |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| PR #43 (#38 single-leaf skip) | open, CI green, rebased on 0.7.0 | patch |
|
|
59
|
+
| PR #50 (#44 isolation) | open, CI running | patch |
|
|
60
|
+
| #45 phase A | plan awaiting approval | minor -> 0.8.0 |
|
|
61
|
+
| #46 / #47 / #48 / #49 | filed | -- |
|
|
62
|
+
|
|
63
|
+
Gate when the human has merged what should ride; version number decided from the
|
|
64
|
+
merged content (0.7.1 if patch-only, 0.8.0 if #45 rides).
|
|
@@ -32,14 +32,32 @@ module OpenehrRails
|
|
|
32
32
|
|
|
33
33
|
def ehr_records
|
|
34
34
|
linked = @ehr_scope.find_each.lazy.map do |ehr|
|
|
35
|
-
{ ehr_id: ehr.ehr_id, compositions:
|
|
35
|
+
{ ehr_id: ehr.ehr_id, compositions: rm_compositions_for(ehr_id: ehr.id) }
|
|
36
36
|
end
|
|
37
|
-
unlinked = [{ ehr_id: nil, compositions:
|
|
37
|
+
unlinked = [{ ehr_id: nil, compositions: rm_compositions_for(ehr_id: nil) }]
|
|
38
38
|
linked + unlinked.lazy
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def
|
|
42
|
-
@composition_scope.where(ehr_id: ehr_id).find_each.lazy
|
|
41
|
+
def rm_compositions_for(ehr_id:)
|
|
42
|
+
@composition_scope.where(ehr_id: ehr_id).find_each.lazy.filter_map { |composition| materialize(composition) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# One composition whose graph cannot be rebuilt (a node type outside
|
|
46
|
+
# RmObjectBuilder::TYPE_CLASSES, or an RM constructor rejecting stored
|
|
47
|
+
# data) must not take every query in the store down with it (#44):
|
|
48
|
+
# warn, naming the composition and the cause, and leave it out. The
|
|
49
|
+
# rest of the EHR stays queryable; the warning is the report.
|
|
50
|
+
def materialize(composition)
|
|
51
|
+
composition.to_rm
|
|
52
|
+
rescue StandardError => e
|
|
53
|
+
report_skip("openehr-rails AQL: skipping composition uid=#{composition.uid} (id=#{composition.id}): " \
|
|
54
|
+
"#{e.class}: #{e.message}")
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def report_skip(message)
|
|
59
|
+
logger = defined?(::Rails) && ::Rails.respond_to?(:logger) ? ::Rails.logger : nil
|
|
60
|
+
logger ? logger.warn(message) : warn(message)
|
|
43
61
|
end
|
|
44
62
|
end
|
|
45
63
|
end
|
|
@@ -10,7 +10,7 @@ module OpenehrRails
|
|
|
10
10
|
# the entry's RM type selects the base FHIR resource, and the leaf
|
|
11
11
|
# ELEMENTs constrain value[x] (single leaf), component slices (many, on
|
|
12
12
|
# Observation) or the real elements named by TypeMap::ENTRY_ELEMENT_MAPS.
|
|
13
|
-
#
|
|
13
|
+
# Entries with neither are skipped and reported (#skipped).
|
|
14
14
|
class ProfileGenerator
|
|
15
15
|
FHIR_VERSION = '5.0.0'
|
|
16
16
|
ARCHETYPE_SYSTEM = 'http://openehr.org/ckm/archetypes'
|
|
@@ -91,13 +91,13 @@ module OpenehrRails
|
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
# The one shared decision point for both generators (#33 ruling, plan
|
|
94
|
-
# section 9.3
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
94
|
+
# section 9.3; widened to any leaf count by #38, section 9.7): an entry
|
|
95
|
+
# whose base resource is not Observation -- so it has neither `value[x]`
|
|
96
|
+
# for a single leaf nor `component` for several -- and which has no row in
|
|
97
|
+
# ENTRY_ELEMENT_MAPS cannot be profiled. Raises UnsupportedProfileError so
|
|
98
|
+
# the caller can skip and report it instead of emitting constraints on a
|
|
99
|
+
# path the resource lacks.
|
|
98
100
|
def assert_supported!(entry)
|
|
99
|
-
return if entry[:fields].size <= 1
|
|
100
|
-
|
|
101
101
|
resource_type = resource_for_entry(entry[:rm_type])
|
|
102
102
|
return if resource_type == 'Observation' || element_map_for(entry[:archetype_id])
|
|
103
103
|
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module OpenehrRails
|
|
4
4
|
module Fhir
|
|
5
|
-
# Raised for an ENTRY that
|
|
6
|
-
#
|
|
7
|
-
# in TypeMap::ENTRY_ELEMENT_MAPS
|
|
8
|
-
# (skoba/openehr-rails#33 ruling,
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# a
|
|
5
|
+
# Raised for an ENTRY that maps to a FHIR resource other than Observation --
|
|
6
|
+
# which has neither `value[x]` (single leaf) nor `component` (several) --
|
|
7
|
+
# and has no row in TypeMap::ENTRY_ELEMENT_MAPS: nothing valid can be
|
|
8
|
+
# generated for it (skoba/openehr-rails#33 ruling and #38,
|
|
9
|
+
# docs/design/multi-leaf-non-observation-plan.md sections 9.3 and 9.7).
|
|
10
|
+
# ProfileGenerator and FshGenerator rescue this per entry, skip the entry
|
|
11
|
+
# and expose the error through #skipped; a caller that wants a hard failure
|
|
12
|
+
# re-raises from there.
|
|
12
13
|
class UnsupportedProfileError < StandardError
|
|
13
14
|
attr_reader :archetype_id, :resource_type, :leaf_count
|
|
14
15
|
|
|
@@ -17,9 +18,10 @@ module OpenehrRails
|
|
|
17
18
|
@resource_type = resource_type
|
|
18
19
|
@leaf_count = leaf_count
|
|
19
20
|
super(
|
|
20
|
-
"#{archetype_id}: #{leaf_count}
|
|
21
|
-
|
|
22
|
-
'archetype -- skipped
|
|
21
|
+
"#{archetype_id}: #{leaf_count} #{leaf_count == 1 ? 'leaf maps' : 'leaves map'} to " \
|
|
22
|
+
"#{resource_type}, which has neither value[x] nor component, and " \
|
|
23
|
+
'TypeMap::ENTRY_ELEMENT_MAPS has no row for this archetype -- skipped ' \
|
|
24
|
+
'(skoba/openehr-rails#33, #38; mapping rows: #35, #37)'
|
|
23
25
|
)
|
|
24
26
|
end
|
|
25
27
|
end
|
|
@@ -4,6 +4,26 @@ require 'openehr'
|
|
|
4
4
|
|
|
5
5
|
module OpenehrRails
|
|
6
6
|
module Rm
|
|
7
|
+
# Raised by RmObjectBuilder for a stored node whose rm_type the graph
|
|
8
|
+
# accepts (Rm::TypeMap::NODE_TYPES) but the read side cannot rebuild
|
|
9
|
+
# (RmObjectBuilder::TYPE_CLASSES) -- SECTION, INSTRUCTION, ACTIVITY,
|
|
10
|
+
# ACTION, ITEM_SINGLE, ITEM_TABLE today (#44; #45 narrows the gap).
|
|
11
|
+
# Names the composition and the node so a warning built from it is
|
|
12
|
+
# actionable.
|
|
13
|
+
class UnsupportedRmTypeError < StandardError
|
|
14
|
+
attr_reader :composition_uid, :rm_type, :path
|
|
15
|
+
|
|
16
|
+
def initialize(composition, node)
|
|
17
|
+
@composition_uid = composition.uid
|
|
18
|
+
@rm_type = node.rm_type
|
|
19
|
+
@path = node.path
|
|
20
|
+
super(
|
|
21
|
+
"composition uid=#{composition.uid}: node #{node.path} has rm_type #{node.rm_type}, " \
|
|
22
|
+
'which RmObjectBuilder::TYPE_CLASSES cannot rebuild as an OpenEHR::RM object'
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
7
27
|
# Builds a full OpenEHR::RM::Composition object from the stored
|
|
8
28
|
# graph, injecting config defaults for mandatory attributes not
|
|
9
29
|
# carried in OPT/stored data. The gem's own constructor validations
|
|
@@ -66,8 +86,12 @@ module OpenehrRails
|
|
|
66
86
|
)
|
|
67
87
|
end
|
|
68
88
|
|
|
89
|
+
def node_class(node)
|
|
90
|
+
TYPE_CLASSES.fetch(node.rm_type) { raise UnsupportedRmTypeError.new(@composition, node) }
|
|
91
|
+
end
|
|
92
|
+
|
|
69
93
|
def build_node(node)
|
|
70
|
-
klass =
|
|
94
|
+
klass = node_class(node)
|
|
71
95
|
attrs = {
|
|
72
96
|
archetype_node_id: node.archetype_node_id,
|
|
73
97
|
name: dv_text(node.name_value || node.archetype_node_id)
|
|
@@ -54,4 +54,51 @@ describe OpenehrRails::Aql::DatasetAdapter do
|
|
|
54
54
|
ensure
|
|
55
55
|
ActiveSupport::Notifications.unsubscribe(subscriber) if subscriber
|
|
56
56
|
end
|
|
57
|
+
|
|
58
|
+
# skoba/openehr-rails#44, resolution shape (a) bug: one composition whose
|
|
59
|
+
# graph cannot be rebuilt as RM objects used to fail every query in the
|
|
60
|
+
# store. Synthetic canonical hash (invented ids): a SECTION is accepted by
|
|
61
|
+
# GraphBuilder (Rm::TypeMap::NODE_TYPES) but not by RmObjectBuilder::
|
|
62
|
+
# TYPE_CLASSES, so it is the smallest graph whose to_rm fails today; #45
|
|
63
|
+
# makes SECTION buildable later, at which point this spec needs a different
|
|
64
|
+
# unsupported type (ITEM_TABLE is the next candidate).
|
|
65
|
+
describe 'a composition whose to_rm fails' do
|
|
66
|
+
let(:section_hash) do
|
|
67
|
+
{
|
|
68
|
+
'_type' => 'COMPOSITION',
|
|
69
|
+
'archetype_node_id' => 'openEHR-EHR-COMPOSITION.synthetic_section_test.v1',
|
|
70
|
+
'archetype_details' => {
|
|
71
|
+
'_type' => 'ARCHETYPED',
|
|
72
|
+
'archetype_id' => { 'value' => 'openEHR-EHR-COMPOSITION.synthetic_section_test.v1' },
|
|
73
|
+
'template_id' => { 'value' => 'synthetic_section_test' },
|
|
74
|
+
'rm_version' => '1.0.4'
|
|
75
|
+
},
|
|
76
|
+
'content' => [
|
|
77
|
+
{
|
|
78
|
+
'_type' => 'SECTION',
|
|
79
|
+
'archetype_node_id' => 'openEHR-EHR-SECTION.synthetic_test.v1',
|
|
80
|
+
'name' => { '_type' => 'DV_TEXT', 'value' => 'Synthetic section' },
|
|
81
|
+
'items' => []
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
before do
|
|
88
|
+
BmiCalculation.create!(height: 170.0, ehr_id: 'ehr-mixed')
|
|
89
|
+
ehr = OpenehrRails::Rm::Ehr.find_by!(ehr_id: 'ehr-mixed')
|
|
90
|
+
OpenehrRails::Rm::CompositionCommitter.commit(section_hash, uid: 'uid-section', ehr: ehr)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'warns, skips that composition, and keeps the rest of the EHR queryable' do
|
|
94
|
+
records = nil
|
|
95
|
+
|
|
96
|
+
expect { records = described_class.build.each_ehr.to_a }
|
|
97
|
+
.to output(/uid-section.*UnsupportedRmTypeError.*SECTION/).to_stderr
|
|
98
|
+
|
|
99
|
+
matching = records.find { |r| r.ehr_id == 'ehr-mixed' }
|
|
100
|
+
expect(matching.compositions.size).to eq(1)
|
|
101
|
+
expect(matching.compositions.map { |c| c.uid.value }).not_to include('uid-section')
|
|
102
|
+
end
|
|
103
|
+
end
|
|
57
104
|
end
|
|
@@ -96,4 +96,32 @@ describe OpenehrRails::Aql::Executor do
|
|
|
96
96
|
expect(OpenehrRails::Aql.execute(height_query).rows).to eq([[170.0]])
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
|
+
|
|
100
|
+
# skoba/openehr-rails#44 (a) bug, end to end: the store also holds a
|
|
101
|
+
# composition whose to_rm fails (synthetic SECTION graph, see
|
|
102
|
+
# dataset_adapter_spec.rb); a query on an unrelated template must still
|
|
103
|
+
# return its rows.
|
|
104
|
+
describe 'with a composition whose to_rm fails in the store' do
|
|
105
|
+
before do
|
|
106
|
+
BmiCalculation.create!(height: 170.0)
|
|
107
|
+
OpenehrRails::Rm::CompositionCommitter.commit(
|
|
108
|
+
{
|
|
109
|
+
'_type' => 'COMPOSITION',
|
|
110
|
+
'archetype_node_id' => 'openEHR-EHR-COMPOSITION.synthetic_section_test.v1',
|
|
111
|
+
'content' => [
|
|
112
|
+
{ '_type' => 'SECTION', 'archetype_node_id' => 'openEHR-EHR-SECTION.synthetic_test.v1', 'items' => [] }
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
uid: 'uid-section-exec'
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'still answers a query on another template, skipping the broken composition' do
|
|
120
|
+
rows = nil
|
|
121
|
+
|
|
122
|
+
expect { rows = described_class.execute(height_query).rows }.to output(/uid-section-exec/).to_stderr
|
|
123
|
+
|
|
124
|
+
expect(rows).to eq([[170.0]])
|
|
125
|
+
end
|
|
126
|
+
end
|
|
99
127
|
end
|
|
@@ -225,6 +225,49 @@ describe OpenehrRails::Fhir::FshGenerator do
|
|
|
225
225
|
end
|
|
226
226
|
end
|
|
227
227
|
|
|
228
|
+
# skoba/openehr-rails#38, resolution shape (a) bug: a single-leaf entry whose
|
|
229
|
+
# base resource is not Observation took the legacy `code` + `value[x]` path,
|
|
230
|
+
# and Condition has no value[x] either (sushi: 1 Error on a probe, plan
|
|
231
|
+
# section 9.5). Same decision point as the multi-leaf case, now regardless of
|
|
232
|
+
# leaf count. Synthetic entry for the same reason as above: no real
|
|
233
|
+
# single-leaf non-Observation fixture exists in this repo.
|
|
234
|
+
describe 'an unmapped single-leaf non-Observation entry' do
|
|
235
|
+
subject(:files) { generator.to_fsh_files }
|
|
236
|
+
|
|
237
|
+
let(:opt_file) { File.expand_path('../../templates/problem_list.opt', __dir__) }
|
|
238
|
+
let(:real_entries) { OpenehrRails::Opt::FieldExtractor.new(template).entries }
|
|
239
|
+
let(:synthetic_id) { 'openEHR-EHR-EVALUATION.synthetic_single_leaf_test.v1' }
|
|
240
|
+
let(:synthetic_entry) do
|
|
241
|
+
real = real_entries.first
|
|
242
|
+
real.merge(
|
|
243
|
+
archetype_id: synthetic_id,
|
|
244
|
+
concept: 'synthetic_single_leaf_test',
|
|
245
|
+
fields: real[:fields].first(1).map { |field| field.merge(archetype_id: synthetic_id) }
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
before do
|
|
250
|
+
extractor = instance_double(OpenehrRails::Opt::FieldExtractor, entries: [synthetic_entry, *real_entries])
|
|
251
|
+
allow(OpenehrRails::Opt::FieldExtractor).to receive(:new).with(template).and_return(extractor)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'skips the entry instead of constraining value[x] on Condition' do
|
|
255
|
+
expect(files.keys).to eq(['openehr-evaluation-problem-diagnosis-v1'])
|
|
256
|
+
expect(files.values.join).not_to include('value[x]')
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
it 'reports it through #skipped with the resource named' do
|
|
260
|
+
files
|
|
261
|
+
error = generator.skipped.first
|
|
262
|
+
|
|
263
|
+
expect(generator.skipped.size).to eq(1)
|
|
264
|
+
expect(error.archetype_id).to eq(synthetic_id)
|
|
265
|
+
expect(error.resource_type).to eq('Condition')
|
|
266
|
+
expect(error.leaf_count).to eq(1)
|
|
267
|
+
expect(error.message).to include('value[x]')
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
228
271
|
describe '#skipped' do
|
|
229
272
|
it 'is empty for an all-Observation template' do
|
|
230
273
|
generator.to_fsh_files
|
|
@@ -241,6 +241,47 @@ describe OpenehrRails::Fhir::ProfileGenerator do
|
|
|
241
241
|
end
|
|
242
242
|
end
|
|
243
243
|
|
|
244
|
+
# skoba/openehr-rails#38, resolution shape (a) bug, JSON facade half: a
|
|
245
|
+
# single-leaf non-Observation entry produced `<Resource>.value[x]`, which
|
|
246
|
+
# Condition does not have. Synthetic entry as above.
|
|
247
|
+
describe 'an unmapped single-leaf non-Observation entry' do
|
|
248
|
+
subject(:profiles) { generator.profiles }
|
|
249
|
+
|
|
250
|
+
let(:opt_file) { File.expand_path('../../templates/problem_list.opt', __dir__) }
|
|
251
|
+
let(:real_entries) { OpenehrRails::Opt::FieldExtractor.new(template).entries }
|
|
252
|
+
let(:synthetic_id) { 'openEHR-EHR-EVALUATION.synthetic_single_leaf_test.v1' }
|
|
253
|
+
let(:synthetic_entry) do
|
|
254
|
+
real = real_entries.first
|
|
255
|
+
real.merge(
|
|
256
|
+
archetype_id: synthetic_id,
|
|
257
|
+
concept: 'synthetic_single_leaf_test',
|
|
258
|
+
fields: real[:fields].first(1).map { |field| field.merge(archetype_id: synthetic_id) }
|
|
259
|
+
)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
before do
|
|
263
|
+
extractor = instance_double(OpenehrRails::Opt::FieldExtractor, entries: [synthetic_entry, *real_entries])
|
|
264
|
+
allow(OpenehrRails::Opt::FieldExtractor).to receive(:new).with(template).and_return(extractor)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it 'skips the entry instead of emitting Condition.value[x]' do
|
|
268
|
+
expect(profiles.map { |p| p[:id] }).to eq(['openehr-evaluation-problem-diagnosis-v1'])
|
|
269
|
+
expect(profiles.flat_map { |p| p[:differential][:element] }.map { |e| e[:path] })
|
|
270
|
+
.not_to include('Condition.value[x]')
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it 'reports it through #skipped with the resource named' do
|
|
274
|
+
profiles
|
|
275
|
+
error = generator.skipped.first
|
|
276
|
+
|
|
277
|
+
expect(generator.skipped.size).to eq(1)
|
|
278
|
+
expect(error.archetype_id).to eq(synthetic_id)
|
|
279
|
+
expect(error.resource_type).to eq('Condition')
|
|
280
|
+
expect(error.leaf_count).to eq(1)
|
|
281
|
+
expect(error.message).to include('value[x]')
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
244
285
|
describe '#skipped' do
|
|
245
286
|
it 'is empty for an all-Observation template' do
|
|
246
287
|
generator.profiles
|
|
@@ -73,4 +73,25 @@ describe OpenehrRails::Rm::RmObjectBuilder do
|
|
|
73
73
|
expect(result).to be_a(OpenEHR::RM::Composition::Composition)
|
|
74
74
|
expect(result.language.code_string).to eq(OpenehrRails.default_language)
|
|
75
75
|
end
|
|
76
|
+
|
|
77
|
+
# skoba/openehr-rails#44 (a) bug: a node type the graph stores but
|
|
78
|
+
# TYPE_CLASSES lacks used to surface as `NoMethodError: undefined method
|
|
79
|
+
# 'new' for nil`. The typed error names what the caller needs to act on.
|
|
80
|
+
it 'raises UnsupportedRmTypeError, naming composition and node, for an rm_type outside TYPE_CLASSES' do
|
|
81
|
+
composition = OpenehrRails::Rm::CompositionCommitter.commit(
|
|
82
|
+
{
|
|
83
|
+
'_type' => 'COMPOSITION',
|
|
84
|
+
'archetype_node_id' => 'openEHR-EHR-COMPOSITION.synthetic_section_test.v1',
|
|
85
|
+
'content' => [
|
|
86
|
+
{ '_type' => 'SECTION', 'archetype_node_id' => 'openEHR-EHR-SECTION.synthetic_test.v1', 'items' => [] }
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
uid: 'uid-section-builder'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
expect { composition.to_rm }.to raise_error(OpenehrRails::Rm::UnsupportedRmTypeError) { |error|
|
|
93
|
+
expect(error.message).to include('uid-section-builder', 'SECTION', 'openEHR-EHR-SECTION.synthetic_test.v1')
|
|
94
|
+
expect(error.rm_type).to eq('SECTION')
|
|
95
|
+
}
|
|
96
|
+
end
|
|
76
97
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openehr-rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.7.
|
|
4
|
+
version: 0.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shinji KOBAYASHI
|
|
@@ -260,9 +260,11 @@ files:
|
|
|
260
260
|
- docs/design/fix-terminology-scope-plan.md
|
|
261
261
|
- docs/design/fsh-generator-plan.md
|
|
262
262
|
- docs/design/multi-leaf-non-observation-plan.md
|
|
263
|
+
- docs/design/rm-object-builder-section-instruction-plan.md
|
|
263
264
|
- docs/reports/binding-extraction-log.md
|
|
264
265
|
- docs/reports/fsh-generator-log.md
|
|
265
266
|
- docs/reports/institutionalization-log.md
|
|
267
|
+
- docs/reports/referral-upstream-log.md
|
|
266
268
|
- gemfiles/rails_7_2.gemfile
|
|
267
269
|
- gemfiles/rails_8_0.gemfile
|
|
268
270
|
- gemfiles/rails_8_1.gemfile
|
|
@@ -410,7 +412,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
410
412
|
- !ruby/object:Gem::Version
|
|
411
413
|
version: '0'
|
|
412
414
|
requirements: []
|
|
413
|
-
rubygems_version: 4.0.
|
|
415
|
+
rubygems_version: 4.0.20
|
|
414
416
|
specification_version: 4
|
|
415
417
|
summary: Rails extension for the openEHR archetypes
|
|
416
418
|
test_files: []
|