oddb2xml 3.0.26 → 3.0.28

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: 3b77fe7dba0df2c76d396dd0509fc77363d6efa5ee2a70580f8068d01f1aa508
4
- data.tar.gz: 471b43cc817114af1b09362a224dc1b969e69eb9f1e6c9cb7975cec30bf2b04f
3
+ metadata.gz: 62af20b2609637da38197880075560b49d0f810142ca162623980490abab4f6e
4
+ data.tar.gz: a5ac57d7d4864331ff34fa0f60f37ccf497c42b186f5d1a0a7ed0ee3322b3178
5
5
  SHA512:
6
- metadata.gz: e5317c541ea869c09bc1bd0ea5b2be5c6aa1be6923777f7daa3c419edf03036ba72cc432ef1b06db1215acb979bac27386311cea3d56fee31e9c39631caf20fd
7
- data.tar.gz: 4dac43d20ae5652b2b3651470492a68dbf9ac6d843ebef98bc6f81f242338a6b4ed339526a7ac2c0d49662641054559a14529cec13ccf8e1b5bada50428fbe0e
6
+ metadata.gz: 5bc3e6e4bb7bf221deb8feee038a59e723b491af3ebfa40a91d4c26d4a6fc9fc5c9417801ddac922b5a3be981310225b4b84e5f0c98a1daa06e2983251c80302
7
+ data.tar.gz: 434a73b313996af5a2f0ee11a7d404a9cd86bcd0c853abf913867fc0a0d7b85b1bf12dcdd5075f9574229d8c6f0d7b5610d2aa2c3062adde35a7f5bc303e086c
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. Reads the BAG **Indikationscode** (`XXXXX.NN`) from the explicit `indicationCode` extension on each `RegulatedAuthorization.indication[].extension[regulatedAuthorization-limitation]` (BAG SL FHIR export >= v2.0.5; handled from 3.0.10). The BAG changelog states the limitation code (`ClinicalUseDefinition.id`) and the indication code are **independent** fields, so the older derivation — combining each indication CUD's `.NN` id-suffix with the reimbursement RA's `FOPHDossierNumber` — is kept only as a fallback for feeds lacking the extension. Exposed as `item[:indication_codes]` and per-package `:indication_codes` (each entry a `{code:, cud_id:, text:}` hash, where `cud_id` is the `limitationIndication` CUD reference used to resolve the text). 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). **Limitation texts** (3.0.8 onwards): the `regulatedAuthorization-limitation` extension has no inline `limitationText` in the live BAG feed — it carries a `limitationIndication` reference to a `ClinicalUseDefinition` whose `indication.diseaseSymptomProcedure.concept.text` is the actual text. The parser stores the ref as `cud_ref` on each Limitation, `Bundle#cud_text_by_id` resolves DE, and `merge_language` propagates FR/IT from the per-language NDJSON files via the same CUD id. Coverage on the live feed jumped from 0 / 9'108 to 9'108 / 9'108 (issue [#116](https://github.com/zdavatz/oddb2xml/issues/116)). **Limitation code / LIMNAMEBAG** (3.0.12 onwards): FHIR has no native BAG limitation code (LIMCD), so `create_limitations_for_package` sets `LimitationCode = cud_ref` (the `limitationIndication` CUD id) instead of `""`. Without this, every FHIR limitation shared an empty `:code`; `Builder#build_artikelstamm` groups its `<LIMITATIONS>` section by code, so all of them collapsed into a single `<LIMITATION>` with an empty `<LIMNAMEBAG>` and only one text survived. Using the CUD id as the key makes each distinct limitation emit and be referenced from its `<PRODUCT>`. The downstream `bin/check_artikelstamm` (`semantic_check.rb`) also crashed on the lone-element output because Ox `:hash_no_attrs` collapses a one-child section into a Hash (and an empty one into nil) — `SemanticCheckXML#get_items` now normalises every section to an Array. **v6 Artikelstamm / per-article INDC (3.0.26 onwards):** `--artikelstamm` now emits the **Elexis Artikelstamm v6** format (namespace `http://elexis.ch/Elexis_Artikelstamm_v6`, file `artikelstamm_DDMMYYYY_v6.xml`/`.csv`, validated against the bundled `Elexis_Artikelstamm_v6.xsd`) — replacing v5. The new piece is a per-`<ITEM>` `<ARTSL>` block carrying the BAG Indikationscodes (issue [#113](https://github.com/zdavatz/oddb2xml/issues/113)): `<PM>true</PM>` plus one `<ARTLIM>` per limitation with `<LIMCD>` (= `cud_ref`, the BAG limitation code), `<INDCD>` (the `XXXXX.NN` indication code from the `indicationCode` extension), and `<VDAT>`/`<VTDAT>` (period start/end). To feed it, `create_limitations_for_package` now also carries `IndicationCode` (→ per-package `:indcd`) and `ValidThruDate` (→ `:vtdate`) on each limitation; `Builder#append_artsl`/`elexis_datetime` emit one `<ARTLIM>` per limitation that has a non-empty `:indcd` (so non-price-model items get no `<ARTSL>`). `PM` is always `true` here because the indication code is required only for SL price-model drugs, which is exactly the set of items that reach this block. The bundled `Elexis_Artikelstamm_v6.xsd` is the canonical MEDEVIT schema extended with oddb2xml's historical Italian elements (`DSCRI` on PRODUCT/LIMITATION/ITEM, `DOSAGE_FORMI` on ITEM) so the output still validates. The legacy `--no-fhir` path emits no `<ARTSL>` (no FHIR limitations).
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. Reads the BAG **Indikationscode** (`XXXXX.NN`) from the explicit `indicationCode` extension on each `RegulatedAuthorization.indication[].extension[regulatedAuthorization-limitation]` (BAG SL FHIR export >= v2.0.5; handled from 3.0.10). The BAG changelog states the limitation code (`ClinicalUseDefinition.id`) and the indication code are **independent** fields, so the older derivation — combining each indication CUD's `.NN` id-suffix with the reimbursement RA's `FOPHDossierNumber` — is kept only as a fallback for feeds lacking the extension. Exposed as `item[:indication_codes]` and per-package `:indication_codes` (each entry a `{code:, cud_id:, text:}` hash, where `cud_id` is the `limitationIndication` CUD reference used to resolve the text). 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). **Limitation texts** (3.0.8 onwards): the `regulatedAuthorization-limitation` extension has no inline `limitationText` in the live BAG feed — it carries a `limitationIndication` reference to a `ClinicalUseDefinition` whose `indication.diseaseSymptomProcedure.concept.text` is the actual text. The parser stores the ref as `cud_ref` on each Limitation, `Bundle#cud_text_by_id` resolves DE, and `merge_language` propagates FR/IT from the per-language NDJSON files via the same CUD id. Coverage on the live feed jumped from 0 / 9'108 to 9'108 / 9'108 (issue [#116](https://github.com/zdavatz/oddb2xml/issues/116)). **Limitation code / LIMNAMEBAG** (3.0.12 onwards): FHIR has no native BAG limitation code (LIMCD), so `create_limitations_for_package` sets `LimitationCode = cud_ref` (the `limitationIndication` CUD id) instead of `""`. Without this, every FHIR limitation shared an empty `:code`; `Builder#build_artikelstamm` groups its `<LIMITATIONS>` section by code, so all of them collapsed into a single `<LIMITATION>` with an empty `<LIMNAMEBAG>` and only one text survived. Using the CUD id as the key makes each distinct limitation emit and be referenced from its `<PRODUCT>`. The downstream `bin/check_artikelstamm` (`semantic_check.rb`) also crashed on the lone-element output because Ox `:hash_no_attrs` collapses a one-child section into a Hash (and an empty one into nil) — `SemanticCheckXML#get_items` now normalises every section to an Array. **v6 Artikelstamm / per-article INDC (3.0.26 onwards):** `--artikelstamm` now emits the **Elexis Artikelstamm v6** format (namespace `http://elexis.ch/Elexis_Artikelstamm_v6`, file `artikelstamm_DDMMYYYY_v6.xml`/`.csv`, validated against the bundled `Elexis_Artikelstamm_v6.xsd`) — replacing v5. The new piece is a per-`<ITEM>` `<ARTSL>` block carrying the BAG Indikationscodes (issue [#113](https://github.com/zdavatz/oddb2xml/issues/113)): `<PM>true</PM>` plus one `<ARTLIM>` per limitation with `<LIMCD>` (= `cud_ref`, the BAG limitation code), `<INDCD>` (the `XXXXX.NN` indication code from the `indicationCode` extension), and `<VDAT>`/`<VTDAT>` (period start/end). To feed it, `create_limitations_for_package` now also carries `IndicationCode` (→ per-package `:indcd`) and `ValidThruDate` (→ `:vtdate`) on each limitation; `Builder#append_artsl`/`elexis_datetime` emit one `<ARTLIM>` per limitation that has a non-empty `:indcd` (so non-price-model items get no `<ARTSL>`). `PM` is always `true` here because the indication code is required only for SL price-model drugs, which is exactly the set of items that reach this block. The bundled `Elexis_Artikelstamm_v6.xsd` is the canonical MEDEVIT schema extended with oddb2xml's historical Italian elements (`DSCRI` on PRODUCT/LIMITATION/ITEM, `DOSAGE_FORMI` on ITEM) so the output still validates. The legacy `--no-fhir` path emits no `<ARTSL>` (no FHIR limitations). **Per-article INDC in the `-e`/`-b` feeds (3.0.27 onwards):** the same indication codes are also emitted per `<ART>` in `oddb_article.xml` via `Builder#append_indication_codes` — one `<INDICATION_CODE code="XXXXX.NN" cud_id="DRUG.NN" limcd="DRUG.NN" vdat=… vtdat=…>limitation text</INDICATION_CODE>` per limitation that carries an `:indcd` (sourced from the article's `pac[:limitations]`). This reuses the flat `<INDICATION_CODE>` element already emitted on `<PRD>` in `oddb_product.xml` (since 3.0.7), enriched with the limitation code and validity dates so it carries the full `<ARTSL>` payload. The bundled `oddb2xml.xsd` now defines `<INDICATION_CODE>` (simpleContent + `code`/`cud_id`/`limcd`/`vdat`/`vtdat` attributes) and references it from both `<ART>` and `<PRD>` — the `<PRD>` reference had been missing since the 3.0.7 addition, so the FHIR product feed only validated once this was added. **Legacy v5 opt-in (3.0.28 onwards):** `--artikelstamm-v5` additionally emits the older Artikelstamm v5 file alongside the v6 one (for consumers not yet migrated). It implies `--artikelstamm`; `Options.parse` sets `@opts[:artikelstamm] = true`. `build_artikelstamm` takes a `version:` keyword (default `6`) that drives the namespace (`Elexis_Artikelstamm_v#{version}`) and CSV/XML filename suffix; `build_artikelstamm_v5` calls it with `version: 5`. The only content difference is that the v5 path skips `append_artsl` (the v5 schema has no `<ARTSL>` element). The CLI `files` map adds an `:artikelstamm_v5` entry (→ `build_artikelstamm_v5` via the `to_xml` subject dispatch) and validates it against the bundled `Elexis_Artikelstamm_v5.xsd`. Both files come from one build — the `@prepared` guard means the shared download/prepare phase runs once.
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 (a) the doubled-dose template bug (`X mg / X mg / Stk`, `fix_double_dose`, guarded by `single_substance?`); (b) the spelled-out German galenic form `Retardtabletten` → house-style abbreviation `Ret Tabl` (`normalize_galenic_form` / `GALENIC_NORMALISATIONS`, issue #112 case #13, e.g. RINVOQ — a narrow word-boundary substitution that leaves legitimate brand suffixes like `TRAMAL retard` and Mepha's `Lactab` untouched); and (c) dose info Refdata dropped from `<FullName>`, sourced from the Swissmedic composition string `pack[:composition_swissmedic]` — `fix_missing_combo_dose` (#6, appends a combination's 2nd component strength), `fix_missing_dose` (#4, inserts a mono product's missing strength before the pack count), `fix_missing_volume` (#7, appends an injectable's per-pen volume); and (d) 50-char-truncation repairs — `fix_truncated_metoject` (#1, rebuilds METOJECT Autoinjektor names from the intact `<brand> Autoinjektor <dose>/<vol>` prefix + Swissmedic `size`, localised DE/FR/IT) and `fix_truncated_volume_unit` (#3, restores the cut `ml` of the VERACTIV Vitamin D3 drops). The (c) and (d) fixes are scoped to explicit IKSNR allow-lists (`COMBO_DOSE_IKSNR`/`MISSING_DOSE_IKSNR`/`MISSING_VOLUME_IKSNR`/`METOJECT_IKSNR`/`VERACTIV_VITD3_IKSNR`): a dry run proved a blanket heuristic mis-fires on hundreds of legitimate names (sodium counter-ion doses, strength-less phyto/powder products, concentration names like `CIMZIA 200 mg/ml`), so only catalogued registrations are touched — add an IKSNR to grow coverage. 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,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oddb2xml (3.0.26)
4
+ oddb2xml (3.0.28)
5
5
  csv
6
6
  htmlentities
7
7
  httpi
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ === 3.0.28 / 30.06.2026
2
+ * New (--artikelstamm-v5): optionally emit the legacy Elexis Artikelstamm Version 5 file in addition to the v6 file. --artikelstamm now always produces the v6 output (artikelstamm_DDMMYYYY_v6.xml/.csv, with the per-ITEM <ARTSL> BAG indication codes since 3.0.26); passing --artikelstamm-v5 implies --artikelstamm and additionally writes artikelstamm_DDMMYYYY_v5.xml/.csv in the older v5 format (namespace http://elexis.ch/Elexis_Artikelstamm_v5, no <ARTSL> block), validated against the bundled Elexis_Artikelstamm_v5.xsd. The shared download/prepare phase runs once, so both files come from a single build. For consumers that have not yet migrated to v6.
3
+
4
+ === 3.0.27 / 29.06.2026
5
+ * New (-e/--extended, -b/--firstbase): carry the BAG Indikationscode (INDC) per article in oddb_article.xml as well. Each SL price-model <ART> now gets one <INDICATION_CODE code="XXXXX.NN" cud_id="DRUG.NN" limcd="DRUG.NN" vdat="..." vtdat="...">limitation text</INDICATION_CODE> child per indication -- the same data as the v6 Artikelstamm <ARTSL> block (3.0.26), but in the flat <INDICATION_CODE> shape already emitted on <PRD> in oddb_product.xml, enriched with the limitation code (limcd) and validity dates (vdat/vtdat). The bundled oddb2xml.xsd now defines <INDICATION_CODE> and references it from both <ART> and <PRD> (the <PRD> reference was missing since the 3.0.7 product-feed addition). Only emitted in --fhir mode (the default), where limitations carry an indication code. See issue #113.
6
+
1
7
  === 3.0.26 / 29.06.2026
2
8
  * New (--artikelstamm): emit the Elexis Artikelstamm v6 format with per-article BAG Indikationscodes (INDC). The output bumps from v5 to v6 -- namespace http://elexis.ch/Elexis_Artikelstamm_v6, file artikelstamm_DDMMYYYY_v6.xml/.csv, validated against the new bundled Elexis_Artikelstamm_v6.xsd -- and every SL price-model <ITEM> now carries an <ARTSL> block: <PM>true</PM> plus one <ARTLIM> per limitation with <LIMCD> (BAG limitation code), <INDCD> (the Indikationscode XXXXX.NN), and the validity dates <VDAT>/<VTDAT>. The indication codes were already read from the BAG SL FHIR feed's indicationCode extension (used since 3.0.7 for oddb_product.xml's <INDICATION_CODE>); they are now also carried per package limitation and grouped onto each article. Mandatory on prescriptions/invoices for SL price-model drugs from 2026-07-01 (issue #113). The v6 XSD is the canonical MEDEVIT schema extended with oddb2xml's historical Italian elements (DSCRI / DOSAGE_FORMI) so the output keeps validating. Consumers must switch from the v5 to the v6 file.
3
9
 
data/README.md CHANGED
@@ -57,6 +57,9 @@ see `--help`.
57
57
  produced files are found under data
58
58
  -a, --append Additional target nonpharma
59
59
  -r, --artikelstamm Create Artikelstamm Version 6 for Elexis >= 3.1
60
+ --artikelstamm-v5 Additionally create the legacy Artikelstamm Version 5
61
+ (without the <ARTSL> BAG indication codes).
62
+ Implies --artikelstamm.
60
63
  -c, --compress-ext=<s> format F. {tar.gz|zip}
61
64
  -e, --extended pharma, non-pharma plus prices and non-pharma from zurrose.
62
65
  Products without EAN-Code will also be listed.
@@ -371,6 +374,30 @@ The output bumps from Artikelstamm v5 to **v6** (namespace
371
374
  `artikelstamm_DDMMYYYY_v6.xml`, validated against the bundled
372
375
  `Elexis_Artikelstamm_v6.xsd`); consumers must switch to the v6 file.
373
376
 
377
+ Consumers that have not yet migrated can keep receiving the **legacy v5
378
+ file** by adding `--artikelstamm-v5` (implies `--artikelstamm`). This writes
379
+ `artikelstamm_DDMMYYYY_v5.xml`/`.csv` *in addition to* the v6 files, in the
380
+ older v5 format (namespace `http://elexis.ch/Elexis_Artikelstamm_v5`, no
381
+ `<ARTSL>` block, validated against the bundled `Elexis_Artikelstamm_v5.xsd`).
382
+ The download and data-preparation phase runs only once, so both versions are
383
+ produced from a single build.
384
+
385
+ Since 3.0.27 the same indication codes are also carried per article in
386
+ the `-e`/`--extended` and `-b`/`--firstbase` feeds: each SL price-model
387
+ `<ART>` in `oddb_article.xml` gets one `<INDICATION_CODE>` child per
388
+ indication — the flat shape already used on `<PRD>` in
389
+ `oddb_product.xml`, enriched with the limitation code and validity
390
+ dates:
391
+
392
+ ```xml
393
+ <ART DT="...">
394
+ ...
395
+ <INDICATION_CODE code="17079.01" cud_id="MABTHERA.01"
396
+ limcd="MABTHERA.01" vdat="2023-05-01T00:00:00"
397
+ vtdat="">In Kombination ...</INDICATION_CODE>
398
+ </ART>
399
+ ```
400
+
374
401
  ## Limitation texts in `--fhir` mode
375
402
 
376
403
  In 3.0.8 we fixed empty `<DescriptionDe/Fr/It>` on every `<Limitation>`
@@ -1190,6 +1190,9 @@ module Oddb2xml
1190
1190
  xml.NINCD nincd
1191
1191
  }
1192
1192
  end
1193
+ # BAG Indikationscodes for SL price-model drugs (issue #113),
1194
+ # mirroring the v6 Artikelstamm <ARTSL> data in oddb_article.xml.
1195
+ append_indication_codes(xml, pac && pac[:limitations])
1193
1196
  end
1194
1197
  end
1195
1198
  xml.RESULT {
@@ -1600,9 +1603,36 @@ module Oddb2xml
1600
1603
  end
1601
1604
  end
1602
1605
 
1603
- def build_artikelstamm
1606
+ # Emit per-article BAG Indikationscodes for the -e/--extended and
1607
+ # -b/--firstbase article feed (oddb_article.xml), one <INDICATION_CODE> per
1608
+ # limitation that carries an INDCD. Same data as the v6 <ARTSL> block, but
1609
+ # in the flat <INDICATION_CODE> shape already used on <PRD> in
1610
+ # oddb_product.xml — enriched here with the limitation code (LIMCD) and the
1611
+ # validity dates (VDAT/VTDAT). See issue #113.
1612
+ def append_indication_codes(xml, limitations)
1613
+ ics = (limitations || []).select { |lim| lim[:indcd] && !lim[:indcd].to_s.empty? }
1614
+ ics = ics.uniq { |lim| [lim[:code], lim[:indcd]] }
1615
+ ics.each do |lim|
1616
+ xml.INDICATION_CODE lim[:desc_de].to_s,
1617
+ code: lim[:indcd].to_s,
1618
+ cud_id: lim[:cud_ref].to_s,
1619
+ limcd: lim[:code].to_s,
1620
+ vdat: elexis_datetime(lim[:vdate]) || "",
1621
+ vtdat: elexis_datetime(lim[:vtdate]) || ""
1622
+ end
1623
+ end
1624
+
1625
+ # Legacy entry point: emit the Artikelstamm in the older Version 5 format
1626
+ # (Elexis_Artikelstamm_v5, no per-ITEM <ARTSL> indication-code block).
1627
+ # Reached via subject :artikelstamm_v5 when --artikelstamm-v5 is set.
1628
+ def build_artikelstamm_v5
1629
+ build_artikelstamm(version: 5)
1630
+ end
1631
+
1632
+ def build_artikelstamm(version: 6)
1604
1633
  @@emitted_v6_gtins = []
1605
- @csv_file = CSV.open(File.join(WORK_DIR, "artikelstamm_#{Date.today.strftime("%d%m%Y")}_v6.csv"), "w+")
1634
+ @artikelstamm_version = version
1635
+ @csv_file = CSV.open(File.join(WORK_DIR, "artikelstamm_#{Date.today.strftime("%d%m%Y")}_v#{version}.csv"), "w+")
1606
1636
  @csv_file << ["gtin", "name", "pkg_size", "galenic_form", "price_ex_factory", "price_public", "prodno", "atc_code", "active_substance", "original", "it-code", "sl-liste"]
1607
1637
  @csv_file.sync = true
1608
1638
  variant = "build_artikelstamm"
@@ -1810,7 +1840,8 @@ module Oddb2xml
1810
1840
  end
1811
1841
  xml.PRODNO prodno if prodno
1812
1842
  # v6 <ARTSL>: per-article BAG Indikationscodes (issue #113).
1813
- append_artsl(xml, package[:limitations])
1843
+ # The legacy v5 schema has no <ARTSL> element, so skip it there.
1844
+ append_artsl(xml, package[:limitations]) if @artikelstamm_version != 5
1814
1845
  @csv_file << [pkg_gtin, name, package[:unit], measure,
1815
1846
  pexf || "",
1816
1847
  ppub || "",
@@ -1936,7 +1967,7 @@ module Oddb2xml
1936
1967
  elexis_strftime_format = "%FT%T\.%L%:z"
1937
1968
  @@cumul_ver = (Date.today.year - 2013) * 12 + Date.today.month
1938
1969
  options_xml = {
1939
- "xmlns" => "http://elexis.ch/Elexis_Artikelstamm_v6",
1970
+ "xmlns" => "http://elexis.ch/Elexis_Artikelstamm_v#{@artikelstamm_version}",
1940
1971
  "CREATION_DATETIME" => Time.new.strftime(elexis_strftime_format),
1941
1972
  "BUILD_DATETIME" => Time.new.strftime(elexis_strftime_format),
1942
1973
  "DATA_SOURCE" => "oddb2xml"
data/lib/oddb2xml/cli.rb CHANGED
@@ -103,14 +103,21 @@ module Oddb2xml
103
103
  end
104
104
  build
105
105
  if @options[:artikelstamm] && system("which xmllint")
106
- elexis_v6_xsd = File.expand_path(File.join(__FILE__, "..", "..", "..", "Elexis_Artikelstamm_v6.xsd"))
107
- cmd = "xmllint --noout --schema #{elexis_v6_xsd} #{@the_files[:artikelstamm]}"
108
- if system(cmd)
109
- puts "Validatied #{@the_files[:artikelstamm]}"
110
- else
111
- puts "Validating failed using #{cmd}"
112
- require'pry'; binding.pry
113
- raise "Validating failed using #{cmd}"
106
+ to_validate = {"Elexis_Artikelstamm_v6.xsd" => @the_files[:artikelstamm]}
107
+ if @options[:artikelstamm_v5] && @the_files[:artikelstamm_v5]
108
+ to_validate["Elexis_Artikelstamm_v5.xsd"] = @the_files[:artikelstamm_v5]
109
+ end
110
+ to_validate.each_pair do |xsd_name, xml_file|
111
+ xsd = File.expand_path(File.join(__FILE__, "..", "..", "..", xsd_name))
112
+ cmd = "xmllint --noout --schema #{xsd} #{xml_file}"
113
+ if system(cmd)
114
+ puts "Validatied #{xml_file}"
115
+ else
116
+ puts "Validating failed using #{cmd}"
117
+ require "pry"
118
+ binding.pry
119
+ raise "Validating failed using #{cmd}"
120
+ end
114
121
  end
115
122
  end
116
123
  compress if @options[:compress_ext]
@@ -400,6 +407,9 @@ module Oddb2xml
400
407
  end
401
408
  if @options[:artikelstamm]
402
409
  @the_files[:artikelstamm] = "artikelstamm_#{Date.today.strftime("%d%m%Y")}_v6.xml"
410
+ if @options[:artikelstamm_v5]
411
+ @the_files[:artikelstamm_v5] = "artikelstamm_#{Date.today.strftime("%d%m%Y")}_v5.xml"
412
+ end
403
413
  elsif @options[:address]
404
414
  @the_files[:company] = "#{prefix}_betrieb.xml"
405
415
  @the_files[:person] = "#{prefix}_medizinalperson.xml"
@@ -18,6 +18,7 @@ module Oddb2xml
18
18
  EOS
19
19
  opt :append, "Additional target nonpharma", default: false
20
20
  opt :artikelstamm, "Create Artikelstamm Version 6 for Elexis >= 3.1"
21
+ opt :artikelstamm_v5, "Additionally create the legacy Artikelstamm Version 5 (without the <ARTSL> BAG indication codes). Implies --artikelstamm.", short: :none, default: false
21
22
  opt :compress_ext, "format F. {tar.gz|zip}", type: :string, default: nil, short: "c"
22
23
  opt :extended, "pharma, non-pharma plus prices and non-pharma from zurrose.
23
24
  Products without EAN-Code will also be listed.
@@ -68,6 +69,11 @@ module Oddb2xml
68
69
  @opts[:price] = :zurrose
69
70
  @opts[:calc] = true
70
71
  end
72
+ # --artikelstamm-v5 additionally emits the legacy v5 file; it requires the
73
+ # full Artikelstamm build, so it implies --artikelstamm (which yields v6).
74
+ if @opts[:artikelstamm_v5]
75
+ @opts[:artikelstamm] = true
76
+ end
71
77
  if @opts[:artikelstamm]
72
78
  @opts[:extended] = true
73
79
  @opts[:price] = :zurrose
@@ -1,3 +1,3 @@
1
1
  module Oddb2xml
2
- VERSION = "3.0.26"
2
+ VERSION = "3.0.28"
3
3
  end
data/oddb2xml.xsd CHANGED
@@ -169,11 +169,25 @@
169
169
  <xs:element ref="ns1:ARTBAR"/>
170
170
  <xs:element minOccurs="0" maxOccurs="unbounded" ref="ns1:ARTPRI"/>
171
171
  <xs:element minOccurs="0" ref="ns1:ARTINS"/>
172
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="ns1:INDICATION_CODE"/>
172
173
  </xs:sequence>
173
174
  <xs:attribute name="DT" use="required"/>
174
175
  <xs:attribute type="xs:string" name="SHA256" use="required"/>
175
176
  </xs:complexType>
176
177
  </xs:element>
178
+ <xs:element name="INDICATION_CODE">
179
+ <xs:complexType>
180
+ <xs:simpleContent>
181
+ <xs:extension base="xs:string">
182
+ <xs:attribute name="code" type="xs:string"/>
183
+ <xs:attribute name="cud_id" type="xs:string"/>
184
+ <xs:attribute name="limcd" type="xs:string"/>
185
+ <xs:attribute name="vdat" type="xs:string"/>
186
+ <xs:attribute name="vtdat" type="xs:string"/>
187
+ </xs:extension>
188
+ </xs:simpleContent>
189
+ </xs:complexType>
190
+ </xs:element>
177
191
  <xs:element name="PHAR" type="xs:integer"/>
178
192
  <xs:element name="SMCAT">
179
193
  <xs:simpleType>
@@ -853,6 +867,7 @@
853
867
  <xs:element minOccurs="0" ref="ns1:IT"/>
854
868
  <xs:element minOccurs="0" ref="ns1:ORPH"/>
855
869
  <xs:element ref="ns1:CPT"/>
870
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="ns1:INDICATION_CODE"/>
856
871
  <xs:element minOccurs="0" ref="ns1:PackGrSwissmedic"/>
857
872
  <xs:element minOccurs="0" ref="ns1:EinheitSwissmedic"/>
858
873
  <xs:element minOccurs="0" ref="ns1:SubstanceSwissmedic"/>
@@ -148,6 +148,11 @@ cat > "$tmp" <<HTML
148
148
  <a href="default/oddb_code.xml">oddb_code.xml</a> sowie
149
149
  <a href="default/oddb2xml.zip">oddb2xml.zip</a> (alle Dateien gepackt).</p>
150
150
 
151
+ <h2>Elexis Artikelstamm</h2>
152
+ <ul>
153
+ <li><a href="artikelstamm/">artikelstamm/</a> <span class="desc">— Elexis Artikelstamm v6 (mit BAG-Indikationscodes) und Legacy-v5, je als XML und CSV, täglich aktualisiert</span></li>
154
+ </ul>
155
+
151
156
  <h2>aips2sqlite — Fachinformationen &amp; AmiKo-Datenbanken</h2>
152
157
  <ul>
153
158
  <li><a href="/aips2sqlite/fis/">fis/</a> <span class="desc">— Fachinformationen als XML/HTML (DE/FR/IT)</span></li>
@@ -11,10 +11,12 @@
11
11
  # silently re-downloaded everything each time.
12
12
  #
13
13
  # Output layout (under $OUT_DIR, default /home/zdavatz/oddb2xml):
14
- # <OUT_DIR>/45/ oddb_*.xml built with +45 %
15
- # <OUT_DIR>/50/ oddb_*.xml built with +50 %
16
- # <OUT_DIR>/55/ oddb_*.xml built with +55 %
17
- # <OUT_DIR>/default/ oddb_*.xml built with no increment
14
+ # <OUT_DIR>/45/ oddb_*.xml built with +45 %
15
+ # <OUT_DIR>/50/ oddb_*.xml built with +50 %
16
+ # <OUT_DIR>/55/ oddb_*.xml built with +55 %
17
+ # <OUT_DIR>/default/ oddb_*.xml built with no increment
18
+ # <OUT_DIR>/artikelstamm/ Elexis Artikelstamm v6 + legacy v5 (xml + csv),
19
+ # served at https://mediupdatexml.oddb.org/artikelstamm
18
20
  # Each destination dir also keeps the source archive as oddb2xml.zip.
19
21
  # The working dir ($BUILD_DIR, default <OUT_DIR>-build) holds the shared
20
22
  # downloads/ cache and the transient zip; it lives OUTSIDE $OUT_DIR so the
@@ -113,10 +115,34 @@ build_one() {
113
115
  log "Staged $dest"
114
116
  }
115
117
 
118
+ # build_artikelstamm — build the Elexis Artikelstamm (v6 + legacy v5) and stage
119
+ # it at $OUT_DIR/artikelstamm, served at
120
+ # https://mediupdatexml.oddb.org/artikelstamm. Re-uses the shared downloads/
121
+ # cache (--skip-download), so it adds no extra upstream fetch beyond the few
122
+ # sources the firstbase builds don't pull (e.g. the ZurRose transfer.dat).
123
+ build_artikelstamm() {
124
+ local dest="$OUT_DIR/artikelstamm"
125
+ log "Building Artikelstamm (v6 + v5) -> $dest"
126
+ rm -f artikelstamm_*.xml artikelstamm_*.csv
127
+ run_with_retry "oddb2xml artikelstamm" -- \
128
+ "$ODDB2XML_BIN" --skip-download --artikelstamm --artikelstamm-v5
129
+
130
+ shopt -s nullglob
131
+ local out=(artikelstamm_*.xml artikelstamm_*.csv)
132
+ shopt -u nullglob
133
+ [[ ${#out[@]} -ge 1 ]] || { log "ERROR: no artikelstamm output produced"; exit 1; }
134
+
135
+ rm -rf "$dest"
136
+ mkdir -p "$dest"
137
+ cp -p "${out[@]}" "$dest/"
138
+ log "Staged ${#out[@]} file(s) to $dest"
139
+ }
140
+
116
141
  for inc in $INCREMENTS; do
117
142
  build_one "$inc" "$inc"
118
143
  done
119
144
  build_one "" "default" # final run with no increment
145
+ build_artikelstamm # Elexis Artikelstamm (v6 + legacy v5)
120
146
 
121
147
  # 2b. Refresh the download landing page with the live PHARMA/NONPHARMA counts
122
148
  # (PHARMA from default/oddb_article.xml, NONPHARMA from the GS1 firstbase CSV).
@@ -512,6 +512,36 @@ Der behandelnde Arzt ist verpflichtet, die erforderlichen Daten laufend im vorge
512
512
  end
513
513
  end
514
514
  end
515
+ context "when artikelstamm-v5 option is given" do
516
+ before(:all) do
517
+ common_run_init
518
+ options = Oddb2xml::Options.parse(["--artikelstamm-v5", "--no-fhir"])
519
+ Oddb2xml::Cli.new(options).run
520
+ @v6_name = File.join(Oddb2xml::WORK_DIR, "artikelstamm_#{Date.today.strftime("%d%m%Y")}_v6.xml")
521
+ @v5_name = File.join(Oddb2xml::WORK_DIR, "artikelstamm_#{Date.today.strftime("%d%m%Y")}_v5.xml")
522
+ end
523
+
524
+ it "emits both the v6 and the legacy v5 file" do
525
+ expect(File.exist?(@v6_name)).to eq true
526
+ expect(File.exist?(@v5_name)).to eq true
527
+ end
528
+
529
+ it "uses the v5 namespace in the v5 file and the v6 namespace in the v6 file" do
530
+ expect(IO.read(@v5_name)).to include("http://elexis.ch/Elexis_Artikelstamm_v5")
531
+ expect(IO.read(@v6_name)).to include("http://elexis.ch/Elexis_Artikelstamm_v6")
532
+ end
533
+
534
+ it "validates the v5 file against Elexis_Artikelstamm_v5.xsd" do
535
+ xsd = File.expand_path(File.join(__FILE__, "..", "..", "Elexis_Artikelstamm_v5.xsd"))
536
+ schema = Nokogiri::XML::Schema(File.read(xsd))
537
+ doc = Nokogiri::XML(File.read(@v5_name))
538
+ expect(schema.validate(doc)).to eq []
539
+ end
540
+
541
+ it "never emits an <ARTSL> block in the v5 file" do
542
+ expect(IO.read(@v5_name)).not_to include("<ARTSL>")
543
+ end
544
+ end
515
545
  context "chapter 70 hack" do
516
546
  before(:all) do
517
547
  mock_downloads
data/spec/fhir_spec.rb CHANGED
@@ -256,4 +256,56 @@ describe "FHIR Indikationscode support" do
256
256
  expect(artsl_xml(nil)).not_to include("<ARTSL>")
257
257
  end
258
258
  end
259
+
260
+ describe Oddb2xml::Builder, "per-article INDICATION_CODE (-e / -b feed)" do
261
+ def indication_xml(limitations)
262
+ builder = described_class.new
263
+ Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
264
+ xml.ART do
265
+ builder.send(:append_indication_codes, xml, limitations)
266
+ end
267
+ end.to_xml
268
+ end
269
+
270
+ it "emits one <INDICATION_CODE> per indication with limcd + validity dates" do
271
+ data = Oddb2xml::FhirExtractor.new(cyramza_fixture).to_hash
272
+ lims = data.values.first[:packages].values.first[:limitations]
273
+ xml = indication_xml(lims)
274
+
275
+ expect(xml.scan("<INDICATION_CODE").size).to eq(2)
276
+ expect(xml).to include('code="20403.01"')
277
+ expect(xml).to include('cud_id="CYRAMZA.01"')
278
+ expect(xml).to include('limcd="CYRAMZA.01"')
279
+ expect(xml).to include('vdat="2024-10-01T00:00:00"')
280
+ # CYRAMZA.02 has an end date -> non-empty vtdat; CYRAMZA.01 has none.
281
+ expect(xml).to include('vtdat="2027-09-30T00:00:00"')
282
+ expect(xml).to include('vtdat=""')
283
+ # The limitation text travels in the element body.
284
+ expect(xml).to include("In Kombination mit Paclitaxel")
285
+ end
286
+
287
+ it "emits nothing when no limitation carries an indication code" do
288
+ expect(indication_xml([{code: "X", indcd: "", vdate: "2020-01-01"}])).not_to include("<INDICATION_CODE")
289
+ expect(indication_xml(nil)).not_to include("<INDICATION_CODE")
290
+ end
291
+
292
+ it "produces an oddb_article.xml that validates against oddb2xml.xsd" do
293
+ items = Oddb2xml::FhirExtractor.new(cyramza_fixture).to_hash
294
+ builder = described_class.new
295
+ {
296
+ "@items" => items, "@refdata" => {}, "@packs" => {}, "@migel" => {},
297
+ "@interactions" => [], "@flags" => {}, "@orphan" => [], "@firstbase" => {},
298
+ "@substances" => [], "@codes" => {}, "@missing" => [], "@tag_suffix" => nil,
299
+ "@infos_zur_rose" => {}, "@lppvs" => {}, "@articles" => nil, "@weleda_sl" => {}
300
+ }.each { |k, v| builder.instance_variable_set(k.to_sym, v) }
301
+
302
+ xml = builder.send(:build_article)
303
+ expect(xml.scan("<INDICATION_CODE").size).to be >= 1
304
+
305
+ xsd_path = File.expand_path(File.join(File.dirname(__FILE__), "..", "oddb2xml.xsd"))
306
+ xsd = Nokogiri::XML::Schema(File.read(xsd_path))
307
+ errors = xsd.validate(Nokogiri::XML(xml))
308
+ expect(errors.map(&:message)).to eq([])
309
+ end
310
+ end
259
311
  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.26
4
+ version: 3.0.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yasuhiro Asaka, Zeno R.R. Davatz, Niklaus Giger