fhirsmith 0.18.1 → 0.18.2
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.
- package/CHANGELOG.md +41 -9
- package/ips/ips.js +251 -26
- package/ips/jurisdictions.js +65 -0
- package/ips/pipeline.js +67 -6
- package/ips/profile-editor.js +283 -4
- package/ips/readme.md +48 -5
- package/ips/render.js +317 -46
- package/ips/scripts.js +46 -0
- package/ips/store.js +55 -0
- package/package.json +3 -3
- package/registry/api.js +179 -1
- package/registry/crawler.js +2 -0
- package/registry/documentation/api.md +68 -2
- package/registry/documentation/internals.md +4 -1
- package/registry/documentation/readme.md +3 -2
- package/registry/model.js +8 -0
- package/registry/openapi.yaml +84 -0
- package/registry/registry.js +53 -2
- package/tx/README.md +4 -0
- package/tx/cm/cm-package.js +21 -15
- package/tx/cs/cs-cs.js +51 -10
- package/tx/cs/cs-loinc.js +8 -1
- package/tx/cs/cs-snomed.js +253 -31
- package/tx/importers/import-sct.module.js +69 -4
- package/tx/library/tho-precedence.js +40 -0
- package/tx/provider.js +36 -25
- package/tx/sct/documentation.md +45 -21
- package/tx/sct/ecl.js +34 -11
- package/tx/sct/structures.js +9 -2
- package/tx/tx.fhir.org.yml +1 -1
- package/tx/vs/vs-package.js +17 -13
- package/tx/vs/vs-vsac.js +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//
|
|
2
|
+
// THO precedence for value sets and concept maps (see Provider.applyTHOPrecedence).
|
|
3
|
+
//
|
|
4
|
+
// Where hl7.terminology provides a resource with the same url as one in the FHIR core
|
|
5
|
+
// package, THO's copy is the one used - versions were reset when content moved to THO, so
|
|
6
|
+
// version order can't decide. But the core copy is still what a reference that names its
|
|
7
|
+
// version (url|5.0.0) means, so it stays available to that reference, and only to it.
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
const { VersionUtilities } = require('../../library/version-utilities');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether a package provider should keep a map key for a resource that THO also provides.
|
|
14
|
+
* Only the keys that name a version are kept - url|version, and url|major.minor for a
|
|
15
|
+
* semver version - and only where THO doesn't provide that version itself. The bare url
|
|
16
|
+
* (the default for a reference with no version) and the resource's id are not kept.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} key - the map key
|
|
19
|
+
* @param {{url: string, version: string}} resource - the core package's resource
|
|
20
|
+
* @param {Set<string>} thoVersions - the versions of the url that hl7.terminology provides
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
function keepForPinnedReference(key, resource, thoVersions) {
|
|
24
|
+
const version = resource.version;
|
|
25
|
+
if (!version) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (key === resource.url + '|' + version) {
|
|
29
|
+
return !thoVersions.has(version);
|
|
30
|
+
}
|
|
31
|
+
if (VersionUtilities.isSemVer(version)) {
|
|
32
|
+
const mm = VersionUtilities.getMajMin(version);
|
|
33
|
+
if (mm && key === resource.url + '|' + mm) {
|
|
34
|
+
return ![...thoVersions].some(v => v && VersionUtilities.isSemVer(v) && VersionUtilities.getMajMin(v) === mm);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { keepForPinnedReference };
|
package/tx/provider.js
CHANGED
|
@@ -632,27 +632,20 @@ class Provider {
|
|
|
632
632
|
* moved from the FHIR core packages to terminology.hl7.org has been QA'd there since, and
|
|
633
633
|
* its versions went backwards (coverage-selfpay is 4.0.1 in hl7.fhir.r4.core but 1.0.1 in
|
|
634
634
|
* hl7.terminology), so version precedence can't decide between them. Instead, a code system
|
|
635
|
-
* from an hl7.terminology package
|
|
636
|
-
*
|
|
635
|
+
* from an hl7.terminology package is always the default for its url - what a reference with
|
|
636
|
+
* no version gets - whatever versions the FHIR core packages have.
|
|
637
637
|
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
*
|
|
641
|
-
*
|
|
638
|
+
* The core versions stay available by url|version, where THO doesn't provide that version
|
|
639
|
+
* itself: a reference that names its version (url|5.0.0) means the core one, and there's
|
|
640
|
+
* nothing else it can mean. They are never the default while THO provides the url (see also
|
|
641
|
+
* deleteCodeSystem).
|
|
642
642
|
*/
|
|
643
643
|
addCodeSystem(cs) {
|
|
644
644
|
if (Provider.#isTHOPackage(cs.sourcePackage)) {
|
|
645
|
-
|
|
646
|
-
if (t.url === cs.url && VersionUtilities.isCorePackage(t.sourcePackage)
|
|
647
|
-
&& (key === t.url || !t.urlHadVersion)) {
|
|
648
|
-
this.codeSystems.delete(key);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
} else if (VersionUtilities.isCorePackage(cs.sourcePackage) && !cs.urlHadVersion) {
|
|
652
|
-
// THO's code system is always the default for its url
|
|
645
|
+
// THO's code system takes over the default from a core one; the core versions stay
|
|
653
646
|
const t = this.codeSystems.get(cs.url);
|
|
654
|
-
if (t &&
|
|
655
|
-
|
|
647
|
+
if (t && t.url === cs.url && VersionUtilities.isCorePackage(t.sourcePackage)) {
|
|
648
|
+
this.codeSystems.delete(cs.url);
|
|
656
649
|
}
|
|
657
650
|
}
|
|
658
651
|
const existing = this.codeSystems.get(cs.url);
|
|
@@ -677,45 +670,63 @@ class Provider {
|
|
|
677
670
|
* THO precedence for value sets and concept maps, as for code systems (see addCodeSystem):
|
|
678
671
|
* once the library's providers have been added after this endpoint's core package, the core
|
|
679
672
|
* package's value sets and concept maps whose urls an hl7.terminology package also provides
|
|
680
|
-
* are
|
|
673
|
+
* are only available to a reference that names their version (and THO doesn't provide that
|
|
674
|
+
* version itself). They are never what a reference with no version gets, and they aren't
|
|
675
|
+
* read by id, found by search, or used for translation unless asked for.
|
|
681
676
|
*/
|
|
682
677
|
applyTHOPrecedence() {
|
|
683
|
-
const thoValueSets = new
|
|
678
|
+
const thoValueSets = new Map();
|
|
684
679
|
for (const vp of this.valueSetProviders) {
|
|
685
680
|
if (vp instanceof PackageValueSetProvider && Provider.#isTHOPackage(vp.sourcePackage())) {
|
|
686
681
|
for (const vs of vp.valueSetMap.values()) {
|
|
687
|
-
thoValueSets
|
|
682
|
+
Provider.#addVersion(thoValueSets, vs);
|
|
688
683
|
}
|
|
689
684
|
}
|
|
690
685
|
}
|
|
691
|
-
const thoConceptMaps = new
|
|
686
|
+
const thoConceptMaps = new Map();
|
|
692
687
|
for (const cmp of this.conceptMapProviders) {
|
|
693
688
|
if (cmp instanceof PackageConceptMapProvider && Provider.#isTHOPackage(cmp.sourcePackage())) {
|
|
694
689
|
for (const cm of cmp.conceptMapMap.values()) {
|
|
695
|
-
thoConceptMaps
|
|
690
|
+
Provider.#addVersion(thoConceptMaps, cm);
|
|
696
691
|
}
|
|
697
692
|
}
|
|
698
693
|
}
|
|
699
694
|
for (const vp of this.valueSetProviders) {
|
|
700
695
|
if (vp instanceof PackageValueSetProvider && VersionUtilities.isCorePackage(vp.sourcePackage())) {
|
|
701
|
-
vp.
|
|
696
|
+
vp.keepPinnedOnly(thoValueSets);
|
|
702
697
|
}
|
|
703
698
|
}
|
|
704
699
|
for (const cmp of this.conceptMapProviders) {
|
|
705
700
|
if (cmp instanceof PackageConceptMapProvider && VersionUtilities.isCorePackage(cmp.sourcePackage())) {
|
|
706
|
-
cmp.
|
|
701
|
+
cmp.keepPinnedOnly(thoConceptMaps);
|
|
707
702
|
}
|
|
708
703
|
}
|
|
709
704
|
}
|
|
710
705
|
|
|
706
|
+
static #addVersion(map, resource) {
|
|
707
|
+
if (!resource || !resource.url) {
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (!map.has(resource.url)) {
|
|
711
|
+
map.set(resource.url, new Set());
|
|
712
|
+
}
|
|
713
|
+
if (resource.version) {
|
|
714
|
+
map.get(resource.url).add(resource.version);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
711
718
|
deleteCodeSystem(cs) {
|
|
712
719
|
this.codeSystems.delete(cs.vurl);
|
|
713
720
|
this.codeSystems.delete(cs.url);
|
|
714
721
|
// If other versions of the SAME url survive, re-point the unversioned [url]
|
|
715
722
|
// entry at the most-recent surviving version. Otherwise leave it deleted.
|
|
723
|
+
// THO precedence (see addCodeSystem): while a THO version survives, a core one
|
|
724
|
+
// can't become the default, however recent its (reset) version looks
|
|
725
|
+
const survivors = [...new Set(this.codeSystems.values())].filter(t => t.url === cs.url);
|
|
726
|
+
const tho = survivors.filter(t => Provider.#isTHOPackage(t.sourcePackage));
|
|
716
727
|
let existing = null;
|
|
717
|
-
for (let t of
|
|
718
|
-
if (
|
|
728
|
+
for (let t of (tho.length > 0 ? tho : survivors)) {
|
|
729
|
+
if (!existing || t.isMoreRecent(existing)) {
|
|
719
730
|
existing = t;
|
|
720
731
|
}
|
|
721
732
|
}
|
package/tx/sct/documentation.md
CHANGED
|
@@ -35,14 +35,20 @@ that the server does not use. `filterECL` is the one that matters.
|
|
|
35
35
|
reference set's own fields (concept, integer and string typed from the RF2
|
|
36
36
|
file name). Since inactive member rows are dropped at import, `active = false`
|
|
37
37
|
never matches.
|
|
38
|
-
-
|
|
38
|
+
- Refinements: `=` and `!=` against an expression value (`!=` counts the
|
|
39
|
+
occurrences whose value is outside the set, so `A != X` needs at least one
|
|
40
|
+
A that is not X, and `[0..0] A != X` means every A is X); reversed
|
|
41
|
+
attributes (`R A = X`: the concept is the value of an A on something in X,
|
|
42
|
+
evaluated over the concept's inbound relationships); attribute names that
|
|
43
|
+
are a concept, `*`, or any sub expression (`<< 762705008 = ...`); attribute
|
|
44
|
+
sets joined by `AND`, `OR` or `,`, with brackets; attribute groups,
|
|
45
|
+
including `AND`/`OR` inside and between groups; attribute and group
|
|
46
|
+
cardinality, including `[0..0]`. Cardinality counts occurrences per role
|
|
47
|
+
group, per the SNOMED Computable Languages Group ruling (see
|
|
48
|
+
`_countAttributeMatches`).
|
|
49
|
+
- `AND` / `OR` / `MINUS`, and `,` as a conjunction at the expression level as
|
|
50
|
+
well as in refinements, with the ECL 6.4 rule that conjunction and
|
|
39
51
|
disjunction cannot be mixed without brackets.
|
|
40
|
-
- Refinements: `=` against an expression value; attribute names that are a
|
|
41
|
-
concept, `*`, or any sub expression (`<< 762705008 = ...`); attribute sets
|
|
42
|
-
joined by `AND`, `OR` or `,`, with brackets; attribute groups, including
|
|
43
|
-
`AND`/`OR` inside and between groups; attribute and group cardinality,
|
|
44
|
-
including `[0..0]`. Cardinality counts occurrences per role group, per the
|
|
45
|
-
SNOMED Computable Languages Group ruling (see `_countAttributeMatches`).
|
|
46
52
|
- Dotted expressions (`< X . 363698007`, `. << 363698007`, `. (A OR B)`).
|
|
47
53
|
- `{{ C ... }}` concept filters on `active`, `definitionStatus`,
|
|
48
54
|
`definitionStatusId`, `moduleId` and `effectiveTime`.
|
|
@@ -56,26 +62,37 @@ that the server does not use. `filterECL` is the one that matters.
|
|
|
56
62
|
must be satisfied by a single description; separate blocks may be satisfied
|
|
57
63
|
by different descriptions. Only active descriptions are considered unless
|
|
58
64
|
the block has an `active` filter.
|
|
65
|
+
- Concrete values in refinements - numeric (`= #5`, `>= #1.5`, `!= #-1`),
|
|
66
|
+
string (`= "x"`, `= match:"x"`, a set) and boolean (`= true`) - against the
|
|
67
|
+
concrete value relationships, which the importer reads from
|
|
68
|
+
`sct2_RelationshipConcreteValues_*` into cache version 19. Numbers compare
|
|
69
|
+
as numbers (`#5` matches `#5.0`); strings use the same match / wild
|
|
70
|
+
semantics as description terms; a value of another kind never matches.
|
|
71
|
+
An edition imported before version 19 has no concrete values and reports
|
|
72
|
+
such a refinement as unsupported rather than answering with nothing.
|
|
73
|
+
- History supplements: `{{ + HISTORY }}` (= MAX), `HISTORY-MIN` (SAME AS),
|
|
74
|
+
`HISTORY-MOD` (SAME AS, WAS A, REPLACED BY, PARTIALLY EQUIVALENT TO),
|
|
75
|
+
`HISTORY-MAX` (every reference set under 900000000000522004 |Historical
|
|
76
|
+
association|), `HISTORY (*)` and `HISTORY (expression)`: the inactive
|
|
77
|
+
concepts that the selected association reference sets associate with a
|
|
78
|
+
concept in the result are added to it. A reference set the edition does not
|
|
79
|
+
carry contributes nothing. Whether a bare `HISTORY` means MAX is taken from
|
|
80
|
+
Snow Owl's reading of the specification - to be confirmed.
|
|
81
|
+
- Alternate identifiers (`LOINC#14394-1`, `"LOINC#14394-1"`): the scheme
|
|
82
|
+
alias names an identifier scheme (`IDENTIFIER_SCHEMES` in `cs-snomed.js`:
|
|
83
|
+
LOINC = 30051010000102, from the LOINC Extension's `sct2_Identifier` file,
|
|
84
|
+
cache version 18+), the code resolves to the concept(s) carrying it, and
|
|
85
|
+
hierarchy operators apply to them as to any set. An unknown scheme or a
|
|
86
|
+
code nobody carries is `INVALID_ECL`.
|
|
59
87
|
- Comments (`/* ... */`), terms (`|...|`, not checked against descriptions).
|
|
60
88
|
|
|
61
89
|
### Parsed but reported as unsupported
|
|
62
90
|
|
|
63
|
-
- Reverse attributes (`R 363698007 = ...`).
|
|
64
|
-
- `!=` against an expression value in a refinement.
|
|
65
|
-
- Concrete values in refinements - numeric (`= #5`, `>= #1.5`), string
|
|
66
|
-
(`= "x"`, `= match:"x"`), boolean (`= true`). The importer does not read
|
|
67
|
-
`sct2_RelationshipConcreteValues_*`, so no concrete values are in the cache
|
|
68
|
-
to compare against. Supporting them means extending the importer and the
|
|
69
|
-
relationship record, and rebuilding every cache.
|
|
70
|
-
- Alternate identifiers (`"ICD10#A01.1"`, `SCTID#123`). No alternate
|
|
71
|
-
identifier reference sets are loaded.
|
|
72
91
|
- Reference set field selection on member-of (`^ [targetComponentId] refset`,
|
|
73
92
|
`^ [referencedComponentId, targetComponentId] refset`). Deliberate: the
|
|
74
93
|
selected fields, and the referenced components of many reference sets,
|
|
75
94
|
are descriptions or other non-concept components, which an expansion has
|
|
76
95
|
no way to represent.
|
|
77
|
-
- History supplements (`{{ + HISTORY-MIN / MOD / MAX }}`). The association
|
|
78
|
-
reference sets they draw on are loaded, so this is implementable.
|
|
79
96
|
|
|
80
97
|
### Not parsed
|
|
81
98
|
|
|
@@ -99,7 +116,13 @@ that the server does not use. `filterECL` is the one that matters.
|
|
|
99
116
|
- `^` on a concept that is not a reference set yields an empty result rather
|
|
100
117
|
than an error (Snowstorm's behaviour; agreed in the tx-ecosystem suite).
|
|
101
118
|
`^` on a reference set whose members are descriptions (a language reference
|
|
102
|
-
set) is reported as unsupported
|
|
119
|
+
set) is reported as unsupported when that reference set is named explicitly
|
|
120
|
+
- there are no concepts to return. Inside a computed operand (`^ *`,
|
|
121
|
+
`^ (< 900000000000455006)`) such a reference set contributes nothing and
|
|
122
|
+
the rest of the operand still counts.
|
|
123
|
+
- An attribute name without a comparison (`< X : 363698007`, or `(< X : A = v
|
|
124
|
+
OR B)` where the OR binds inside the refinement) is a syntax error, as the
|
|
125
|
+
grammar requires.
|
|
103
126
|
- The ECL result for a value set filter is cached per (value set, code system
|
|
104
127
|
version), so the first expansion of a large constraint is slow and later ones
|
|
105
128
|
are not.
|
|
@@ -181,8 +204,9 @@ role group pairing.
|
|
|
181
204
|
|
|
182
205
|
- Stated relationships and the OWL axiom reference sets. Everything is driven
|
|
183
206
|
by the distributed inferred relationships; there is no classification.
|
|
184
|
-
- Concrete value relationships (
|
|
185
|
-
-
|
|
207
|
+
- Concrete value relationships are read into cache version 19 (October 2026);
|
|
208
|
+
caches built before that have none, and a concrete-value ECL refinement on
|
|
209
|
+
them is reported as unsupported.
|
|
186
210
|
- Inactive reference set member rows (only active rows are stored).
|
|
187
211
|
- Module ids and definition status on concepts are recorded by the current
|
|
188
212
|
importer; caches built before mid-2026 have neither (every concept looks
|
package/tx/sct/ecl.js
CHANGED
|
@@ -538,8 +538,11 @@ class ECLParser {
|
|
|
538
538
|
// this compound sequence and reject a mix (e.g. "<< A OR << B AND << C").
|
|
539
539
|
let compoundOp = null;
|
|
540
540
|
|
|
541
|
-
|
|
542
|
-
|
|
541
|
+
// "," is a conjunction in the ECL grammar (conjunction = "AND" / ","), at
|
|
542
|
+
// this level as well as in refinements; a comma that reaches here was not
|
|
543
|
+
// claimed by a refinement.
|
|
544
|
+
while (this.match(ECLTokenType.AND, ECLTokenType.OR, ECLTokenType.MINUS, ECLTokenType.COMMA)) {
|
|
545
|
+
const operator = this.match(ECLTokenType.COMMA) ? { ...this.current, type: ECLTokenType.AND } : this.current;
|
|
543
546
|
if (compoundOp !== null && operator.type !== compoundOp) {
|
|
544
547
|
throw new Error(
|
|
545
548
|
'Ambiguous ECL expression: conjunction, disjunction and exclusion ' +
|
|
@@ -726,18 +729,30 @@ class ECLParser {
|
|
|
726
729
|
return { type: ECLNodeType.FILTER_CONSTRAINT, kind, filters };
|
|
727
730
|
}
|
|
728
731
|
|
|
729
|
-
if (kind
|
|
730
|
-
//
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
732
|
+
if (kind === '+') {
|
|
733
|
+
// historySupplement = "{{" "+" "HISTORY" [ "-MIN" / "-MOD" / "-MAX" / "(" (expressionConstraint / "*") ")" ] "}}"
|
|
734
|
+
if (!this.match(ECLTokenType.SCTID) || !/^HISTORY(-MIN|-MOD|-MAX)?$/i.test(this.current.value)) {
|
|
735
|
+
this.error('Expected HISTORY, HISTORY-MIN, HISTORY-MOD or HISTORY-MAX after {{ +');
|
|
736
|
+
}
|
|
737
|
+
const word = this.current.value.toUpperCase();
|
|
738
|
+
this.advance();
|
|
739
|
+
const node = { type: ECLNodeType.FILTER_CONSTRAINT, kind, profile: word === 'HISTORY' ? 'MAX' : word.substring(8) };
|
|
740
|
+
if (this.match(ECLTokenType.LPAREN)) {
|
|
741
|
+
if (node.profile !== 'MAX' || word !== 'HISTORY') {
|
|
742
|
+
this.error('A history profile (HISTORY-MIN/MOD/MAX) cannot also name reference sets');
|
|
735
743
|
}
|
|
736
|
-
raw.push(this.current.value);
|
|
737
744
|
this.advance();
|
|
745
|
+
if (this.match(ECLTokenType.WILDCARD)) {
|
|
746
|
+
this.advance();
|
|
747
|
+
node.profile = 'MAX';
|
|
748
|
+
} else {
|
|
749
|
+
node.profile = 'EXPLICIT';
|
|
750
|
+
node.refsets = this.parseExpressionConstraint();
|
|
751
|
+
}
|
|
752
|
+
this.expect(ECLTokenType.RPAREN);
|
|
738
753
|
}
|
|
739
|
-
this.
|
|
740
|
-
return
|
|
754
|
+
this.expect(ECLTokenType.RFILTER);
|
|
755
|
+
return node;
|
|
741
756
|
}
|
|
742
757
|
|
|
743
758
|
const filters = [this.parseMemberFilter()];
|
|
@@ -1430,6 +1445,14 @@ class ECLParser {
|
|
|
1430
1445
|
};
|
|
1431
1446
|
}
|
|
1432
1447
|
|
|
1448
|
+
if (!comparison) {
|
|
1449
|
+
// eclAttribute = [cardinality] [reverseFlag] eclAttributeName comparison - a
|
|
1450
|
+
// bare name is a syntax error. This is what "(A : B = C OR D)" trips on:
|
|
1451
|
+
// the OR binds inside the refinement, so D is an attribute name without a
|
|
1452
|
+
// comparison.
|
|
1453
|
+
this.error('Expected a comparison operator (=, !=, <, <=, >, >=) after the attribute name');
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1433
1456
|
return {
|
|
1434
1457
|
type: ECLNodeType.ATTRIBUTE,
|
|
1435
1458
|
cardinality,
|
package/tx/sct/structures.js
CHANGED
|
@@ -1358,7 +1358,7 @@ class SnomedFileReader {
|
|
|
1358
1358
|
}
|
|
1359
1359
|
|
|
1360
1360
|
// Determine settings based on cache version string (matching Pascal logic)
|
|
1361
|
-
if (result.cacheVersion === '17' || result.cacheVersion === '18') { //
|
|
1361
|
+
if (result.cacheVersion === '17' || result.cacheVersion === '18' || result.cacheVersion === '19') { // 18 = 17 + alternate identifiers; 19 = 18 + concrete values
|
|
1362
1362
|
result.hasLangs = true;
|
|
1363
1363
|
} else if (result.cacheVersion === '16') { // Assuming this is UTF8 version for now
|
|
1364
1364
|
result.isUTF16 = false;
|
|
@@ -1384,9 +1384,16 @@ class SnomedFileReader {
|
|
|
1384
1384
|
|
|
1385
1385
|
// Alternate identifiers (version 18): [[scheme, module, code, conceptId], ...]
|
|
1386
1386
|
result.identifiers = [];
|
|
1387
|
-
if (result.cacheVersion === '18') {
|
|
1387
|
+
if (result.cacheVersion === '18' || result.cacheVersion === '19') {
|
|
1388
1388
|
result.identifiers = JSON.parse(this.readBytes().toString('utf8'));
|
|
1389
1389
|
}
|
|
1390
|
+
// Concrete value relationships (version 19): [[sourceId, typeId, group, value], ...].
|
|
1391
|
+
// null (not []) when the cache predates them, so the server can tell "none" from
|
|
1392
|
+
// "not loaded".
|
|
1393
|
+
result.concreteValues = null;
|
|
1394
|
+
if (result.cacheVersion === '19') {
|
|
1395
|
+
result.concreteValues = JSON.parse(this.readBytes().toString('utf8'));
|
|
1396
|
+
}
|
|
1390
1397
|
|
|
1391
1398
|
return result;
|
|
1392
1399
|
}
|
package/tx/tx.fhir.org.yml
CHANGED
|
@@ -32,7 +32,7 @@ sources:
|
|
|
32
32
|
- snomed:sct_us_20230301.cache
|
|
33
33
|
- snomed:sct_us_20240301.cache
|
|
34
34
|
- snomed:sct_us_20250901.cache
|
|
35
|
-
- snomed:
|
|
35
|
+
- snomed:sct_test_20250909e.cache
|
|
36
36
|
- cpt:CodeSystem-cpt.db|cpt-2023-fragment-0.1.db
|
|
37
37
|
- omop:omop_v20250227.db
|
|
38
38
|
- npm:hl7.terminology=tho
|
package/tx/vs/vs-package.js
CHANGED
|
@@ -8,6 +8,7 @@ const { checkContained } = require('../library/canonical-resource');
|
|
|
8
8
|
const { VersionUtilities } = require('../../library/version-utilities');
|
|
9
9
|
const {validateParameter} = require("../../library/utilities");
|
|
10
10
|
const { applyIdPrefix, stripIdPrefix } = require('../library/resource-ids');
|
|
11
|
+
const { keepForPinnedReference } = require('../library/tho-precedence');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Package-based ValueSet provider using shared database layer
|
|
@@ -267,7 +268,7 @@ class PackageValueSetProvider extends AbstractValueSetProvider {
|
|
|
267
268
|
|
|
268
269
|
// Only process each ValueSet once (use URL to deduplicate)
|
|
269
270
|
const vsUrl = json.url;
|
|
270
|
-
if (seen.has(vsUrl)) {
|
|
271
|
+
if (seen.has(vsUrl) || this.#excludedUrls.has(vsUrl)) {
|
|
271
272
|
continue;
|
|
272
273
|
}
|
|
273
274
|
|
|
@@ -460,35 +461,38 @@ class PackageValueSetProvider extends AbstractValueSetProvider {
|
|
|
460
461
|
return this.#excludedUrls.size === 0 ? urls : urls.filter(url => !this.#excludedUrls.has(url));
|
|
461
462
|
}
|
|
462
463
|
|
|
463
|
-
// the value sets left out by
|
|
464
|
+
// the value sets left out by keepPinnedOnly - by url, and by their own (unprefixed) id
|
|
464
465
|
#excludedUrls = new Set();
|
|
465
466
|
#excludedIds = new Set();
|
|
466
467
|
|
|
467
468
|
/**
|
|
468
|
-
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
469
|
+
* The value sets with these urls are hl7.terminology's to provide (see
|
|
470
|
+
* Provider.applyTHOPrecedence): from this (core) package they are only available to a
|
|
471
|
+
* reference that names their version, where THO doesn't provide that version itself. They
|
|
472
|
+
* aren't the default for their url, can't be read by id, aren't found by search or listed.
|
|
473
|
+
* Must be called once the provider is initialized - the database is shared, so it isn't
|
|
474
|
+
* changed.
|
|
472
475
|
*
|
|
473
|
-
* @param {Set<string
|
|
476
|
+
* @param {Map<string, Set<string>>} thoVersions - url -> the versions hl7.terminology provides
|
|
474
477
|
*/
|
|
475
|
-
|
|
476
|
-
const
|
|
478
|
+
keepPinnedOnly(thoVersions) {
|
|
479
|
+
const affected = new Set();
|
|
477
480
|
for (const vs of this.valueSetMap.values()) {
|
|
478
|
-
if (vs &&
|
|
479
|
-
|
|
481
|
+
if (vs && thoVersions.has(vs.url)) {
|
|
482
|
+
affected.add(vs);
|
|
480
483
|
}
|
|
481
484
|
}
|
|
482
485
|
for (const [key, vs] of [...this.valueSetMap]) {
|
|
483
|
-
if (
|
|
486
|
+
if (affected.has(vs) && !keepForPinnedReference(key, vs, thoVersions.get(vs.url))) {
|
|
484
487
|
this.valueSetMap.delete(key);
|
|
485
488
|
}
|
|
486
489
|
}
|
|
487
|
-
for (const vs of
|
|
490
|
+
for (const vs of affected) {
|
|
488
491
|
this.#excludedUrls.add(vs.url);
|
|
489
492
|
this.#excludedIds.add(stripIdPrefix(vs.id, this.spaceId) ?? vs.id);
|
|
490
493
|
}
|
|
491
494
|
}
|
|
495
|
+
|
|
492
496
|
}
|
|
493
497
|
|
|
494
498
|
module.exports = {
|
package/tx/vs/vs-vsac.js
CHANGED
|
@@ -42,7 +42,7 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
|
|
|
42
42
|
this.apiKey = config.apiKey;
|
|
43
43
|
// Optional operator password for the on-demand resync form (see info()).
|
|
44
44
|
this.resyncPassword = config.resyncPassword || null;
|
|
45
|
-
this.cacheFolder = folders.
|
|
45
|
+
this.cacheFolder = folders.ensureFolder("terminology-cache", "vsac");
|
|
46
46
|
// https: every request carries the API key. (VSAC redirects http to https, but only after
|
|
47
47
|
// the key has been sent in the clear)
|
|
48
48
|
this.baseUrl = (config.baseUrl || 'https://cts.nlm.nih.gov/fhir').replace(/\/+$/, '');
|