fhirsmith 0.18.1 → 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 +190 -9
- 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 +331 -33
- package/ips/jurisdictions.js +65 -0
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +131 -17
- package/ips/presenter.js +1 -1
- package/ips/profile-editor.js +283 -4
- package/ips/readme.md +170 -20
- package/ips/render.js +769 -109
- package/ips/scripts.js +46 -0
- package/ips/store.js +123 -11
- 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/api.js +179 -1
- package/registry/crawler.js +86 -108
- package/registry/documentation/api.md +68 -2
- package/registry/documentation/configuration.md +34 -0
- 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-template.html +5 -8
- package/registry/registry.js +58 -6
- 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 +158 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +50 -16
- 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 +199 -24
- 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 +193 -67
- 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 +861 -95
- 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 +125 -61
- 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/tho-precedence.js +40 -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 +314 -67
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/documentation.md +45 -21
- package/tx/sct/ecl.js +81 -17
- 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 +175 -15
- 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 +44 -17
- package/tx/vs/vs-vsac.js +649 -101
- 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/workers/validate.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
//
|
|
13
13
|
|
|
14
14
|
const { TerminologyWorker, Unknown_Code_in_VersionSCT, SCTVersion } = require('./worker');
|
|
15
|
+
const { splitFilters, supplementFilter } = require('./supplement-filters');
|
|
15
16
|
const {Languages, Language} = require("../../library/languages");
|
|
16
17
|
const {Extensions} = require("../library/extensions");
|
|
17
18
|
const {validateParameter, isAbsoluteUrl, validateOptionalParameter, getValuePrimitive} = require("../../library/utilities");
|
|
@@ -128,6 +129,11 @@ class ValueSetChecker {
|
|
|
128
129
|
} catch (error) {
|
|
129
130
|
this.worker.log.error(error);
|
|
130
131
|
debugLog(error);
|
|
132
|
+
if (error instanceof Issue && error.statusCode >= 400 && error.statusCode < 500) {
|
|
133
|
+
// the value set can't be expanded (a code system it imports is unknown, it's too big...):
|
|
134
|
+
// that's the answer, not a server failure
|
|
135
|
+
throw error.handleAsOO(error.statusCode);
|
|
136
|
+
}
|
|
131
137
|
throw new Error('Exception expanding value set in order to infer system: ' + error.message);
|
|
132
138
|
}
|
|
133
139
|
return result;
|
|
@@ -154,64 +160,64 @@ class ValueSetChecker {
|
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
async determineSystem(opContext, code, systems, op) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
let mayNeedExpansion = false;
|
|
160
|
-
|
|
161
|
-
for (let vsi of this.valueSet.jsonObj.compose.exclude || []) {
|
|
162
|
-
if (vsi.valueSet || !vsi.system || vsi.filter) {
|
|
163
|
-
needDoExpansion = true;
|
|
164
|
-
}
|
|
163
|
+
if (!this.valueSet.jsonObj.compose) {
|
|
164
|
+
return await this.determineSystemFromExpansion(code, systems);
|
|
165
165
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
166
|
+
// Check the code against each include, and each value set they import, rather than expanding
|
|
167
|
+
// the value set: that costs as much as the value set is big (all of UCUM, say), and an
|
|
168
|
+
// expansion can be too big to produce at all
|
|
169
|
+
const scratch = new OperationOutcome(); // the reasons the code isn't in an include don't matter
|
|
170
|
+
for (const s of await this.systemsForCode(code, op, scratch)) {
|
|
171
|
+
systems.add(s);
|
|
173
172
|
}
|
|
173
|
+
return systems.size === 1 ? [...systems][0] : '';
|
|
174
|
+
}
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
/**
|
|
177
|
+
* The systems of the includes that have the code (in this value set, and the ones it imports), less
|
|
178
|
+
* those of this value set's excludes that leave it out
|
|
179
|
+
* @returns {Promise<Set<string>>}
|
|
180
|
+
*/
|
|
181
|
+
async systemsForCode(code, op, scratch) {
|
|
182
|
+
const systems = new Set();
|
|
183
|
+
const check = async (vsi, role) => {
|
|
184
|
+
this.worker.deadCheck('determineSystem');
|
|
185
|
+
const v = this.worker.determineVersionBase(vsi.system, vsi.version, this.params);
|
|
186
|
+
const cs = await this.worker.findCodeSystem(vsi.system, v, this.params, ['complete', 'fragment'], op, true, false, false, this.worker.requiredSupplements);
|
|
187
|
+
if (cs === null) {
|
|
188
|
+
// The system can only be inferred from all of the value set: if an include names a code
|
|
189
|
+
// system that isn't known, the code might be in it, so the request can't be answered (422)
|
|
190
|
+
const url = vsi.version ? vsi.system + '|' + vsi.version : vsi.system;
|
|
191
|
+
throw new Issue('error', 'not-found', null, 'UNABLE_TO_INFER_CODESYSTEM_UNKNOWN_SYSTEM',
|
|
192
|
+
this.worker.i18n.translate('UNABLE_TO_INFER_CODESYSTEM_UNKNOWN_SYSTEM', this.params.HTTPLanguages, [code, this.valueSet.vurl, url]), 'not-found')
|
|
193
|
+
.handleAsOO(422);
|
|
194
|
+
}
|
|
195
|
+
return await this.checkConceptSet('', role, cs, vsi, code, new Designations(this.worker.i18n.languageDefinitions), this.valueSet, '',
|
|
196
|
+
{value: false}, {value: ''}, {value: ''}, scratch, null, []);
|
|
197
|
+
};
|
|
198
|
+
for (const vsi of this.valueSet.jsonObj.compose.include || []) {
|
|
199
|
+
if (vsi.system) {
|
|
200
|
+
// (an include that also imports value sets narrows this one: the validation that follows checks that)
|
|
201
|
+
if (await check(vsi, 'in')) {
|
|
202
|
+
systems.add(vsi.system);
|
|
183
203
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
systems.add(
|
|
190
|
-
if (!result) {
|
|
191
|
-
result = vsi.system;
|
|
192
|
-
} else if (result !== vsi.system) {
|
|
193
|
-
return '';
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
let loc = await cs.locate(code);
|
|
199
|
-
if (loc.context !== null) {
|
|
200
|
-
systems.add(vsi.system);
|
|
201
|
-
if (!result) {
|
|
202
|
-
result = vsi.system;
|
|
203
|
-
} else if (result !== vsi.system) {
|
|
204
|
-
if (mayNeedExpansion) {
|
|
205
|
-
result = await this.determineSystemFromExpansion(code, systems);
|
|
206
|
-
} else {
|
|
207
|
-
return '';
|
|
208
|
-
}
|
|
204
|
+
} else {
|
|
205
|
+
for (const u of vsi.valueSet || []) {
|
|
206
|
+
const other = this.others.get(this.worker.pinValueSet(u));
|
|
207
|
+
if (other) {
|
|
208
|
+
for (const s of await other.systemsForCode(code, op, scratch)) {
|
|
209
|
+
systems.add(s);
|
|
209
210
|
}
|
|
210
211
|
}
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
214
|
}
|
|
214
|
-
|
|
215
|
+
for (const vsi of this.valueSet.jsonObj.compose.exclude || []) {
|
|
216
|
+
if (vsi.system && !(vsi.valueSet && vsi.valueSet.length) && await check(vsi, 'not in')) {
|
|
217
|
+
systems.delete(vsi.system);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return systems;
|
|
215
221
|
}
|
|
216
222
|
|
|
217
223
|
async determineVersion(path, system, versionVS, versionCoding, op, unknownSystems, messages) {
|
|
@@ -365,7 +371,10 @@ class ValueSetChecker {
|
|
|
365
371
|
|
|
366
372
|
async prepareConceptSet(desc, cc, vs) {
|
|
367
373
|
this.worker.deadCheck('prepareConceptSet');
|
|
368
|
-
Extensions.checkNoModifiers(cc, 'ValueSetChecker.prepare', desc, vs.vurl);
|
|
374
|
+
Extensions.checkNoModifiers(cc, 'ValueSetChecker.prepare', 'ValueSet.compose.' + desc, vs.vurl);
|
|
375
|
+
for (const [ci, c] of (cc.concept || []).entries()) {
|
|
376
|
+
Extensions.checkNoModifiers(c, 'ValueSetChecker.prepare', 'ValueSet.compose.' + desc + '.concept[' + ci + ']', vs.vurl);
|
|
377
|
+
}
|
|
369
378
|
this.worker.opContext.addNote(this.valueSet, 'Prepare ' + desc + ': "' + this.worker.renderer.displayValueSetInclude(cc) + '"', this.indentCount);
|
|
370
379
|
if (cc.valueSet) {
|
|
371
380
|
for (let u of cc.valueSet) {
|
|
@@ -395,12 +404,12 @@ class ValueSetChecker {
|
|
|
395
404
|
let i = 0;
|
|
396
405
|
for (let ccf of cc.filter || []) {
|
|
397
406
|
this.worker.deadCheck('prepareConceptSet#2');
|
|
398
|
-
Extensions.checkNoModifiers(ccf, 'ValueSetChecker.prepare', desc + '.filter', this.valueSet.vurl);
|
|
407
|
+
Extensions.checkNoModifiers(ccf, 'ValueSetChecker.prepare', 'ValueSet.compose.' + desc + '.filter[' + i + ']', this.valueSet.vurl);
|
|
399
408
|
if (!ccf.value) {
|
|
400
409
|
throw new Issue('error', 'invalid', "ValueSet.compose."+desc+".filter["+i+"]", 'UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE',
|
|
401
410
|
this.worker.i18n.translate('UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE', this.params.HTTPLanguages, [cs.system(), ccf.property, ccf.op]), "vs-invalid").handleAsOO(400);
|
|
402
411
|
}
|
|
403
|
-
if (!(ccf.property === 'concept' && ['is-a', 'descendent-of'].includes(ccf.op))) {
|
|
412
|
+
if (!(ccf.property === 'concept' && ['is-a', 'descendent-of'].includes(ccf.op)) && !(await supplementFilter(cs, ccf))) {
|
|
404
413
|
if (!(await cs.doesFilter(ccf.property, ccf.op, ccf.value))) {
|
|
405
414
|
throw new Issue('error', 'not-supported', "ValueSet.compose."+desc+".filter["+i+"]", 'FILTER_NOT_UNDERSTOOD', this.worker.i18n.translate('FILTER_NOT_UNDERSTOOD',
|
|
406
415
|
this.params.HTTPLanguages, [ccf.property, ccf.op, ccf.value, this.valueSet.url, cs.system()]), "vs-invalid").handleAsOO(400);
|
|
@@ -716,6 +725,10 @@ class ValueSetChecker {
|
|
|
716
725
|
determinedVersion = await this.pickApplicableVersion(vset, system, code, display);
|
|
717
726
|
}
|
|
718
727
|
}
|
|
728
|
+
// why the code wasn't in each include (a message from the code system - an error from
|
|
729
|
+
// another server, say). Only worth reporting if the code turns out not to be in the value
|
|
730
|
+
// set at all: if another include has it, they're beside the point
|
|
731
|
+
const reasons = [];
|
|
719
732
|
const includes = [...(this.valueSet.jsonObj.compose.include || [])];
|
|
720
733
|
includes.sort((a, b) => {
|
|
721
734
|
if (a.system === b.system && a.version && b.version) {
|
|
@@ -770,7 +783,7 @@ class ValueSetChecker {
|
|
|
770
783
|
contentMode.value = cs.contentMode();
|
|
771
784
|
|
|
772
785
|
let msg = '';
|
|
773
|
-
if ((system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'in', cs, cc, code, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc,
|
|
786
|
+
if ((system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'in', cs, cc, code, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc, reasons)) {
|
|
774
787
|
ver.value = cs.version();
|
|
775
788
|
result = true;
|
|
776
789
|
} else {
|
|
@@ -835,6 +848,13 @@ class ValueSetChecker {
|
|
|
835
848
|
}
|
|
836
849
|
}
|
|
837
850
|
}
|
|
851
|
+
if (result !== true) {
|
|
852
|
+
for (const r of reasons) {
|
|
853
|
+
if (!messages.includes(r)) {
|
|
854
|
+
messages.push(r);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
838
858
|
} else if (Extensions.checkNoModifiers(this.valueSet.jsonObj.expansion, 'ValueSetChecker.prepare', 'ValueSet.expansion', this.valueSet.vurl)) {
|
|
839
859
|
let ccc = this.valueSet.findContains(system, version, code);
|
|
840
860
|
if (ccc === null) {
|
|
@@ -989,13 +1009,11 @@ class ValueSetChecker {
|
|
|
989
1009
|
let i = 0;
|
|
990
1010
|
let impliedSystem = { value: '' };
|
|
991
1011
|
for (let c of code.coding || []) {
|
|
992
|
-
if (this.worker.opContext.usageTracker) {
|
|
993
|
-
this.worker.opContext.usageTracker.seeConcept(c.system, c.code);
|
|
994
|
-
}
|
|
995
1012
|
let vsImpliedVersion = this.findVSVersionForSystem(c.system);
|
|
996
1013
|
const csd = await this.worker.findCodeSystem(c.system, vsImpliedVersion, this.params, ['complete', 'fragment'], false, true, false, false, this.worker.requiredSupplements);
|
|
997
1014
|
this.worker.seeSourceProvider(csd, c.system);
|
|
998
|
-
|
|
1015
|
+
// a turn for the event loop now and then, so a client that has gone is seen
|
|
1016
|
+
await this.worker.checkAndYield('check-b#1');
|
|
999
1017
|
let path;
|
|
1000
1018
|
if (issuePath === 'CodeableConcept') {
|
|
1001
1019
|
path = addToPath(issuePath, 'coding[' + i + ']');
|
|
@@ -1021,6 +1039,15 @@ class ValueSetChecker {
|
|
|
1021
1039
|
noCode = true;
|
|
1022
1040
|
break;
|
|
1023
1041
|
}
|
|
1042
|
+
// versionNeeded = true means a version must be specified whenever the code system is
|
|
1043
|
+
// referenced. That's a check on the coding itself: a version pinned by the value set or by
|
|
1044
|
+
// the parameters is specific to that context, and doesn't travel with the coding, so it
|
|
1045
|
+
// makes no difference here
|
|
1046
|
+
if (csd && !c.version && csd.versionNeeded()) {
|
|
1047
|
+
let m = this.worker.i18n.translate('CODESYSTEM_VERSION_NEEDED', this.params.HTTPLanguages, [csd.system()]);
|
|
1048
|
+
msg(m);
|
|
1049
|
+
op.addIssue(new Issue('warning', 'business-rule', addToPath(path, 'version'), 'CODESYSTEM_VERSION_NEEDED', m, 'invalid-data'));
|
|
1050
|
+
}
|
|
1024
1051
|
list.clear();
|
|
1025
1052
|
let ver = { value: '' };
|
|
1026
1053
|
let contentMode = { value: null };
|
|
@@ -1031,6 +1058,10 @@ class ValueSetChecker {
|
|
|
1031
1058
|
}
|
|
1032
1059
|
if (v === true) {
|
|
1033
1060
|
matchedCodings.add(i);
|
|
1061
|
+
// only codes that are valid: anything else is never in an expansion to count
|
|
1062
|
+
if (this.worker.opContext.usageTracker) {
|
|
1063
|
+
this.worker.opContext.usageTracker.seeConcept(c.system, c.code);
|
|
1064
|
+
}
|
|
1034
1065
|
}
|
|
1035
1066
|
let ws;
|
|
1036
1067
|
if (impliedSystem.value) {
|
|
@@ -1093,13 +1124,9 @@ class ValueSetChecker {
|
|
|
1093
1124
|
if (ver.value) {
|
|
1094
1125
|
pver = ver.value;
|
|
1095
1126
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
if (!pdisp) {
|
|
1101
|
-
pdisp = list.preferredDisplay(null);
|
|
1102
|
-
}
|
|
1127
|
+
// this coding's display, and no other: one from a coding before it that isn't in the
|
|
1128
|
+
// value set doesn't go with this code
|
|
1129
|
+
pdisp = list.preferredDisplay(this.params.workingLanguages()) || list.preferredDisplay(null);
|
|
1103
1130
|
}
|
|
1104
1131
|
} else if (!this.params.membershipOnly && ws) {
|
|
1105
1132
|
if (!isAbsoluteUrl(ws)) {
|
|
@@ -1218,14 +1245,19 @@ class ValueSetChecker {
|
|
|
1218
1245
|
vstatus.value = st;
|
|
1219
1246
|
}
|
|
1220
1247
|
}
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1248
|
+
// this coding isn't in the value set: its display is only reported if no coding is
|
|
1249
|
+
// (the display goes with the code that's reported - the first coding that's in the
|
|
1250
|
+
// value set - and a coding after that one mustn't replace it)
|
|
1251
|
+
if (pcode === undefined) {
|
|
1252
|
+
let pd = list.preferredDisplay(this.params.workingLanguages());
|
|
1253
|
+
if (pd) {
|
|
1254
|
+
pdisp = pd;
|
|
1255
|
+
}
|
|
1256
|
+
if (!pdisp) {
|
|
1257
|
+
pdisp = list.preferredDisplay(null);
|
|
1258
|
+
}
|
|
1227
1259
|
}
|
|
1228
|
-
let severity = this.dispWarning();
|
|
1260
|
+
let severity = list.extensionCodeDisplay === 'referenced' ? 'warning' : this.dispWarning();
|
|
1229
1261
|
if (c.display && list.designations.length > 0 && !list.hasDisplay(this.params.workingLanguages(), defLang.value, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE).found) {
|
|
1230
1262
|
let baseMsg;
|
|
1231
1263
|
if (list.hasDisplay(this.params.workingLanguages(), defLang.value, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE).difference === DisplayDifference.Normalized) {
|
|
@@ -1471,8 +1503,11 @@ class ValueSetChecker {
|
|
|
1471
1503
|
let hd = list.hasDisplay(this.params.workingLanguages(), null, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE)
|
|
1472
1504
|
if (!hd.found) {
|
|
1473
1505
|
let baseMsg;
|
|
1474
|
-
|
|
1475
|
-
|
|
1506
|
+
// a display an extension-codes supplement that is not global gives a user-defined code is not
|
|
1507
|
+
// the only correct one, so another display is a warning. One from a global supplement is as if
|
|
1508
|
+
// the code system defined it
|
|
1509
|
+
let severity = list.extensionCodeDisplay === 'referenced' ? 'warning' : this.dispWarning();
|
|
1510
|
+
if (list.userDefined && !list.extensionCodeDisplay) {
|
|
1476
1511
|
baseMsg = 'Display_Name_Not_Fixed_use_Supplement';
|
|
1477
1512
|
severity = 'information';
|
|
1478
1513
|
} else if (hd.difference === DisplayDifference.Normalized) {
|
|
@@ -1602,7 +1637,7 @@ class ValueSetChecker {
|
|
|
1602
1637
|
* code systems with a classification code have two spellings, so for the rest this is
|
|
1603
1638
|
* one call and false.
|
|
1604
1639
|
*/
|
|
1605
|
-
async sameConceptDifferentForm(cs, code, enumerated) {
|
|
1640
|
+
async sameConceptDifferentForm(cs, code, enumerated, prefetched = null) {
|
|
1606
1641
|
if (code === enumerated) {
|
|
1607
1642
|
return false;
|
|
1608
1643
|
}
|
|
@@ -1610,10 +1645,36 @@ class ValueSetChecker {
|
|
|
1610
1645
|
if (!a.context || !(await cs.classificationCode(a.context))) {
|
|
1611
1646
|
return false;
|
|
1612
1647
|
}
|
|
1613
|
-
const b = await cs.locate(enumerated);
|
|
1648
|
+
const b = await cs.locate(enumerated, prefetched);
|
|
1614
1649
|
return !!b.context && (await cs.code(a.context)) === (await cs.code(b.context));
|
|
1615
1650
|
}
|
|
1616
1651
|
|
|
1652
|
+
/**
|
|
1653
|
+
* The listed concept that is the code written in its other form (ICD-11's short code and
|
|
1654
|
+
* entity id), or null. sameConceptDifferentForm asks two things of each listed concept: does
|
|
1655
|
+
* the code have another form at all (locate the code, ask for its classification code), and
|
|
1656
|
+
* is this listed concept that form. The first depends only on the code, so it is asked once
|
|
1657
|
+
* here, and the listed concepts are compared only when the answer is yes.
|
|
1658
|
+
*/
|
|
1659
|
+
async otherFormMatch(cs, code, concepts) {
|
|
1660
|
+
if (!concepts || concepts.length === 0 || concepts.some(cc => cc.code === code)) {
|
|
1661
|
+
return null;
|
|
1662
|
+
}
|
|
1663
|
+
const a = await cs.locate(code);
|
|
1664
|
+
if (!a.context || !(await cs.classificationCode(a.context))) {
|
|
1665
|
+
return null;
|
|
1666
|
+
}
|
|
1667
|
+
// each listed code is located next: a provider may read them together, and hand back what it read
|
|
1668
|
+
const prefetched = await cs.prefetchCodes(concepts.map(cc => cc.code), false);
|
|
1669
|
+
for (const cc of concepts) {
|
|
1670
|
+
this.worker.deadCheck('checkConceptSet#3');
|
|
1671
|
+
if (await this.sameConceptDifferentForm(cs, code, cc.code, prefetched)) {
|
|
1672
|
+
return cc;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
return null;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1617
1678
|
async noteClassificationCode(cs, context, code, normalForm) {
|
|
1618
1679
|
// the code system decides whether this spelling of the code earns a classificationCode
|
|
1619
1680
|
// (ICD-11: when it was written with entity ids, in any order - not when it was written
|
|
@@ -1694,15 +1755,7 @@ class ValueSetChecker {
|
|
|
1694
1755
|
} else if (this.params.activeOnly && await cs.isInactive(loc.context)) {
|
|
1695
1756
|
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1696
1757
|
result = false;
|
|
1697
|
-
|
|
1698
|
-
inactive.value = true;
|
|
1699
|
-
inactive.path = path;
|
|
1700
|
-
inactive.code = code;
|
|
1701
|
-
vstatus.value = await cs.getStatus(loc.context);
|
|
1702
|
-
let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
1703
|
-
messages.push(msg);
|
|
1704
|
-
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
1705
|
-
}
|
|
1758
|
+
await this.#excludedByActiveOnly(cs, loc.context, code, path, role, op, messages, inactive, vstatus);
|
|
1706
1759
|
} else {
|
|
1707
1760
|
result = true;
|
|
1708
1761
|
if (role !== 'not in') {
|
|
@@ -1730,15 +1783,7 @@ class ValueSetChecker {
|
|
|
1730
1783
|
}
|
|
1731
1784
|
|
|
1732
1785
|
// an enumerated concept may be written in the other form (ICD-11 short code vs uri)
|
|
1733
|
-
|
|
1734
|
-
if (cset.concept && !cset.concept.some(cc => cc.code === code)) {
|
|
1735
|
-
for (const cc of cset.concept) {
|
|
1736
|
-
if (await this.sameConceptDifferentForm(cs, code, cc.code)) {
|
|
1737
|
-
altMatch = cc;
|
|
1738
|
-
break;
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
}
|
|
1786
|
+
const altMatch = await this.otherFormMatch(cs, code, cset.concept);
|
|
1742
1787
|
for (let cc of cset.concept || []) {
|
|
1743
1788
|
this.worker.deadCheck('checkConceptSet#1');
|
|
1744
1789
|
let c = cc.code;
|
|
@@ -1761,6 +1806,10 @@ class ValueSetChecker {
|
|
|
1761
1806
|
inactive.code = code;
|
|
1762
1807
|
vstatus.value = await cs.getStatus(loc);
|
|
1763
1808
|
}
|
|
1809
|
+
} else if (this.params.activeOnly && await cs.isInactive(loc)) {
|
|
1810
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1811
|
+
result = false;
|
|
1812
|
+
await this.#excludedByActiveOnly(cs, loc, code, path, role, op, messages, inactive, vstatus);
|
|
1764
1813
|
} else {
|
|
1765
1814
|
if (vcc !== null) {
|
|
1766
1815
|
if (!vcc.coding) {vcc.coding = [];}
|
|
@@ -1792,29 +1841,43 @@ class ValueSetChecker {
|
|
|
1792
1841
|
|
|
1793
1842
|
if (cset.filter) {
|
|
1794
1843
|
let cfl = cset.filter;
|
|
1795
|
-
let prep = await cs.getPrepContext(false);
|
|
1796
1844
|
for (let fc of cfl) {
|
|
1797
|
-
this.worker.deadCheck('checkConceptSet#2');
|
|
1798
1845
|
if (!fc.value) {
|
|
1799
1846
|
throw new Issue('error', 'invalid', null, 'UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE', this.worker.i18n.translate('UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE', this.params.HTTPLanguages, [cs.system(), fc.property, fc.op]));
|
|
1800
1847
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1848
|
+
}
|
|
1849
|
+
// filters on properties that supplements in use define are evaluated from the supplements;
|
|
1850
|
+
// the code system does the rest
|
|
1851
|
+
const { base: baseFilters, supplement: suppFilters } = await splitFilters(cs, cfl);
|
|
1852
|
+
let loc;
|
|
1853
|
+
if (baseFilters.length > 0) {
|
|
1854
|
+
let prep = await cs.getPrepContext(false, this.params.requestedLanguages());
|
|
1855
|
+
for (let fc of baseFilters) {
|
|
1856
|
+
this.worker.deadCheck('checkConceptSet#2');
|
|
1857
|
+
await cs.filter(prep, false, fc.property, fc.op, fc.value, (vs && vs.isCached) ? fc : null);
|
|
1858
|
+
}
|
|
1859
|
+
let filters = await cs.executeFilters(prep);
|
|
1860
|
+
if (!filters || filters.length == 0) {
|
|
1861
|
+
throw new Error("executing filters failed for " + cs.name());
|
|
1862
|
+
}
|
|
1863
|
+
loc = await cs.filterLocate(prep, filters[0], code);
|
|
1864
|
+
if (loc != null && !(typeof loc === 'string') && filters.length > 1) {
|
|
1865
|
+
for (let i = 1; i < filters.length; i++) {
|
|
1866
|
+
if ((await cs.filterCheck(prep, filters[i], loc)) != true) { // a failing check may return a message, which is truthy
|
|
1867
|
+
loc = null;
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1816
1870
|
}
|
|
1817
1871
|
}
|
|
1872
|
+
} else {
|
|
1873
|
+
const located = await cs.locate(code);
|
|
1874
|
+
loc = located ? located.context : null;
|
|
1875
|
+
}
|
|
1876
|
+
if (loc != null && !(typeof loc === 'string') && suppFilters.length > 0) {
|
|
1877
|
+
const lcode = await cs.code(loc);
|
|
1878
|
+
if (!suppFilters.every(sf => sf.matches(lcode))) {
|
|
1879
|
+
loc = null;
|
|
1880
|
+
}
|
|
1818
1881
|
}
|
|
1819
1882
|
if (loc != null && !(typeof loc === 'string')) {
|
|
1820
1883
|
await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
|
|
@@ -1832,6 +1895,10 @@ class ValueSetChecker {
|
|
|
1832
1895
|
inactive.code = code;
|
|
1833
1896
|
vstatus.value = await cs.getStatus(loc);
|
|
1834
1897
|
}
|
|
1898
|
+
} else if (this.params.activeOnly && await cs.isInactive(loc)) {
|
|
1899
|
+
result = false;
|
|
1900
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1901
|
+
await this.#excludedByActiveOnly(cs, loc, code, path, role, op, messages, inactive, vstatus);
|
|
1835
1902
|
} else {
|
|
1836
1903
|
this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1837
1904
|
if (vcc !== null) {
|
|
@@ -1850,6 +1917,13 @@ class ValueSetChecker {
|
|
|
1850
1917
|
if (st) {
|
|
1851
1918
|
vstatus.value = st;
|
|
1852
1919
|
}
|
|
1920
|
+
} else if (!vstatus.value) {
|
|
1921
|
+
// an active code can still have a status worth reporting (deprecated), as it is
|
|
1922
|
+
// when the value set lists the code
|
|
1923
|
+
const st = await cs.getStatus(loc);
|
|
1924
|
+
if (st) {
|
|
1925
|
+
vstatus.value = st;
|
|
1926
|
+
}
|
|
1853
1927
|
}
|
|
1854
1928
|
}
|
|
1855
1929
|
if (role !== 'not in') {
|
|
@@ -1860,7 +1934,10 @@ class ValueSetChecker {
|
|
|
1860
1934
|
}
|
|
1861
1935
|
} else if (loc != null) {
|
|
1862
1936
|
this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+ ": "+loc, this.indentCount);
|
|
1863
|
-
|
|
1937
|
+
// why it's not in an include (see check()); an exclude that doesn't have the code is no news
|
|
1938
|
+
if (messages && role !== 'not in') {
|
|
1939
|
+
messages.push(loc);
|
|
1940
|
+
}
|
|
1864
1941
|
} else {
|
|
1865
1942
|
this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1866
1943
|
}
|
|
@@ -1896,6 +1973,27 @@ class ValueSetChecker {
|
|
|
1896
1973
|
}
|
|
1897
1974
|
|
|
1898
1975
|
|
|
1976
|
+
/**
|
|
1977
|
+
* activeOnly: the code is checked against the value set as expanded with activeOnly, which has no
|
|
1978
|
+
* inactive codes - whether the value set includes all of the code system, lists the code or
|
|
1979
|
+
* selects it with a filter - so an inactive code is not in it
|
|
1980
|
+
*/
|
|
1981
|
+
async #excludedByActiveOnly(cs, loc, code, path, role, op, messages, inactive, vstatus) {
|
|
1982
|
+
if (role === 'not in') {
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
inactive.value = true;
|
|
1986
|
+
inactive.path = path;
|
|
1987
|
+
inactive.code = code;
|
|
1988
|
+
const st = await cs.getStatus(loc);
|
|
1989
|
+
if (st) {
|
|
1990
|
+
vstatus.value = st;
|
|
1991
|
+
}
|
|
1992
|
+
const msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
1993
|
+
messages.push(msg);
|
|
1994
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1899
1997
|
excludeInactives() {
|
|
1900
1998
|
return this.valueSet.jsonObj.compose && this.valueSet.jsonObj.compose.inactive != undefined && !this.valueSet.jsonObj.compose.inactive;
|
|
1901
1999
|
}
|
|
@@ -2045,13 +2143,12 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2045
2143
|
this.log.error(error);
|
|
2046
2144
|
debugLog(error);
|
|
2047
2145
|
if (error instanceof Issue) {
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
}
|
|
2146
|
+
// Issues raised inside the validation are turned into a result by the inner method;
|
|
2147
|
+
// one that reaches here (e.g. cache-id-unknown from setupAdditionalResources, before
|
|
2148
|
+
// the inner try) must still be answered, or the request never completes.
|
|
2149
|
+
let oo = new OperationOutcome();
|
|
2150
|
+
oo.addIssue(error);
|
|
2151
|
+
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
2055
2152
|
} else {
|
|
2056
2153
|
return res.status(error.statusCode || 500).json(outcomeFromError(error));
|
|
2057
2154
|
}
|
|
@@ -2326,6 +2423,8 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2326
2423
|
|
|
2327
2424
|
let csp = await this.provider.getCodeSystemProvider(this.opContext, url, version, supplements);
|
|
2328
2425
|
if (csp) {
|
|
2426
|
+
// released at the end of the operation, like the providers findCodeSystem() returns
|
|
2427
|
+
this.opContext.registerProvider(csp);
|
|
2329
2428
|
return csp;
|
|
2330
2429
|
} else {
|
|
2331
2430
|
let vs = await this.findValueSet(url, version, null);
|
|
@@ -2371,7 +2470,12 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2371
2470
|
return fromAdditional;
|
|
2372
2471
|
}
|
|
2373
2472
|
|
|
2374
|
-
|
|
2473
|
+
// an implicit value set in the generic form is drawn from the code system version the
|
|
2474
|
+
// request asks for (system-version and the like)
|
|
2475
|
+
const systemVersionFor = txParams && txParams.requestedSystemVersion
|
|
2476
|
+
? (system) => txParams.requestedSystemVersion(system)
|
|
2477
|
+
: null;
|
|
2478
|
+
let vs = await this.provider.findValueSet(this.opContext, url, version, systemVersionFor);
|
|
2375
2479
|
this.seeSourceVS(vs, url);
|
|
2376
2480
|
if (vs == null) {
|
|
2377
2481
|
throw new Issue('error', 'not-found', null, 'Unable_to_resolve_value_Set_', this.i18n.translate('Unable_to_resolve_value_Set_', params.HTTPLanguages, [url+(version ? "|"+version : "")]), 'not-found', 422);
|