oddb2xml 3.0.5 → 3.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad986fc08f7abf1826b696378727e91dcb94cc53d5cdcdc2e0d009b755b92410
4
- data.tar.gz: 7df4f478dc0cb3cfbd2dfcb796b87ad047aadfdbc37bc9eab80a58b9943078b1
3
+ metadata.gz: 6c01ea6ec31975e297588db6fe417753aed570dc5ddf6123facf03a21224adf5
4
+ data.tar.gz: 74074f93366162137d62bca64833e98564137f80dde6481f567dd844b0d0ae14
5
5
  SHA512:
6
- metadata.gz: 9b7a3266179a31992706cd2d3c999cc73b94cacf293617935475d801c1cc00ed5d23ed91daec53cf2c4f4a234d7057dfe82390980465f9b5e7a19bbff6bcca6c
7
- data.tar.gz: d223b395fa978c6361c3f6c76ab2111558b029f97e9fad09b2c53d379f24f42f46c9b667da531c7505bcf7c121b5bd21cdd588ec64b530e80c718654c605a27f
6
+ metadata.gz: 1ab9e25407b856b59ae06f1ec07ae93cf462ba0dfda587cf7f6e8dd22440e44df22ca67e49d4144ac60e27e7fc9cc8eccd7ee0b5888cec0ee32782a02cf0b48c
7
+ data.tar.gz: da022887409106853d0c8c114a337edcae566d5573f7febc8e34642e923269790192d87519144324e90bf93df78f187b12bcf6f7ca28429781d23ff28dcaff99
data/CLAUDE.md CHANGED
@@ -47,7 +47,7 @@ The system follows a **download → extract → build → compress** pipeline:
47
47
 
48
48
  6. **Compressor** (`lib/oddb2xml/compressor.rb`) — Optional ZIP/TAR.GZ output compression.
49
49
 
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.
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. Parses `ClinicalUseDefinition` resources and combines each indication's `.NN` id-suffix with the reimbursement RA's `FOPHDossierNumber` to construct the BAG **Indikationscode** (`XXXXX.NN`); exposed as `item[:indication_codes]` and per-package `:indication_codes` (each entry a `{code:, cud_id:, text:}` hash). From 3.0.7 onwards, `Builder#build_product` emits one `<INDICATION_CODE code="XXXXX.NN" cud_id="DRUG.NN">limitation text</INDICATION_CODE>` child per indication on every `<PRD>` in `oddb_product.xml`; live feed numbers: 539 products / 1,293 codes / 100 % with non-empty indication text. Mandatory on prescriptions/invoices for SL price-model drugs from 2026-07-01 — see issue [#113](https://github.com/zdavatz/oddb2xml/issues/113).
51
51
 
52
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
53
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oddb2xml (3.0.4)
4
+ oddb2xml (3.0.7)
5
+ csv
5
6
  htmlentities
6
7
  httpi
7
8
  mechanize (>= 2.8.5)
@@ -26,7 +27,7 @@ PATH
26
27
  GEM
27
28
  remote: https://rubygems.org/
28
29
  specs:
29
- addressable (2.8.8)
30
+ addressable (2.9.0)
30
31
  public_suffix (>= 2.0.2, < 8.0)
31
32
  akami (1.3.3)
32
33
  base64
@@ -43,6 +44,7 @@ GEM
43
44
  crack (1.0.1)
44
45
  bigdecimal
45
46
  rexml
47
+ csv (3.3.5)
46
48
  diff-lcs (1.6.2)
47
49
  domain_name (0.6.20240107)
48
50
  flexmock (3.0.2)
@@ -57,7 +59,7 @@ GEM
57
59
  rack
58
60
  socksify
59
61
  io-console (0.8.2)
60
- json (2.18.1)
62
+ json (2.19.5)
61
63
  language_server-protocol (3.17.0.5)
62
64
  lint_roller (1.1.0)
63
65
  logger (1.7.0)
@@ -114,7 +116,7 @@ GEM
114
116
  pry (~> 0.11)
115
117
  yard (~> 0.9.21)
116
118
  psych (3.3.4)
117
- public_suffix (7.0.2)
119
+ public_suffix (7.0.5)
118
120
  racc (1.8.1)
119
121
  rack (3.2.6)
120
122
  rainbow (3.1.1)
@@ -208,7 +210,7 @@ GEM
208
210
  webrobots (0.1.2)
209
211
  xml-simple (1.1.9)
210
212
  rexml
211
- yard (0.9.38)
213
+ yard (0.9.43)
212
214
 
213
215
  PLATFORMS
214
216
  arm64-darwin-25
data/History.txt CHANGED
@@ -1,3 +1,13 @@
1
+ === 3.0.7 / 06.05.2026
2
+ * oddb_product.xml: emit one <INDICATION_CODE code="XXXXX.NN" cud_id="DRUG.NN">limitation text</INDICATION_CODE> child per indication on each <PRD>. Source: FHIR ClinicalUseDefinition resources joined with the reimbursement RegulatedAuthorization's FOPHDossierNumber. Counted on the live FOPH feed: 539 products carry a total of 1,293 codes; all have non-empty indication text. Resolves the missing wiring between FhirExtractor and Builder noted in 3.0.6 (issue #113).
3
+
4
+ === 3.0.6 / 06.05.2026
5
+ * 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)
6
+ * Declare csv as a runtime dependency in the gemspec (csv was removed from Ruby's default gems in 3.4)
7
+
8
+ === 3.0.5 / 25.04.2026
9
+ * 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)
10
+
1
11
  === 3.0.4 / 24.04.2026
2
12
  * 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
13
 
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.5
54
+ /opt/src/oddb2xml/bin/oddb2xml version 3.0.7
55
55
  Usage:
56
56
  oddb2xml [option]
57
57
  produced files are found under data
@@ -292,6 +292,43 @@ 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
+ ## Indikationscode (BAG XXXXX.NN)
296
+
297
+ In `--fhir` mode, oddb2xml extracts the BAG **Indikationscode** for SL
298
+ price-model drugs and emits one repeated `<INDICATION_CODE>` child per
299
+ indication on each `<PRD>` in `oddb_product.xml`:
300
+
301
+ ```xml
302
+ <PRD DT="...">
303
+ <GTIN>7680123456789</GTIN>
304
+ ...
305
+ <INDICATION_CODE code="20403.01" cud_id="CYRAMZA.01">In Kombination mit Paclitaxel für die Behandlung ...</INDICATION_CODE>
306
+ <INDICATION_CODE code="20403.02" cud_id="CYRAMZA.02">In Kombination mit FOLFIRI ...</INDICATION_CODE>
307
+ <PackGrSwissmedic>...</PackGrSwissmedic>
308
+ </PRD>
309
+ ```
310
+
311
+ The code is built per FHIR bundle by combining the
312
+ `FOPHDossierNumber` from the reimbursement `RegulatedAuthorization`
313
+ (`XXXXX`) with the `.NN` suffix of each sibling
314
+ `ClinicalUseDefinition` whose `type == "indication"` (e.g.
315
+ `CYRAMZA.01` / `CYRAMZA.02` → `20403.01` / `20403.02`). The element
316
+ body carries the human-readable indication text from
317
+ `indication.diseaseSymptomProcedure.concept.text`.
318
+
319
+ The same data is also exposed in-memory on each item and package as
320
+ `item[:indication_codes]` (an array of `{code:, cud_id:, text:}`
321
+ hashes) for downstream consumers that build their own emitters.
322
+
323
+ Counted on the live FOPH feed (`foph-sl-export-latest-de.ndjson`,
324
+ 6'775 bundles): **539** products carry a total of **1'293** codes.
325
+
326
+ Per BAG Rundschreiben vom 19. Februar 2026, the Indikationscode must
327
+ be transmitted with every prescription and invoice for SL price-model
328
+ drugs from **2026-07-01**; from **2027-01-01** insurers may reject
329
+ invoices without it. See issue
330
+ [#113](https://github.com/zdavatz/oddb2xml/issues/113).
331
+
295
332
  ## Refdata data-quality compensation
296
333
 
297
334
  Refdata.Articles.xml from `files.refdata.ch` ships with a number of recurring
@@ -780,6 +780,14 @@ module Oddb2xml
780
780
  # xml.REMD
781
781
  # xml.REMF
782
782
  # }
783
+ # BAG Indikationscode (XXXXX.NN) for SL price-model drugs.
784
+ # Mandatory on prescriptions/invoices from 2026-07-01 — see
785
+ # GitHub issue #113.
786
+ if seq && seq[:indication_codes].is_a?(Array)
787
+ seq[:indication_codes].each do |ic|
788
+ xml.INDICATION_CODE ic[:text].to_s, code: ic[:code], cud_id: ic[:cud_id]
789
+ end
790
+ end
783
791
  xml.PackGrSwissmedic obj[:siz] unless obj[:siz].empty?
784
792
  xml.EinheitSwissmedic obj[:eht] unless obj[:eht].empty?
785
793
  xml.SubstanceSwissmedic obj[:sub] unless obj[:sub].empty?
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Oddb2xml
2
- VERSION = "3.0.5"
2
+ VERSION = "3.0.7"
3
3
  end
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,85 @@
1
+ require "spec_helper"
2
+ require "oddb2xml/downloader"
3
+ require "oddb2xml/extractor"
4
+ require "oddb2xml/fhir_support"
5
+ require "oddb2xml/builder"
6
+
7
+ describe "FHIR Indikationscode support" do
8
+ let(:cyramza_fixture) { File.join(Oddb2xml::SpecData, "fhir", "cyramza.ndjson") }
9
+
10
+ describe Oddb2xml::FHIR::ClinicalUseDefinition do
11
+ it "extracts the .NN suffix from id" do
12
+ cud = described_class.new("id" => "CYRAMZA.02", "type" => "indication")
13
+ expect(cud.nn_suffix).to eq("02")
14
+ expect(cud.indication?).to be true
15
+ end
16
+
17
+ it "returns nil suffix for non-conforming ids" do
18
+ cud = described_class.new("id" => "CYRAMZA")
19
+ expect(cud.nn_suffix).to be_nil
20
+ end
21
+ end
22
+
23
+ describe Oddb2xml::FHIR::Bundle do
24
+ it "collects ClinicalUseDefinition entries" do
25
+ line = File.read(cyramza_fixture).lines.first
26
+ bundle = described_class.new(line)
27
+ expect(bundle.clinical_use_definitions).not_to be_empty
28
+ expect(bundle.clinical_use_definitions.map(&:id)).to include("CYRAMZA.01", "CYRAMZA.02")
29
+ end
30
+ end
31
+
32
+ describe Oddb2xml::FHIR::PreparationsParser do
33
+ it "constructs XXXXX.NN indication codes from FOPHDossierNumber + CUD suffix" do
34
+ parser = described_class.new(cyramza_fixture)
35
+ prep = parser.preparations.first
36
+ codes = prep.IndicationCodes.map(&:code)
37
+ expect(codes).to include("20403.01", "20403.02")
38
+ end
39
+ end
40
+
41
+ describe Oddb2xml::FhirExtractor do
42
+ it "exposes indication_codes on each item and package" do
43
+ data = described_class.new(cyramza_fixture).to_hash
44
+ expect(data).not_to be_empty
45
+
46
+ item = data.values.first
47
+ codes = item[:indication_codes].map { |ic| ic[:code] }
48
+ expect(codes).to include("20403.01", "20403.02")
49
+
50
+ pkg = item[:packages].values.first
51
+ expect(pkg[:indication_codes]).to eq(item[:indication_codes])
52
+ end
53
+ end
54
+
55
+ describe Oddb2xml::Builder, "PRD INDICATION_CODE emission" do
56
+ it "emits one <INDICATION_CODE> child per indication on the PRD" do
57
+ items = Oddb2xml::FhirExtractor.new(cyramza_fixture).to_hash
58
+ builder = described_class.new
59
+ builder.instance_variable_set(:@items, items)
60
+ builder.instance_variable_set(:@refdata, {})
61
+ builder.instance_variable_set(:@packs, {})
62
+ builder.instance_variable_set(:@migel, {})
63
+ builder.instance_variable_set(:@interactions, [])
64
+ builder.instance_variable_set(:@flags, {})
65
+ builder.instance_variable_set(:@orphan, [])
66
+ builder.instance_variable_set(:@firstbase, {})
67
+ builder.instance_variable_set(:@substances, [])
68
+ builder.instance_variable_set(:@codes, {})
69
+ builder.instance_variable_set(:@missing, [])
70
+ builder.instance_variable_set(:@tag_suffix, nil)
71
+
72
+ xml = builder.send(:build_product)
73
+ expect(xml).to include("<INDICATION_CODE")
74
+ expect(xml).to include('code="20403.01"')
75
+ expect(xml).to include('code="20403.02"')
76
+ expect(xml).to include('cud_id="CYRAMZA.01"')
77
+ expect(xml).to include('cud_id="CYRAMZA.02"')
78
+ # The CUD's indication text travels into the element body so
79
+ # downstream consumers can show the human-readable indication
80
+ # next to the code.
81
+ expect(xml).to include("In Kombination mit Paclitaxel")
82
+ expect(xml).to include("In Kombination mit FOLFIRI")
83
+ end
84
+ end
85
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oddb2xml
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.5
4
+ version: 3.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yasuhiro Asaka, Zeno R.R. Davatz, Niklaus Giger
@@ -261,6 +261,20 @@ dependencies:
261
261
  - - ">="
262
262
  - !ruby/object:Gem::Version
263
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'
264
278
  - !ruby/object:Gem::Dependency
265
279
  name: standardrb
266
280
  requirement: !ruby/object:Gem::Requirement
@@ -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,6 +554,7 @@ 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
@@ -607,6 +623,7 @@ test_files:
607
623
  - spec/data/compressor/oddb2xml_files_lppv.txt
608
624
  - spec/data/compressor/oddb2xml_files_nonpharma.xls
609
625
  - spec/data/epha_interactions.csv
626
+ - spec/data/fhir/cyramza.ndjson
610
627
  - spec/data/listen_neu.html
611
628
  - spec/data/medregbm_betrieb.txt
612
629
  - spec/data/medregbm_person.txt
@@ -634,6 +651,7 @@ test_files:
634
651
  - spec/data_helper.rb
635
652
  - spec/downloader_spec.rb
636
653
  - spec/extractor_spec.rb
654
+ - spec/fhir_spec.rb
637
655
  - spec/fixtures/vcr_cassettes/artikelstamm.json
638
656
  - spec/fixtures/vcr_cassettes/oddb2xml.json
639
657
  - spec/galenic_spec.rb