fhirsmith 0.18.2 → 1.0.0
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/ACCESSIBILITY.md +119 -0
- package/CHANGELOG.md +149 -0
- package/CONFIGURATION.md +305 -0
- package/LICENSE +29 -0
- package/README.md +29 -122
- package/cluster.js +1220 -0
- package/cluster.md +707 -0
- package/config-template.json +77 -8
- package/extension-tracker/extension-tracker-template.html +5 -8
- package/extension-tracker/extension-tracker.js +0 -2
- package/folder/folder.js +7 -6
- package/ips/analysis.js +560 -0
- package/ips/ips-template.html +5 -8
- package/ips/ips.js +132 -59
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +74 -21
- package/ips/presenter.js +1 -1
- package/ips/readme.md +132 -25
- package/ips/render.js +466 -77
- package/ips/store.js +79 -22
- package/ips/structure.js +247 -0
- package/ips/terminology.js +1 -1
- package/library/body-limit.js +22 -0
- package/library/cache-generation.js +15 -0
- package/library/cgroup-memory.js +133 -1
- package/library/cluster-counts.js +121 -0
- package/library/cluster-events.js +153 -0
- package/library/cluster-metrics.js +193 -0
- package/library/cluster-status.js +468 -0
- package/library/config-reader.js +96 -0
- package/library/daily-report.js +100 -2
- package/library/html-server.js +27 -1
- package/library/i18nsupport.js +23 -1
- package/library/keep-alive.js +18 -0
- package/library/languages.js +17 -3
- package/library/logger.js +31 -0
- package/library/memory-guard.js +428 -0
- package/library/native-memory.js +52 -0
- package/library/npm-audit.js +51 -25
- package/library/openapi-doc.js +7 -5
- package/library/package-manager.js +8 -0
- package/library/role.js +197 -0
- package/library/server-credentials.js +169 -0
- package/library/sqlite-utilities.js +59 -0
- package/library/ssrf-guard.js +100 -0
- package/library/static-response-stats.js +16 -0
- package/library/version-utilities.js +50 -0
- package/library/worker-count.js +192 -0
- package/nginx.md +30 -11
- package/npmprojector/indexer.js +18 -10
- package/package.json +11 -8
- package/packages/feed-check-page.js +117 -0
- package/packages/feed-check.js +279 -0
- package/packages/npm-check.js +57 -3
- package/packages/npm-signing.js +219 -0
- package/packages/openapi.yaml +207 -1
- package/packages/package-crawler.js +322 -41
- package/packages/packages-template.html +6 -8
- package/packages/packages.js +604 -57
- package/packages/readme.md +113 -1
- package/publisher/publisher-template.html +2 -5
- package/publisher/publisher.js +121 -110
- package/publisher/readme.md +75 -0
- package/registry/crawler.js +84 -108
- package/registry/documentation/configuration.md +34 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +5 -4
- package/root-bare-template.html +7 -4
- package/root-template.html +5 -8
- package/server.js +398 -198
- package/shl/shl.js +38 -37
- package/static/a11y/a11y-check.js +832 -0
- package/static/a11y/axe.min.js +12 -0
- package/static/assets/css/bootstrap-fhir.css +15 -15
- package/static/assets/images/flags/au.svg +18 -0
- package/static/assets/images/flags/unknown.svg +11 -0
- package/static/assets/images/flags/us.svg +63 -0
- package/static/assets/images/icon_audit.png +0 -0
- package/static/assets/images/icon_clinical.png +0 -0
- package/static/assets/images/icon_document.png +0 -0
- package/static/assets/images/icon_external.png +0 -0
- package/static/assets/images/icon_medication.png +0 -0
- package/static/assets/images/icon_organization.png +0 -0
- package/static/assets/images/icon_other.png +0 -0
- package/static/assets/images/icon_person.png +0 -0
- package/static/assets/images/icon_section.png +0 -0
- package/stats-db.js +45 -0
- package/stats.js +197 -10
- package/testing/testing-template.html +5 -8
- package/token/nginx_passport_setup.md +1 -1
- package/token/security_guide.md +1 -1
- package/token/token-template.html +2 -5
- package/token/token.js +40 -36
- package/translations/Messages.properties +19 -0
- package/tx/README.md +154 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +29 -1
- package/tx/cs/cs-api.js +284 -19
- package/tx/cs/cs-api.md +41 -4
- package/tx/cs/cs-areacode.js +1 -1
- package/tx/cs/cs-country.js +17 -5
- package/tx/cs/cs-cpt.js +33 -16
- package/tx/cs/cs-cs.js +148 -14
- package/tx/cs/cs-currency.js +1 -1
- package/tx/cs/cs-icd11.js +21 -13
- package/tx/cs/cs-icf.js +22 -3
- package/tx/cs/cs-lang.js +2 -2
- package/tx/cs/cs-loinc.js +185 -66
- package/tx/cs/cs-mimetypes.js +12 -2
- package/tx/cs/cs-ndc.js +40 -25
- package/tx/cs/cs-omop.js +54 -29
- package/tx/cs/cs-rxnorm.js +300 -196
- package/tx/cs/cs-snomed.js +608 -64
- package/tx/cs/cs-ucum.js +14 -4
- package/tx/cs/cs-unii.js +18 -14
- package/tx/cs/cs-virtual.js +210 -0
- package/tx/ecosystem-resolver.js +413 -0
- package/tx/html/cluster-dash-metrics.liquid +26 -0
- package/tx/html/cluster-home-metrics.liquid +31 -0
- package/tx/html/cluster-metrics-script.liquid +251 -0
- package/tx/html/codesystem-operations.liquid +3 -3
- package/tx/html/ecl-panel.liquid +1 -1
- package/tx/html/external-metrics.liquid +17 -0
- package/tx/html/operations-form.liquid +17 -17
- package/tx/html/search-form.liquid +11 -11
- package/tx/html/status-line.liquid +58 -0
- package/tx/html/status-panel.liquid +140 -0
- package/tx/html/status-tasks.liquid +25 -0
- package/tx/html/tx-template.html +2 -5
- package/tx/importers/atc-to-fhir.js +17 -16
- package/tx/importers/export-icd11-codesystem.js +3 -2
- package/tx/importers/import-icd11.js +4 -3
- package/tx/importers/import-loinc.module.js +118 -56
- package/tx/importers/import-ndc.module.js +56 -44
- package/tx/importers/import-rxnorm.module.js +118 -79
- package/tx/importers/import-sct.module.js +57 -58
- package/tx/importers/import-unii.module.js +37 -36
- package/tx/importers/subset-loinc.module.js +49 -48
- package/tx/importers/subset-rxnorm.module.js +29 -28
- package/tx/importers/terminal.js +18 -0
- package/tx/importers/tx-import-base.js +13 -12
- package/tx/importers/tx-import-settings.js +5 -4
- package/tx/importers/tx-import.js +53 -52
- package/tx/library/canonical-resource.js +49 -0
- package/tx/library/codesystem.js +94 -5
- package/tx/library/designations.js +45 -2
- package/tx/library/extensions.js +13 -7
- package/tx/library/implicit-valuesets.js +66 -0
- package/tx/library/renderer.js +77 -21
- package/tx/library/sqlite-utilities.js +2 -0
- package/tx/library/ucum-parsers.js +11 -1
- package/tx/library/wait-for-cache.js +43 -0
- package/tx/library.js +321 -22
- package/tx/openapi-operations.js +6 -4
- package/tx/operation-context.js +460 -99
- package/tx/params.js +25 -0
- package/tx/problems.js +3 -5
- package/tx/provider.js +278 -42
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/ecl.js +47 -6
- package/tx/sct/editions.js +39 -0
- package/tx/sct/expressions.js +10 -1
- package/tx/sct/load-config.js +107 -0
- package/tx/sct/mmap.js +109 -0
- package/tx/sct/structures.js +166 -13
- package/tx/sct/text-index.js +5 -1
- package/tx/syndication/syndication-feed.js +2 -2
- package/tx/tx-html.js +44 -8
- package/tx/tx.fhir.org.yml +8 -8
- package/tx/tx.js +282 -80
- package/tx/usage-tracker.js +39 -11
- package/tx/vs/vs-api.js +19 -0
- package/tx/vs/vs-database.js +386 -62
- package/tx/vs/vs-package.js +27 -4
- package/tx/vs/vs-vsac.js +648 -100
- package/tx/workers/batch-validate.js +37 -10
- package/tx/workers/batch.js +6 -1
- package/tx/workers/cache-control.js +15 -4
- package/tx/workers/closure.js +156 -13
- package/tx/workers/compare.js +8 -5
- package/tx/workers/expand.js +1173 -289
- package/tx/workers/metadata.js +35 -0
- package/tx/workers/search.js +53 -1
- package/tx/workers/supplement-filters.js +145 -0
- package/tx/workers/translate.js +169 -4
- package/tx/workers/validate.js +223 -119
- package/tx/workers/worker.js +123 -31
- package/tx/xversion/xv-valueset.js +7 -4
- package/utilities/submit-test-report.js +3 -1
- package/utilities/tx-stress-test.js +482 -0
- package/vcl/readme.md +37 -0
- package/vcl/vcl-parser.js +15 -0
- package/xig/readme.md +52 -0
- package/xig/xig-template.html +5 -8
- package/xig/xig.js +238 -422
- package/changelog-template.md +0 -22
- package/registry/test-runner.js +0 -237
- package/release.sh +0 -80
- package/test-scripts/repro-re2-wasm-leak.js +0 -34
- package/tsconfig.eslint.json +0 -9
- package/tx/html/dash-metrics.liquid +0 -147
- package/tx/html/home-metrics.liquid +0 -225
package/tx/params.js
CHANGED
|
@@ -502,6 +502,19 @@ class TxParameters {
|
|
|
502
502
|
return '';
|
|
503
503
|
}
|
|
504
504
|
|
|
505
|
+
// The code system version the request's version rules ask for - force-system-version,
|
|
506
|
+
// else system-version, else check-system-version - or null when they name none
|
|
507
|
+
requestedSystemVersion(systemURI) {
|
|
508
|
+
const rules = this.rulesForSystem(systemURI).filter(t => !t.vs);
|
|
509
|
+
for (const mode of ['override', 'default', 'check']) {
|
|
510
|
+
const rule = rules.find(t => t.mode === mode);
|
|
511
|
+
if (rule) {
|
|
512
|
+
return rule.version;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
|
|
505
518
|
rulesForSystem(systemURI) {
|
|
506
519
|
let result = [];
|
|
507
520
|
for (let t of this.FVersionRules) {
|
|
@@ -521,6 +534,18 @@ class TxParameters {
|
|
|
521
534
|
}
|
|
522
535
|
}
|
|
523
536
|
|
|
537
|
+
// The languages the request named - displayLanguage, else the HTTP Accept-Language - or
|
|
538
|
+
// null when it named none (workingLanguages() falls back to a default; this does not)
|
|
539
|
+
requestedLanguages() {
|
|
540
|
+
if (this.FHasDisplayLanguages && this.FDisplayLanguages) {
|
|
541
|
+
return this.FDisplayLanguages;
|
|
542
|
+
}
|
|
543
|
+
if (this.FHasHTTPLanguages && this.FHTTPLanguages) {
|
|
544
|
+
return this.FHTTPLanguages;
|
|
545
|
+
}
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
|
|
524
549
|
workingLanguages() {
|
|
525
550
|
if (this.FDisplayLanguages) {
|
|
526
551
|
return this.FDisplayLanguages;
|
package/tx/problems.js
CHANGED
|
@@ -11,13 +11,11 @@ class ProblemFinder {
|
|
|
11
11
|
unknownVersions[vsp.sourcePackage()] = sourceUnknownVersions;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
let vs = await vsp.fetchValueSet(url);
|
|
17
|
-
if (vs && vs.jsonObj.compose) {
|
|
14
|
+
await vsp.scanAllValueSets(async (vs) => {
|
|
15
|
+
if (vs.jsonObj.compose) {
|
|
18
16
|
await this.scanValueSet(vs.jsonObj.compose, sourceUnknownVersions);
|
|
19
17
|
}
|
|
20
|
-
}
|
|
18
|
+
});
|
|
21
19
|
}
|
|
22
20
|
let result = '';
|
|
23
21
|
for (let sp of provider.listValueSetSourceCodes()) {
|
package/tx/provider.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { chooseImplicitValueSetFactory } = require('./library/implicit-valuesets');
|
|
1
2
|
const { CodeSystem } = require("./library/codesystem");
|
|
2
3
|
const { assertNoActiveContent } = require('../library/narrative-check');
|
|
3
4
|
const {VersionUtilities} = require("../library/version-utilities");
|
|
@@ -11,6 +12,31 @@ const {PackageValueSetProvider} = require("./vs/vs-package");
|
|
|
11
12
|
const ValueSet = require("./library/valueset");
|
|
12
13
|
const {PackageConceptMapProvider} = require("./cm/cm-package");
|
|
13
14
|
const { applyIdPrefix, CORE_PREFIX } = require("./library/resource-ids");
|
|
15
|
+
const { CodeSystemFactoryProvider } = require("./cs/cs-api");
|
|
16
|
+
const { VirtualCodeSystemProvider } = require("./cs/cs-virtual");
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How many codes resolveCodes() locates at once. Enough to keep a database-backed code
|
|
21
|
+
* system busy without queueing a whole page's worth of queries at the same time.
|
|
22
|
+
*/
|
|
23
|
+
const RESOLVE_CODES_CONCURRENCY = 8;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* fn over items, at most `limit` at a time, with the results in the order of the items.
|
|
27
|
+
*/
|
|
28
|
+
async function mapConcurrently(items, limit, fn) {
|
|
29
|
+
const results = new Array(items.length);
|
|
30
|
+
let next = 0;
|
|
31
|
+
const worker = async () => {
|
|
32
|
+
while (next < items.length) {
|
|
33
|
+
const i = next++;
|
|
34
|
+
results[i] = await fn(items[i], i);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, worker));
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
14
40
|
|
|
15
41
|
/**
|
|
16
42
|
* This class holds what information is in context
|
|
@@ -23,6 +49,86 @@ const { applyIdPrefix, CORE_PREFIX } = require("./library/resource-ids");
|
|
|
23
49
|
* - a list of concept map providers that can provide concept map instances
|
|
24
50
|
*
|
|
25
51
|
*/
|
|
52
|
+
/**
|
|
53
|
+
* The provider's CodeSystem map (keys url and url|version), with an index of the CodeSystems it
|
|
54
|
+
* holds by the url they supplement, so that loadSupplements() looks only at those instead of
|
|
55
|
+
* every loaded CodeSystem on every code-system lookup.
|
|
56
|
+
*
|
|
57
|
+
* The index is part of the map, not a cache: set, delete and clear are the only ways to change a
|
|
58
|
+
* Map, and each updates the index in the same call. Provider.addCodeSystem and deleteCodeSystem
|
|
59
|
+
* are the only callers that change this map.
|
|
60
|
+
*
|
|
61
|
+
* supplementsOf(url) gives every value whose CodeSystem.isSupplementFor(url, version) can be
|
|
62
|
+
* true, in the map's own order, and possibly some for which it is false; loadSupplements still
|
|
63
|
+
* asks isSupplementFor of each. isSupplementFor is true only when `supplements` equals url or
|
|
64
|
+
* starts with url + '|', and then the part of `supplements` before any '|' is the part of url
|
|
65
|
+
* before any '|': that is the index key.
|
|
66
|
+
*/
|
|
67
|
+
class CodeSystemMap extends Map {
|
|
68
|
+
constructor(entries) {
|
|
69
|
+
super();
|
|
70
|
+
this.supplementIndex = new Map(); // key used by supplementsOf -> Set of map keys
|
|
71
|
+
this.indexedUnder = new Map(); // map key -> its key in supplementIndex
|
|
72
|
+
this.position = new Map(); // map key -> insertion number, to keep the map's order
|
|
73
|
+
this.nextPosition = 0;
|
|
74
|
+
if (entries) {
|
|
75
|
+
for (const [k, v] of entries) this.set(k, v);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** the index key for a url, or for a CodeSystem's supplements value: the part before any '|' */
|
|
80
|
+
static indexKey(url) {
|
|
81
|
+
return String(url).split('|')[0];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
set(k, v) {
|
|
85
|
+
if (!super.has(k)) {
|
|
86
|
+
this.position.set(k, this.nextPosition++); // a Map keeps a replaced key where it was
|
|
87
|
+
}
|
|
88
|
+
this.#unindex(k);
|
|
89
|
+
super.set(k, v);
|
|
90
|
+
const supplements = v && v.jsonObj ? v.jsonObj.supplements : null;
|
|
91
|
+
if (supplements) {
|
|
92
|
+
const u = CodeSystemMap.indexKey(supplements);
|
|
93
|
+
let keys = this.supplementIndex.get(u);
|
|
94
|
+
if (!keys) this.supplementIndex.set(u, keys = new Set());
|
|
95
|
+
keys.add(k);
|
|
96
|
+
this.indexedUnder.set(k, u);
|
|
97
|
+
}
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
delete(k) {
|
|
102
|
+
this.#unindex(k);
|
|
103
|
+
this.position.delete(k);
|
|
104
|
+
return super.delete(k);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
clear() {
|
|
108
|
+
this.supplementIndex.clear();
|
|
109
|
+
this.indexedUnder.clear();
|
|
110
|
+
this.position.clear();
|
|
111
|
+
super.clear();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// removes k from the index under the key it was indexed under when it was set
|
|
115
|
+
#unindex(k) {
|
|
116
|
+
const u = this.indexedUnder.get(k);
|
|
117
|
+
if (u === undefined) return;
|
|
118
|
+
this.indexedUnder.delete(k);
|
|
119
|
+
const keys = this.supplementIndex.get(u);
|
|
120
|
+
keys.delete(k);
|
|
121
|
+
if (keys.size === 0) this.supplementIndex.delete(u);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** the values that may supplement url (any version), in the order values() gives them */
|
|
125
|
+
supplementsOf(url) {
|
|
126
|
+
const keys = this.supplementIndex.get(CodeSystemMap.indexKey(url));
|
|
127
|
+
if (!keys) return [];
|
|
128
|
+
return [...keys].sort((a, b) => this.position.get(a) - this.position.get(b)).map(k => super.get(k));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
26
132
|
class Provider {
|
|
27
133
|
i18n;
|
|
28
134
|
fhirVersion;
|
|
@@ -57,6 +163,10 @@ class Provider {
|
|
|
57
163
|
|
|
58
164
|
baseUrl = null;
|
|
59
165
|
path;
|
|
166
|
+
/**
|
|
167
|
+
* {EcosystemResolver|null} finds other servers for code systems that aren't held here (modules.tx.ecosystem)
|
|
168
|
+
*/
|
|
169
|
+
ecosystem = null;
|
|
60
170
|
cacheFolder = null;
|
|
61
171
|
startTime = Date.now();
|
|
62
172
|
startMemory = process.memoryUsage();
|
|
@@ -98,7 +208,7 @@ class Provider {
|
|
|
98
208
|
factory = this.codeSystemFactories.get(vurlMM);
|
|
99
209
|
}
|
|
100
210
|
if (factory != null) {
|
|
101
|
-
return
|
|
211
|
+
return this.buildOnce(opContext, factory, supplements);
|
|
102
212
|
}
|
|
103
213
|
let cs = this.codeSystems.get(vurl);
|
|
104
214
|
if (cs == null && vurlMM) {
|
|
@@ -107,7 +217,61 @@ class Provider {
|
|
|
107
217
|
if (cs != null) {
|
|
108
218
|
return this.createCodeSystemProvider(opContext, cs, supplements);
|
|
109
219
|
}
|
|
110
|
-
return
|
|
220
|
+
return await this.findInEcosystem(opContext, system, version, supplements);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* A code system (and version) that isn't held here, provided by another server in the
|
|
225
|
+
* ecosystem, if modules.tx.ecosystem.use is true, and the request didn't come from
|
|
226
|
+
* another server doing the same thing (see EcosystemResolver)
|
|
227
|
+
*
|
|
228
|
+
* @returns {Promise<VirtualCodeSystemProvider|null>}
|
|
229
|
+
*/
|
|
230
|
+
async findInEcosystem(opContext, system, version, supplements) {
|
|
231
|
+
if (!this.ecosystem || opContext.ecosystemForwarded) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const server = await this.ecosystem.findServer(system, version, this.fhirVersion, opContext);
|
|
235
|
+
if (!server) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
// the registry's claims are broad (tx.fhir.org is authoritative for all of http://hl7.org/*),
|
|
239
|
+
// so the server has to say that it actually has the code system - and the version: the one
|
|
240
|
+
// asked for, or, for a version that is a url, the latest release of it (a SNOMED CT edition
|
|
241
|
+
// asked for without a release date). A partial semver is not completed this way
|
|
242
|
+
const versions = await this.ecosystem.listVersions(server, system, opContext);
|
|
243
|
+
if (versions == null) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const codes = versions.map(v => v.code);
|
|
247
|
+
let actual = version;
|
|
248
|
+
if (!version) {
|
|
249
|
+
// no version asked for: it's the server's default version - the only version it has, or
|
|
250
|
+
// the one it says is the default
|
|
251
|
+
const defaults = versions.length === 1 ? versions : versions.filter(v => v.isDefault);
|
|
252
|
+
if (defaults.length !== 1) {
|
|
253
|
+
throw new TerminologyError(`Unable to use ${server} for ${system}: its TerminologyCapabilities doesn't say which version of it is the default`);
|
|
254
|
+
}
|
|
255
|
+
actual = defaults[0].code;
|
|
256
|
+
} else if (!codes.includes(version)) {
|
|
257
|
+
const matches = codes.filter(v => v.startsWith(version + '/')).sort();
|
|
258
|
+
if (matches.length === 0) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
actual = matches[matches.length - 1];
|
|
262
|
+
}
|
|
263
|
+
opContext.log('Using ' + server + ' for ' + system + (actual ? '|' + actual : ''));
|
|
264
|
+
return new VirtualCodeSystemProvider(opContext, supplements, server, system, actual, this.ecosystem);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* factory.build(), once per operation, factory and set of supplements (see
|
|
269
|
+
* OperationContext.buildProviderOnce). The caller registers the provider for closing.
|
|
270
|
+
*/
|
|
271
|
+
buildOnce(opContext, factory, supplements) {
|
|
272
|
+
// the factory is the code system and version; the supplements make a different provider
|
|
273
|
+
const variant = (supplements || []).map(cs => cs.vurl || cs.url).sort().join(' ');
|
|
274
|
+
return opContext.buildProviderOnce(factory, variant, () => factory.build(opContext, supplements));
|
|
111
275
|
}
|
|
112
276
|
|
|
113
277
|
loadSupplements(url, version, statedSupplements) {
|
|
@@ -115,9 +279,12 @@ class Provider {
|
|
|
115
279
|
for (let csp of this.codeSystemFactories.values()) {
|
|
116
280
|
csp.listSupplements(supplements, url, version, statedSupplements);
|
|
117
281
|
}
|
|
118
|
-
|
|
282
|
+
// the index gives the same candidates in the same order as values(), less the ones that
|
|
283
|
+
// can't be supplements of url (see CodeSystemMap)
|
|
284
|
+
const candidates = this.codeSystems instanceof CodeSystemMap ? this.codeSystems.supplementsOf(url) : this.codeSystems.values();
|
|
285
|
+
for (let cs of candidates) {
|
|
119
286
|
if (cs.isSupplementFor(url, version)) {
|
|
120
|
-
if (cs.
|
|
287
|
+
if (cs.isGlobalSupplement() || this.isStatedSupplement(cs, statedSupplements)) {
|
|
121
288
|
supplements.set(cs.vurl, cs);
|
|
122
289
|
}
|
|
123
290
|
}
|
|
@@ -161,9 +328,12 @@ class Provider {
|
|
|
161
328
|
* share ids, and so does other content (hl7.terminology), so no endpoint can serve them on
|
|
162
329
|
* their own ids (see tx/library/resource-ids.js)
|
|
163
330
|
*
|
|
331
|
+
* @param {Object} [options]
|
|
332
|
+
* @param {boolean} [options.readOnly] - a tx-worker (cluster.md): the package's value set and
|
|
333
|
+
* concept map caches are the master's, opened read-only (see Library.packageCachesReadOnly)
|
|
164
334
|
* @returns {Promise<PackageContentLoader|undefined>} the loaded package (mode "npm")
|
|
165
335
|
*/
|
|
166
|
-
async loadNpm(packageManager, cacheFolder, details, isDefault, mode) {
|
|
336
|
+
async loadNpm(packageManager, cacheFolder, details, isDefault, mode, options = {}) {
|
|
167
337
|
// Parse packageId and version from details (e.g., "hl7.terminology.r4#6.0.2")
|
|
168
338
|
let packageId = details;
|
|
169
339
|
let version = null;
|
|
@@ -210,11 +380,11 @@ class Provider {
|
|
|
210
380
|
applyIdPrefix(cs, CORE_PREFIX);
|
|
211
381
|
this.addCodeSystem(cs);
|
|
212
382
|
}
|
|
213
|
-
const vs = new PackageValueSetProvider(contentLoader);
|
|
383
|
+
const vs = new PackageValueSetProvider(contentLoader, { readOnly: !!options.readOnly });
|
|
214
384
|
await vs.initialize();
|
|
215
385
|
vs.assignIds(CORE_PREFIX);
|
|
216
386
|
this.valueSetProviders.push(vs);
|
|
217
|
-
const cm = new PackageConceptMapProvider(contentLoader);
|
|
387
|
+
const cm = new PackageConceptMapProvider(contentLoader, { readOnly: !!options.readOnly });
|
|
218
388
|
await cm.initialize();
|
|
219
389
|
cm.assignIds(CORE_PREFIX);
|
|
220
390
|
this.conceptMapProviders.push(cm);
|
|
@@ -254,7 +424,7 @@ class Provider {
|
|
|
254
424
|
return null;
|
|
255
425
|
}
|
|
256
426
|
|
|
257
|
-
async findValueSet(opContext, url, version) {
|
|
427
|
+
async findValueSet(opContext, url, version, systemVersionFor = null) {
|
|
258
428
|
for (const vp of this.valueSetProviders) {
|
|
259
429
|
if (opContext) opContext.deadCheck('findValueSet');
|
|
260
430
|
let vs = await vp.fetchValueSet(url, version);
|
|
@@ -262,7 +432,7 @@ class Provider {
|
|
|
262
432
|
return vs;
|
|
263
433
|
}
|
|
264
434
|
}
|
|
265
|
-
let vs1 = await this.findKnownValueSet(url, version);
|
|
435
|
+
let vs1 = await this.findKnownValueSet(url, version, systemVersionFor);
|
|
266
436
|
return vs1;
|
|
267
437
|
}
|
|
268
438
|
|
|
@@ -325,7 +495,18 @@ class Provider {
|
|
|
325
495
|
return result;
|
|
326
496
|
}
|
|
327
497
|
|
|
328
|
-
async findKnownValueSet(url, version) {
|
|
498
|
+
async findKnownValueSet(url, version, systemVersionFor = null) {
|
|
499
|
+
// A code system with several versions loaded (SNOMED CT editions) says which form of
|
|
500
|
+
// implicit value set url is: the generic form comes from the version the request asks
|
|
501
|
+
// for (systemVersionFor, from system-version and the like) or else the default version
|
|
502
|
+
// (the one registered under the bare system), the edition form from the latest version
|
|
503
|
+
// of that edition, and the version form from that version alone - not from whichever
|
|
504
|
+
// version happens to be asked first.
|
|
505
|
+
const chosen = chooseImplicitValueSetFactory(this.codeSystemFactories.values(), system => this.codeSystemFactories.get(system), url, systemVersionFor);
|
|
506
|
+
if (chosen) {
|
|
507
|
+
const vs = await chosen.buildKnownValueSet(url, version);
|
|
508
|
+
return vs != null ? new ValueSet(vs) : null;
|
|
509
|
+
}
|
|
329
510
|
for (let csp of this.codeSystemFactories.values()) {
|
|
330
511
|
let vs = await csp.buildKnownValueSet(url, version);
|
|
331
512
|
if (vs != null) {
|
|
@@ -351,10 +532,17 @@ class Provider {
|
|
|
351
532
|
const targetBase = targetSystem.split('|')[0];
|
|
352
533
|
let uris = new Set();
|
|
353
534
|
let asked = new Set(); // a factory is registered under several keys (system, system|version...)
|
|
535
|
+
// A source version that names a version without a date (a SNOMED CT edition URI, say)
|
|
536
|
+
// matches every version of it the server holds; it means the one it resolves to, as it
|
|
537
|
+
// does everywhere else - asking each would give the same translation once per release
|
|
538
|
+
const chosen = sourceVersion ? this.codeSystemFactories.get(sourceSystem + '|' + sourceVersion) : null;
|
|
354
539
|
for (let csp of this.codeSystemFactories.values()) {
|
|
355
540
|
if (asked.has(csp)) {
|
|
356
541
|
continue;
|
|
357
542
|
}
|
|
543
|
+
if (chosen && csp !== chosen && csp.system() === sourceSystem) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
358
546
|
if (csp.system() === sourceSystem || csp.system() === targetBase || !uris.has(csp.system())) {
|
|
359
547
|
uris.add(csp.system());
|
|
360
548
|
asked.add(csp);
|
|
@@ -441,10 +629,31 @@ class Provider {
|
|
|
441
629
|
}
|
|
442
630
|
|
|
443
631
|
async resolveCode(opContext, system, version, code) {
|
|
632
|
+
validateParameter(code, "code", String);
|
|
633
|
+
return (await this.resolveCodes(opContext, system, version, [code]))[0];
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* resolveCode() for many codes of one code system: where each code lives (a link) and
|
|
638
|
+
* its display, or null, in the order of the codes.
|
|
639
|
+
*
|
|
640
|
+
* The code system is found and its provider built once for all of them, and the codes
|
|
641
|
+
* are located RESOLVE_CODES_CONCURRENCY at a time rather than one after another.
|
|
642
|
+
*
|
|
643
|
+
* @param {OperationContext} opContext
|
|
644
|
+
* @param {string} system
|
|
645
|
+
* @param {string|null} version
|
|
646
|
+
* @param {string[]} codes
|
|
647
|
+
* @returns {Promise<Array<{link: string, description: string}|null>>}
|
|
648
|
+
*/
|
|
649
|
+
async resolveCodes(opContext, system, version, codes) {
|
|
444
650
|
validateParameter(opContext, "opContext", OperationContext);
|
|
445
651
|
validateParameter(system, "system", String);
|
|
446
652
|
validateOptionalParameter(version, "version", String);
|
|
447
|
-
|
|
653
|
+
validateArrayParameter(codes, "codes", String);
|
|
654
|
+
if (codes.length === 0) {
|
|
655
|
+
return [];
|
|
656
|
+
}
|
|
448
657
|
|
|
449
658
|
if (system.includes("|")) {
|
|
450
659
|
const url = system.substring(0, system.indexOf("|"));
|
|
@@ -464,41 +673,68 @@ class Provider {
|
|
|
464
673
|
if (factory == null && vurlMM) {
|
|
465
674
|
factory = this.codeSystemFactories.get(vurlMM);
|
|
466
675
|
}
|
|
467
|
-
if
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
description: csp.display(c)
|
|
476
|
-
}
|
|
477
|
-
} else {
|
|
478
|
-
const link = factory.codeLink(c);
|
|
479
|
-
if (link) {
|
|
480
|
-
return {
|
|
481
|
-
link: link,
|
|
482
|
-
description: csp.display(c)
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
676
|
+
// A code system from a factory gives a code a link only if the factory is iteratable
|
|
677
|
+
// (US states) or has a codeLink() of its own. For the rest (RxNorm, LOINC, SNOMED CT,
|
|
678
|
+
// ...) what locate() finds would be thrown away, so they are not built or asked.
|
|
679
|
+
// That matters: RxNorm looks for a code it does not have in its archive table, which
|
|
680
|
+
// takes 36 ms a code, and 4,529 of Medication Brand Name's codes are not current.
|
|
681
|
+
const linksCodes = factory != null
|
|
682
|
+
&& (factory.iteratable() || factory.codeLink !== CodeSystemFactoryProvider.prototype.codeLink);
|
|
683
|
+
const csp = linksCodes ? await this.resolveCodeProvider(opContext, factory) : null;
|
|
488
684
|
let cs = this.codeSystems.get(vurl);
|
|
489
685
|
if (cs == null && vurlMM) {
|
|
490
686
|
cs = this.codeSystems.get(vurlMM);
|
|
491
687
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
688
|
+
|
|
689
|
+
return mapConcurrently(codes, RESOLVE_CODES_CONCURRENCY, async (code) => {
|
|
690
|
+
if (csp) {
|
|
691
|
+
const located = await csp.locate(code);
|
|
692
|
+
const c = located ? located.context : null;
|
|
693
|
+
if (c) {
|
|
694
|
+
if (factory.iteratable()) {
|
|
695
|
+
return {
|
|
696
|
+
link: this.path + "/CodeSystem/x-" + factory.id(),
|
|
697
|
+
description: await csp.display(c)
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
const link = factory.codeLink(c);
|
|
701
|
+
if (link) {
|
|
702
|
+
return {
|
|
703
|
+
link: link,
|
|
704
|
+
description: await csp.display(c)
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
499
709
|
}
|
|
500
|
-
|
|
501
|
-
|
|
710
|
+
if (cs != null) {
|
|
711
|
+
const c = cs.codeMap.get(code);
|
|
712
|
+
if (c) {
|
|
713
|
+
return {
|
|
714
|
+
link: this.path + "/CodeSystem/" + cs.id + '#' + code,
|
|
715
|
+
description: c.display
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* The provider resolveCode() uses for a code system factory in this operation: built
|
|
725
|
+
* the first time it is asked for (see buildOnce), registered for closing when the
|
|
726
|
+
* operation ends, and reused after that. A page asks resolveCode about every code it shows.
|
|
727
|
+
*
|
|
728
|
+
* @param {OperationContext} opContext
|
|
729
|
+
* @param {CodeSystemFactoryProvider} factory
|
|
730
|
+
* @returns {Promise<CodeSystemProvider|null>}
|
|
731
|
+
*/
|
|
732
|
+
resolveCodeProvider(opContext, factory) {
|
|
733
|
+
// the same provider getCodeSystemProvider() builds for this code system with no supplements
|
|
734
|
+
return this.buildOnce(opContext, factory, []).then(p => {
|
|
735
|
+
opContext.registerProvider(p);
|
|
736
|
+
return p;
|
|
737
|
+
});
|
|
502
738
|
}
|
|
503
739
|
|
|
504
740
|
/**
|
|
@@ -737,4 +973,4 @@ class Provider {
|
|
|
737
973
|
|
|
738
974
|
}
|
|
739
975
|
|
|
740
|
-
module.exports = { Provider };
|
|
976
|
+
module.exports = { CodeSystemMap, Provider };
|