oddb2xml 3.0.32 → 3.0.33
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/CLAUDE.md +2 -2
- data/Gemfile.lock +1 -1
- data/History.txt +4 -0
- data/README.md +44 -0
- data/lib/oddb2xml/cli.rb +8 -0
- data/lib/oddb2xml/extractor.rb +12 -0
- data/lib/oddb2xml/version.rb +1 -1
- data/spec/extractor_spec.rb +45 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f81e1fe93a63834d7fda23c353903f34b6b7136146bf79c1e8b465653ed5585c
|
|
4
|
+
data.tar.gz: 516d5916ab9dd00a48973db7f923f367b8e5a2281cc48e1d0a50020153e71d0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a19787a1570d2078c25e7525d397af16fac509425de63ded2383f2781219479485f8fa720a888618af879d08a9e206b974d457bfc5d45a57dacf70deeb263062
|
|
7
|
+
data.tar.gz: e8592299737e7ee1bb7e86331ff0579c62d5e550ff075264bdb118a23f85aaf5f5ebb35310d92048e4fb71870e817f2aae9ca184ce51dd192c03ea9a9cc1801b
|
data/CLAUDE.md
CHANGED
|
@@ -14,7 +14,7 @@ The system follows a **download → extract → build → compress** pipeline:
|
|
|
14
14
|
|
|
15
15
|
2. **Downloaders** — 11 subclasses of `Downloader`, each fetching from a specific Swiss data source. 10 live in `lib/oddb2xml/downloader.rb`; the FHIR downloader lives in `lib/oddb2xml/fhir_support.rb`. Files cached in `./downloads/`.
|
|
16
16
|
|
|
17
|
-
3. **Extractors** (`lib/oddb2xml/extractor.rb`) — Matching extractor classes that parse downloaded files into Ruby hashes. Formats include XML (nokogiri/sax-machine), XLSX (rubyXL), CSV, and fixed-width text. Refdata uses the new SwissReg XML format from a zip download (`files.refdata.ch`).
|
|
17
|
+
3. **Extractors** (`lib/oddb2xml/extractor.rb`) — Matching extractor classes that parse downloaded files into Ruby hashes. Formats include XML (nokogiri/sax-machine), XLSX (rubyXL), CSV, and fixed-width text. Refdata uses the new SwissReg XML format from a zip download (`files.refdata.ch`). **Barcode-less Refdata articles (3.0.33 onwards):** Refdata publishes a few PHARMA articles with no `<DataCarrierIdentifier>` — the Swiss Red Cross blood products under the collective registration 99999, which appeared in August 2026 — and `RefdataExtractor#to_hash` called `.size` on that `nil`. `Cli#download_as` rescues extractor errors into `Oddb2xml.log`, which prints **nothing** without `--log` (issue #102 wanted the build to survive a Refdata outage), so the `NoMethodError` silently dropped all 16'093 Refdata PHARMA articles while the run still reported success: `<SMNO>` in `oddb_article.xml` fell from ~15'300 to exactly 10'210 (the rest arriving from the BAG FHIR SL feed) and stayed frozen there for twelve days, then to 50 once the 3.0.32 url move removed FHIR too. NONPHARMA was unaffected because the product-type `next` runs before the barcode is read. Such records are now skipped, and both rescued Refdata failures print a warning to stderr. See GitHub issue #122.
|
|
18
18
|
|
|
19
19
|
4. **Builder** (`lib/oddb2xml/builder.rb`) — The largest file (~1900 lines). Merges extracted data and generates output XML/DAT files. Methods follow `prepare_*` (data assembly) and `build_*` (output generation) naming.
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@ The system follows a **download → extract → build → compress** pipeline:
|
|
|
22
22
|
|
|
23
23
|
6. **Compressor** (`lib/oddb2xml/compressor.rb`) — Optional ZIP/TAR.GZ output compression.
|
|
24
24
|
|
|
25
|
-
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-publication-latest-{de,fr,it}.ndjson`) from `epl.bag.admin.ch/static/sl/publication/fhir` (BAG moved the export there on 24.08.2026; the old `/static/fhir/foph-sl-export-*` answers 404 for `-latest-` while the old dated snapshots remain, so the failure looks like an import that does nothing) 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. **Selbstbehalt / costShare (3.0.31 onwards):** the `reimbursementSL` extension's `costShare` valueInteger is the BAG Selbstbehalt percentage; it maps to the legacy `FlagSB` semantics of the old BAG `Preparations.xml` — `40` → `item[:deductible] = "Y"` (raised Selbstbehalt: `<SLOPLUS>1</SLOPLUS>` in `oddb_article.xml`, `<DEDUCTIBLE>40</DEDUCTIBLE>` in the Artikelstamm), `10` → `"N"` (ordinary Selbstbehalt: `SLOPLUS` 2 / `DEDUCTIBLE` 10), `20` → `deductible20 = "Y"` (transitional rate, issue #81). Until 3.0.31 the mapping was inverted (`10` → `"Y"`), so **every** SL article claimed a 40 % Selbstbehalt. On the live feed only 385 of 10'388 packages (222 preparations) carry `costShare` 40; the value never mixes within one preparation, so keeping the flag at sequence level is safe.
|
|
25
|
+
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-publication-latest-{de,fr,it}.ndjson`) from `epl.bag.admin.ch/static/sl/publication/fhir` (**3.0.32 onwards**; BAG moved the export there on 24.08.2026, announced it the next evening, and oddb2xml followed in 3.0.32; the old `/static/fhir/foph-sl-export-*` answers 404 for `-latest-` while the old dated snapshots remain, so the failure looks like an import that does nothing) 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. **Selbstbehalt / costShare (3.0.31 onwards):** the `reimbursementSL` extension's `costShare` valueInteger is the BAG Selbstbehalt percentage; it maps to the legacy `FlagSB` semantics of the old BAG `Preparations.xml` — `40` → `item[:deductible] = "Y"` (raised Selbstbehalt: `<SLOPLUS>1</SLOPLUS>` in `oddb_article.xml`, `<DEDUCTIBLE>40</DEDUCTIBLE>` in the Artikelstamm), `10` → `"N"` (ordinary Selbstbehalt: `SLOPLUS` 2 / `DEDUCTIBLE` 10), `20` → `deductible20 = "Y"` (transitional rate, issue #81). Until 3.0.31 the mapping was inverted (`10` → `"Y"`), so **every** SL article claimed a 40 % Selbstbehalt. On the live feed only 385 of 10'388 packages (222 preparations) carry `costShare` 40; the value never mixes within one preparation, so keeping the flag at sequence level is safe.
|
|
26
26
|
|
|
27
27
|
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.
|
|
28
28
|
|
data/Gemfile.lock
CHANGED
data/History.txt
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
=== 3.0.33 / 26.08.2026
|
|
2
|
+
* Bugfix (Refdata): skip PHARMA articles that carry no barcode instead of losing every single one of them. Refdata started publishing a handful of PHARMA articles without a <DataCarrierIdentifier> in August 2026 -- the Swiss Red Cross blood products under the collective registration 99999 (Erythrozytenkonzentrat & co), which have no GTIN at all. RefdataExtractor#to_hash called .size on that nil, and Cli#download_as rescues the resulting NoMethodError into an Oddb2xml.log call that prints nothing unless --log is given, so all 16'093 Refdata PHARMA articles silently vanished from the feeds while the build still reported success. On mediupdatexml.oddb.org the <SMNO> count in oddb_article.xml fell from ~15'300 to exactly 10'210 and stayed frozen there for twelve days -- the remainder came from the BAG FHIR SL feed -- and then to 50 once the BAG url move (3.0.32) took the FHIR feed away too. Refdata NONPHARMA was never affected, because the product-type check runs before the barcode is read. See GitHub issue #122.
|
|
3
|
+
* The two rescued Refdata failures (download and extraction) now also print a warning to stderr. Issue #102 asked the build to continue past a Refdata error, and it still does, but a total loss of every Swissmedic-registered medicine must not look like a successful run in the nightly log.
|
|
4
|
+
|
|
1
5
|
=== 3.0.32 / 26.08.2026
|
|
2
6
|
* Bugfix (--fhir, the default): follow the BAG SL export to its new URL. BAG moved the publication on 24.08.2026 and announced it the next evening: /static/fhir/foph-sl-export-*.ndjson became /static/sl/publication/fhir/foph-sl-publication-*.ndjson. Only the -latest- alias is gone (the old dated snapshots stay in place), so the old URL failed by downloading nothing and reporting success rather than by crashing -- ch.oddb.org ran that way for twelve mornings with three HTTP 404 lines in a log as the only trace. Without the NDJSON there are no French/Italian names, no SL prices, no limitations and no Indikationscodes. Per the announcement there is no language-less default any more; every URL has to name de, fr or it, which FhirDownloader already did. A preliminary publication now exists in parallel under /static/sl/preliminary/fhir/foph-sl-preliminary-*, which oddb2xml does not use. The file prefix is its own constant now, so the next move touches one line.
|
|
3
7
|
|
data/README.md
CHANGED
|
@@ -573,3 +573,47 @@ themselves.
|
|
|
573
573
|
** Removing the directory fixtures and running @bundle exec rspec spec/downloader_spec.rb@ gets the actual content from the different servers
|
|
574
574
|
** To minimize the downloaded size we use several @before_record@ hooks to select the desired content, eg. only the 5 items from EPha.
|
|
575
575
|
|
|
576
|
+
|
|
577
|
+
## Releasing a new version
|
|
578
|
+
|
|
579
|
+
1. Bump `Oddb2xml::VERSION` in `lib/oddb2xml/version.rb` and the `oddb2xml (X.Y.Z)`
|
|
580
|
+
line in `Gemfile.lock`.
|
|
581
|
+
2. Prepend an entry to `History.txt` (`=== X.Y.Z / DD.MM.YYYY`).
|
|
582
|
+
3. Commit, then tag and push:
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
git tag -a vX.Y.Z -m "Version X.Y.Z"
|
|
586
|
+
git push origin master && git push origin vX.Y.Z
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
The tag triggers `.github/workflows/release.yml`, which builds the gem and
|
|
590
|
+
publishes a GitHub release with the `.gem` attached. It does **not** push to
|
|
591
|
+
rubygems.org — that step is manual.
|
|
592
|
+
4. Build and push the gem:
|
|
593
|
+
|
|
594
|
+
```bash
|
|
595
|
+
gem build oddb2xml.gemspec
|
|
596
|
+
gem push oddb2xml-X.Y.Z.gem --otp <code>
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
The push needs an API key in the file that `ruby -e 'require "rubygems"; puts
|
|
600
|
+
Gem.configuration.credentials_path'` reports — on Debian with the system Ruby
|
|
601
|
+
that is `~/.local/share/gem/credentials`, not the `~/.gem/credentials` most
|
|
602
|
+
documentation names. Create it with `gem signin`, or write it by hand as
|
|
603
|
+
|
|
604
|
+
```yaml
|
|
605
|
+
---
|
|
606
|
+
:rubygems_api_key: rubygems_...
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
and `chmod 600` it. Multi-factor authentication is enabled on this gem, so
|
|
610
|
+
`--otp` is required **in addition to** the API key; a missing key reports
|
|
611
|
+
`Invalid credentials / code: 401` rather than anything about the OTP, which is
|
|
612
|
+
easy to misread as an expired code.
|
|
613
|
+
|
|
614
|
+
Finally, update the download server, which runs the released gem rather than
|
|
615
|
+
this checkout:
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
sudo gem install oddb2xml
|
|
619
|
+
```
|
data/lib/oddb2xml/cli.rb
CHANGED
|
@@ -85,6 +85,8 @@ module Oddb2xml
|
|
|
85
85
|
threads << download(:refdata, type) # refdata
|
|
86
86
|
rescue => error
|
|
87
87
|
# Should continue even when error #102
|
|
88
|
+
$stderr.puts "WARNING: Refdata #{type} download failed (#{error.class}: #{error})"
|
|
89
|
+
$stderr.flush
|
|
88
90
|
Oddb2xml.log("Error in downloading refdata #{error}")
|
|
89
91
|
end
|
|
90
92
|
end
|
|
@@ -377,6 +379,12 @@ module Oddb2xml
|
|
|
377
379
|
@refdata_types[type]
|
|
378
380
|
rescue => error
|
|
379
381
|
# Should continue even when error https://github.com/zdavatz/oddb2xml/issues/102
|
|
382
|
+
# ...but say so loudly: without the Refdata articles the feeds lose every
|
|
383
|
+
# Swissmedic-registered medicine, and Oddb2xml.log is a no-op unless --log
|
|
384
|
+
# is given, so this used to fail completely silently. See issue #122.
|
|
385
|
+
$stderr.puts "WARNING: RefdataExtractor #{type} failed (#{error.class}: #{error}) -- " \
|
|
386
|
+
"no #{type} articles from Refdata in this run!"
|
|
387
|
+
$stderr.flush
|
|
380
388
|
Oddb2xml.log("Error in RefdataExtractor #{error}")
|
|
381
389
|
end
|
|
382
390
|
end
|
data/lib/oddb2xml/extractor.rb
CHANGED
|
@@ -216,6 +216,18 @@ module Oddb2xml
|
|
|
216
216
|
next
|
|
217
217
|
end
|
|
218
218
|
ean13 = @type == "PHARMA" ? article.PackagedProduct.DataCarrierIdentifier : article.MedicinalProduct.Identifier
|
|
219
|
+
if ean13.nil? || ean13.empty?
|
|
220
|
+
# Refdata publishes a handful of PHARMA articles that carry no barcode at
|
|
221
|
+
# all -- e.g. the Swiss Red Cross blood products under the collective
|
|
222
|
+
# registration 99999 (Erythrozytenkonzentrat & co), which appeared in
|
|
223
|
+
# August 2026. They have no article identifier we could key on, so skip
|
|
224
|
+
# them. Before this guard the nil blew up the whole extraction, and
|
|
225
|
+
# Cli#download_as rescues that error into a silent Oddb2xml.log call, so
|
|
226
|
+
# *every* Refdata PHARMA article vanished from the feeds while the build
|
|
227
|
+
# still reported success. See GitHub issue #122.
|
|
228
|
+
puts "Refdata #{@type} skipping #{article.MedicinalProduct.Identifier} without an EAN13" if $VERBOSE
|
|
229
|
+
next
|
|
230
|
+
end
|
|
219
231
|
if ean13.size < 13
|
|
220
232
|
puts "Refdata #{@type} use 13 chars not #{ean13.size} for #{ean13}" if $VERBOSE
|
|
221
233
|
ean13 = ean13.rjust(13, "0")
|
data/lib/oddb2xml/version.rb
CHANGED
data/spec/extractor_spec.rb
CHANGED
|
@@ -80,6 +80,51 @@ describe Oddb2xml::RefdataExtractor do
|
|
|
80
80
|
expect(item_found).to eq(expected)
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
|
+
context "when Refdata omits the barcode (issue #122)" do
|
|
84
|
+
# The Swiss Red Cross blood products under the collective registration 99999
|
|
85
|
+
# arrived in August 2026 with no <DataCarrierIdentifier> at all. The nil used
|
|
86
|
+
# to blow up the whole extraction -- and Cli#download_as rescues that into a
|
|
87
|
+
# silent Oddb2xml.log, so every Refdata PHARMA article disappeared from the
|
|
88
|
+
# feeds while the build still reported success.
|
|
89
|
+
let(:xml) do
|
|
90
|
+
<<~XML
|
|
91
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
92
|
+
<Articles xmlns="https://simisinfo.refdata.ch/Articles/1.0/" totalArticles="2">
|
|
93
|
+
<Article>
|
|
94
|
+
<MedicinalProduct>
|
|
95
|
+
<Identifier>CH-7601001320451-62069</Identifier>
|
|
96
|
+
<ProductClassification><ProductClass>PHARMA</ProductClass><Atc>N03AX14</Atc></ProductClassification>
|
|
97
|
+
</MedicinalProduct>
|
|
98
|
+
<PackagedProduct>
|
|
99
|
+
<RegulatedAuthorisationIdentifier>62069008</RegulatedAuthorisationIdentifier>
|
|
100
|
+
<DataCarrierIdentifier>#{Oddb2xml::LEVETIRACETAM_GTIN}</DataCarrierIdentifier>
|
|
101
|
+
<Holder><Identifier>7601001320451</Identifier><Name>Desitin Pharma GmbH</Name></Holder>
|
|
102
|
+
<Name><Language>DE</Language><FullName>LEVETIRACETAM DESITIN Mini Filmtab 250 mg 30 Stk</FullName></Name>
|
|
103
|
+
</PackagedProduct>
|
|
104
|
+
</Article>
|
|
105
|
+
<Article>
|
|
106
|
+
<MedicinalProduct>
|
|
107
|
+
<Identifier>CH-7601002120890-9999900</Identifier>
|
|
108
|
+
<ProductClassification><ProductClass>PHARMA</ProductClass><Atc>Z00Z000</Atc></ProductClassification>
|
|
109
|
+
</MedicinalProduct>
|
|
110
|
+
<PackagedProduct>
|
|
111
|
+
<RegulatedAuthorisationIdentifier>99999001</RegulatedAuthorisationIdentifier>
|
|
112
|
+
<Holder><Identifier>7601002120890</Identifier><Name>Schweizerisches Rotes Kreuz - SRK</Name></Holder>
|
|
113
|
+
<Name><Language>DE</Language><FullName>Erythrozytenkonzentrat (EK)</FullName></Name>
|
|
114
|
+
</PackagedProduct>
|
|
115
|
+
</Article>
|
|
116
|
+
</Articles>
|
|
117
|
+
XML
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "skips the barcode-less article instead of losing every other one" do
|
|
121
|
+
items = Oddb2xml::RefdataExtractor.new(xml, "PHARMA").to_hash
|
|
122
|
+
expect(items.size).to eq 1
|
|
123
|
+
expect(items.keys).to eq [Oddb2xml::LEVETIRACETAM_GTIN.to_s]
|
|
124
|
+
expect(items.values.first[:no8]).to eq "62069008"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
83
128
|
context "should handle nonpharma articles" do
|
|
84
129
|
subject do
|
|
85
130
|
@downloader = Oddb2xml::RefdataDownloader.new({}, :nonpharma)
|