oddb2xml 3.0.8 → 3.0.10
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 +1 -1
- data/Gemfile.lock +1 -1
- data/History.txt +6 -0
- data/README.md +17 -9
- data/lib/oddb2xml/fhir_support.rb +29 -1
- data/lib/oddb2xml/options.rb +6 -1
- data/lib/oddb2xml/version.rb +1 -1
- data/spec/fhir_spec.rb +36 -1
- 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: 371c2ba25fb99bf0255bc0d336b0ae8a8a0cee64e9cd869e249e7fe7cd1d9da0
|
|
4
|
+
data.tar.gz: b85bd319a9179098af8c97bd6c1d8671a1e1cf656c0e83fdc7a0ede20ccf60fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bab282fd20949a78607099dba3d64288cc63809b9e25f3e95015cd7f826a274a4010f9654117710976391561524c8f57b2c0727ed8b9ef7e8511f5fe9cd31c72
|
|
7
|
+
data.tar.gz: b4eef9e9aa441a567ac050c0ff27170ef8f9d64f8f9993b7c014ee9b7e79241ba6d5f917b956649cb194fcb59e8b06694db4d8e4500f113adb3f1212236d8430
|
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. 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)).
|
|
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
data/History.txt
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
=== 3.0.10 / 01.06.2026
|
|
2
|
+
* FHIR: read the BAG Indikationscode (XXXXX.NN) from the explicit `indicationCode` extension now carried on each limitation (BAG SL FHIR export >= v2.0.5) instead of reconstructing it from FOPHDossierNumber + the ClinicalUseDefinition id suffix. The changelog states the limitation code (CUD id) and the indication code are independent fields, so the old derivation was no longer guaranteed correct. Falls back to the previous derivation for older feeds that lack the extension. Output is identical on the current live feed. Other changelog items (limitation text moved to ClinicalUseDefinition, sanitized CUD ids, un-truncated `<` in texts, deduplicated/ordered Ingredients) were already handled or are transparent upstream fixes.
|
|
3
|
+
|
|
4
|
+
=== 3.0.9 / 01.06.2026
|
|
5
|
+
* FHIR is now the default source for -e/--extended and -b/--firstbase. As of June 2026, running oddb2xml -e or oddb2xml -b downloads the FOPH/BAG FHIR NDJSON feed instead of the BAG-XML Spezialitätenliste. Plain runs are unchanged. Opt out with --no-fhir; explicit --fhir/--fhir-url still work.
|
|
6
|
+
|
|
1
7
|
=== 3.0.8 / 28.05.2026
|
|
2
8
|
* FHIR: fix empty <DescriptionDe/Fr/It> on every <Limitation>. The live BAG FHIR feed does not carry limitation text inline; it stores a `limitationIndication` reference to a `ClinicalUseDefinition` whose `indication.diseaseSymptomProcedure.concept.text` holds the actual text (one per language). FR/IT come from the per-language NDJSON files and are merged via the CUD id. Result on the live feed: 9'108 / 9'108 limitations (100%) now carry text, up from 0% (issue #116).
|
|
3
9
|
|
data/README.md
CHANGED
|
@@ -51,7 +51,7 @@ HIN (http://hin.ch) creates daily the actual file. They can be downloaded from `
|
|
|
51
51
|
see `--help`.
|
|
52
52
|
|
|
53
53
|
```
|
|
54
|
-
/opt/src/oddb2xml/bin/oddb2xml version 3.0.
|
|
54
|
+
/opt/src/oddb2xml/bin/oddb2xml version 3.0.10
|
|
55
55
|
Usage:
|
|
56
56
|
oddb2xml [option]
|
|
57
57
|
produced files are found under data
|
|
@@ -64,6 +64,8 @@ see `--help`.
|
|
|
64
64
|
--fhir Use FHIR NDJSON format from FOPH/BAG instead of XML
|
|
65
65
|
from Spezialitätenliste. Downloads per-language
|
|
66
66
|
NDJSON files (de, fr, it) from epl.bag.admin.ch.
|
|
67
|
+
Default ON for -e/--extended and -b/--firstbase
|
|
68
|
+
since June 2026; disable with --no-fhir.
|
|
67
69
|
--fhir-url=<s> Specific FHIR NDJSON URL to download (implies --fhir)
|
|
68
70
|
-f, --format=<s> File format F, default is xml. {xml|dat}
|
|
69
71
|
If F is given, -o option is ignored. (Default: xml)
|
|
@@ -97,7 +99,8 @@ $ oddb2xml -t md # => md_article.xml, md_product.xml, md_
|
|
|
97
99
|
$ oddb2xml -a nonpharma -t md -c tar.gz # => md_xml_dd.mm.yyyy_hh.mm.tar.gz
|
|
98
100
|
$ oddb2xml -f dat # => oddb.dat
|
|
99
101
|
$ oddb2xml -f dat -a nonpharma # => oddb_with_migel.dat
|
|
100
|
-
$ oddb2xml -e # => oddb_article.xml
|
|
102
|
+
$ oddb2xml -e # => oddb_article.xml (FHIR source by default since June 2026)
|
|
103
|
+
$ oddb2xml -e --no-fhir # => oddb_article.xml using the old BAG-XML Spezialitätenliste
|
|
101
104
|
```
|
|
102
105
|
|
|
103
106
|
output.
|
|
@@ -308,13 +311,18 @@ indication on each `<PRD>` in `oddb_product.xml`:
|
|
|
308
311
|
</PRD>
|
|
309
312
|
```
|
|
310
313
|
|
|
311
|
-
|
|
312
|
-
`
|
|
313
|
-
(`
|
|
314
|
-
|
|
315
|
-
`
|
|
316
|
-
|
|
317
|
-
`
|
|
314
|
+
Since 3.0.10 the code is read directly from the explicit
|
|
315
|
+
`indicationCode` extension carried on each limitation
|
|
316
|
+
(`RegulatedAuthorization.indication[].extension[regulatedAuthorization-limitation]`),
|
|
317
|
+
introduced in the BAG SL FHIR export ≥ v2.0.5 (e.g. `20403.01` /
|
|
318
|
+
`20403.02`). The BAG changelog states that the limitation code
|
|
319
|
+
(`ClinicalUseDefinition.id`) and the indication code are **independent**
|
|
320
|
+
fields, so the previous reconstruction from `FOPHDossierNumber` + the
|
|
321
|
+
`.NN` CUD-id suffix is no longer used (it is kept only as a fallback for
|
|
322
|
+
older feeds without the extension). `cud_id` still carries the
|
|
323
|
+
`limitationIndication` reference, and the element body carries the
|
|
324
|
+
human-readable indication text from the referenced
|
|
325
|
+
`ClinicalUseDefinition`'s `indication.diseaseSymptomProcedure.concept.text`.
|
|
318
326
|
|
|
319
327
|
The same data is also exposed in-memory on each item and package as
|
|
320
328
|
`item[:indication_codes]` (an array of `{code:, cud_id:, text:}`
|
|
@@ -395,6 +395,11 @@ module Oddb2xml
|
|
|
395
395
|
when "limitationText"
|
|
396
396
|
# Not present in the live FHIR feed — kept for forward-compat.
|
|
397
397
|
limitation[:text] = sub_ext["valueString"]
|
|
398
|
+
when "indicationCode"
|
|
399
|
+
# Authoritative BAG Indikationscode XXXXX.NN (feed >= v2.0.5).
|
|
400
|
+
# Independent of the CUD id, so it must be read here rather than
|
|
401
|
+
# reconstructed from FOPHDossierNumber + CUD suffix.
|
|
402
|
+
limitation[:indication_code] = sub_ext["valueString"]
|
|
398
403
|
when "limitationIndication"
|
|
399
404
|
ref = sub_ext.dig("valueReference", "reference")
|
|
400
405
|
limitation[:cud_ref] = ref&.sub(%r{\A.*ClinicalUseDefinition/}, "")
|
|
@@ -591,7 +596,30 @@ module Oddb2xml
|
|
|
591
596
|
|
|
592
597
|
def build_indication_codes(bundle)
|
|
593
598
|
reimbursement = bundle.authorizations.find(&:reimbursement_sl?)
|
|
594
|
-
|
|
599
|
+
return [] unless reimbursement
|
|
600
|
+
|
|
601
|
+
cud_texts = bundle.cud_text_by_id
|
|
602
|
+
|
|
603
|
+
# Preferred (BAG feed >= v2.0.5): read the explicit `indicationCode`
|
|
604
|
+
# carried on each limitation. The changelog warns that the limitation
|
|
605
|
+
# code (CUD id) and the indication code are independent, so we must NOT
|
|
606
|
+
# reconstruct XXXXX.NN from the CUD id suffix. Text is resolved via the
|
|
607
|
+
# limitationIndication reference (cud_ref) into the CUD's text.
|
|
608
|
+
from_ext = reimbursement.limitations.each_with_object([]) do |lim, acc|
|
|
609
|
+
code = lim[:indication_code]
|
|
610
|
+
next unless code && !code.empty?
|
|
611
|
+
cud_ref = lim[:cud_ref]
|
|
612
|
+
acc << OpenStruct.new(
|
|
613
|
+
code: code,
|
|
614
|
+
cud_id: cud_ref,
|
|
615
|
+
text: (cud_ref && cud_texts[cud_ref]) || lim[:text]
|
|
616
|
+
)
|
|
617
|
+
end
|
|
618
|
+
return from_ext unless from_ext.empty?
|
|
619
|
+
|
|
620
|
+
# Fallback for older feeds without the indicationCode extension:
|
|
621
|
+
# derive XXXXX.NN from FOPHDossierNumber + each indication CUD's suffix.
|
|
622
|
+
dossier = reimbursement.foph_dossier_no
|
|
595
623
|
return [] unless dossier && !bundle.clinical_use_definitions.empty?
|
|
596
624
|
|
|
597
625
|
bundle.clinical_use_definitions.each_with_object([]) do |cud, acc|
|
data/lib/oddb2xml/options.rb
CHANGED
|
@@ -22,7 +22,7 @@ module Oddb2xml
|
|
|
22
22
|
opt :extended, "pharma, non-pharma plus prices and non-pharma from zurrose.
|
|
23
23
|
Products without EAN-Code will also be listed.
|
|
24
24
|
File oddb_calc.xml will also be generated"
|
|
25
|
-
opt :fhir, "Use FHIR NDJSON format from FOPH/BAG instead of XML from Spezialitätenliste", default: false, short: :none
|
|
25
|
+
opt :fhir, "Use FHIR NDJSON format from FOPH/BAG instead of XML from Spezialitätenliste. Default ON for -e/--extended and -b/--firstbase since June 2026; disable with --no-fhir", default: false, short: :none
|
|
26
26
|
opt :fhir_url, "Specific FHIR NDJSON URL to download (implies --fhir)", type: :string, default: nil, short: :none
|
|
27
27
|
opt :format, "File format F, default is xml. {xml|dat}
|
|
28
28
|
If F is given, -o option is ignored.", type: :string, default: "xml"
|
|
@@ -71,6 +71,11 @@ module Oddb2xml
|
|
|
71
71
|
@opts[:extended] = true
|
|
72
72
|
@opts[:price] = :zurrose
|
|
73
73
|
end
|
|
74
|
+
# From June 2026, -e/--extended and -b/--firstbase default to the FHIR
|
|
75
|
+
# (FOPH/BAG NDJSON) source. Users can still opt out with --no-fhir.
|
|
76
|
+
if (@opts[:extended] || @opts[:firstbase]) && !@opts[:fhir_given]
|
|
77
|
+
@opts[:fhir] = true
|
|
78
|
+
end
|
|
74
79
|
# FHIR URL implies FHIR mode
|
|
75
80
|
if @opts[:fhir_url]
|
|
76
81
|
@opts[:fhir] = true
|
data/lib/oddb2xml/version.rb
CHANGED
data/spec/fhir_spec.rb
CHANGED
|
@@ -32,11 +32,46 @@ describe "FHIR Indikationscode support" do
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
describe Oddb2xml::FHIR::PreparationsParser do
|
|
35
|
-
it "
|
|
35
|
+
it "reads the explicit indicationCode (XXXXX.NN) from each limitation" do
|
|
36
36
|
parser = described_class.new(cyramza_fixture)
|
|
37
37
|
prep = parser.preparations.first
|
|
38
38
|
codes = prep.IndicationCodes.map(&:code)
|
|
39
39
|
expect(codes).to include("20403.01", "20403.02")
|
|
40
|
+
# cud_id still carries the CUD reference so the text can be resolved.
|
|
41
|
+
expect(prep.IndicationCodes.map(&:cud_id)).to include("CYRAMZA.01", "CYRAMZA.02")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "uses the explicit indicationCode field, not a dossier+CUD-suffix derivation" do
|
|
45
|
+
# The BAG changelog (>= v2.0.5) states the limitation code (CUD id) and
|
|
46
|
+
# the indication code are independent. Rewrite the explicit indicationCode
|
|
47
|
+
# values so they no longer correspond to FOPHDossierNumber + CUD suffix,
|
|
48
|
+
# and confirm the parser surfaces the explicit values verbatim.
|
|
49
|
+
bundle = JSON.parse(File.read(cyramza_fixture))
|
|
50
|
+
bundle["entry"].each do |entry|
|
|
51
|
+
res = entry["resource"]
|
|
52
|
+
next unless res["resourceType"] == "RegulatedAuthorization"
|
|
53
|
+
Array(res["indication"]).each do |ind|
|
|
54
|
+
Array(ind["extension"]).each do |ext|
|
|
55
|
+
next unless ext["url"].to_s.include?("regulatedAuthorization-limitation")
|
|
56
|
+
Array(ext["extension"]).each do |sub|
|
|
57
|
+
sub["valueString"] = "99999.77" if sub["url"] == "indicationCode"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
file = Tempfile.new(["cyramza-indc", ".ndjson"])
|
|
64
|
+
begin
|
|
65
|
+
file.write(JSON.generate(bundle))
|
|
66
|
+
file.flush
|
|
67
|
+
parser = described_class.new(file.path)
|
|
68
|
+
codes = parser.preparations.first.IndicationCodes.map(&:code)
|
|
69
|
+
expect(codes).to all(eq("99999.77"))
|
|
70
|
+
expect(codes).not_to include("20403.01", "20403.02")
|
|
71
|
+
ensure
|
|
72
|
+
file.close
|
|
73
|
+
file.unlink
|
|
74
|
+
end
|
|
40
75
|
end
|
|
41
76
|
end
|
|
42
77
|
|