oddb2xml 3.0.4 → 3.0.6
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/.ruby-version +1 -1
- data/CLAUDE.md +3 -1
- data/Gemfile.lock +92 -69
- data/History.txt +7 -0
- data/README.md +23 -2
- data/lib/oddb2xml/builder.rb +30 -0
- data/lib/oddb2xml/fhir_support.rb +52 -2
- data/lib/oddb2xml/refdata_cleanup.rb +34 -0
- data/lib/oddb2xml/version.rb +1 -1
- data/lib/oddb2xml.rb +1 -0
- data/oddb2xml.gemspec +1 -0
- data/spec/data/fhir/cyramza.ndjson +1 -0
- data/spec/fhir_spec.rb +53 -0
- data/spec/refdata_cleanup_spec.rb +151 -0
- metadata +24 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b3a91c4a9ff36983011294c705fd882a5cf2a7f60f972cd1e2de61877e353db
|
|
4
|
+
data.tar.gz: 7eb411ee5192d1a1eec417536e62761c5cbb833a075221ae283f1de577f5a4f6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92721b900cb1f754d87463c8a22fd552eabceee1ec12e16aca202084bfd18fa03f4def7ea3c2232ba4598d225c49f165a9c49cf995aae54f9a655d8551cb7267
|
|
7
|
+
data.tar.gz: ef7383d72a3535996c70d9d723b12649b09927cff97b9575e49f15553c87c92d142c940eded2fd22d4ef8399bf6f7d2538c72e413623485150837bfa43d0faf1
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.5
|
data/CLAUDE.md
CHANGED
|
@@ -49,6 +49,8 @@ The system follows a **download → extract → build → compress** pipeline:
|
|
|
49
49
|
|
|
50
50
|
7. **FHIR support** (`lib/oddb2xml/fhir_support.rb`) — Self-contained module providing `FhirDownloader` and FHIR NDJSON parsing. Activated via `--fhir` (or `--fhir-url=<URL>`). Downloads per-language NDJSON files (`foph-sl-export-latest-{de,fr,it}.ndjson`) from `epl.bag.admin.ch` to populate French and Italian product names/descriptions. Maps legal status codes `756005022007` and `756005022008` to Swissmedic category D.
|
|
51
51
|
|
|
52
|
+
8. **Refdata cleanup** (`lib/oddb2xml/refdata_cleanup.rb`) — Compensates for known data-quality issues in upstream Refdata.Articles.xml before they reach the output. Each fix is guarded by a Swissmedic-side heuristic (e.g. comma in `substance_swissmedic` to distinguish mono products from real combinations). Currently fixes the doubled-dose template bug (`X mg / X mg / Stk`). Called from `Builder#apply_refdata_description_cleanups!` at the start of `prepare_articles`. See GitHub issue #112 for the catalogue.
|
|
53
|
+
|
|
52
54
|
### Key data identifiers
|
|
53
55
|
- **GTIN/EAN13**: Primary article identifier (13-digit barcode)
|
|
54
56
|
- **Pharmacode**: Swiss pharmacy code
|
|
@@ -68,4 +70,4 @@ YAML files in `data/` provide manual overrides and mappings: `article_overrides.
|
|
|
68
70
|
## Ruby Version
|
|
69
71
|
|
|
70
72
|
- Minimum: Ruby >= 2.5.0 (gemspec)
|
|
71
|
-
- Current development: Ruby 3.
|
|
73
|
+
- Current development: Ruby 3.3.6 (`.ruby-version`)
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
oddb2xml (3.0.
|
|
4
|
+
oddb2xml (3.0.6)
|
|
5
|
+
csv
|
|
5
6
|
htmlentities
|
|
6
7
|
httpi
|
|
7
8
|
mechanize (>= 2.8.5)
|
|
@@ -28,32 +29,38 @@ GEM
|
|
|
28
29
|
specs:
|
|
29
30
|
addressable (2.8.8)
|
|
30
31
|
public_suffix (>= 2.0.2, < 8.0)
|
|
31
|
-
akami (1.3.
|
|
32
|
+
akami (1.3.3)
|
|
33
|
+
base64
|
|
32
34
|
gyoku (>= 0.4.0)
|
|
33
35
|
nokogiri
|
|
34
|
-
ast (2.4.
|
|
36
|
+
ast (2.4.3)
|
|
35
37
|
base64 (0.3.0)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
bigdecimal (4.0.1)
|
|
39
|
+
builder (3.3.0)
|
|
40
|
+
byebug (13.0.0)
|
|
41
|
+
reline (>= 0.6.0)
|
|
38
42
|
coderay (1.1.3)
|
|
39
43
|
connection_pool (3.0.2)
|
|
40
|
-
crack (0.
|
|
44
|
+
crack (1.0.1)
|
|
45
|
+
bigdecimal
|
|
41
46
|
rexml
|
|
42
|
-
|
|
47
|
+
csv (3.3.5)
|
|
48
|
+
diff-lcs (1.6.2)
|
|
43
49
|
domain_name (0.6.20240107)
|
|
44
|
-
flexmock (
|
|
50
|
+
flexmock (3.0.2)
|
|
45
51
|
gyoku (1.4.0)
|
|
46
52
|
builder (>= 2.1.2)
|
|
47
53
|
rexml (~> 3.0)
|
|
48
|
-
hashdiff (1.
|
|
49
|
-
htmlentities (4.
|
|
54
|
+
hashdiff (1.2.1)
|
|
55
|
+
htmlentities (4.4.2)
|
|
50
56
|
http-cookie (1.1.0)
|
|
51
57
|
domain_name (~> 0.5)
|
|
52
58
|
httpi (2.5.0)
|
|
53
59
|
rack
|
|
54
60
|
socksify
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
io-console (0.8.2)
|
|
62
|
+
json (2.18.1)
|
|
63
|
+
language_server-protocol (3.17.0.5)
|
|
57
64
|
lint_roller (1.1.0)
|
|
58
65
|
logger (1.7.0)
|
|
59
66
|
mechanize (2.14.0)
|
|
@@ -69,84 +76,95 @@ GEM
|
|
|
69
76
|
rubyntlm (~> 0.6, >= 0.6.3)
|
|
70
77
|
webrick (~> 1.7)
|
|
71
78
|
webrobots (~> 0.1.2)
|
|
72
|
-
method_source (1.
|
|
79
|
+
method_source (1.1.0)
|
|
73
80
|
mime-types (3.7.0)
|
|
74
81
|
logger
|
|
75
82
|
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
|
76
83
|
mime-types-data (3.2026.0203)
|
|
77
84
|
mini_portile2 (2.8.9)
|
|
78
|
-
minitar (0
|
|
79
|
-
multi_json (1.
|
|
85
|
+
minitar (1.1.0)
|
|
86
|
+
multi_json (1.19.1)
|
|
80
87
|
mutex_m (0.3.0)
|
|
81
88
|
net-http-digest_auth (1.4.1)
|
|
82
89
|
net-http-persistent (4.0.8)
|
|
83
90
|
connection_pool (>= 2.2.4, < 4)
|
|
84
91
|
nkf (0.2.0)
|
|
85
|
-
nokogiri (1.19.
|
|
92
|
+
nokogiri (1.19.2)
|
|
86
93
|
mini_portile2 (~> 2.8.2)
|
|
87
94
|
racc (~> 1.4)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
nokogiri (1.19.2-arm64-darwin)
|
|
96
|
+
racc (~> 1.4)
|
|
97
|
+
nori (2.7.1)
|
|
98
|
+
bigdecimal
|
|
99
|
+
optimist (3.2.1)
|
|
100
|
+
ox (2.14.23)
|
|
101
|
+
bigdecimal (>= 3.0)
|
|
102
|
+
parallel (1.27.0)
|
|
103
|
+
parser (3.3.10.2)
|
|
93
104
|
ast (~> 2.4.1)
|
|
94
105
|
racc
|
|
95
106
|
parslet (2.0.0)
|
|
96
|
-
|
|
107
|
+
prism (1.9.0)
|
|
108
|
+
pry (0.16.0)
|
|
97
109
|
coderay (~> 1.1)
|
|
98
110
|
method_source (~> 1.0)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
reline (>= 0.6.0)
|
|
112
|
+
pry-byebug (3.12.0)
|
|
113
|
+
byebug (~> 13.0)
|
|
114
|
+
pry (>= 0.13, < 0.17)
|
|
115
|
+
pry-doc (1.7.0)
|
|
103
116
|
pry (~> 0.11)
|
|
104
|
-
yard (~> 0.9.
|
|
117
|
+
yard (~> 0.9.21)
|
|
105
118
|
psych (3.3.4)
|
|
106
119
|
public_suffix (7.0.2)
|
|
107
120
|
racc (1.8.1)
|
|
108
|
-
rack (3.2.
|
|
121
|
+
rack (3.2.6)
|
|
109
122
|
rainbow (3.1.1)
|
|
110
|
-
rake (13.
|
|
123
|
+
rake (13.3.1)
|
|
111
124
|
rdoc (6.3.4.1)
|
|
112
|
-
regexp_parser (2.
|
|
125
|
+
regexp_parser (2.11.3)
|
|
126
|
+
reline (0.6.3)
|
|
127
|
+
io-console (~> 0.5)
|
|
113
128
|
rexml (3.4.4)
|
|
114
|
-
rspec (3.
|
|
115
|
-
rspec-core (~> 3.
|
|
116
|
-
rspec-expectations (~> 3.
|
|
117
|
-
rspec-mocks (~> 3.
|
|
118
|
-
rspec-core (3.
|
|
119
|
-
rspec-support (~> 3.
|
|
120
|
-
rspec-expectations (3.
|
|
129
|
+
rspec (3.13.2)
|
|
130
|
+
rspec-core (~> 3.13.0)
|
|
131
|
+
rspec-expectations (~> 3.13.0)
|
|
132
|
+
rspec-mocks (~> 3.13.0)
|
|
133
|
+
rspec-core (3.13.6)
|
|
134
|
+
rspec-support (~> 3.13.0)
|
|
135
|
+
rspec-expectations (3.13.5)
|
|
121
136
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
122
|
-
rspec-support (~> 3.
|
|
123
|
-
rspec-mocks (3.
|
|
137
|
+
rspec-support (~> 3.13.0)
|
|
138
|
+
rspec-mocks (3.13.7)
|
|
124
139
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
125
|
-
rspec-support (~> 3.
|
|
126
|
-
rspec-support (3.
|
|
127
|
-
rubocop (1.
|
|
140
|
+
rspec-support (~> 3.13.0)
|
|
141
|
+
rspec-support (3.13.7)
|
|
142
|
+
rubocop (1.84.2)
|
|
128
143
|
json (~> 2.3)
|
|
144
|
+
language_server-protocol (~> 3.17.0.2)
|
|
145
|
+
lint_roller (~> 1.1.0)
|
|
129
146
|
parallel (~> 1.10)
|
|
130
|
-
parser (>= 3.
|
|
147
|
+
parser (>= 3.3.0.2)
|
|
131
148
|
rainbow (>= 2.2.2, < 4.0)
|
|
132
|
-
regexp_parser (>=
|
|
133
|
-
|
|
134
|
-
rubocop-ast (>= 1.28.0, < 2.0)
|
|
149
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
150
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
135
151
|
ruby-progressbar (~> 1.7)
|
|
136
|
-
unicode-display_width (>= 2.4.0, <
|
|
137
|
-
rubocop-ast (1.
|
|
138
|
-
parser (>= 3.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
152
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
153
|
+
rubocop-ast (1.49.0)
|
|
154
|
+
parser (>= 3.3.7.2)
|
|
155
|
+
prism (~> 1.7)
|
|
156
|
+
rubocop-performance (1.26.1)
|
|
157
|
+
lint_roller (~> 1.1)
|
|
158
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
159
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
160
|
+
ruby-ole (1.2.13.1)
|
|
143
161
|
ruby-progressbar (1.13.0)
|
|
144
|
-
rubyXL (3.4.
|
|
162
|
+
rubyXL (3.4.33)
|
|
145
163
|
nokogiri (>= 1.10.8)
|
|
146
164
|
rubyzip (>= 1.3.0)
|
|
147
165
|
rubyntlm (0.6.5)
|
|
148
166
|
base64
|
|
149
|
-
rubyzip (3.0.
|
|
167
|
+
rubyzip (3.0.2)
|
|
150
168
|
savon (2.12.1)
|
|
151
169
|
akami (~> 1.2)
|
|
152
170
|
builder (>= 2.1.2)
|
|
@@ -156,31 +174,35 @@ GEM
|
|
|
156
174
|
nori (~> 2.4)
|
|
157
175
|
wasabi (~> 3.4)
|
|
158
176
|
sax-machine (1.3.2)
|
|
159
|
-
socksify (1.
|
|
160
|
-
spreadsheet (1.3.
|
|
177
|
+
socksify (1.8.1)
|
|
178
|
+
spreadsheet (1.3.4)
|
|
179
|
+
bigdecimal
|
|
180
|
+
logger
|
|
161
181
|
ruby-ole
|
|
162
|
-
standard (1.
|
|
182
|
+
standard (1.54.0)
|
|
163
183
|
language_server-protocol (~> 3.17.0.2)
|
|
164
184
|
lint_roller (~> 1.0)
|
|
165
|
-
rubocop (~> 1.
|
|
185
|
+
rubocop (~> 1.84.0)
|
|
166
186
|
standard-custom (~> 1.0.0)
|
|
167
|
-
standard-performance (~> 1.
|
|
187
|
+
standard-performance (~> 1.8)
|
|
168
188
|
standard-custom (1.0.2)
|
|
169
189
|
lint_roller (~> 1.0)
|
|
170
190
|
rubocop (~> 1.50)
|
|
171
|
-
standard-performance (1.0
|
|
172
|
-
lint_roller (~> 1.
|
|
173
|
-
rubocop-performance (~> 1.
|
|
191
|
+
standard-performance (1.9.0)
|
|
192
|
+
lint_roller (~> 1.1)
|
|
193
|
+
rubocop-performance (~> 1.26.0)
|
|
174
194
|
standardrb (1.0.1)
|
|
175
195
|
standard
|
|
176
|
-
timecop (0.9.
|
|
177
|
-
unicode-display_width (2.
|
|
178
|
-
|
|
196
|
+
timecop (0.9.10)
|
|
197
|
+
unicode-display_width (3.2.0)
|
|
198
|
+
unicode-emoji (~> 4.1)
|
|
199
|
+
unicode-emoji (4.2.0)
|
|
200
|
+
vcr (6.4.0)
|
|
179
201
|
wasabi (3.7.0)
|
|
180
202
|
addressable
|
|
181
203
|
httpi (~> 2.0)
|
|
182
204
|
nokogiri (>= 1.4.2)
|
|
183
|
-
webmock (3.
|
|
205
|
+
webmock (3.26.1)
|
|
184
206
|
addressable (>= 2.8.0)
|
|
185
207
|
crack (>= 0.3.2)
|
|
186
208
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
@@ -188,9 +210,10 @@ GEM
|
|
|
188
210
|
webrobots (0.1.2)
|
|
189
211
|
xml-simple (1.1.9)
|
|
190
212
|
rexml
|
|
191
|
-
yard (0.9.
|
|
213
|
+
yard (0.9.38)
|
|
192
214
|
|
|
193
215
|
PLATFORMS
|
|
216
|
+
arm64-darwin-25
|
|
194
217
|
ruby
|
|
195
218
|
|
|
196
219
|
DEPENDENCIES
|
|
@@ -211,4 +234,4 @@ DEPENDENCIES
|
|
|
211
234
|
webmock
|
|
212
235
|
|
|
213
236
|
BUNDLED WITH
|
|
214
|
-
2.
|
|
237
|
+
2.5.22
|
data/History.txt
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
=== 3.0.6 / 06.05.2026
|
|
2
|
+
* FHIR: extract Indikationscode (XXXXX.NN) from ClinicalUseDefinition resources by combining FOPHDossierNumber with each CUD's .NN id-suffix; expose as item[:indication_codes] (and per package) so downstream builders can include it on prescriptions and invoices as required by BAG from 2026-07-01 (issue #113)
|
|
3
|
+
* Declare csv as a runtime dependency in the gemspec (csv was removed from Ruby's default gems in 3.4)
|
|
4
|
+
|
|
5
|
+
=== 3.0.5 / 25.04.2026
|
|
6
|
+
* Refdata cleanup: compensate the doubled-dose template bug in Refdata.Articles.xml (e.g. "30 mg / 30 mg / 100 Tablette") by collapsing to a single token. Guarded by a Swissmedic-side comma check so real combination products are untouched (issue #112)
|
|
7
|
+
|
|
1
8
|
=== 3.0.4 / 24.04.2026
|
|
2
9
|
* Firstbase: switch -b/--firstbase from the deprecated pillbox.oddb.org XLSX to the GS1 Switzerland CSV at https://id.gs1.ch/01/07612345000961 (full firstbase barcode registry, ~189k items). Downloaded file is now firstbase.csv; FirstbaseExtractor now parses CSV with headers instead of XLSX.
|
|
3
10
|
|
data/README.md
CHANGED
|
@@ -51,7 +51,7 @@ HIN (http://hin.ch) creates daily the actual file. They can be downloaded from `
|
|
|
51
51
|
see `--help`.
|
|
52
52
|
|
|
53
53
|
```
|
|
54
|
-
/opt/src/oddb2xml/bin/oddb2xml version 3.0.
|
|
54
|
+
/opt/src/oddb2xml/bin/oddb2xml version 3.0.5
|
|
55
55
|
Usage:
|
|
56
56
|
oddb2xml [option]
|
|
57
57
|
produced files are found under data
|
|
@@ -112,7 +112,7 @@ FR
|
|
|
112
112
|
|
|
113
113
|
## Supported ruby version
|
|
114
114
|
|
|
115
|
-
You will need ruby >= 2.5 to work correctly. Current development happens on Ruby 3.
|
|
115
|
+
You will need ruby >= 2.5 to work correctly. Current development happens on Ruby 3.3 (`.ruby-version`).
|
|
116
116
|
CI runs on Ruby 3.0, 3.1 and 3.2 via GitHub Actions — see the badge above for the latest spec results.
|
|
117
117
|
|
|
118
118
|
|
|
@@ -292,6 +292,27 @@ We use the following files:
|
|
|
292
292
|
* https://epl.bag.admin.ch/static/fhir/foph-sl-export-latest-{de,fr,it}.ndjson (FHIR NDJSON, used with `--fhir`)
|
|
293
293
|
* https://id.gs1.ch/01/07612345000961 (GS1 Switzerland firstbase CSV — full barcode registry, used with `-b`/`--firstbase`)
|
|
294
294
|
|
|
295
|
+
## Refdata data-quality compensation
|
|
296
|
+
|
|
297
|
+
Refdata.Articles.xml from `files.refdata.ch` ships with a number of recurring
|
|
298
|
+
data-quality issues that propagate into downstream systems unchanged. oddb2xml
|
|
299
|
+
applies a small set of conservative cleanups before emitting any output. See
|
|
300
|
+
GitHub issue [#112](https://github.com/zdavatz/oddb2xml/issues/112) for the
|
|
301
|
+
full catalogue and the parallel report sent to Refdata.
|
|
302
|
+
|
|
303
|
+
Currently active fixes (`lib/oddb2xml/refdata_cleanup.rb`):
|
|
304
|
+
|
|
305
|
+
* **Doubled dose token** — Refdata sometimes emits the strength twice in
|
|
306
|
+
`<FullName>`, e.g. `MIRTAZAPIN Sandoz eco 30 mg / 30 mg / 100 Tablette`.
|
|
307
|
+
When the matching Swissmedic entry shows a single active substance, the
|
|
308
|
+
duplicate token is collapsed to a single occurrence. Real combination
|
|
309
|
+
products (e.g. PHESGO 600 mg / 600 mg / 10 ml — pertuzumab + trastuzumab)
|
|
310
|
+
are detected via the comma in `substance_swissmedic` and left untouched.
|
|
311
|
+
|
|
312
|
+
The cleanup runs at the start of `prepare_articles` in `Builder` and is
|
|
313
|
+
idempotent. Each rule is guarded by a Swissmedic-side heuristic so genuine
|
|
314
|
+
data is never altered.
|
|
315
|
+
|
|
295
316
|
## Rules for matching GTIN (aka EAN13), product number and IKSNR
|
|
296
317
|
|
|
297
318
|
For drugs which appear in Packungen.xlsx file published by Swissmedic the following rule is used to create the GTIN
|
data/lib/oddb2xml/builder.rb
CHANGED
|
@@ -88,12 +88,42 @@ module Oddb2xml
|
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
+
# Mutates @refdata in place to compensate for known Refdata.Articles.xml
|
|
92
|
+
# data-quality issues (see GitHub issue #112). Idempotent: subsequent
|
|
93
|
+
# calls are no-ops within the same Builder instance.
|
|
94
|
+
def apply_refdata_description_cleanups!
|
|
95
|
+
return if @refdata_descriptions_cleaned
|
|
96
|
+
@refdata_descriptions_cleaned = true
|
|
97
|
+
return if @refdata.nil? || @refdata.empty?
|
|
98
|
+
double_dose_fixed = 0
|
|
99
|
+
@refdata.each_value do |item|
|
|
100
|
+
next unless item.is_a?(Hash)
|
|
101
|
+
no8 = item[:no8]
|
|
102
|
+
next if no8.nil? || no8.empty?
|
|
103
|
+
pack = @packs[no8]
|
|
104
|
+
next unless pack
|
|
105
|
+
substance = pack[:substance_swissmedic]
|
|
106
|
+
[:desc_de, :desc_fr, :desc_it].each do |key|
|
|
107
|
+
original = item[key]
|
|
108
|
+
cleaned = RefdataCleanup.fix_double_dose(original, substance)
|
|
109
|
+
if cleaned != original
|
|
110
|
+
item[key] = cleaned
|
|
111
|
+
double_dose_fixed += 1
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
if double_dose_fixed > 0
|
|
116
|
+
Oddb2xml.log("Refdata cleanup: fixed double-dose pattern in #{double_dose_fixed} description(s)")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
91
120
|
private_class_method
|
|
92
121
|
|
|
93
122
|
def prepare_articles(reset = false)
|
|
94
123
|
@articles = nil if reset
|
|
95
124
|
unless @articles
|
|
96
125
|
Oddb2xml.log("prepare_articles starting with #{@articles ? @articles.size : "no"} articles.")
|
|
126
|
+
apply_refdata_description_cleanups!
|
|
97
127
|
@articles = []
|
|
98
128
|
@refdata.each do |ean13, obj|
|
|
99
129
|
unless SKIP_MIGEL_DOWNLOADER
|
|
@@ -159,7 +159,7 @@ module Oddb2xml
|
|
|
159
159
|
module FHIR
|
|
160
160
|
# Bundle represents one line in the NDJSON file
|
|
161
161
|
class Bundle
|
|
162
|
-
attr_reader :medicinal_product, :packages, :authorizations, :ingredients
|
|
162
|
+
attr_reader :medicinal_product, :packages, :authorizations, :ingredients, :clinical_use_definitions
|
|
163
163
|
|
|
164
164
|
def initialize(json_line)
|
|
165
165
|
data = JSON.parse(json_line)
|
|
@@ -174,6 +174,7 @@ module Oddb2xml
|
|
|
174
174
|
@packages = []
|
|
175
175
|
@authorizations = []
|
|
176
176
|
@ingredients = []
|
|
177
|
+
@clinical_use_definitions = []
|
|
177
178
|
|
|
178
179
|
@entries.each do |entry|
|
|
179
180
|
resource = entry["resource"]
|
|
@@ -186,11 +187,32 @@ module Oddb2xml
|
|
|
186
187
|
@authorizations << Authorization.new(resource)
|
|
187
188
|
when "Ingredient"
|
|
188
189
|
@ingredients << Ingredient.new(resource)
|
|
190
|
+
when "ClinicalUseDefinition"
|
|
191
|
+
@clinical_use_definitions << ClinicalUseDefinition.new(resource)
|
|
189
192
|
end
|
|
190
193
|
end
|
|
191
194
|
end
|
|
192
195
|
end
|
|
193
196
|
|
|
197
|
+
# ClinicalUseDefinition carries one indication. Its `id` ends in ".NN",
|
|
198
|
+
# the per-indication suffix that combines with the FOPHDossierNumber
|
|
199
|
+
# (XXXXX) on the reimbursement RegulatedAuthorization to form the
|
|
200
|
+
# Indikationscode XXXXX.NN required by BAG from 2026-07-01.
|
|
201
|
+
class ClinicalUseDefinition
|
|
202
|
+
attr_reader :id, :nn_suffix, :type, :text
|
|
203
|
+
|
|
204
|
+
def initialize(resource)
|
|
205
|
+
@id = resource["id"]
|
|
206
|
+
@type = resource["type"]
|
|
207
|
+
@nn_suffix = @id&.[](/\.(\d{2})\z/, 1)
|
|
208
|
+
@text = resource.dig("indication", "diseaseSymptomProcedure", "concept", "text")
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def indication?
|
|
212
|
+
@type == "indication"
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
194
216
|
class MedicinalProduct
|
|
195
217
|
attr_reader :names, :atc_code, :classification, :it_codes
|
|
196
218
|
|
|
@@ -427,6 +449,11 @@ module Oddb2xml
|
|
|
427
449
|
prep.OrgGenCode = map_org_gen_code(mp.classification)
|
|
428
450
|
prep.ItCode = mp.it_code # Add IT code
|
|
429
451
|
|
|
452
|
+
# Indikationscodes (BAG: XXXXX.NN, mandatory on prescriptions/invoices
|
|
453
|
+
# from 2026-07-01). Build from FOPHDossierNumber (reimbursement auth)
|
|
454
|
+
# plus each ClinicalUseDefinition's .NN suffix. See issue #113.
|
|
455
|
+
prep.IndicationCodes = build_indication_codes(bundle)
|
|
456
|
+
|
|
430
457
|
# Map packages
|
|
431
458
|
prep.Packs = OpenStruct.new
|
|
432
459
|
prep.Packs.Pack = bundle.packages.map do |pkg|
|
|
@@ -535,6 +562,21 @@ module Oddb2xml
|
|
|
535
562
|
reimbursement&.cost_share
|
|
536
563
|
end
|
|
537
564
|
|
|
565
|
+
def build_indication_codes(bundle)
|
|
566
|
+
reimbursement = bundle.authorizations.find(&:reimbursement_sl?)
|
|
567
|
+
dossier = reimbursement&.foph_dossier_no
|
|
568
|
+
return [] unless dossier && !bundle.clinical_use_definitions.empty?
|
|
569
|
+
|
|
570
|
+
bundle.clinical_use_definitions.each_with_object([]) do |cud, acc|
|
|
571
|
+
next unless cud.indication? && cud.nn_suffix
|
|
572
|
+
acc << OpenStruct.new(
|
|
573
|
+
code: "#{dossier}.#{cud.nn_suffix}",
|
|
574
|
+
cud_id: cud.id,
|
|
575
|
+
text: cud.text
|
|
576
|
+
)
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
|
|
538
580
|
def map_org_gen_code(classification)
|
|
539
581
|
return nil unless classification
|
|
540
582
|
|
|
@@ -616,6 +658,13 @@ module Oddb2xml
|
|
|
616
658
|
item[:comment_it] = ""
|
|
617
659
|
item[:it_code] = (itc = seq.ItCode) ? itc : "" # NOW available in FHIR!
|
|
618
660
|
|
|
661
|
+
# Indikationscodes (BAG XXXXX.NN, see issue #113). Each entry is a
|
|
662
|
+
# Hash with :code, :cud_id, :text — mandatory on rx/invoices from
|
|
663
|
+
# 2026-07-01.
|
|
664
|
+
item[:indication_codes] = Array(seq.IndicationCodes).map do |ic|
|
|
665
|
+
{code: ic.code, cud_id: ic.cud_id, text: ic.text}
|
|
666
|
+
end
|
|
667
|
+
|
|
619
668
|
# Build substances array
|
|
620
669
|
item[:substances] = []
|
|
621
670
|
if seq.Substances && seq.Substances.Substance
|
|
@@ -673,7 +722,8 @@ module Oddb2xml
|
|
|
673
722
|
sl_entry: true,
|
|
674
723
|
swissmedic_category: (cat = pac.SwissmedicCategory) ? cat : "",
|
|
675
724
|
swissmedic_number8: (num = pac.SwissmedicNo8) ? num : "",
|
|
676
|
-
prices: {exf_price: exf, pub_price: pub}
|
|
725
|
+
prices: {exf_price: exf, pub_price: pub},
|
|
726
|
+
indication_codes: item[:indication_codes]
|
|
677
727
|
}
|
|
678
728
|
|
|
679
729
|
# Map limitations from FHIR
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Oddb2xml
|
|
2
|
+
# Compensates for known data-quality issues in upstream Refdata.Articles.xml
|
|
3
|
+
# before they reach the generated output. Each fix is opt-in and guarded by
|
|
4
|
+
# a heuristic against Swissmedic data so we never alter genuine combination
|
|
5
|
+
# products. See GitHub issue #112 for the catalogue of upstream problems.
|
|
6
|
+
module RefdataCleanup
|
|
7
|
+
DOSE_TOKEN = /\d+(?:[.,]\d+)?\s*(?:mg|µg|mcg|g|ml|UI|U\.I\.|IE|%)/i
|
|
8
|
+
# Matches "<dose> / <same dose> /" – the templating bug where Refdata
|
|
9
|
+
# repeats the strength once. The backreference \1 only matches when the
|
|
10
|
+
# exact same dose string appears twice, which keeps real combos
|
|
11
|
+
# (e.g. PHESGO 600 mg / 600 mg / 10 ml) safe – those are caught by the
|
|
12
|
+
# single_substance? guard, but the literal-match also acts as a backstop.
|
|
13
|
+
DOUBLE_DOSE_RE = /(#{DOSE_TOKEN})\s*\/\s*\1\s*\/\s*/
|
|
14
|
+
|
|
15
|
+
# A Swissmedic compositions cell like "mirtazapinum" indicates a mono
|
|
16
|
+
# product; "atovaquonum, proguanili hydrochloridum" or
|
|
17
|
+
# "pertuzumabum, trastuzumabum" indicates a real combination.
|
|
18
|
+
def self.single_substance?(swissmedic_substance)
|
|
19
|
+
return false if swissmedic_substance.nil?
|
|
20
|
+
str = swissmedic_substance.to_s.strip
|
|
21
|
+
return false if str.empty?
|
|
22
|
+
!str.include?(",")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Removes the duplicated dose token in mono products. Returns the
|
|
26
|
+
# cleaned description, or the original string if no change applies.
|
|
27
|
+
def self.fix_double_dose(desc, swissmedic_substance)
|
|
28
|
+
return desc if desc.nil? || desc.empty?
|
|
29
|
+
return desc unless DOUBLE_DOSE_RE.match?(desc)
|
|
30
|
+
return desc unless single_substance?(swissmedic_substance)
|
|
31
|
+
desc.sub(DOUBLE_DOSE_RE, '\1 / ')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/oddb2xml/version.rb
CHANGED
data/lib/oddb2xml.rb
CHANGED
data/oddb2xml.gemspec
CHANGED
|
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
|
|
|
38
38
|
spec.add_dependency "htmlentities"
|
|
39
39
|
spec.add_dependency "webrick", ">= 1.8.2"
|
|
40
40
|
spec.add_dependency "rexml", ">= 3.3.9"
|
|
41
|
+
spec.add_dependency "csv" # bundled with Ruby <= 3.3, gem from 3.4 onwards
|
|
41
42
|
spec.add_dependency "standardrb"
|
|
42
43
|
spec.add_dependency "rack", ">= 3.1.20"
|
|
43
44
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"resourceType":"Bundle","id":"9a91dc6e-3410-4020-9375-b2cae207e1bf","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-bundle"]},"type":"collection","entry":[{"fullUrl":"http://fhir.epl.bag.admin.ch/MedicinalProductDefinition/de872be1-02ff-4b8e-8691-658099182eb7","resource":{"resourceType":"MedicinalProductDefinition","id":"de872be1-02ff-4b8e-8691-658099182eb7","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-medicinalproductdefinition"]},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Cyramza Inf Konz 100 mg/10 ml</div>"},"classification":[{"coding":[{"system":"http://www.whocc.no/atc","code":"L01FG02","display":"Ramucirumab"}]},{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-index-therapeuticus","code":"070000","display":"07. STOFFWECHSEL"}]},{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-index-therapeuticus","code":"071600","display":"07.16. Oncologica"}]},{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-index-therapeuticus","code":"071610","display":"07.16.10. Cytostatica"}]}],"name":[{"productName":"Cyramza Inf Konz 100 mg/10 ml","usage":[{"country":{"coding":[{"system":"urn:iso:std:iso:3166","code":"CH","display":"Switzerland"}]},"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"de-CH","display":"German (Switzerland)"}]}}]},{"productName":"Cyramza conc perf 100 mg/10 ml","usage":[{"country":{"coding":[{"system":"urn:iso:std:iso:3166","code":"CH","display":"Switzerland"}]},"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"fr-CH","display":"French (Switzerland)"}]}}]},{"productName":"Cyramza Inf Konz 100 mg/10 ml","usage":[{"country":{"coding":[{"system":"urn:iso:std:iso:3166","code":"CH","display":"Switzerland"}]},"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"it-CH","display":"Italian (Switzerland)"}]}}]}]}},{"fullUrl":"http://fhir.epl.bag.admin.ch/RegulatedAuthorization/65206","resource":{"resourceType":"RegulatedAuthorization","id":"65206","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-regulatedauthorization"]},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Marketing Authorisation 65206 for MedicinalProductDefinition de872be1-02ff-4b8e-8691-658099182eb7</div>"},"contained":[{"resourceType":"Organization","id":"7601001261853","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-organization"]},"identifier":[{"system":"urn:oid:2.51.1.3","value":"7601001261853"}],"name":"Eli Lilly (Suisse) SA"}],"identifier":[{"system":"http://fhir.ch/ig/ch-epl/sid/authno","value":"65206"}],"subject":[{"reference":"CHIDMPMedicinalProductDefinition/de872be1-02ff-4b8e-8691-658099182eb7"}],"type":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-authorisation-type","code":"756000002001","display":"Marketing Authorisation"}]},"region":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"CH","display":"Switzerland"}]}],"holder":{"reference":"#7601001261853"}}},{"fullUrl":"http://fhir.epl.bag.admin.ch/PackagedProductDefinition/c3a247c9-d686-49ab-9344-892d8b93f148","resource":{"resourceType":"PackagedProductDefinition","id":"c3a247c9-d686-49ab-9344-892d8b93f148","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-packagedproductdefinition"]},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Cyramza Inf Konz 100 mg/10 ml Durchstf 1 Stk</div>"},"packageFor":[{"reference":"CHIDMPMedicinalProductDefinition/de872be1-02ff-4b8e-8691-658099182eb7"}],"containedItemQuantity":[{"unit":"Durchstf 1 Stk"}],"description":"Cyramza Inf Konz 100 mg/10 ml Durchstf 1 Stk","legalStatusOfSupply":[{"code":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-SMC-legal-status-of-supply","code":"756005022001","display":"Medicinal product subject to medical or veterinary prescription single dispensation (A)"}]}}],"packaging":{"identifier":[{"system":"urn:oid:2.51.1.1","value":"7680652060015"}]}}},{"fullUrl":"http://fhir.epl.bag.admin.ch/RegulatedAuthorization/65206001-c3a247c9-d686-49ab-9344-892d8b93f148","resource":{"resourceType":"RegulatedAuthorization","id":"65206001-c3a247c9-d686-49ab-9344-892d8b93f148","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-regulatedauthorization"]},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Marketing Authorisation 65206001-c3a247c9-d686-49ab-9344-892d8b93f148 for PackagedProductDefinition c3a247c9-d686-49ab-9344-892d8b93f148</div>"},"contained":[{"resourceType":"Organization","id":"7601001261853","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-organization"]},"identifier":[{"system":"urn:oid:2.51.1.3","value":"7601001261853"}],"name":"Eli Lilly (Suisse) SA"}],"identifier":[{"system":"http://fhir.ch/ig/ch-epl/sid/authno","value":"65206001"}],"subject":[{"reference":"CHIDMPPackagedProductDefinition/c3a247c9-d686-49ab-9344-892d8b93f148"}],"type":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-authorisation-type","code":"756000002001","display":"Marketing Authorisation"}]},"region":[{"coding":[{"system":"urn:iso:std:iso:3166","code":"CH","display":"Switzerland"}]}],"holder":{"reference":"#7601001261853"}}},{"fullUrl":"http://fhir.epl.bag.admin.ch/RegulatedAuthorization/9c50920a-2d9d-476f-89d8-9477c8840de9","resource":{"resourceType":"RegulatedAuthorization","id":"9c50920a-2d9d-476f-89d8-9477c8840de9","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-regulatedauthorization"]},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Reimbursement SL 9c50920a-2d9d-476f-89d8-9477c8840de9 for PackagedProductDefinition c3a247c9-d686-49ab-9344-892d8b93f148</div>"},"contained":[{"resourceType":"Organization","id":"7601001261853","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-organization"]},"identifier":[{"system":"urn:oid:2.51.1.3","value":"7601001261853"}],"name":"Eli Lilly (Suisse) SA"}],"extension":[{"url":"http://fhir.ch/ig/ch-epl/StructureDefinition/reimbursementSL","extension":[{"url":"FOPHDossierNumber","valueIdentifier":{"system":"urn:oid:2.16.756.1","value":"20403"}},{"url":"status","valueCodeableConcept":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-reimbursement-status","code":"756001021001","display":"Reimbursed"}]}},{"url":"statusDate","valueDate":"2019-06-01"},{"url":"listingStatus","valueCodeableConcept":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-listing-status","code":"756001002001","display":"Listed"}]}},{"url":"listingPeriod","valuePeriod":{"start":"2019-06-01"}},{"url":"firstListingDate","valueDate":"2016-03-01"},{"url":"costShare","valueInteger":10},{"url":"priceModel","valueBoolean":true},{"url":"http://fhir.ch/ig/ch-epl/StructureDefinition/productPrice","extension":[{"url":"type","valueCodeableConcept":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-price-type","code":"756002005001","display":"Retail price"}]}},{"url":"changeType","valueCodeableConcept":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-type-of-price-change","code":"756002006009","display":"Normal price mutation"}]}},{"url":"value","valueMoney":{"value":421.65,"currency":"CHF"}},{"url":"changeDate","valueDate":"2024-10-01"}]},{"url":"http://fhir.ch/ig/ch-epl/StructureDefinition/productPrice","extension":[{"url":"type","valueCodeableConcept":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-price-type","code":"756002005002","display":"Ex-factory price"}]}},{"url":"changeType","valueCodeableConcept":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-type-of-price-change","code":"756002006009","display":"Normal price mutation"}]}},{"url":"value","valueMoney":{"value":372.61,"currency":"CHF"}},{"url":"changeDate","valueDate":"2024-10-01"}]}]}],"subject":[{"reference":"CHIDMPPackagedProductDefinition/c3a247c9-d686-49ab-9344-892d8b93f148"}],"type":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-authorisation-type","code":"756000002003","display":"Reimbursement SL"}]},"indication":[{"extension":[{"url":"http://fhir.ch/ig/ch-epl/StructureDefinition/regulatedAuthorization-limitation","extension":[{"url":"status","valueCodeableConcept":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-limitationstatus","code":"756002071001","display":"Limitation Reimbursed"}]}},{"url":"statusDate","valueDate":"2024-10-01"},{"url":"period","valuePeriod":{"start":"2024-10-01"}},{"url":"firstLimitationDate","valueDate":"2016-03-01"},{"url":"indicationCode","valueString":"20403.01"},{"url":"limitationIndication","valueReference":{"reference":"ClinicalUseDefinition/CYRAMZA.01"}}]}]},{"extension":[{"url":"http://fhir.ch/ig/ch-epl/StructureDefinition/regulatedAuthorization-limitation","extension":[{"url":"status","valueCodeableConcept":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-epl-foph-limitationstatus","code":"756002071001","display":"Limitation Reimbursed"}]}},{"url":"statusDate","valueDate":"2024-10-01"},{"url":"period","valuePeriod":{"start":"2024-10-01","end":"2027-09-30"}},{"url":"reimbursementEndDate","valueDate":"2027-12-31"},{"url":"firstLimitationDate","valueDate":"2016-03-01"},{"url":"indicationCode","valueString":"20403.02"},{"url":"limitationIndication","valueReference":{"reference":"ClinicalUseDefinition/CYRAMZA.02"}}]}]}],"holder":{"reference":"#7601001261853"}}},{"fullUrl":"http://fhir.epl.bag.admin.ch/Ingredient/a5edbabc-f9d6-4019-ac6f-2a80fe033339","resource":{"resourceType":"Ingredient","id":"a5edbabc-f9d6-4019-ac6f-2a80fe033339","meta":{"profile":["http://fhir.ch/ig/ch-epl/StructureDefinition/ch-idmp-ingredient"]},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Ramucirumabum 100 mg</div>"},"status":"draft","for":[{"reference":"CHIDMPMedicinalProductDefinition/de872be1-02ff-4b8e-8691-658099182eb7"}],"role":{"coding":[{"system":"http://fhir.ch/ig/ch-epl/CodeSystem/ch-SMC-ingredient-role","code":"756005051001","display":"Active"}]},"substance":{"code":{"concept":{"text":"Ramucirumabum"}},"strength":[{"presentationQuantity":{"value":100,"unit":"mg"}}]}}},{"fullUrl":"http://fhir.epl.bag.admin.ch/ClinicalUseDefinition/CYRAMZA.01","resource":{"resourceType":"ClinicalUseDefinition","id":"CYRAMZA.01","text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">In Kombination mit Paclitaxel für die Behandlung von erwachsenen Patienten mit ECOG-Status 0 oder 1 mit fortgeschrittenem Adenokarzinom des Magens oder gastroösophagealen Übergangs mit einem Progress nach vorausgegangener Platin- und Fluoropyrimidin-haltiger Chemotherapie.\nAls Monotherapie für die Behandlung von erwachsenen Patienten mit ECOG-Status 0 oder 1 mit fortgeschrittenem Adenokarzinom des Magens oder des gastroösophagealen Übergangs mit einem Progress nach vorausgegangener Platin- oder Fluoropyrimidin-haltiger Chemotherapie, wenn diese Patienten für eine Kombinationstherapie mit Paclitaxel nicht geeignet sind.\nDie Behandlung bedarf der Kostengutsprache durch den Krankenversicherer nach vorgängiger Konsultation des Vertrauensarztes.\nFolgender Indikationscode ist an den Krankenversicherer zu übermitteln: 20403.01</div>"},"type":"indication","indication":{"diseaseSymptomProcedure":{"concept":{"text":"In Kombination mit Paclitaxel für die Behandlung von erwachsenen Patienten mit ECOG-Status 0 oder 1 mit fortgeschrittenem Adenokarzinom des Magens oder gastroösophagealen Übergangs mit einem Progress nach vorausgegangener Platin- und Fluoropyrimidin-haltiger Chemotherapie.\nAls Monotherapie für die Behandlung von erwachsenen Patienten mit ECOG-Status 0 oder 1 mit fortgeschrittenem Adenokarzinom des Magens oder des gastroösophagealen Übergangs mit einem Progress nach vorausgegangener Platin- oder Fluoropyrimidin-haltiger Chemotherapie, wenn diese Patienten für eine Kombinationstherapie mit Paclitaxel nicht geeignet sind.\nDie Behandlung bedarf der Kostengutsprache durch den Krankenversicherer nach vorgängiger Konsultation des Vertrauensarztes.\nFolgender Indikationscode ist an den Krankenversicherer zu übermitteln: 20403.01"}}}}},{"fullUrl":"http://fhir.epl.bag.admin.ch/ClinicalUseDefinition/CYRAMZA.02","resource":{"resourceType":"ClinicalUseDefinition","id":"CYRAMZA.02","text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">In Kombination mit FOLFIRI (Irinotecan, Folsäure und 5-Fluorouracil) zur Behandlung von erwachsenen Patienten mit metastasiertem kolorektalem Karzinom (mKRK) mit Progress während oder nach vorausgegangener Therapie mit Bevacizumab, Oxaliplatin und einem Fluoropyrimidin. Die Behandlung bedarf der Kostengutsprache durch den Krankenversicherer nach vorgängiger Konsultation des Vertrauensarztes.\nDie Eli Lilly (Suisse) SA vergütet dem Krankenversicherer, bei dem die versicherte Person zum Zeitpunkt des Bezugs versichert war, auf dessen erste Aufforderung hin für auf jede bezogene Packung CYRAMZA einen festgelegten Anteil des Fabrikabgabepreises zurück. Sie gibt dem Krankenversicherer die Höhe der Rückvergütung bekannt. Die Mehrwertsteuer kann nicht zusätzlich zu diesem Anteil des Fabrikabgabepreises zurückgefordert werden. Die Aufforderung zur Rückvergütung soll ab dem Zeitpunkt der Verabreichung erfolgen.\nFolgender Indikationscode ist an den Krankenversicherer zu übermitteln: 20403.02</div>"},"type":"indication","indication":{"diseaseSymptomProcedure":{"concept":{"text":"In Kombination mit FOLFIRI (Irinotecan, Folsäure und 5-Fluorouracil) zur Behandlung von erwachsenen Patienten mit metastasiertem kolorektalem Karzinom (mKRK) mit Progress während oder nach vorausgegangener Therapie mit Bevacizumab, Oxaliplatin und einem Fluoropyrimidin. Die Behandlung bedarf der Kostengutsprache durch den Krankenversicherer nach vorgängiger Konsultation des Vertrauensarztes.\nDie Eli Lilly (Suisse) SA vergütet dem Krankenversicherer, bei dem die versicherte Person zum Zeitpunkt des Bezugs versichert war, auf dessen erste Aufforderung hin für auf jede bezogene Packung CYRAMZA einen festgelegten Anteil des Fabrikabgabepreises zurück. Sie gibt dem Krankenversicherer die Höhe der Rückvergütung bekannt. Die Mehrwertsteuer kann nicht zusätzlich zu diesem Anteil des Fabrikabgabepreises zurückgefordert werden. Die Aufforderung zur Rückvergütung soll ab dem Zeitpunkt der Verabreichung erfolgen.\nFolgender Indikationscode ist an den Krankenversicherer zu übermitteln: 20403.02"}}}}}]}
|
data/spec/fhir_spec.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "oddb2xml/downloader"
|
|
3
|
+
require "oddb2xml/extractor"
|
|
4
|
+
require "oddb2xml/fhir_support"
|
|
5
|
+
|
|
6
|
+
describe "FHIR Indikationscode support" do
|
|
7
|
+
let(:cyramza_fixture) { File.join(Oddb2xml::SpecData, "fhir", "cyramza.ndjson") }
|
|
8
|
+
|
|
9
|
+
describe Oddb2xml::FHIR::ClinicalUseDefinition do
|
|
10
|
+
it "extracts the .NN suffix from id" do
|
|
11
|
+
cud = described_class.new("id" => "CYRAMZA.02", "type" => "indication")
|
|
12
|
+
expect(cud.nn_suffix).to eq("02")
|
|
13
|
+
expect(cud.indication?).to be true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "returns nil suffix for non-conforming ids" do
|
|
17
|
+
cud = described_class.new("id" => "CYRAMZA")
|
|
18
|
+
expect(cud.nn_suffix).to be_nil
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe Oddb2xml::FHIR::Bundle do
|
|
23
|
+
it "collects ClinicalUseDefinition entries" do
|
|
24
|
+
line = File.read(cyramza_fixture).lines.first
|
|
25
|
+
bundle = described_class.new(line)
|
|
26
|
+
expect(bundle.clinical_use_definitions).not_to be_empty
|
|
27
|
+
expect(bundle.clinical_use_definitions.map(&:id)).to include("CYRAMZA.01", "CYRAMZA.02")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe Oddb2xml::FHIR::PreparationsParser do
|
|
32
|
+
it "constructs XXXXX.NN indication codes from FOPHDossierNumber + CUD suffix" do
|
|
33
|
+
parser = described_class.new(cyramza_fixture)
|
|
34
|
+
prep = parser.preparations.first
|
|
35
|
+
codes = prep.IndicationCodes.map(&:code)
|
|
36
|
+
expect(codes).to include("20403.01", "20403.02")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe Oddb2xml::FhirExtractor do
|
|
41
|
+
it "exposes indication_codes on each item and package" do
|
|
42
|
+
data = described_class.new(cyramza_fixture).to_hash
|
|
43
|
+
expect(data).not_to be_empty
|
|
44
|
+
|
|
45
|
+
item = data.values.first
|
|
46
|
+
codes = item[:indication_codes].map { |ic| ic[:code] }
|
|
47
|
+
expect(codes).to include("20403.01", "20403.02")
|
|
48
|
+
|
|
49
|
+
pkg = item[:packages].values.first
|
|
50
|
+
expect(pkg[:indication_codes]).to eq(item[:indication_codes])
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "oddb2xml/refdata_cleanup"
|
|
3
|
+
|
|
4
|
+
describe Oddb2xml::RefdataCleanup do
|
|
5
|
+
describe ".single_substance?" do
|
|
6
|
+
it "returns true for a single Swissmedic substance" do
|
|
7
|
+
expect(described_class.single_substance?("mirtazapinum")).to be true
|
|
8
|
+
expect(described_class.single_substance?("methotrexatum")).to be true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "returns false when multiple substances are listed (combo)" do
|
|
12
|
+
expect(described_class.single_substance?("pertuzumabum, trastuzumabum")).to be false
|
|
13
|
+
expect(described_class.single_substance?("atovaquonum, proguanili hydrochloridum")).to be false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "returns false when input is nil or empty" do
|
|
17
|
+
expect(described_class.single_substance?(nil)).to be false
|
|
18
|
+
expect(described_class.single_substance?("")).to be false
|
|
19
|
+
expect(described_class.single_substance?(" ")).to be false
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe ".fix_double_dose" do
|
|
24
|
+
let(:mono) { "mirtazapinum" }
|
|
25
|
+
let(:combo) { "pertuzumabum, trastuzumabum" }
|
|
26
|
+
|
|
27
|
+
it "removes the duplicate dose for a mono product" do
|
|
28
|
+
input = "MIRTAZAPIN Sandoz eco 30 mg / 30 mg / 100 Tablette"
|
|
29
|
+
expected = "MIRTAZAPIN Sandoz eco 30 mg / 100 Tablette"
|
|
30
|
+
expect(described_class.fix_double_dose(input, mono)).to eq expected
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "handles ICATIBANT-style spacing" do
|
|
34
|
+
input = "ICATIBANT Spirig HC 30 mg / 30 mg / 1 x 3 ml"
|
|
35
|
+
expected = "ICATIBANT Spirig HC 30 mg / 1 x 3 ml"
|
|
36
|
+
expect(described_class.fix_double_dose(input, mono)).to eq expected
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "leaves real combinations untouched (PHESGO 600 mg / 600 mg / 10 ml)" do
|
|
40
|
+
input = "PHESGO Inj Lös 600 mg/600 mg/10 ml Durchstf"
|
|
41
|
+
expect(described_class.fix_double_dose(input, combo)).to eq input
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "leaves descriptions without the double-dose pattern untouched" do
|
|
45
|
+
input = "LEVOCETIRIZIN Spirig HC Filmtabl 5 mg 10 Stk"
|
|
46
|
+
expect(described_class.fix_double_dose(input, mono)).to eq input
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "leaves the description untouched when Swissmedic substance is unknown" do
|
|
50
|
+
input = "MIRTAZAPIN Sandoz eco 30 mg / 30 mg / 100 Tablette"
|
|
51
|
+
expect(described_class.fix_double_dose(input, nil)).to eq input
|
|
52
|
+
expect(described_class.fix_double_dose(input, "")).to eq input
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "is a no-op for nil or empty descriptions" do
|
|
56
|
+
expect(described_class.fix_double_dose(nil, mono)).to be_nil
|
|
57
|
+
expect(described_class.fix_double_dose("", mono)).to eq ""
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "does not collapse different doses (X mg / Y mg)" do
|
|
61
|
+
input = "FOO 250 mg / 100 mg / 12 Stk"
|
|
62
|
+
expect(described_class.fix_double_dose(input, combo)).to eq input
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe Oddb2xml::Builder do
|
|
68
|
+
describe "#apply_refdata_description_cleanups!" do
|
|
69
|
+
let(:builder) { Oddb2xml::Builder.new }
|
|
70
|
+
|
|
71
|
+
it "fixes double-dose entries on mono products" do
|
|
72
|
+
builder.packs = {
|
|
73
|
+
"69475006" => {substance_swissmedic: "mirtazapinum"}
|
|
74
|
+
}
|
|
75
|
+
builder.refdata = {
|
|
76
|
+
"7680694750066" => {
|
|
77
|
+
ean13: "7680694750066",
|
|
78
|
+
no8: "69475006",
|
|
79
|
+
desc_de: "MIRTAZAPIN Sandoz eco 30 mg / 30 mg / 100 Tablette",
|
|
80
|
+
desc_fr: "MIRTAZAPIN Sandoz eco 30 mg / 30 mg / 100 comprimé(",
|
|
81
|
+
desc_it: ""
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
builder.apply_refdata_description_cleanups!
|
|
86
|
+
|
|
87
|
+
item = builder.refdata["7680694750066"]
|
|
88
|
+
expect(item[:desc_de]).to eq "MIRTAZAPIN Sandoz eco 30 mg / 100 Tablette"
|
|
89
|
+
expect(item[:desc_fr]).to eq "MIRTAZAPIN Sandoz eco 30 mg / 100 comprimé("
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it "leaves combo products untouched" do
|
|
93
|
+
builder.packs = {
|
|
94
|
+
"67828001" => {substance_swissmedic: "pertuzumabum, trastuzumabum"}
|
|
95
|
+
}
|
|
96
|
+
original = "PHESGO Inj Lös 600 mg/600 mg/10 ml Durchstf"
|
|
97
|
+
builder.refdata = {
|
|
98
|
+
"7680678280013" => {
|
|
99
|
+
ean13: "7680678280013",
|
|
100
|
+
no8: "67828001",
|
|
101
|
+
desc_de: original,
|
|
102
|
+
desc_fr: "",
|
|
103
|
+
desc_it: ""
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
builder.apply_refdata_description_cleanups!
|
|
108
|
+
|
|
109
|
+
expect(builder.refdata["7680678280013"][:desc_de]).to eq original
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "is idempotent" do
|
|
113
|
+
builder.packs = {
|
|
114
|
+
"69475006" => {substance_swissmedic: "mirtazapinum"}
|
|
115
|
+
}
|
|
116
|
+
builder.refdata = {
|
|
117
|
+
"7680694750066" => {
|
|
118
|
+
ean13: "7680694750066",
|
|
119
|
+
no8: "69475006",
|
|
120
|
+
desc_de: "MIRTAZAPIN Sandoz eco 30 mg / 30 mg / 100 Tablette",
|
|
121
|
+
desc_fr: "",
|
|
122
|
+
desc_it: ""
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
builder.apply_refdata_description_cleanups!
|
|
127
|
+
builder.apply_refdata_description_cleanups!
|
|
128
|
+
|
|
129
|
+
expect(builder.refdata["7680694750066"][:desc_de])
|
|
130
|
+
.to eq "MIRTAZAPIN Sandoz eco 30 mg / 100 Tablette"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "skips entries without a Swissmedic match" do
|
|
134
|
+
builder.packs = {}
|
|
135
|
+
input = "MIRTAZAPIN Sandoz eco 30 mg / 30 mg / 100 Tablette"
|
|
136
|
+
builder.refdata = {
|
|
137
|
+
"7680694750066" => {
|
|
138
|
+
ean13: "7680694750066",
|
|
139
|
+
no8: "69475006",
|
|
140
|
+
desc_de: input,
|
|
141
|
+
desc_fr: "",
|
|
142
|
+
desc_it: ""
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
builder.apply_refdata_description_cleanups!
|
|
147
|
+
|
|
148
|
+
expect(builder.refdata["7680694750066"][:desc_de]).to eq input
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: oddb2xml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yasuhiro Asaka, Zeno R.R. Davatz, Niklaus Giger
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: rubyzip
|
|
@@ -262,6 +261,20 @@ dependencies:
|
|
|
262
261
|
- - ">="
|
|
263
262
|
- !ruby/object:Gem::Version
|
|
264
263
|
version: 3.3.9
|
|
264
|
+
- !ruby/object:Gem::Dependency
|
|
265
|
+
name: csv
|
|
266
|
+
requirement: !ruby/object:Gem::Requirement
|
|
267
|
+
requirements:
|
|
268
|
+
- - ">="
|
|
269
|
+
- !ruby/object:Gem::Version
|
|
270
|
+
version: '0'
|
|
271
|
+
type: :runtime
|
|
272
|
+
prerelease: false
|
|
273
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
274
|
+
requirements:
|
|
275
|
+
- - ">="
|
|
276
|
+
- !ruby/object:Gem::Version
|
|
277
|
+
version: '0'
|
|
265
278
|
- !ruby/object:Gem::Dependency
|
|
266
279
|
name: standardrb
|
|
267
280
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -470,6 +483,7 @@ files:
|
|
|
470
483
|
- lib/oddb2xml/fhir_support.rb
|
|
471
484
|
- lib/oddb2xml/options.rb
|
|
472
485
|
- lib/oddb2xml/parslet_compositions.rb
|
|
486
|
+
- lib/oddb2xml/refdata_cleanup.rb
|
|
473
487
|
- lib/oddb2xml/semantic_check.rb
|
|
474
488
|
- lib/oddb2xml/util.rb
|
|
475
489
|
- lib/oddb2xml/version.rb
|
|
@@ -512,6 +526,7 @@ files:
|
|
|
512
526
|
- spec/data/compressor/oddb2xml_files_lppv.txt
|
|
513
527
|
- spec/data/compressor/oddb2xml_files_nonpharma.xls
|
|
514
528
|
- spec/data/epha_interactions.csv
|
|
529
|
+
- spec/data/fhir/cyramza.ndjson
|
|
515
530
|
- spec/data/listen_neu.html
|
|
516
531
|
- spec/data/medregbm_betrieb.txt
|
|
517
532
|
- spec/data/medregbm_person.txt
|
|
@@ -539,11 +554,13 @@ files:
|
|
|
539
554
|
- spec/data_helper.rb
|
|
540
555
|
- spec/downloader_spec.rb
|
|
541
556
|
- spec/extractor_spec.rb
|
|
557
|
+
- spec/fhir_spec.rb
|
|
542
558
|
- spec/fixtures/vcr_cassettes/artikelstamm.json
|
|
543
559
|
- spec/fixtures/vcr_cassettes/oddb2xml.json
|
|
544
560
|
- spec/galenic_spec.rb
|
|
545
561
|
- spec/options_spec.rb
|
|
546
562
|
- spec/parslet_spec.rb
|
|
563
|
+
- spec/refdata_cleanup_spec.rb
|
|
547
564
|
- spec/spec_helper.rb
|
|
548
565
|
- test_options.rb
|
|
549
566
|
- tools/cacert.pem
|
|
@@ -553,7 +570,6 @@ homepage: https://github.com/zdavatz/oddb2xml
|
|
|
553
570
|
licenses:
|
|
554
571
|
- GPL-3.0-only
|
|
555
572
|
metadata: {}
|
|
556
|
-
post_install_message:
|
|
557
573
|
rdoc_options: []
|
|
558
574
|
require_paths:
|
|
559
575
|
- lib
|
|
@@ -568,8 +584,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
568
584
|
- !ruby/object:Gem::Version
|
|
569
585
|
version: '0'
|
|
570
586
|
requirements: []
|
|
571
|
-
rubygems_version: 3.
|
|
572
|
-
signing_key:
|
|
587
|
+
rubygems_version: 3.6.9
|
|
573
588
|
specification_version: 4
|
|
574
589
|
summary: oddb2xml creates xml files.
|
|
575
590
|
test_files:
|
|
@@ -608,6 +623,7 @@ test_files:
|
|
|
608
623
|
- spec/data/compressor/oddb2xml_files_lppv.txt
|
|
609
624
|
- spec/data/compressor/oddb2xml_files_nonpharma.xls
|
|
610
625
|
- spec/data/epha_interactions.csv
|
|
626
|
+
- spec/data/fhir/cyramza.ndjson
|
|
611
627
|
- spec/data/listen_neu.html
|
|
612
628
|
- spec/data/medregbm_betrieb.txt
|
|
613
629
|
- spec/data/medregbm_person.txt
|
|
@@ -635,9 +651,11 @@ test_files:
|
|
|
635
651
|
- spec/data_helper.rb
|
|
636
652
|
- spec/downloader_spec.rb
|
|
637
653
|
- spec/extractor_spec.rb
|
|
654
|
+
- spec/fhir_spec.rb
|
|
638
655
|
- spec/fixtures/vcr_cassettes/artikelstamm.json
|
|
639
656
|
- spec/fixtures/vcr_cassettes/oddb2xml.json
|
|
640
657
|
- spec/galenic_spec.rb
|
|
641
658
|
- spec/options_spec.rb
|
|
642
659
|
- spec/parslet_spec.rb
|
|
660
|
+
- spec/refdata_cleanup_spec.rb
|
|
643
661
|
- spec/spec_helper.rb
|