fhirsmith 0.3.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/CHANGELOG.md +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
|
@@ -0,0 +1,2504 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Validate Worker - Handles $validate-code operations
|
|
3
|
+
//
|
|
4
|
+
// GET /CodeSystem/$validate-code?{params}
|
|
5
|
+
// POST /CodeSystem/$validate-code
|
|
6
|
+
// GET /CodeSystem/{id}/$validate-code?{params}
|
|
7
|
+
// POST /CodeSystem/{id}/$validate-code
|
|
8
|
+
// GET /ValueSet/$validate-code?{params}
|
|
9
|
+
// POST /ValueSet/$validate-code
|
|
10
|
+
// GET /ValueSet/{id}/$validate-code?{params}
|
|
11
|
+
// POST /ValueSet/{id}/$validate-code
|
|
12
|
+
//
|
|
13
|
+
|
|
14
|
+
const { TerminologyWorker } = require('./worker');
|
|
15
|
+
const {Languages, Language} = require("../../library/languages");
|
|
16
|
+
const {Extensions} = require("../library/extensions");
|
|
17
|
+
const {validateParameter, isAbsoluteUrl, validateOptionalParameter, getValuePrimitive} = require("../../library/utilities");
|
|
18
|
+
const {TxParameters} = require("../params");
|
|
19
|
+
const {OperationOutcome, Issue} = require("../library/operation-outcome");
|
|
20
|
+
const {Parameters} = require("../library/parameters");
|
|
21
|
+
const {Designations, DisplayCheckingStyle, DisplayDifference, SearchFilterText} = require("../library/designations");
|
|
22
|
+
const ValueSet = require("../library/valueset");
|
|
23
|
+
const {ValueSetExpander} = require("./expand");
|
|
24
|
+
const {FhirCodeSystemProvider} = require("../cs/cs-cs");
|
|
25
|
+
const {CodeSystem} = require("../library/codesystem");
|
|
26
|
+
|
|
27
|
+
const DEV_IGNORE_VALUESET = false; // todo: what's going on with this (ported from pascal)
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validation check mode - affects how errors are reported
|
|
31
|
+
*/
|
|
32
|
+
const ValidationCheckMode = {
|
|
33
|
+
Code: 'code', // Just code string, infer system
|
|
34
|
+
Coding: 'coding', // Single coding with system/code
|
|
35
|
+
CodeableConcept: 'codeableConcept' // Multiple codings, any match is success
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Value Set Checker - performs validation against a ValueSet
|
|
40
|
+
* Port of TValueSetChecker from Pascal
|
|
41
|
+
*/
|
|
42
|
+
class ValueSetChecker {
|
|
43
|
+
worker;
|
|
44
|
+
valueSet;
|
|
45
|
+
params;
|
|
46
|
+
requiredSupplements = [];
|
|
47
|
+
others = new Map();
|
|
48
|
+
|
|
49
|
+
constructor(worker, valueSet, params) {
|
|
50
|
+
validateParameter(worker, "worker", TerminologyWorker);
|
|
51
|
+
validateOptionalParameter(valueSet, "valueSet", ValueSet);
|
|
52
|
+
validateParameter(params, "params", TxParameters);
|
|
53
|
+
this.worker = worker;
|
|
54
|
+
this.valueSet = valueSet;
|
|
55
|
+
this.params = params;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
checkCanonicalStatus(path, op, resource, source) {
|
|
59
|
+
if (resource.jsonObj) {
|
|
60
|
+
resource = resource.jsonObj;
|
|
61
|
+
}
|
|
62
|
+
this.checkCanonicalStatusFull(path, op, resource.resourceType, this.worker.makeVurl(resource), resource.SourcePackage, resource.status, Extensions.readString(resource, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status'), resource.experimental, source);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async checkCanonicalStatusCS(path, op, cs, source) {
|
|
66
|
+
let status = await cs.status();
|
|
67
|
+
if (cs.version()) {
|
|
68
|
+
this.checkCanonicalStatusFull(path, op, 'CodeSystem', cs.system() + '|' + cs.version(), cs.sourcePackage(), status.status, status.standardsStatus, status.experimental, source);
|
|
69
|
+
} else {
|
|
70
|
+
this.checkCanonicalStatusFull(path, op, 'CodeSystem', cs.system(), cs.sourcePackage(), status.status, status.standardsStatus, status.experimental, source);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
checkCanonicalStatusFull(path, op, rtype, vurl, pid, status, standardsStatus, experimental, source) {
|
|
75
|
+
if (op !== null) {
|
|
76
|
+
if (standardsStatus === 'deprecated') {
|
|
77
|
+
op.addIssue(new Issue('information', 'business-rule', '', 'MSG_DEPRECATED', this.worker.i18n.translate('MSG_DEPRECATED', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
|
|
78
|
+
} else if (standardsStatus === 'withdrawn') {
|
|
79
|
+
op.addIssue(new Issue('information', 'business-rule', '', 'MSG_WITHDRAWN', this.worker.i18n.translate('MSG_WITHDRAWN', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
|
|
80
|
+
} else if (status === 'retired') {
|
|
81
|
+
op.addIssue(new Issue('information', 'business-rule', '', 'MSG_RETIRED', this.worker.i18n.translate('MSG_RETIRED', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
|
|
82
|
+
} else if (source !== null) {
|
|
83
|
+
if (experimental && !source.experimental) {
|
|
84
|
+
op.addIssue(new Issue('information', 'business-rule', '', 'MSG_EXPERIMENTAL', this.worker.i18n.translate('MSG_EXPERIMENTAL', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
|
|
85
|
+
} else if ((status === 'draft' || standardsStatus === 'draft') &&
|
|
86
|
+
!((source.status === 'draft') || (Extensions.readString(source, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status') === 'draft'))) {
|
|
87
|
+
if (pid) {
|
|
88
|
+
op.addIssue(new Issue('information', 'business-rule', '', 'MSG_DRAFT', this.worker.i18n.translate('MSG_DRAFT_SRC', this.params.HTTPLanguages, [vurl, pid, rtype]), 'status-check'), false);
|
|
89
|
+
} else {
|
|
90
|
+
op.addIssue(new Issue('information', 'business-rule', '', 'MSG_DRAFT', this.worker.i18n.translate('MSG_DRAFT', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
dispWarning() {
|
|
98
|
+
if (this.params.displayWarning) {
|
|
99
|
+
return 'warning';
|
|
100
|
+
} else {
|
|
101
|
+
return 'error';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async determineSystemFromExpansion(code, systems) {
|
|
106
|
+
let result;
|
|
107
|
+
try {
|
|
108
|
+
let txpe = this.params.clone();
|
|
109
|
+
txpe.limit = 10000;
|
|
110
|
+
let exp = new ValueSetExpander(this.worker, txpe);
|
|
111
|
+
let vse = await exp.expand(this.valueSet, new SearchFilterText(''), true);
|
|
112
|
+
result = '';
|
|
113
|
+
for (let c of vse.expansion.contains || []) {
|
|
114
|
+
this.worker.deadCheck('determineSystemFromExpansion');
|
|
115
|
+
if (c.code === code) {
|
|
116
|
+
systems.add(c.system);
|
|
117
|
+
if (!result) {
|
|
118
|
+
result = c.system;
|
|
119
|
+
} else {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error(e);
|
|
126
|
+
throw new Error('Exception expanding value set in order to infer system: ' + e.message);
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fixedSystemFromValueSet() {
|
|
132
|
+
if (this.valueSet === null) {
|
|
133
|
+
return '';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
let result = '';
|
|
137
|
+
for (let c of this.valueSet.jsonObj.include) {
|
|
138
|
+
this.worker.deadCheck('fixedSystemFromValueSet');
|
|
139
|
+
if (c.hasValueSets || !c.system) {
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
if (!result) {
|
|
143
|
+
result = c.system;
|
|
144
|
+
} else if (result !== c.system) {
|
|
145
|
+
return '';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async determineSystem(opContext, code, systems, op) {
|
|
152
|
+
let result = '';
|
|
153
|
+
let needDoExpansion = false;
|
|
154
|
+
|
|
155
|
+
for (let vsi of this.valueSet.jsonObj.compose.exclude || []) {
|
|
156
|
+
if (vsi.valueSet || !vsi.system || vsi.filter) {
|
|
157
|
+
needDoExpansion = true;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (let vsi of this.valueSet.jsonObj.compose.include || []) {
|
|
161
|
+
if (vsi.valueSet || !vsi.system || vsi.filter) {
|
|
162
|
+
needDoExpansion = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (needDoExpansion) {
|
|
167
|
+
result = await this.determineSystemFromExpansion(code, systems);
|
|
168
|
+
} else {
|
|
169
|
+
for (let vsi of this.valueSet.jsonObj.compose.include) {
|
|
170
|
+
this.worker.deadCheck('determineSystem');
|
|
171
|
+
let cs = await this.worker.findCodeSystem(vsi.system, '', null, ['complete', 'fragment'], op,true);
|
|
172
|
+
if (cs === null) {
|
|
173
|
+
return '';
|
|
174
|
+
}
|
|
175
|
+
if (vsi.concept) {
|
|
176
|
+
for (let cc of vsi.concept) {
|
|
177
|
+
this.worker.deadCheck('determineSystem#2');
|
|
178
|
+
let match = cc.code === code;
|
|
179
|
+
if (match) {
|
|
180
|
+
systems.add(vsi.system);
|
|
181
|
+
if (!result) {
|
|
182
|
+
result = vsi.system;
|
|
183
|
+
} else if (result !== vsi.system) {
|
|
184
|
+
return '';
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
let loc = await cs.locate(code);
|
|
190
|
+
if (loc.context !== null) {
|
|
191
|
+
systems.add(vsi.system);
|
|
192
|
+
if (!result) {
|
|
193
|
+
result = vsi.system;
|
|
194
|
+
} else if (result !== vsi.system) {
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async determineVersion(path, system, versionVS, versionCoding, op, unknownSystems, messages) {
|
|
205
|
+
validateParameter(path, "path", String);
|
|
206
|
+
validateParameter(system, "system", String);
|
|
207
|
+
validateOptionalParameter(versionVS, "versionVS", String);
|
|
208
|
+
validateOptionalParameter(versionCoding, "versionCoding", String);
|
|
209
|
+
validateParameter(op, "op", OperationOutcome);
|
|
210
|
+
validateParameter(unknownSystems, "unknownSystems", Set);
|
|
211
|
+
validateParameter(messages, "messages", Array);
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
let result;
|
|
215
|
+
let csa = await this.worker.findCodeSystem(system, '', this.params, "*", op,true, false, true);
|
|
216
|
+
|
|
217
|
+
result = this.worker.determineVersionBase(system, versionVS, this.params);
|
|
218
|
+
|
|
219
|
+
if (versionCoding) {
|
|
220
|
+
if (result) {
|
|
221
|
+
if (csa !== null) {
|
|
222
|
+
if (versionCoding != result && csa.versionIsMoreDetailed(result, versionCoding)) {
|
|
223
|
+
result = versionCoding;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!result) {
|
|
228
|
+
let vl = await this.worker.listVersions(system);
|
|
229
|
+
if (vl.find(v => v == versionCoding || (csa && csa.versionIsMoreDetailed(versionCoding, v)))) {
|
|
230
|
+
result = versionCoding;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let cs = await this.worker.findCodeSystem(system, result, this.params, ['complete', 'fragment'], op,true, false, false);
|
|
235
|
+
if (cs !== null && cs.version() !== versionCoding && !cs.versionIsMoreDetailed(versionCoding, cs.version())) {
|
|
236
|
+
let errLvl = 'error';
|
|
237
|
+
let msg, mid;
|
|
238
|
+
if (!result) {
|
|
239
|
+
if (!cs.versionNeeded()) {
|
|
240
|
+
errLvl = 'warning';
|
|
241
|
+
}
|
|
242
|
+
msg = this.worker.i18n.translate('VALUESET_VALUE_MISMATCH_DEFAULT', this.params.HTTPLanguages, [system, cs.version(), versionVS, versionCoding]);
|
|
243
|
+
mid = 'VALUESET_VALUE_MISMATCH_DEFAULT';
|
|
244
|
+
} else if (result !== versionVS) {
|
|
245
|
+
msg = this.worker.i18n.translate('VALUESET_VALUE_MISMATCH_CHANGED', this.params.HTTPLanguages, [system, result, versionVS ? versionVS : "", versionCoding]);
|
|
246
|
+
mid = 'VALUESET_VALUE_MISMATCH_CHANGED';
|
|
247
|
+
} else {
|
|
248
|
+
msg = this.worker.i18n.translate('VALUESET_VALUE_MISMATCH', this.params.HTTPLanguages, [system, versionVS, versionCoding]);
|
|
249
|
+
mid = 'VALUESET_VALUE_MISMATCH';
|
|
250
|
+
}
|
|
251
|
+
op.addIssue(new Issue(errLvl, 'invalid', addToPath(path, 'version'), mid, msg, 'vs-invalid'));
|
|
252
|
+
if (errLvl === 'error') {
|
|
253
|
+
messages.push(msg);
|
|
254
|
+
}
|
|
255
|
+
let cs2 = await this.worker.findCodeSystem(system, versionCoding, this.params, ['complete', 'fragment'], op, true, false, true);
|
|
256
|
+
if (cs2 !== null) {
|
|
257
|
+
cs2 = null;
|
|
258
|
+
} else {
|
|
259
|
+
let vl = await this.worker.listVersions(system);
|
|
260
|
+
unknownSystems.add(system + '|' + versionCoding);
|
|
261
|
+
msg = this.worker.i18n.translate('UNKNOWN_CODESYSTEM_VERSION', this.params.HTTPLanguages, [system, versionCoding, this.worker.presentVersionList(vl)]);
|
|
262
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'system'), 'UNKNOWN_CODESYSTEM_VERSION', msg, 'not-found'));
|
|
263
|
+
messages.push(msg);
|
|
264
|
+
}
|
|
265
|
+
} else if (cs === null && result !== versionCoding) {
|
|
266
|
+
let msg = this.worker.i18n.translate('VALUESET_VALUE_MISMATCH', this.params.HTTPLanguages, [system, result, versionCoding]);
|
|
267
|
+
op.addIssue(new Issue('error', 'invalid', addToPath(path, 'version'), 'VALUESET_VALUE_MISMATCH', msg, 'vs-invalid'));
|
|
268
|
+
messages.push(msg);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
seeValueSet() {
|
|
275
|
+
this.worker.opContext.seeContext(this.valueSet.vurl);
|
|
276
|
+
if (this.valueSet.jsonObj.compose && this.valueSet.jsonObj.compose.extension) {
|
|
277
|
+
for (let ext of this.valueSet.jsonObj.compose.extension) {
|
|
278
|
+
if (ext.url === 'http://hl7.org/fhir/StructureDefinition/valueset-expansion-parameter' || ext.url === 'http://hl7.org/fhir/tools/StructureDefinition/valueset-expansion-parameter') {
|
|
279
|
+
let n = Extensions.readString(ext, 'name');
|
|
280
|
+
let v = Extensions.readValue(ext, 'value');
|
|
281
|
+
this.params.seeParameter(n, v, false);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!this.params.HTTPLanguages && this.valueSet.jsonObj.language) {
|
|
286
|
+
this.params.HTTPLanguages = Languages.fromAcceptLanguage(this.valueSet.jsonObj.language, this.worker.languages, false);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async prepare() {
|
|
291
|
+
if (this.valueSet === null) {
|
|
292
|
+
throw new Issue('error', 'not-found', null, null, 'Error Error: vs = nil');
|
|
293
|
+
} else {
|
|
294
|
+
this.seeValueSet();
|
|
295
|
+
this.worker.opContext.addNote(this.valueSet, 'Analysing ' + this.valueSet.vurl + ' for validation purposes', this.indentCount);
|
|
296
|
+
if (this.indentCount === 0) {
|
|
297
|
+
this.worker.opContext.addNote(this.valueSet, 'Parameters: ' + this.params.summary, this.indentCount);
|
|
298
|
+
let vrs = this.params.verSummary;
|
|
299
|
+
if (vrs) {
|
|
300
|
+
this.worker.opContext.addNote(this.valueSet, 'Version Rules: ' + vrs, this.indentCount);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
this.requiredSupplements = [];
|
|
304
|
+
for (let ext of Extensions.list(this.valueSet.jsonObj, 'http://hl7.org/fhir/StructureDefinition/valueset-supplement')) {
|
|
305
|
+
this.requiredSupplements.push(getValuePrimitive(ext));
|
|
306
|
+
}
|
|
307
|
+
if (this.requiredSupplements.length > 0) {
|
|
308
|
+
await this.checkSupplementsExist(this.valueSet);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Extensions.checkNoImplicitRules(this.valueSet, 'ValueSetChecker.prepare', 'ValueSet');
|
|
312
|
+
Extensions.checkNoModifiers(this.valueSet, 'ValueSetChecker.prepare', 'ValueSet');
|
|
313
|
+
|
|
314
|
+
this.allValueSet = this.valueSet.url === 'http://hl7.org/fhir/ValueSet/@all';
|
|
315
|
+
|
|
316
|
+
if (this.valueSet.jsonObj.compose) {
|
|
317
|
+
Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose');
|
|
318
|
+
let i = 0;
|
|
319
|
+
for (let cc of this.valueSet.jsonObj.compose.include || []) {
|
|
320
|
+
await this.prepareConceptSet('include['+i+']', cc);
|
|
321
|
+
i++;
|
|
322
|
+
}
|
|
323
|
+
i = 0;
|
|
324
|
+
for (let cc of this.valueSet.jsonObj.compose.exclude || []) {
|
|
325
|
+
await this.prepareConceptSet('exclude['+i+']', cc);
|
|
326
|
+
i++;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (this.requiredSupplements.length > 0) {
|
|
331
|
+
throw new Issue('error', 'not-found', null, 'VALUESET_SUPPLEMENT_MISSING', this.worker.i18n.translatePlural(this.requiredSupplements.length, 'VALUESET_SUPPLEMENT_MISSING', this.params.HTTPLanguages, [this.requiredSupplements.join(',')])).handleAsOO(400);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async prepareConceptSet(desc, cc) {
|
|
336
|
+
this.worker.deadCheck('prepareConceptSet');
|
|
337
|
+
Extensions.checkNoModifiers(cc, 'ValueSetChecker.prepare', desc);
|
|
338
|
+
this.worker.opContext.addNote(this.valueSet, 'Prepare ' + desc + ': "' + this.worker.renderer.displayValueSetInclude(cc) + '"', this.indentCount);
|
|
339
|
+
if (cc.valueSet) {
|
|
340
|
+
for (let u of cc.valueSet) {
|
|
341
|
+
let s = this.worker.pinValueSet(u);
|
|
342
|
+
this.worker.deadCheck('prepareConceptSet');
|
|
343
|
+
if (!this.others.has(s)) {
|
|
344
|
+
let other = await this.worker.findValueSet(s, '');
|
|
345
|
+
if (other === null) {
|
|
346
|
+
throw new Issue('error', 'not-found', null, 'Unable_to_resolve_value_Set_', this.worker.i18n.translate('Unable_to_resolve_value_Set_', this.params.HTTPLanguages, [s]), 'not-found');
|
|
347
|
+
}
|
|
348
|
+
let checker = new ValueSetChecker(this.worker, other, this.params);
|
|
349
|
+
checker.indentCount = this.indentCount + 1;
|
|
350
|
+
await checker.prepare(other, this.params, null);
|
|
351
|
+
this.others.set(s, checker);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
let v = this.worker.determineVersionBase(cc.system, cc.version, this.params);
|
|
356
|
+
let cs = await this.worker.findCodeSystem(cc.system, v, this.params, ['complete', 'fragment'], null, true, false, false);
|
|
357
|
+
if (cs !== null) {
|
|
358
|
+
this.worker.opContext.addNote(this.valueSet, 'CodeSystem found: "' + this.worker.renderer.displayCoded(cs) + '"', this.indentCount);
|
|
359
|
+
for (let i = this.requiredSupplements.length - 1; i >= 0; i--) {
|
|
360
|
+
if (cs.hasSupplement(this.requiredSupplements[i])) {
|
|
361
|
+
this.requiredSupplements.splice(i, 1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
let i = 0;
|
|
365
|
+
for (let ccf of cc.filter || []) {
|
|
366
|
+
this.worker.deadCheck('prepareConceptSet#2');
|
|
367
|
+
Extensions.checkNoModifiers(ccf, 'ValueSetChecker.prepare', desc + '.filter');
|
|
368
|
+
if (!ccf.value) {
|
|
369
|
+
throw new Issue('error', 'invalid', "ValueSet.compose."+desc+".filter["+i+"]", 'UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE',
|
|
370
|
+
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);
|
|
371
|
+
}
|
|
372
|
+
if (!(ccf.property === 'concept' && ['is-a', 'descendent-of'].includes(ccf.op))) {
|
|
373
|
+
if (!(await cs.doesFilter(ccf.property, ccf.op, ccf.value))) {
|
|
374
|
+
throw new Issue('error', 'not-supported', "ValueSet.compose."+desc+".filter["+i+"]", 'FILTER_NOT_UNDERSTOOD', this.worker.i18n.translate('FILTER_NOT_UNDERSTOOD',
|
|
375
|
+
this.params.HTTPLanguages, [ccf.property, ccf.op, ccf.value, this.valueSet.url, cs.system]), "vs-invalid").handleAsOO(400);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
i++;
|
|
379
|
+
}
|
|
380
|
+
} else if (cc.system) {
|
|
381
|
+
this.worker.opContext.addNote(this.valueSet, 'CodeSystem version ' + v + ' not found: "' + this.worker.renderer.displayCoded(cc.system, cc.version) + '"', this.indentCount);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async findCode(cs, code, list, displays, isabstract) {
|
|
386
|
+
let result = false;
|
|
387
|
+
for (let i = 0; i < list.length; i++) {
|
|
388
|
+
this.worker.deadCheck('findCode');
|
|
389
|
+
if (code === list[i].code) {
|
|
390
|
+
result = true;
|
|
391
|
+
if (cs === null) {
|
|
392
|
+
isabstract.value = false;
|
|
393
|
+
} else {
|
|
394
|
+
isabstract.value = await cs.isAbstract(list[i]);
|
|
395
|
+
}
|
|
396
|
+
displays.baseLang = this.FLanguages.parse(cs.language);
|
|
397
|
+
!displays.addDesignation(true, "active", '', '', list[i].displayElement);
|
|
398
|
+
throw new Error("Check this");
|
|
399
|
+
// return result;
|
|
400
|
+
}
|
|
401
|
+
let ccl = list[i].conceptList;
|
|
402
|
+
if (await this.findCode(cs, code, ccl, displays, isabstract)) {
|
|
403
|
+
result = true;
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
getName() {
|
|
411
|
+
if (this.valueSet !== null) {
|
|
412
|
+
return this.valueSet.name;
|
|
413
|
+
} else {
|
|
414
|
+
return '??';
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async checkSimple(issuePath, system, version, code, abstractOk, inferSystem, op) {
|
|
419
|
+
this.worker.opContext.clearContexts();
|
|
420
|
+
if (inferSystem) {
|
|
421
|
+
this.worker.opContext.addNote(this.valueSet, 'Validate "' + code + '" and infer system', this.indentCount);
|
|
422
|
+
} else {
|
|
423
|
+
this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(system, version, code) + '"', this.indentCount);
|
|
424
|
+
}
|
|
425
|
+
let unknownSystems = new Set();
|
|
426
|
+
let ts = [];
|
|
427
|
+
let msgs = [];
|
|
428
|
+
let ver = {value: ''};
|
|
429
|
+
let inactive = {value: false};
|
|
430
|
+
let normalForm = {value: ''};
|
|
431
|
+
let vstatus = {value: ''};
|
|
432
|
+
let it = {value: null};
|
|
433
|
+
let contentMode = {value: null};
|
|
434
|
+
let impliedSystem = {value: ''};
|
|
435
|
+
let defLang = {value: null};
|
|
436
|
+
return await this.check(issuePath, system, version, code, abstractOk, inferSystem, null, unknownSystems, ver, inactive, normalForm, vstatus, it, op, null, null, contentMode, impliedSystem, ts, msgs, defLang);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async check(path, system, version, code, abstractOk, inferSystem, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, params, contentMode, impliedSystem, unkCodes, messages, defLang) {
|
|
440
|
+
defLang.value = new Language('en');
|
|
441
|
+
this.worker.opContext.addNote(this.valueSet, 'Check "' + this.worker.renderer.displayCoded(system, version, code) + '"', this.indentCount);
|
|
442
|
+
if (!system && !inferSystem) {
|
|
443
|
+
let msg = this.worker.i18n.translate('Coding_has_no_system__cannot_validate', this.params.HTTPLanguages, []);
|
|
444
|
+
messages.push(msg);
|
|
445
|
+
op.addIssue(new Issue('warning', 'invalid', path, 'Coding_has_no_system__cannot_validate', msg, 'invalid-data'));
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let result;
|
|
450
|
+
let s = this.valueSet.url;
|
|
451
|
+
if (s === 'http://hl7.org/fhir/ValueSet/@all') {
|
|
452
|
+
if (system) {
|
|
453
|
+
let msg = this.worker.i18n.translate('Coding_has_no_system__cannot_validate_NO_INFER', this.params.HTTPLanguages, []);
|
|
454
|
+
messages.push(msg);
|
|
455
|
+
op.addIssue(new Issue('warning', 'invalid', path, 'Coding_has_no_system__cannot_validate_NO_INFER', msg, 'invalid-data'));
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
458
|
+
let cs = await this.worker.findCodeSystem(system, version, this.params, ['complete', 'fragment'], op,true);
|
|
459
|
+
this.seeSourceProvider(cs, system);
|
|
460
|
+
if (cs === null) {
|
|
461
|
+
this.worker.opContext.addNote(this.valueSet, 'Didn\'t find CodeSystem "' + this.worker.renderer.displayCoded(system, version) + '"', this.indentCount);
|
|
462
|
+
result = null;
|
|
463
|
+
cause.value = 'not-found';
|
|
464
|
+
let vss = await this.worker.findValueSet(system, '');
|
|
465
|
+
if (vss !== null) {
|
|
466
|
+
vss = null;
|
|
467
|
+
let msg = this.worker.i18n.translate('Terminology_TX_System_ValueSet2', this.params.HTTPLanguages, [system]);
|
|
468
|
+
messages.push(msg);
|
|
469
|
+
op.addIssue(new Issue('error', 'invalid', addToPath(path, 'system'), 'Terminology_TX_System_ValueSet2', msg, 'invalid-data'));
|
|
470
|
+
unknownSystems.add(system);
|
|
471
|
+
} else {
|
|
472
|
+
let css = await this.worker.findCodeSystem(system, version, this.params, ['supplement'], op,true);
|
|
473
|
+
if (css !== null) {
|
|
474
|
+
vss = null;
|
|
475
|
+
let msg = this.worker.i18n.translate('CODESYSTEM_CS_NO_SUPPLEMENT', this.params.HTTPLanguages, [this.canonical(css.system(), css.version())]);
|
|
476
|
+
messages.push(msg);
|
|
477
|
+
op.addIssue(new Issue('error', 'invalid', addToPath(path, 'system'), 'CODESYSTEM_CS_NO_SUPPLEMENT', msg, 'invalid-data'));
|
|
478
|
+
unknownSystems.add(system);
|
|
479
|
+
} else if (version) {
|
|
480
|
+
let vl = await this.worker.listVersions(system);
|
|
481
|
+
let mid, vn;
|
|
482
|
+
if (vl.length == 0) {
|
|
483
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION_NONE';
|
|
484
|
+
vn = system;
|
|
485
|
+
} else {
|
|
486
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION';
|
|
487
|
+
vn = system + '|' + version;
|
|
488
|
+
}
|
|
489
|
+
let msg = this.worker.i18n.translate(mid, this.params.HTTPLanguages, [system, version, this.worker.presentVersionList(vl)]);
|
|
490
|
+
messages.push(msg);
|
|
491
|
+
if (!unknownSystems.has(vn)) {
|
|
492
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'system'), mid, msg, 'not-found'));
|
|
493
|
+
unknownSystems.add(vn);
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
let msg = this.worker.i18n.translate('UNKNOWN_CODESYSTEM', this.params.HTTPLanguages, [system]);
|
|
497
|
+
messages.push(msg);
|
|
498
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'system'), 'UNKNOWN_CODESYSTEM', msg, 'not-found'));
|
|
499
|
+
unknownSystems.add(system);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} else {
|
|
503
|
+
defLang.value = cs.defLang();
|
|
504
|
+
this.worker.opContext.addNote(this.valueSet, 'Using CodeSystem "' + this.worker.renderer.displayCoded(cs) + '" (content = ' + cs.contentMode() + ')', this.indentCount);
|
|
505
|
+
await this.checkCanonicalStatus(path, op, cs, this.valueSet);
|
|
506
|
+
ver.value = cs.version();
|
|
507
|
+
contentMode.value = cs.contentMode();
|
|
508
|
+
let ctxt = await cs.locate(code);
|
|
509
|
+
if (ctxt.context === null) {
|
|
510
|
+
unkCodes.push(cs.system() + '|' + cs.version + '#' + code);
|
|
511
|
+
if (cs.contentMode() !== 'complete') {
|
|
512
|
+
result = true;
|
|
513
|
+
cause.value = 'code-invalid';
|
|
514
|
+
this.worker.opContext.addNote(this.valueSet, 'Not found in Incomplete Code System', this.indentCount);
|
|
515
|
+
let msg = this.worker.i18n.translate('UNKNOWN_CODE_IN_FRAGMENT', this.params.HTTPLanguages, [code, cs.system(), cs.version()]);
|
|
516
|
+
// messages.push(msg); disabled 15-1-2026 GG - it's not considered invalid if it's just a warning
|
|
517
|
+
op.addIssue(new Issue('warning', 'code-invalid', addToPath(path, 'code'), 'UNKNOWN_CODE_IN_FRAGMENT', msg, 'invalid-code'));
|
|
518
|
+
} else {
|
|
519
|
+
result = false;
|
|
520
|
+
cause.value = 'code-invalid';
|
|
521
|
+
this.worker.opContext.addNote(this.valueSet, 'Unknown code', this.indentCount);
|
|
522
|
+
let msg = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]);
|
|
523
|
+
messages.push(msg);
|
|
524
|
+
op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version', msg, 'invalid-code'));
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
if (vcc !== null) {
|
|
528
|
+
vcc.addCoding(cs.system(), cs.version(), await cs.code(ctxt), cs.display(ctxt, this.params.workingLanguages()));
|
|
529
|
+
}
|
|
530
|
+
cause.value = 'null';
|
|
531
|
+
if (!(abstractOk || !(await cs.IsAbstract(ctxt)))) {
|
|
532
|
+
result = false;
|
|
533
|
+
this.worker.opContext.addNote(this.valueSet, 'Abstract code when not allowed', this.indentCount);
|
|
534
|
+
cause.value = 'business-rule';
|
|
535
|
+
let msg = this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [system, code]);
|
|
536
|
+
messages.push(msg);
|
|
537
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', msg, 'code-rule'));
|
|
538
|
+
} else if (this.params !== null && this.params.activeOnly && await cs.isInactive(ctxt)) {
|
|
539
|
+
result = false;
|
|
540
|
+
this.worker.opContext.addNote(this.valueSet, 'Inactive code when not allowed', this.indentCount);
|
|
541
|
+
cause.value = 'business-rule';
|
|
542
|
+
let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
543
|
+
messages.push(msg);
|
|
544
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
545
|
+
} else {
|
|
546
|
+
this.worker.opContext.addNote(this.valueSet, 'found OK', this.indentCount);
|
|
547
|
+
result = true;
|
|
548
|
+
if ((await cs.code(ctxt.context)) !== code) {
|
|
549
|
+
let msg = this.worker.i18n.translate('CODE_CASE_DIFFERENCE', this.params.HTTPLanguages, [code, await cs.code(ctxt), cs.system]);
|
|
550
|
+
messages.push(msg);
|
|
551
|
+
op.addIssue(new Issue('warning', 'business-rule', addToPath(path, 'code'), 'CODE_CASE_DIFFERENCE', msg, 'code-rule'));
|
|
552
|
+
}
|
|
553
|
+
let msg = await cs.incompleteValidationMessage(ctxt.context, this.params.HTTPLanguages);
|
|
554
|
+
if (msg) {
|
|
555
|
+
op.addIssueNoId('information', 'informational', addToPath(path, 'code'), msg, 'process-note');
|
|
556
|
+
}
|
|
557
|
+
inactive.value = await cs.isInactive(ctxt.context);
|
|
558
|
+
inactive.path = path;
|
|
559
|
+
vstatus.value = await cs.getStatus(ctxt.context);
|
|
560
|
+
}
|
|
561
|
+
if (displays !== null) {
|
|
562
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, ctxt.context);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
} else if (DEV_IGNORE_VALUESET) {
|
|
567
|
+
// anyhow, we ignore the value set (at least for now)
|
|
568
|
+
let cs = await this.worker.findCodeSystem(system, version, this.params, ['complete', 'fragment'], op, true, true, false);
|
|
569
|
+
if (cs === null) {
|
|
570
|
+
result = null;
|
|
571
|
+
cause.value = 'not-found';
|
|
572
|
+
this.worker.opContext.addNote(this.valueSet, 'Unknown code system', this.indentCount);
|
|
573
|
+
let vl, mid, vn;
|
|
574
|
+
if (version) {
|
|
575
|
+
vl = this.listVersions(system);
|
|
576
|
+
if (vl.length == 0) {
|
|
577
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION_NONE';
|
|
578
|
+
vn = system + '|' + version;
|
|
579
|
+
} else {
|
|
580
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION';
|
|
581
|
+
vn = system + '|' + version;
|
|
582
|
+
}
|
|
583
|
+
let msg = this.worker.i18n.translate(mid, this.params.HTTPLanguages, [system, version, this.worker.presentVersionList(vl)]);
|
|
584
|
+
messages.push(msg);
|
|
585
|
+
if (!unknownSystems.has(vn)) {
|
|
586
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'system'), mid, msg, 'not-found'));
|
|
587
|
+
unknownSystems.add(vn);
|
|
588
|
+
}
|
|
589
|
+
} else {
|
|
590
|
+
let msg = this.worker.i18n.translate('UNKNOWN_CODESYSTEM', this.params.HTTPLanguages, [system]);
|
|
591
|
+
messages.push(msg);
|
|
592
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'system'), 'UNKNOWN_CODESYSTEM', msg, 'not-found'));
|
|
593
|
+
unknownSystems.add(system);
|
|
594
|
+
}
|
|
595
|
+
} else {
|
|
596
|
+
defLang.value = cs.defLang();
|
|
597
|
+
this.checkCanonicalStatus(path, op, cs, this.valueSet);
|
|
598
|
+
ver.value = cs.version();
|
|
599
|
+
contentMode.value = cs.contentMode;
|
|
600
|
+
let ctxt = cs.locate(code);
|
|
601
|
+
if (ctxt.context === null) {
|
|
602
|
+
unkCodes.push(system + '|' + version + '#' + code);
|
|
603
|
+
if (cs.contentMode !== 'complete') {
|
|
604
|
+
result = true;
|
|
605
|
+
cause.value = 'code-invalid';
|
|
606
|
+
this.worker.opContext.addNote(this.valueSet, 'Not found in Incomplete Code System', this.indentCount);
|
|
607
|
+
let msg = this.worker.i18n.translate('UNKNOWN_CODE_IN_FRAGMENT', this.params.HTTPLanguages, [code, system, version]);
|
|
608
|
+
// messages.push(msg); it's just a warning
|
|
609
|
+
op.addIssue(new Issue('warning', 'code-invalid', addToPath(path, 'code'), 'UNKNOWN_CODE_IN_FRAGMENT', msg, 'invalid-code'));
|
|
610
|
+
} else {
|
|
611
|
+
result = false;
|
|
612
|
+
cause.value = 'code-invalid';
|
|
613
|
+
this.worker.opContext.addNote(this.valueSet, 'Unknown code', this.indentCount);
|
|
614
|
+
let msg = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(system), this.params.HTTPLanguages, [code, system, version, SCTVersion(system, version)]);
|
|
615
|
+
messages.push(msg);
|
|
616
|
+
op.addIssue(new Issue('warning', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version', msg, 'invalid-code'));
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
ctxt = ctxt.context;
|
|
620
|
+
cause.value = 'null';
|
|
621
|
+
if (!(abstractOk || !cs.IsAbstract(ctxt))) {
|
|
622
|
+
result = false;
|
|
623
|
+
this.worker.opContext.addNote(this.valueSet, 'Abstract code when not allowed', this.indentCount);
|
|
624
|
+
cause.value = 'business-rule';
|
|
625
|
+
let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
626
|
+
messages.push(msg);
|
|
627
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
628
|
+
} else if (this.params !== null && this.params.activeOnly && await cs.isInactive(ctxt)) {
|
|
629
|
+
result = false;
|
|
630
|
+
this.worker.opContext.addNote(this.valueSet, 'Inactive code when not allowed', this.indentCount);
|
|
631
|
+
cause.value = 'business-rule';
|
|
632
|
+
let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
633
|
+
messages.push(msg);
|
|
634
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
635
|
+
} else {
|
|
636
|
+
this.worker.opContext.addNote(this.valueSet, 'found', this.indentCount);
|
|
637
|
+
result = true;
|
|
638
|
+
}
|
|
639
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, ctxt);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
if (!system && inferSystem) {
|
|
644
|
+
let systems = new Set();
|
|
645
|
+
system = await this.determineSystem(this.worker.opContext, code, systems, op);
|
|
646
|
+
if (!system) {
|
|
647
|
+
let msg;
|
|
648
|
+
if (systems.size > 1) {
|
|
649
|
+
msg = this.worker.i18n.translate('Unable_to_resolve_system__value_set_has_multiple_matches', this.params.HTTPLanguages, [code, this.valueSet.vurl, Array.from(systems).join(',')]);
|
|
650
|
+
messages.push(msg);
|
|
651
|
+
op.addIssue(new Issue('error', 'not-found', 'code', 'Unable_to_resolve_system__value_set_has_multiple_matches', msg, 'cannot-infer'));
|
|
652
|
+
} else {
|
|
653
|
+
msg = this.worker.i18n.translate('UNABLE_TO_INFER_CODESYSTEM', this.params.HTTPLanguages, [code, this.valueSet.vurl]);
|
|
654
|
+
messages.push(msg);
|
|
655
|
+
op.addIssue(new Issue('error', 'not-found', 'code', 'UNABLE_TO_INFER_CODESYSTEM', msg, 'cannot-infer'));
|
|
656
|
+
}
|
|
657
|
+
return false;
|
|
658
|
+
} else {
|
|
659
|
+
impliedSystem.value = system;
|
|
660
|
+
this.worker.opContext.addNote(this.valueSet, 'Inferred CodeSystem = "' + system + '"', this.indentCount);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (this.requiredSupplements.length > 0) {
|
|
665
|
+
throw new Issue('error', 'not-found', null, 'VALUESET_SUPPLEMENT_MISSING', this.worker.i18n.translatePlural(this.requiredSupplements.length, 'VALUESET_SUPPLEMENT_MISSING', this.params.HTTPLanguages, [this.requiredSupplements.join(',')])).handleAsOO(400);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose')) {
|
|
669
|
+
result = false;
|
|
670
|
+
for (let cc of this.valueSet.jsonObj.compose.include || []) {
|
|
671
|
+
this.worker.deadCheck('check#2');
|
|
672
|
+
if (!cc.system) {
|
|
673
|
+
result = true;
|
|
674
|
+
} else if (cc.system === system || system === '%%null%%') {
|
|
675
|
+
let v = await this.determineVersion(path, cc.system, cc.version, version, op, unknownSystems, messages);
|
|
676
|
+
let cs = await this.worker.findCodeSystem(system, v, this.params, ["complete", "fragment"], op,true, true, false);
|
|
677
|
+
if (cs === null) {
|
|
678
|
+
this.worker.opContext.addNote(this.valueSet, 'CodeSystem not found: ' + this.worker.renderer.displayCoded(cc.system, v), this.indentCount);
|
|
679
|
+
if (!this.params.membershipOnly) {
|
|
680
|
+
let bAdd = true;
|
|
681
|
+
let msg, mid, vn;
|
|
682
|
+
if (!v) {
|
|
683
|
+
msg = this.worker.i18n.translate('UNKNOWN_CODESYSTEM', this.params.HTTPLanguages, [system]);
|
|
684
|
+
unknownSystems.add(system);
|
|
685
|
+
mid = 'UNKNOWN_CODESYSTEM';
|
|
686
|
+
} else {
|
|
687
|
+
let vl = await this.worker.listVersions(system);
|
|
688
|
+
if (vl.length == 0) {
|
|
689
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION_NONE';
|
|
690
|
+
vn = system;
|
|
691
|
+
} else {
|
|
692
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION';
|
|
693
|
+
vn = system + '|' + v;
|
|
694
|
+
}
|
|
695
|
+
msg = this.worker.i18n.translate(mid, this.params.HTTPLanguages, [system, v, this.worker.presentVersionList(vl)]);
|
|
696
|
+
bAdd = !unknownSystems.has(vn);
|
|
697
|
+
if (bAdd) {
|
|
698
|
+
unknownSystems.add(vn);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
messages.push(msg);
|
|
702
|
+
if (bAdd) {
|
|
703
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'system'), mid, msg, 'not-found'));
|
|
704
|
+
}
|
|
705
|
+
return null;
|
|
706
|
+
} else {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
defLang.value = new Language(cs.defLang());
|
|
711
|
+
this.worker.opContext.addNote(this.valueSet, 'CodeSystem found: ' + this.worker.renderer.displayCoded(cs) + ' for ' + this.worker.renderer.displayCoded(cc.system, v), this.indentCount);
|
|
712
|
+
await this.checkCanonicalStatusCS(path, op, cs, this.valueSet);
|
|
713
|
+
ver.value = cs.version();
|
|
714
|
+
this.worker.checkSupplements(cs, cc, this.requiredSupplements);
|
|
715
|
+
contentMode.value = cs.contentMode();
|
|
716
|
+
|
|
717
|
+
let msg = '';
|
|
718
|
+
if ((system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'in', cs, cc, code, abstractOk, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc, messages)) {
|
|
719
|
+
result = true;
|
|
720
|
+
} else {
|
|
721
|
+
result = false;
|
|
722
|
+
}
|
|
723
|
+
if (msg) {
|
|
724
|
+
messages.push(msg);
|
|
725
|
+
}
|
|
726
|
+
} else {
|
|
727
|
+
result = false;
|
|
728
|
+
}
|
|
729
|
+
for (let u of cc.valueSet || []) {
|
|
730
|
+
this.worker.deadCheck('check#3');
|
|
731
|
+
let s = this.worker.pinValueSet(u);
|
|
732
|
+
this.worker.opContext.addNote(this.valueSet, 'Check included value set ' + s, this.indentCount);
|
|
733
|
+
let checker = this.others.get(s);
|
|
734
|
+
if (checker === null || checker === undefined) {
|
|
735
|
+
throw new Issue('error', 'unknown', null, null, 'No Match for ' + s + ' in ' + Array.from(this.others.keys()).join(','));
|
|
736
|
+
}
|
|
737
|
+
this.checkCanonicalStatus(path, op, checker.valueSet, this.valueSet);
|
|
738
|
+
if (result === true) {
|
|
739
|
+
result = await checker.check(path, system, version, code, abstractOk, inferSystem, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, params, contentMode, impliedSystem, unkCodes, messages, defLang);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (result === true) {
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (result === true) {
|
|
747
|
+
for (let cc of this.valueSet.jsonObj.compose.exclude || []) {
|
|
748
|
+
this.worker.deadCheck('check#4');
|
|
749
|
+
let excluded;
|
|
750
|
+
if (cc.system) {
|
|
751
|
+
excluded = true;
|
|
752
|
+
} else {
|
|
753
|
+
let cs = await this.worker.findCodeSystem(cc.system, cc.version, this.params, ['complete', 'fragment'], op,true, true, false);
|
|
754
|
+
if (cs === null) {
|
|
755
|
+
throw new Issue('error', 'unknown', null, null, 'No Match for ' + cc.system + '|' + cc.version);
|
|
756
|
+
}
|
|
757
|
+
await this.checkCanonicalStatus(path, op, cs, this.valueSet);
|
|
758
|
+
this.worker.checkSupplements(cs, cc, this.requiredSupplements);
|
|
759
|
+
ver.value = cs.version();
|
|
760
|
+
contentMode.value = cs.contentMode();
|
|
761
|
+
let msg = '';
|
|
762
|
+
excluded = (system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'not in', cs, cc, code, abstractOk, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc);
|
|
763
|
+
if (msg) {
|
|
764
|
+
messages.push(msg);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
for (let u of cc.valueSets || []) {
|
|
768
|
+
this.worker.deadCheck('check#5');
|
|
769
|
+
let s = this.worker.pinValueSet(u);
|
|
770
|
+
let checker = this.others.get(s);
|
|
771
|
+
if (checker === null) {
|
|
772
|
+
throw new Issue('error', 'unknown', null, null, 'No Match for ' + cc.system + '|' + cc.version + ' in ' + Array.from(this.others.keys()).join(','));
|
|
773
|
+
}
|
|
774
|
+
this.checkCanonicalStatus(path, op, checker.valueSet, this.valueSet);
|
|
775
|
+
excluded = excluded && (await checker.check(path, system, version, code, abstractOk, inferSystem, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, params, contentMode, impliedSystem, unkCodes, messages, defLang) === true);
|
|
776
|
+
}
|
|
777
|
+
if (excluded) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
} else if (Extensions.checkNoModifiers(this.valueSet.jsonObj.expansion, 'ValueSetChecker.prepare', 'ValueSet.expansion')) {
|
|
783
|
+
let ccc = this.valueSet.findContains(system, version, code);
|
|
784
|
+
if (ccc === null) {
|
|
785
|
+
result = false;
|
|
786
|
+
} else {
|
|
787
|
+
let v;
|
|
788
|
+
if (!ccc.version && !version) {
|
|
789
|
+
v = '';
|
|
790
|
+
} else if (!ccc.version) {
|
|
791
|
+
v = version;
|
|
792
|
+
} else if (!version || version === ccc.version) {
|
|
793
|
+
v = ccc.version;
|
|
794
|
+
} else if (cs !== null && cs.versionIsMoreDetailed(ccc.version, version)) {
|
|
795
|
+
v = version;
|
|
796
|
+
} else {
|
|
797
|
+
let msg = 'The code system "' + ccc.system + '" version "' + ccc.version + '" in the ValueSet expansion is different to the one in the value ("' + version + '")';
|
|
798
|
+
messages.push(msg);
|
|
799
|
+
op.addIssueNoId('error', 'not-found', addToPath(path, 'version'), msg, 'vs-invalid');
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
let cs = await this.worker.findCodeSystem(system, v, this.params, ['complete', 'fragment'], op, true, true, false);
|
|
803
|
+
if (cs === null) {
|
|
804
|
+
if (!this.params.membershipOnly) {
|
|
805
|
+
let bAdd = true;
|
|
806
|
+
let msg, mid, vn;
|
|
807
|
+
if (!v) {
|
|
808
|
+
mid = 'UNKNOWN_CODESYSTEM';
|
|
809
|
+
msg = this.worker.i18n.translate('UNKNOWN_CODESYSTEM', this.params.HTTPLanguages, [system]);
|
|
810
|
+
unknownSystems.add(system);
|
|
811
|
+
} else {
|
|
812
|
+
bAdd = !unknownSystems.has(system + '|' + version);
|
|
813
|
+
if (bAdd) {
|
|
814
|
+
let vl = await this.listVersions(system);
|
|
815
|
+
if (vl.length == 0) {
|
|
816
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION_NONE';
|
|
817
|
+
vn = system;
|
|
818
|
+
} else {
|
|
819
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION';
|
|
820
|
+
vn = system + '|' + v;
|
|
821
|
+
}
|
|
822
|
+
msg = this.worker.i18n.translate(mid, this.params.HTTPLanguages, [system, v, this.worker.presentVersionList(vl)]);
|
|
823
|
+
unknownSystems.add(vn);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
messages.push(msg);
|
|
827
|
+
if (bAdd) {
|
|
828
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'system'), mid, msg, 'not-found'));
|
|
829
|
+
}
|
|
830
|
+
return null;
|
|
831
|
+
} else {
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
defLang.value = cs.defLang();
|
|
836
|
+
await this.checkCanonicalStatus(path, op, cs, this.valueSet);
|
|
837
|
+
ver.value = cs.version();
|
|
838
|
+
contentMode.value = cs.contentMode();
|
|
839
|
+
let msg = '';
|
|
840
|
+
if ((system === '%%null%%' || cs.system() === system) && await this.checkExpansion(path, cs, ccc, code, abstractOk, displays, this.valueSet, msg, inactive, vstatus, op)) {
|
|
841
|
+
result = true;
|
|
842
|
+
} else {
|
|
843
|
+
result = false;
|
|
844
|
+
}
|
|
845
|
+
if (msg) {
|
|
846
|
+
messages.push(msg);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
} else {
|
|
850
|
+
result = false;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
return result;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async checkCoding(issuePath, coding, abstractOk, inferSystem) {
|
|
858
|
+
let inactive = false;
|
|
859
|
+
let path = issuePath;
|
|
860
|
+
let unknownSystems = new Set();
|
|
861
|
+
let unkCodes = [];
|
|
862
|
+
let messages = [];
|
|
863
|
+
let result = new Parameters();
|
|
864
|
+
|
|
865
|
+
this.worker.opContext.clearContexts();
|
|
866
|
+
if (inferSystem) {
|
|
867
|
+
this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(coding) + '" and infer system', this.indentCount);
|
|
868
|
+
} else {
|
|
869
|
+
this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(coding) + '"', this.indentCount);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
let op = new OperationOutcome();
|
|
873
|
+
this.checkCanonicalStatus(path, op, this.valueSet, this.valueSet);
|
|
874
|
+
let list = new Designations(this.worker.languages);
|
|
875
|
+
let ver = {value: ''};
|
|
876
|
+
inactive = {value: false};
|
|
877
|
+
let normalForm = {value: ''};
|
|
878
|
+
let vstatus = {value: ''};
|
|
879
|
+
let cause = {value: null};
|
|
880
|
+
let contentMode = {value: null};
|
|
881
|
+
let impliedSystem = {value: ''};
|
|
882
|
+
let defLang = {value: null};
|
|
883
|
+
|
|
884
|
+
let ok = await this.check(path, coding.system, coding.version, coding.code, abstractOk, inferSystem, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, result, contentMode, impliedSystem, unkCodes, messages, defLang);
|
|
885
|
+
if (ok === true) {
|
|
886
|
+
result.AddParamBool('result', true);
|
|
887
|
+
if ((cause.value === 'not-found' && contentMode.value !== 'complete') || contentMode.value === 'example') {
|
|
888
|
+
result.addParamStr('message', 'The system "' + coding.system + ' was found but did not contain enough information to properly validate the code (mode = ' + contentMode.value + ')');
|
|
889
|
+
}
|
|
890
|
+
if (coding.display && !list.hasDisplay(this.params.workingLanguages(), defLang.value, coding.display, false, DisplayCheckingStyle.CASE_INSENSITIVE).found) {
|
|
891
|
+
let baseMsg = 'Display_Name_for__should_be_one_of__instead_of';
|
|
892
|
+
let dc = list.displayCount(this.params.workingLanguages(), null, true);
|
|
893
|
+
if (dc > 0) {
|
|
894
|
+
if (list.hasDisplay(this.params.workingLanguages(), defLang.value, coding.display, false, DisplayCheckingStyle.CASE_INSENSITIVE).difference === DisplayDifference.Normalised) {
|
|
895
|
+
baseMsg = 'Display_Name_WS_for__should_be_one_of__instead_of';
|
|
896
|
+
}
|
|
897
|
+
if (dc === 1) {
|
|
898
|
+
result.addParamStr('message', this.worker.i18n.translate(baseMsg + '_one', this.params.HTTPLanguages,
|
|
899
|
+
['', coding.system, coding.code, list.present(this.params.workingLanguages(), defLang.value, true), coding.display, this.params.langSummary()]));
|
|
900
|
+
} else {
|
|
901
|
+
result.addParamStr('message', this.worker.i18n.translate(baseMsg + '_other', this.params.HTTPLanguages, [dc.toString(), coding.system, coding.code, list.present(this.params.workingLanguages(), defLang.value, true), coding.display, this.params.langSummary()]));
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
let pd = list.preferredDisplay(this.params.workingLanguages());
|
|
906
|
+
if (pd) {
|
|
907
|
+
result.addParamStr('display', pd);
|
|
908
|
+
}
|
|
909
|
+
result.addParamUri('system', coding.system);
|
|
910
|
+
if (ver.value) {
|
|
911
|
+
result.addParamStr('version', ver.value);
|
|
912
|
+
}
|
|
913
|
+
if (cause.value !== 'null') {
|
|
914
|
+
result.AddParamCode('cause', cause.value);
|
|
915
|
+
}
|
|
916
|
+
if (inactive.value) {
|
|
917
|
+
result.AddParamBool('inactive', inactive.value);
|
|
918
|
+
if (vstatus.value && vstatus.value !== 'inactive') {
|
|
919
|
+
result.addParamStr('status', vstatus.value);
|
|
920
|
+
}
|
|
921
|
+
let msg = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, coding.code]);
|
|
922
|
+
messages.push(msg);
|
|
923
|
+
op.addIssue(new Issue('warning', 'business-rule', path, 'INACTIVE_CONCEPT_FOUND', msg, 'code-comment'));
|
|
924
|
+
} else if (vstatus.value.toLowerCase() === 'deprecated') {
|
|
925
|
+
result.addParamStr('status', vstatus.value);
|
|
926
|
+
let msg = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, coding.code]);
|
|
927
|
+
messages.push(msg);
|
|
928
|
+
op.addIssue(new Issue('warning', 'business-rule', path, 'DEPRECATED_CONCEPT_FOUND', msg, 'code-comment'));
|
|
929
|
+
}
|
|
930
|
+
} else if (ok === null) {
|
|
931
|
+
result.AddParamBool('result', false);
|
|
932
|
+
result.addParamStr('message', 'The CodeSystem "' + coding.system + '" is unknown, so the code "' + coding.code + '" is not known to be in the ' + this.valueSet.name);
|
|
933
|
+
for (let us of unknownSystems) {
|
|
934
|
+
result.addParamCanonical('x-caused-by-unknown-system', us);
|
|
935
|
+
}
|
|
936
|
+
} else {
|
|
937
|
+
result.AddParamBool('result', false);
|
|
938
|
+
if (ver.value) {
|
|
939
|
+
result.addParamStr('version', ver.value);
|
|
940
|
+
}
|
|
941
|
+
result.addParamStr('message', 'The system/code "' + coding.system + '"/"' + coding.code + '" is not in the value set ' + this.valueSet.name);
|
|
942
|
+
if (cause.value !== 'null') {
|
|
943
|
+
result.AddParamCode('cause', cause.value);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
if (op.hasIssues) {
|
|
947
|
+
result.addParam('issues').resource = op.jsonObj;
|
|
948
|
+
}
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
valueSetDependsOnCodeSystem(url, version) {
|
|
953
|
+
for (let inc of this.valueSet.include) {
|
|
954
|
+
this.worker.deadCheck('valueSetDependsOnCodeSystem');
|
|
955
|
+
if (inc.system === url && (!version || version === inc.version || !inc.version)) {
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return false;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async checkSupplementsExist(vs) {
|
|
963
|
+
for (let inc of vs.jsonObj.compose.include) {
|
|
964
|
+
if (inc.system) {
|
|
965
|
+
let cs = await this.worker.findCodeSystem(inc.system, inc.version, this.params, ['complete', 'fragment'], null,true);
|
|
966
|
+
if (cs !== null) {
|
|
967
|
+
await this.worker.checkSupplements(cs, null, this.requiredSupplements);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
async checkCodeableConcept(issuePath, code, abstractOk, inferSystem, mode) {
|
|
974
|
+
this.worker.opContext.clearContexts();
|
|
975
|
+
if (inferSystem) {
|
|
976
|
+
this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(code) + '" and infer system', this.indentCount);
|
|
977
|
+
} else {
|
|
978
|
+
this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(code) + '"', this.indentCount);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
let inactive = { value: false };
|
|
982
|
+
let cause = { value: "null" };
|
|
983
|
+
if (this.valueSet === null) {
|
|
984
|
+
throw new Issue('error', 'invalid', null, null, 'Error: cannot validate a CodeableConcept without a nominated valueset');
|
|
985
|
+
}
|
|
986
|
+
let pdisp;
|
|
987
|
+
let psys;
|
|
988
|
+
let pcode;
|
|
989
|
+
let pver;
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
let vstatus = {value: ''};
|
|
993
|
+
let normalForm = {value: ''};
|
|
994
|
+
let mt = [];
|
|
995
|
+
let ts = [];
|
|
996
|
+
let tsys = '';
|
|
997
|
+
let tcode = '';
|
|
998
|
+
let tver = '';
|
|
999
|
+
let vcc = {};
|
|
1000
|
+
if (code.text) {
|
|
1001
|
+
vcc.text = code.text;
|
|
1002
|
+
}
|
|
1003
|
+
let unknownSystems = new Set();
|
|
1004
|
+
let result = new Parameters();
|
|
1005
|
+
|
|
1006
|
+
const msg = (s, clear = false) => {
|
|
1007
|
+
if (!s) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
if (clear) {
|
|
1011
|
+
mt = [];
|
|
1012
|
+
}
|
|
1013
|
+
if (!mt.includes(s)) {
|
|
1014
|
+
mt.push(s);
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
let op = new OperationOutcome();
|
|
1019
|
+
this.checkCanonicalStatus(issuePath, op, this.valueSet.jsonObj, this.valueSet.jsonObj);
|
|
1020
|
+
let list = new Designations(this.worker.languages);
|
|
1021
|
+
let ok = false;
|
|
1022
|
+
let codelist = '';
|
|
1023
|
+
mt = [];
|
|
1024
|
+
let i = 0;
|
|
1025
|
+
let impliedSystem = { value: '' };
|
|
1026
|
+
for (let c of code.coding) {
|
|
1027
|
+
const csd = await this.worker.findCodeSystem(c.system, null, this.params, ['complete', 'fragment'], false, true);
|
|
1028
|
+
this.worker.seeSourceProvider(csd, c.system);
|
|
1029
|
+
|
|
1030
|
+
this.worker.deadCheck('check-b#1');
|
|
1031
|
+
let path;
|
|
1032
|
+
if (issuePath === 'CodeableConcept') {
|
|
1033
|
+
path = addToPath(issuePath, 'coding[' + i + ']');
|
|
1034
|
+
} else {
|
|
1035
|
+
path = issuePath;
|
|
1036
|
+
}
|
|
1037
|
+
list.clear();
|
|
1038
|
+
let ver = { value: '' };
|
|
1039
|
+
let contentMode = { value: null };
|
|
1040
|
+
let defLang = { value: null };
|
|
1041
|
+
let v = await this.check(path, c.system, c.version, c.code, abstractOk, inferSystem, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, result, contentMode, impliedSystem, ts, mt, defLang);
|
|
1042
|
+
if (v === false) {
|
|
1043
|
+
cause.value = 'code-invalid';
|
|
1044
|
+
}
|
|
1045
|
+
let ws;
|
|
1046
|
+
if (impliedSystem.value) {
|
|
1047
|
+
ws = impliedSystem.value;
|
|
1048
|
+
} else {
|
|
1049
|
+
ws = c.system;
|
|
1050
|
+
}
|
|
1051
|
+
if (!tcode || v == true) {
|
|
1052
|
+
tsys = c.system;
|
|
1053
|
+
tcode = c.code;
|
|
1054
|
+
tver = c.version;
|
|
1055
|
+
}
|
|
1056
|
+
let cc;
|
|
1057
|
+
if (!ws) {
|
|
1058
|
+
ws = "";
|
|
1059
|
+
}
|
|
1060
|
+
if (!c.version) {
|
|
1061
|
+
cc = ws + '#' + c.code;
|
|
1062
|
+
} else {
|
|
1063
|
+
cc = ws + '|' + c.version + '#' + c.code;
|
|
1064
|
+
}
|
|
1065
|
+
if (c.display) {
|
|
1066
|
+
cc = cc + ' (\'' + c.display + '\')';
|
|
1067
|
+
}
|
|
1068
|
+
codelist = !codelist ? '\'' + cc + '\'' : codelist + ', \'' + cc + '\'';
|
|
1069
|
+
|
|
1070
|
+
if (v === false && !this.valueSet.jsonObj.internallyDefined && mode === 'codeableConcept') {
|
|
1071
|
+
let m = this.worker.i18n.translate('None_of_the_provided_codes_are_in_the_value_set_one', this.params.HTTPLanguages, ['', this.valueSet.vurl, '\'' + cc + '\'']);
|
|
1072
|
+
let p = issuePath + '.coding[' + i + '].code';
|
|
1073
|
+
op.addIssue(new Issue('information', 'code-invalid', p, 'None_of_the_provided_codes_are_in_the_value_set_one', m, 'this-code-not-in-vs'));
|
|
1074
|
+
if (cause.value === 'null') {
|
|
1075
|
+
cause.value = 'unknown';
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
if (ok !== true && v !== false) {
|
|
1080
|
+
ok = v;
|
|
1081
|
+
}
|
|
1082
|
+
if (v === true) {
|
|
1083
|
+
if ((cause.value === 'not-found' && contentMode.value !== 'complete') || contentMode.value === 'example') {
|
|
1084
|
+
let m = 'The system ' + c.system + ' was found but did not contain enough information to properly validate the code "' + c.code + '" ("' + c.display + '") (mode = ' + contentMode.value + ')';
|
|
1085
|
+
msg(m);
|
|
1086
|
+
op.addIssueNoId('warning', 'not-found', path, m, 'vs-invalid');
|
|
1087
|
+
} else if (c.display && list.designations.length > 0) {
|
|
1088
|
+
await this.checkDisplays(list, defLang, c, msg, op, path);
|
|
1089
|
+
}
|
|
1090
|
+
psys = c.system;
|
|
1091
|
+
pcode = c.code;
|
|
1092
|
+
if (ver.value) {
|
|
1093
|
+
pver = ver.value;
|
|
1094
|
+
}
|
|
1095
|
+
let pd = list.preferredDisplay(this.params.workingLanguages());
|
|
1096
|
+
if (pd) {
|
|
1097
|
+
pdisp = pd;
|
|
1098
|
+
}
|
|
1099
|
+
if (!pdisp) {
|
|
1100
|
+
pdisp = list.preferredDisplay(null);
|
|
1101
|
+
}
|
|
1102
|
+
} else if (!this.params.membershipOnly && ws) {
|
|
1103
|
+
if (!isAbsoluteUrl(ws)) {
|
|
1104
|
+
let m = this.worker.i18n.translate('Terminology_TX_System_Relative', this.params.HTTPLanguages, []);
|
|
1105
|
+
let p;
|
|
1106
|
+
if (mode === 'coding') {
|
|
1107
|
+
p = issuePath + '.system';
|
|
1108
|
+
} else if (mode === 'codeableConcept') {
|
|
1109
|
+
p = issuePath + '.coding[' + i + '].system';
|
|
1110
|
+
} else {
|
|
1111
|
+
p = issuePath;
|
|
1112
|
+
}
|
|
1113
|
+
mt.push(m);
|
|
1114
|
+
op.addIssue(new Issue('error', 'invalid', p, 'Terminology_TX_System_Relative', m, 'invalid-data'));
|
|
1115
|
+
}
|
|
1116
|
+
let prov = await this.worker.findCodeSystem(ws, c.version, this.params, ['complete', 'fragment'], op,true, true, false);
|
|
1117
|
+
if (prov === null) {
|
|
1118
|
+
let vss = await this.worker.findValueSet(ws, '');
|
|
1119
|
+
if (vss !== null) {
|
|
1120
|
+
vss = null;
|
|
1121
|
+
let m = this.worker.i18n.translate('Terminology_TX_System_ValueSet2', this.params.HTTPLanguages, [ws]);
|
|
1122
|
+
msg(m);
|
|
1123
|
+
op.addIssue(new Issue('error', 'invalid', addToPath(path, 'system'), 'Terminology_TX_System_ValueSet2', m, 'invalid-data'));
|
|
1124
|
+
cause.value = 'invalid';
|
|
1125
|
+
} else {
|
|
1126
|
+
let provS = await this.worker.findCodeSystem(ws, c.version, this.params, ['supplement'], op,true, true, false);
|
|
1127
|
+
if (provS !== null) {
|
|
1128
|
+
vss = null;
|
|
1129
|
+
let m = this.worker.i18n.translate('CODESYSTEM_CS_NO_SUPPLEMENT', this.params.HTTPLanguages, [provS.vurl()]);
|
|
1130
|
+
msg(m);
|
|
1131
|
+
op.addIssue(new Issue('error', 'invalid', addToPath(path, 'system'), 'CODESYSTEM_CS_NO_SUPPLEMENT', m, 'invalid-data'));
|
|
1132
|
+
cause.value = 'invalid';
|
|
1133
|
+
} else {
|
|
1134
|
+
let prov2 = await this.worker.findCodeSystem(ws, '', this.params, ['complete', 'fragment'], op,true, true, false);
|
|
1135
|
+
let bAdd = true;
|
|
1136
|
+
let m, mid, vn;
|
|
1137
|
+
if (prov2 === null && !c.version) {
|
|
1138
|
+
mid = 'UNKNOWN_CODESYSTEM';
|
|
1139
|
+
m = this.worker.i18n.translate('UNKNOWN_CODESYSTEM', this.params.HTTPLanguages, [ws]);
|
|
1140
|
+
bAdd = !unknownSystems.has(ws);
|
|
1141
|
+
if (bAdd) {
|
|
1142
|
+
unknownSystems.add(ws);
|
|
1143
|
+
}
|
|
1144
|
+
} else {
|
|
1145
|
+
let vl = await this.worker.listVersions(c.system);
|
|
1146
|
+
if (vl.length == 0) {
|
|
1147
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION_NONE';
|
|
1148
|
+
vn = ws;
|
|
1149
|
+
} else {
|
|
1150
|
+
mid = 'UNKNOWN_CODESYSTEM_VERSION';
|
|
1151
|
+
vn = ws + '|' + c.version;
|
|
1152
|
+
}
|
|
1153
|
+
m = this.worker.i18n.translate(mid, this.params.HTTPLanguages, [ws, c.version, this.worker.presentVersionList(vl)]);
|
|
1154
|
+
bAdd = !unknownSystems.has(vn);
|
|
1155
|
+
if (bAdd) {
|
|
1156
|
+
unknownSystems.add(vn);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (bAdd) {
|
|
1160
|
+
op.addIssue(new Issue('error', 'not-found', addToPath(path, 'system'), mid, m, 'not-found'));
|
|
1161
|
+
}
|
|
1162
|
+
msg(m);
|
|
1163
|
+
cause.value = 'not-found';
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
} else {
|
|
1167
|
+
this.checkCanonicalStatusCS(path, op, prov, this.valueSet);
|
|
1168
|
+
let ctxt = await prov.locate(c.code);
|
|
1169
|
+
if (!ctxt.context) {
|
|
1170
|
+
// message can never be populated in pascal?
|
|
1171
|
+
// if (ctxt.message) {
|
|
1172
|
+
// let p;
|
|
1173
|
+
// if (mode !== 'code') {
|
|
1174
|
+
// p = path + '.code';
|
|
1175
|
+
// }
|
|
1176
|
+
// op.addIssue(new Issue('information', cause.value, p, null, ctxt.message, 'invalid-code'));
|
|
1177
|
+
// message = '';
|
|
1178
|
+
// }
|
|
1179
|
+
if (vcc.coding) {
|
|
1180
|
+
vcc.coding = vcc.coding.filter(ccc => ccc.system === prov.system() && (!prov.version() || ccc.version == prov.version()) && ccc.code === c.code);
|
|
1181
|
+
}
|
|
1182
|
+
let vs = ws + '|' + prov.version() + '#' + c.code;
|
|
1183
|
+
if (!ts.includes(vs)) {
|
|
1184
|
+
ts.push(vs);
|
|
1185
|
+
let m;
|
|
1186
|
+
if (prov.contentMode() === 'complete') {
|
|
1187
|
+
m = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(ws), this.params.HTTPLanguages, [c.code, ws, prov.version(), SCTVersion(ws, prov.version())]);
|
|
1188
|
+
cause.value = 'code-invalid';
|
|
1189
|
+
msg(m);
|
|
1190
|
+
op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version', m, 'invalid-code'), true);
|
|
1191
|
+
} else {
|
|
1192
|
+
m = this.worker.i18n.translate('UNKNOWN_CODE_IN_FRAGMENT', this.params.HTTPLanguages, [c.code, ws, prov.version()]);
|
|
1193
|
+
cause.value = 'code-invalid';
|
|
1194
|
+
// msg(m); - it's just a warning
|
|
1195
|
+
op.addIssue(new Issue('warning', 'code-invalid', addToPath(path, 'code'), 'UNKNOWN_CODE_IN_FRAGMENT', m, 'invalid-code'), true);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
} else {
|
|
1199
|
+
await this.worker.listDisplaysFromCodeSystem(list, prov, ctxt.context);
|
|
1200
|
+
let pd = list.preferredDisplay(this.params.workingLanguages());
|
|
1201
|
+
if (pd) {
|
|
1202
|
+
pdisp = pd;
|
|
1203
|
+
}
|
|
1204
|
+
if (!pdisp) {
|
|
1205
|
+
pdisp = list.preferredDisplay(null);
|
|
1206
|
+
}
|
|
1207
|
+
let severity = this.dispWarning();
|
|
1208
|
+
if (c.display && list.designations.length > 0 && !list.hasDisplay(this.params.workingLanguages(), defLang.value, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE).found) {
|
|
1209
|
+
let baseMsg;
|
|
1210
|
+
if (list.hasDisplay(this.params.workingLanguages(), defLang.value, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE).difference === DisplayDifference.Normalized) {
|
|
1211
|
+
baseMsg = 'Display_Name_WS_for__should_be_one_of__instead_of';
|
|
1212
|
+
} else {
|
|
1213
|
+
baseMsg = 'Display_Name_for__should_be_one_of__instead_of';
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
let dc = list.displayCount(this.params.workingLanguages(), null, true);
|
|
1217
|
+
let m;
|
|
1218
|
+
if (dc === 0) {
|
|
1219
|
+
severity = 'warning';
|
|
1220
|
+
baseMsg = 'NO_VALID_DISPLAY_AT_ALL';
|
|
1221
|
+
m = this.worker.i18n.translate('NO_VALID_DISPLAY_AT_ALL', this.params.HTTPLanguages,
|
|
1222
|
+
[c.display, prov.system(), c.code, this.params.langSummary()]);
|
|
1223
|
+
} else if (dc === 1) {
|
|
1224
|
+
m = this.worker.i18n.translate(baseMsg + '_one', this.params.HTTPLanguages,
|
|
1225
|
+
['', prov.system(), c.code, list.present(this.params.workingLanguages(), defLang.value, true), c.display, this.params.langSummary()]);
|
|
1226
|
+
} else {
|
|
1227
|
+
m = this.worker.i18n.translate(baseMsg + '_other', this.params.HTTPLanguages,
|
|
1228
|
+
[dc.toString(), prov.system(), c.code, list.present(this.params.workingLanguages(), defLang.value, true), c.display, this.params.langSummary()]);
|
|
1229
|
+
}
|
|
1230
|
+
msg(m);
|
|
1231
|
+
op.addIssue(new Issue(severity, 'invalid', addToPath(path, 'display'), baseMsg, m, 'invalid-display'));
|
|
1232
|
+
}
|
|
1233
|
+
if (prov.version()) {
|
|
1234
|
+
result.addParamStr('version', prov.version());
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
i++;
|
|
1240
|
+
}
|
|
1241
|
+
if (ok === false && !this.valueSet.jsonObj.internallyDefined) {
|
|
1242
|
+
let mid, m, p;
|
|
1243
|
+
if (mode === 'codeableConcept') {
|
|
1244
|
+
mid = 'TX_GENERAL_CC_ERROR_MESSAGE';
|
|
1245
|
+
m = this.worker.i18n.translate('TX_GENERAL_CC_ERROR_MESSAGE', this.params.HTTPLanguages, [this.valueSet.vurl]);
|
|
1246
|
+
} else {
|
|
1247
|
+
mid = 'None_of_the_provided_codes_are_in_the_value_set_one';
|
|
1248
|
+
m = this.worker.i18n.translate('None_of_the_provided_codes_are_in_the_value_set_one', this.params.HTTPLanguages, ['', this.valueSet.vurl, codelist]);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
if (mode === 'codeableConcept') {
|
|
1252
|
+
p = '';
|
|
1253
|
+
} else if (!issuePath) {
|
|
1254
|
+
p = 'code';
|
|
1255
|
+
} else if (issuePath !== 'CodeableConcept') {
|
|
1256
|
+
p = issuePath + '.code';
|
|
1257
|
+
} else if (code.codingCount === 1) {
|
|
1258
|
+
p = issuePath + '.coding[0].code';
|
|
1259
|
+
} else {
|
|
1260
|
+
p = issuePath;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
if (op.addIssue(new Issue('error', 'code-invalid', p, mid, m, 'not-in-vs'))) {
|
|
1264
|
+
msg(m);
|
|
1265
|
+
}
|
|
1266
|
+
if (cause.value === 'null') {
|
|
1267
|
+
cause.value = 'unknown';
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
result.addParamBool('result', ok === true && !op.hasErrors());
|
|
1272
|
+
if (psys) {
|
|
1273
|
+
result.addParamUri('system', psys);
|
|
1274
|
+
} else if (ok === true && impliedSystem.value) {
|
|
1275
|
+
result.addParamUri('system', impliedSystem.value);
|
|
1276
|
+
} else if (tsys && mode !== 'codeableConcept') {
|
|
1277
|
+
result.addParamUri('system', tsys);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
for (let us of unknownSystems) {
|
|
1281
|
+
if (ok === false) {
|
|
1282
|
+
result.addParamCanonical('x-unknown-system', us);
|
|
1283
|
+
} else {
|
|
1284
|
+
result.addParamCanonical('x-caused-by-unknown-system', us);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (normalForm.value) {
|
|
1288
|
+
result.addParamCode('normalized-code', normalForm.value);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
if (pcode) {
|
|
1292
|
+
result.addParamCode('code', pcode);
|
|
1293
|
+
} else if (tcode && mode !== 'codeableConcept') {
|
|
1294
|
+
result.addParamCode('code', tcode);
|
|
1295
|
+
}
|
|
1296
|
+
if (pver) {
|
|
1297
|
+
result.addParamStr('version', pver);
|
|
1298
|
+
} else if (tver && mode !== 'codeableConcept') {
|
|
1299
|
+
result.addParamStr('version', tver);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
if (pdisp && (pcode || (tcode && mode !== 'codeableConcept'))) {
|
|
1303
|
+
result.addParamStr('display', pdisp);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
if (inactive.value) {
|
|
1307
|
+
result.addParamBool('inactive', inactive.value);
|
|
1308
|
+
if (vstatus.value && vstatus.value !== 'inactive') {
|
|
1309
|
+
result.addParamStr('status', vstatus.value);
|
|
1310
|
+
}
|
|
1311
|
+
let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
|
|
1312
|
+
msg(m);
|
|
1313
|
+
op.addIssue(new Issue('warning', 'business-rule', inactive.path, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
|
|
1314
|
+
} else if (vstatus.value && vstatus.value.toLowerCase() === 'deprecated') {
|
|
1315
|
+
result.addParamStr('status', 'deprecated');
|
|
1316
|
+
let m = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
|
|
1317
|
+
msg(m);
|
|
1318
|
+
op.addIssue(new Issue('warning', 'business-rule', issuePath, 'DEPRECATED_CONCEPT_FOUND', m, 'code-comment'));
|
|
1319
|
+
}
|
|
1320
|
+
for (let iss of op.issue || []) {
|
|
1321
|
+
if (iss.severity === 'error') {
|
|
1322
|
+
if (!mt.includes(iss.display)) {
|
|
1323
|
+
mt.push(iss.display);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
op.listMissedErrors(mt);
|
|
1328
|
+
if (mt.length > 0) {
|
|
1329
|
+
mt.sort();
|
|
1330
|
+
result.addParamStr('message', toText(mt, '; '));
|
|
1331
|
+
}
|
|
1332
|
+
if (mode === 'codeableConcept') {
|
|
1333
|
+
result.addParam('codeableConcept', 'valueCodeableConcept', code);
|
|
1334
|
+
}
|
|
1335
|
+
if (op.hasIssues()) {
|
|
1336
|
+
result.addParamResource('issues', op.jsonObj);
|
|
1337
|
+
}
|
|
1338
|
+
return result;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
async checkDisplays(list, defLang, c, msg, op, path) {
|
|
1342
|
+
let hd = list.hasDisplay(this.params.workingLanguages(), null, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE)
|
|
1343
|
+
if (!hd.found) {
|
|
1344
|
+
let baseMsg;
|
|
1345
|
+
if (hd.difference === DisplayDifference.Normalized) {
|
|
1346
|
+
baseMsg = 'Display_Name_WS_for__should_be_one_of__instead_of';
|
|
1347
|
+
} else {
|
|
1348
|
+
baseMsg = 'Display_Name_for__should_be_one_of__instead_of';
|
|
1349
|
+
}
|
|
1350
|
+
let mid = baseMsg;
|
|
1351
|
+
let dc = list.displayCount(this.params.workingLanguages(), null, true);
|
|
1352
|
+
let severity = this.dispWarning();
|
|
1353
|
+
if (dc === 0) {
|
|
1354
|
+
severity = 'warning';
|
|
1355
|
+
dc = list.displayCount(this.params.workingLanguages(), null, false);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
let m, ds;
|
|
1359
|
+
if (dc === 0) {
|
|
1360
|
+
ds = await list.preferredDisplay(null);
|
|
1361
|
+
if (!ds) {
|
|
1362
|
+
m = this.worker.i18n.translate('NO_VALID_DISPLAY_AT_ALL', this.params.HTTPLanguages, [c.display, c.system, c.code]);
|
|
1363
|
+
mid = 'NO_VALID_DISPLAY_AT_ALL';
|
|
1364
|
+
} else {
|
|
1365
|
+
if (ds === c.display) {
|
|
1366
|
+
m = this.worker.i18n.translate('NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_OK', this.params.HTTPLanguages, [c.display, c.system, c.code, this.params.langSummary(), ds]);
|
|
1367
|
+
mid = 'NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_OK';
|
|
1368
|
+
severity = 'information';
|
|
1369
|
+
} else {
|
|
1370
|
+
m = this.worker.i18n.translate('NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR', this.params.HTTPLanguages, [c.display, c.system, c.code, this.params.langSummary(), ds]);
|
|
1371
|
+
mid = 'NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_ERR';
|
|
1372
|
+
if (this.params.displayWarning) {
|
|
1373
|
+
severity = 'warning';
|
|
1374
|
+
} else {
|
|
1375
|
+
severity = 'error';
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
} else if (dc === 1) {
|
|
1380
|
+
m = this.worker.i18n.translate(baseMsg + '_one', this.params.workingLanguages(),
|
|
1381
|
+
['', c.system, c.code, list.present(this.params.workingLanguages(), defLang.value, dc > 0), c.display, this.params.langSummary()]);
|
|
1382
|
+
} else {
|
|
1383
|
+
m = this.worker.i18n.translate(baseMsg + '_other', this.params.workingLanguages(),
|
|
1384
|
+
[dc.toString(), c.system, c.code, list.present(this.params.workingLanguages(), defLang.value, dc > 0), c.display, this.params.langSummary()]);
|
|
1385
|
+
}
|
|
1386
|
+
msg(m);
|
|
1387
|
+
op.addIssue(new Issue(severity, 'invalid', addToPath(path, 'display'), mid, m, 'invalid-display'));
|
|
1388
|
+
} else {
|
|
1389
|
+
let hd = list.hasDisplay(this.params.workingLanguages(), null, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE);
|
|
1390
|
+
if (!hd.found) {
|
|
1391
|
+
let m, mid;
|
|
1392
|
+
if (list.source !== null && list.source.hasAnyDisplays(this.params.workingLanguages())) {
|
|
1393
|
+
mid = 'NO_VALID_DISPLAY_FOUND_LANG_SOME';
|
|
1394
|
+
m = this.worker.i18n.translatePlural(this.params.workingLanguages().length, 'NO_VALID_DISPLAY_FOUND_LANG_SOME', this.params.HTTPLanguages,
|
|
1395
|
+
[c.system, c.code, c.display, this.params.workingLanguages().toString(), c.display]);
|
|
1396
|
+
} else {
|
|
1397
|
+
mid = 'NO_VALID_DISPLAY_FOUND_LANG_NONE';
|
|
1398
|
+
m = this.worker.i18n.translatePlural(this.params.workingLanguages().length, 'NO_VALID_DISPLAY_FOUND_LANG_NONE', this.params.HTTPLanguages,
|
|
1399
|
+
[c.system, c.code, c.display, this.params.workingLanguages().toString(), c.display]);
|
|
1400
|
+
}
|
|
1401
|
+
op.addIssue(new Issue('information', 'invalid', addToPath(path, 'display'), mid, m, 'display-comment'));
|
|
1402
|
+
} else {
|
|
1403
|
+
let hd = list.hasDisplay(this.params.workingLanguages(), null, c.display, true, DisplayCheckingStyle.CASE_INSENSITIVE);
|
|
1404
|
+
if (!hd.found) {
|
|
1405
|
+
let ts2 = [];
|
|
1406
|
+
list.allowedDisplays(ts2, null, defLang.value);
|
|
1407
|
+
let mid = 'INACTIVE_DISPLAY_FOUND';
|
|
1408
|
+
let m = this.worker.i18n.translatePlural(ts2.length, mid, this.params.HTTPLanguages, [c.display, c.code, ts2.join(','), list.status(c.display)]);
|
|
1409
|
+
op.addIssue(new Issue('warning', 'invalid', addToPath(path, 'display'), mid, m, 'display-comment'));
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
async checkSystemCode(issuePath, system, version, code, inferSystem) {
|
|
1416
|
+
this.worker.opContext.clearContexts();
|
|
1417
|
+
if (inferSystem) {
|
|
1418
|
+
this.worker.opContext.addNote(this.valueSet, 'Validate "' + code + '" and infer system', this.indentCount);
|
|
1419
|
+
} else {
|
|
1420
|
+
this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(system, version, code) + '"', this.indentCount);
|
|
1421
|
+
}
|
|
1422
|
+
let unknownSystems = new Set();
|
|
1423
|
+
let unkCodes = [];
|
|
1424
|
+
let messages = [];
|
|
1425
|
+
let result = new Parameters();
|
|
1426
|
+
let op = new OperationOutcome();
|
|
1427
|
+
this.checkCanonicalStatus(issuePath, op, this.valueSet, this.valueSet);
|
|
1428
|
+
let list = new Designations(this.worker.languages);
|
|
1429
|
+
let ver = {value: ''};
|
|
1430
|
+
let inactive = {value: false};
|
|
1431
|
+
let normalForm = {value: ''};
|
|
1432
|
+
let vstatus = {value: ''};
|
|
1433
|
+
let cause = {value: null};
|
|
1434
|
+
let contentMode = {value: null};
|
|
1435
|
+
let impliedSystem = {value: ''};
|
|
1436
|
+
let defLang = {value: null};
|
|
1437
|
+
|
|
1438
|
+
let ok = await this.check(issuePath, system, version, code, true, inferSystem, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, result, contentMode, impliedSystem, unkCodes, messages, defLang);
|
|
1439
|
+
if (ok === true) {
|
|
1440
|
+
result.AddParamBool('result', true);
|
|
1441
|
+
let pd = list.preferredDisplay(this.params.workingLanguages());
|
|
1442
|
+
if (pd) {
|
|
1443
|
+
result.addParamStr('display', pd);
|
|
1444
|
+
}
|
|
1445
|
+
result.addParamUri('system', system);
|
|
1446
|
+
if ((cause.value === 'not-found' && contentMode.value !== 'complete') || contentMode.value === 'example') {
|
|
1447
|
+
result.addParamStr('message', 'The system "' + system + ' was found but did not contain enough information to properly validate the code (mode = ' + contentMode.value + ')');
|
|
1448
|
+
}
|
|
1449
|
+
if (cause.value) {
|
|
1450
|
+
result.addParamCode('cause', cause.value);
|
|
1451
|
+
}
|
|
1452
|
+
if (inactive.value) {
|
|
1453
|
+
result.addParamBool('inactive', inactive.value);
|
|
1454
|
+
if (vstatus.value && vstatus.value !== 'inactive') {
|
|
1455
|
+
result.addParamStr('status', vstatus.value);
|
|
1456
|
+
}
|
|
1457
|
+
let msg = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, code]);
|
|
1458
|
+
messages.push(msg);
|
|
1459
|
+
op.addIssue(new Issue('warning', 'business-rule', 'code', 'INACTIVE_CONCEPT_FOUND', msg, 'code-comment'));
|
|
1460
|
+
} else if (vstatus.value.toLowerCase() === 'deprecated') {
|
|
1461
|
+
result.addParamStr('status', vstatus.value);
|
|
1462
|
+
let msg = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, code]);
|
|
1463
|
+
messages.push(msg);
|
|
1464
|
+
op.addIssue(new Issue('warning', 'business-rule', 'code', 'DEPRECATED_CONCEPT_FOUND', msg, 'code-comment'));
|
|
1465
|
+
}
|
|
1466
|
+
} else if (ok === null) {
|
|
1467
|
+
result.AddParamBool('result', false);
|
|
1468
|
+
result.addParamStr('message', 'The system "' + system + '" is unknown so the /"' + code + '" cannot be confirmed to be in the value set ' + this.valueSet.name);
|
|
1469
|
+
op.addIssueNoId('error', cause.value, 'code', 'The system "' + system + '" is unknown so the /"' + code + '" cannot be confirmed to be in the value set ' + this.valueSet.name, 'not-found');
|
|
1470
|
+
for (let us of unknownSystems) {
|
|
1471
|
+
result.addParamCanonical('x-caused-by-unknown-system', us);
|
|
1472
|
+
}
|
|
1473
|
+
} else {
|
|
1474
|
+
result.AddParamBool('result', false);
|
|
1475
|
+
result.addParamStr('message', 'The system/code "' + system + '"/"' + code + '" is not in the value set ' + this.valueSet.name);
|
|
1476
|
+
op.addIssueNoId('error', cause.value, 'code', 'The system/code "' + system + '"/"' + code + '" is not in the value set ' + this.valueSet.name, 'not-in-vs');
|
|
1477
|
+
if (cause.value) {
|
|
1478
|
+
result.AddParamCode('cause', cause.value);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
if (op.hasIssues()) {
|
|
1482
|
+
result.addParam('issues').resource = op.jsonObj;
|
|
1483
|
+
}
|
|
1484
|
+
return result;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
async checkConceptSet(path, role, cs, cset, code, abstractOk, displays, vs, message, inactive, normalForm, vstatus, op, vcc, messages) {
|
|
1488
|
+
this.worker.opContext.addNote(vs, 'check code ' + role + ' ' + this.worker.renderer.displayValueSetInclude(cset) + ' at ' + path, this.indentCount);
|
|
1489
|
+
inactive.value = false;
|
|
1490
|
+
let result = false;
|
|
1491
|
+
if (!cset.concept && !cset.filter) {
|
|
1492
|
+
let loc = await cs.locate(code);
|
|
1493
|
+
result = false;
|
|
1494
|
+
if (loc.context == null) {
|
|
1495
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+": "+loc.mesage, this.indentCount);
|
|
1496
|
+
if (!this.params.membershipOnly) {
|
|
1497
|
+
if (cs.contentMode() !== 'complete') {
|
|
1498
|
+
op.addIssue(new Issue('warning', 'code-invalid', addToPath(path, 'code'), 'UNKNOWN_CODE_IN_FRAGMENT', this.worker.i18n.translate('UNKNOWN_CODE_IN_FRAGMENT', this.params.HTTPLanguages, [code, cs.system(), cs.version()]), 'invalid-code'));
|
|
1499
|
+
result = true;
|
|
1500
|
+
} else {
|
|
1501
|
+
op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version',
|
|
1502
|
+
this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system()), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]), 'invalid-code'));
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
if (loc.message && op) {
|
|
1506
|
+
op.addIssue(new Issue('information', 'code-invalid', addToPath(path, 'code'), null, loc.message, 'invalid-code'));
|
|
1507
|
+
}
|
|
1508
|
+
} else if (!(abstractOk || !cs.IsAbstract(loc.context))) {
|
|
1509
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
|
|
1510
|
+
if (!this.params.membershipOnly) {
|
|
1511
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
|
|
1512
|
+
}
|
|
1513
|
+
} else if (this.excludeInactives() && await cs.isInactive(loc.context)) {
|
|
1514
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1515
|
+
let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
1516
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
1517
|
+
result = false;
|
|
1518
|
+
messages.push(msg);
|
|
1519
|
+
if (!this.params.membershipOnly) {
|
|
1520
|
+
inactive.value = true;
|
|
1521
|
+
inactive.path = path;
|
|
1522
|
+
if (inactive.value) {
|
|
1523
|
+
vstatus.value = await cs.getStatus(loc.context);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
} else if (this.params.activeOnly && await cs.isInactive(loc.context)) {
|
|
1527
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1528
|
+
result = false;
|
|
1529
|
+
inactive.value = true;
|
|
1530
|
+
inactive.path = path;
|
|
1531
|
+
vstatus.value = await cs.getStatus(loc.context);
|
|
1532
|
+
let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
1533
|
+
messages.push(msg);
|
|
1534
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
1535
|
+
} else {
|
|
1536
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1537
|
+
result = true;
|
|
1538
|
+
if (await cs.code(loc.context) != code) {
|
|
1539
|
+
let msg;
|
|
1540
|
+
if (cs.version()) {
|
|
1541
|
+
msg = this.worker.i18n.translate('CODE_CASE_DIFFERENCE', this.params.HTTPLanguages, [code, await cs.code(loc.context), cs.system() + '|' + cs.version()]);
|
|
1542
|
+
} else {
|
|
1543
|
+
msg = this.worker.i18n.translate('CODE_CASE_DIFFERENCE', this.params.HTTPLanguages, [code, await cs.code(loc.context), cs.system()]);
|
|
1544
|
+
}
|
|
1545
|
+
op.addIssue(new Issue('information', 'business-rule', addToPath(path, 'code'), 'CODE_CASE_DIFFERENCE', msg, 'code-rule'));
|
|
1546
|
+
normalForm.value = await cs.code(loc.context);
|
|
1547
|
+
}
|
|
1548
|
+
let msg = await cs.incompleteValidationMessage(loc.context, this.params.HTTPLanguages);
|
|
1549
|
+
if (msg) {
|
|
1550
|
+
op.addIssue(new Issue('information', 'informational', addToPath(path, 'code'), null, msg, 'process-note'));
|
|
1551
|
+
}
|
|
1552
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, loc.context);
|
|
1553
|
+
inactive.value = await cs.isInactive(loc.context);
|
|
1554
|
+
inactive.path = path;
|
|
1555
|
+
vstatus.value = await cs.getStatus(loc.context);
|
|
1556
|
+
|
|
1557
|
+
if (vcc !== null) {
|
|
1558
|
+
if (!vcc.coding) {
|
|
1559
|
+
vcc.coding = [];
|
|
1560
|
+
}
|
|
1561
|
+
vcc.coding.push({
|
|
1562
|
+
system: cs.system(),
|
|
1563
|
+
version: cs.version(),
|
|
1564
|
+
code: await cs.code(loc.context),
|
|
1565
|
+
display: displays.preferredDisplay(this.params.workingLanguages())
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
return result;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
for (let cc of cset.concept || []) {
|
|
1573
|
+
this.worker.deadCheck('checkConceptSet#1');
|
|
1574
|
+
let c = cc.code;
|
|
1575
|
+
if (code === c) {
|
|
1576
|
+
let loc = await cs.locate(code);
|
|
1577
|
+
if (loc.context !== null) {
|
|
1578
|
+
loc = loc.context;
|
|
1579
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1580
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
|
|
1581
|
+
this.worker.listDisplaysFromIncludeConcept(displays, cc, vs);
|
|
1582
|
+
if (!(abstractOk || !cs.IsAbstract(loc))) {
|
|
1583
|
+
if (!this.params.membershipOnly) {
|
|
1584
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
|
|
1585
|
+
}
|
|
1586
|
+
} else if (this.excludeInactives() && await cs.isInactive(loc)) {
|
|
1587
|
+
result = false;
|
|
1588
|
+
if (!this.params.membershipOnly) {
|
|
1589
|
+
inactive.value = true;
|
|
1590
|
+
inactive.path = path;
|
|
1591
|
+
vstatus.value = await cs.getStatus(loc);
|
|
1592
|
+
}
|
|
1593
|
+
} else {
|
|
1594
|
+
if (vcc !== null) {
|
|
1595
|
+
if (!vcc.coding) {vcc.coding = [];}
|
|
1596
|
+
vcc.coding.push({system: cs.system(), version: cs.version(), code: await cs.code(loc), display: displays.preferredDisplay(this.params.workingLanguages())});
|
|
1597
|
+
}
|
|
1598
|
+
let sstatus = Extensions.readString(cc, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status');
|
|
1599
|
+
if (['withdrawn', 'deprecated'].includes(sstatus)) {
|
|
1600
|
+
op.addIssue(new Issue('warning', 'business-rule', addToPath(path, 'code'), 'CONCEPT_DEPRECATED_IN_VALUESET', this.worker.i18n.translate('CONCEPT_DEPRECATED_IN_VALUESET', this.params.HTTPLanguages, [cs.system(), code, sstatus, vs.vurl]), 'code-comment'));
|
|
1601
|
+
} else if (Extensions.has(cc,'http://hl7.org/fhir/StructureDefinition/valueset-deprecated')) {
|
|
1602
|
+
op.addIssue(new Issue('warning', 'business-rule', addToPath(path, 'code'), 'CONCEPT_DEPRECATED_IN_VALUESET', this.worker.i18n.translate('CONCEPT_DEPRECATED_IN_VALUESET', this.params.HTTPLanguages, [cs.system(), code, 'deprecated', vs.vurl]), 'code-comment'));
|
|
1603
|
+
}
|
|
1604
|
+
inactive.value = await cs.isInactive(loc);
|
|
1605
|
+
inactive.path = path;
|
|
1606
|
+
vstatus.value = await cs.getStatus(loc);
|
|
1607
|
+
result = true;
|
|
1608
|
+
return result;
|
|
1609
|
+
}
|
|
1610
|
+
} else {
|
|
1611
|
+
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" in concept list, but not found in ' + this.worker.renderer.displayCoded(cs)+": "+loc.message, this.indentCount);
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
if (cset.filter) {
|
|
1617
|
+
let cfl = cset.filter;
|
|
1618
|
+
let prep = await cs.getPrepContext(false);
|
|
1619
|
+
for (let fc of cfl) {
|
|
1620
|
+
this.worker.deadCheck('checkConceptSet#2');
|
|
1621
|
+
if (!fc.value) {
|
|
1622
|
+
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]));
|
|
1623
|
+
}
|
|
1624
|
+
await cs.filter(prep, fc.property, fc.op, fc.value);
|
|
1625
|
+
// if (f === null) {
|
|
1626
|
+
// throw new Issue('error', 'not-supported', null, 'FILTER_NOT_UNDERSTOOD', this.worker.i18n.translate('FILTER_NOT_UNDERSTOOD', this.params.HTTPLanguages, [fc.property, fc.op, fc.value, vs.vurl, cs.system()]) + ' (2)', 'vs-invalid');
|
|
1627
|
+
// }
|
|
1628
|
+
}
|
|
1629
|
+
let filters = await cs.executeFilters(prep);
|
|
1630
|
+
if (filters) {
|
|
1631
|
+
let ctxt = filters[0];
|
|
1632
|
+
let loc = await cs.filterLocate(prep, ctxt, code);
|
|
1633
|
+
if (loc != null && !(typeof loc === 'string')) {
|
|
1634
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
|
|
1635
|
+
if (!(abstractOk || !cs.IsAbstract(loc))) {
|
|
1636
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
|
|
1637
|
+
if (!this.params.membershipOnly) {
|
|
1638
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
|
|
1639
|
+
}
|
|
1640
|
+
} else if (this.excludeInactives() && await cs.isInactive(loc)) {
|
|
1641
|
+
result = false;
|
|
1642
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1643
|
+
if (!this.params.membershipOnly) {
|
|
1644
|
+
inactive.value = true;
|
|
1645
|
+
inactive.path = path;
|
|
1646
|
+
vstatus.value = await cs.getStatus(loc);
|
|
1647
|
+
}
|
|
1648
|
+
} else {
|
|
1649
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1650
|
+
if (vcc !== null) {
|
|
1651
|
+
if (!vcc.coding) { vcc.coding = []}
|
|
1652
|
+
vcc.coding.push( { system : cs.system(), version: cs.version(), code: await cs.code(loc), display: displays.preferredDisplay(this.params.workingLanguages())});
|
|
1653
|
+
}
|
|
1654
|
+
result = true;
|
|
1655
|
+
return result;
|
|
1656
|
+
}
|
|
1657
|
+
} else if (loc != null) {
|
|
1658
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+ ": "+loc, this.indentCount);
|
|
1659
|
+
messages.push(loc);
|
|
1660
|
+
} else {
|
|
1661
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1662
|
+
}
|
|
1663
|
+
} else {
|
|
1664
|
+
result = true;
|
|
1665
|
+
let i = 0;
|
|
1666
|
+
for (let fc of cfl) {
|
|
1667
|
+
this.worker.deadCheck('checkConceptSet#3');
|
|
1668
|
+
if (fc.propertyerty === 'concept' && ["is-a", "descendent-of"].includes(fc.op)) {
|
|
1669
|
+
let loc = await cs.locateIsA(code, fc.value, fc.op === "descendent-of");
|
|
1670
|
+
if (loc !== null) {
|
|
1671
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
|
|
1672
|
+
if (!(abstractOk || !cs.IsAbstract(loc))) {
|
|
1673
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter "' + fc.property + '' + fc.op + '' + fc.value + '": Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
|
|
1674
|
+
if (!this.params.membershipOnly) {
|
|
1675
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
|
|
1676
|
+
}
|
|
1677
|
+
} else {
|
|
1678
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter "' + fc.property + fc.op + fc.value + '": Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1679
|
+
if (vcc !== null) {
|
|
1680
|
+
vcc.addCoding(cs.system(), cs.version(), await cs.code(loc), displays.preferredDisplay(this.params.workingLanguages()));
|
|
1681
|
+
}
|
|
1682
|
+
result = true;
|
|
1683
|
+
return result;
|
|
1684
|
+
}
|
|
1685
|
+
} else {
|
|
1686
|
+
result = false;
|
|
1687
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter "' + fc.property + fc.op + fc.value + '": Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1688
|
+
}
|
|
1689
|
+
} else if (fc.property === 'concept' && fc.op === 'is-not-a') {
|
|
1690
|
+
let loc = await cs.locateIsA(code, fc.value);
|
|
1691
|
+
result = loc === null;
|
|
1692
|
+
if (result) {
|
|
1693
|
+
let msg;
|
|
1694
|
+
loc = await cs.locate(code, null, msg);
|
|
1695
|
+
if (loc !== null) {
|
|
1696
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
|
|
1697
|
+
if (!(abstractOk || !cs.IsAbstract(loc))) {
|
|
1698
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + fc.property + fc.op + fc.value + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
|
|
1699
|
+
if (!this.params.membershipOnly) {
|
|
1700
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
|
|
1701
|
+
}
|
|
1702
|
+
} else if (this.excludeInactives() && await cs.isInactive(loc)) {
|
|
1703
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + fc.property + fc.op + fc.value + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1704
|
+
result = false;
|
|
1705
|
+
if (!this.params.membershipOnly) {
|
|
1706
|
+
inactive.value = true;
|
|
1707
|
+
inactive.path = path;
|
|
1708
|
+
vstatus.value = await cs.getStatus(loc);
|
|
1709
|
+
}
|
|
1710
|
+
} else {
|
|
1711
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + fc.property + fc.op + fc.value + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1712
|
+
if (vcc !== null) {
|
|
1713
|
+
vcc.addCoding(cs.system(), cs.version(), await cs.code(loc), displays.preferredDisplay(this.params.workingLanguages()));
|
|
1714
|
+
}
|
|
1715
|
+
result = true;
|
|
1716
|
+
return result;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
} else {
|
|
1720
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + fc.property + fc.op + fc.value + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1721
|
+
}
|
|
1722
|
+
} else {
|
|
1723
|
+
let ctxt = filters[i];
|
|
1724
|
+
result = false;
|
|
1725
|
+
let loc = await cs.filterLocate(prep, ctxt, code);
|
|
1726
|
+
if (!(typeof loc === 'string')) {
|
|
1727
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
|
|
1728
|
+
if (!(abstractOk || !cs.IsAbstract(loc))) {
|
|
1729
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
|
|
1730
|
+
if (!this.params.membershipOnly) {
|
|
1731
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
|
|
1732
|
+
}
|
|
1733
|
+
} else if (this.excludeInactives() && await cs.isInactive(loc)) {
|
|
1734
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1735
|
+
result = false;
|
|
1736
|
+
if (!this.params.membershipOnly) {
|
|
1737
|
+
inactive.value = true;
|
|
1738
|
+
inactive.path = path;
|
|
1739
|
+
vstatus.value = await cs.getStatus(loc);
|
|
1740
|
+
}
|
|
1741
|
+
} else {
|
|
1742
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
|
|
1743
|
+
if (vcc !== null) {
|
|
1744
|
+
vcc.addCoding(cs.system(), cs.version(), await cs.code(loc), displays.preferredDisplay(this.params.workingLanguages()));
|
|
1745
|
+
}
|
|
1746
|
+
result = true;
|
|
1747
|
+
return result;
|
|
1748
|
+
}
|
|
1749
|
+
} else {
|
|
1750
|
+
this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+": "+loc, this.indentCount);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
if (!result) {
|
|
1754
|
+
break;
|
|
1755
|
+
}
|
|
1756
|
+
i++;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
return result;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
async checkExpansion(path, cs, cset, code, abstractOk, displays, vs, message, inactive, vstatus, op) {
|
|
1764
|
+
let result = false;
|
|
1765
|
+
let loc = await cs.locate(code, null, message);
|
|
1766
|
+
result = false;
|
|
1767
|
+
if (loc === null || loc.context == null) {
|
|
1768
|
+
if (!this.params.membershipOnly) {
|
|
1769
|
+
op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version',
|
|
1770
|
+
this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system()), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]), 'invalid-code'));
|
|
1771
|
+
}
|
|
1772
|
+
} else if (!(abstractOk || !cs.IsAbstract(loc.context))) {
|
|
1773
|
+
if (!this.params.membershipOnly) {
|
|
1774
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
|
|
1775
|
+
}
|
|
1776
|
+
} else {
|
|
1777
|
+
result = true;
|
|
1778
|
+
inactive.value = await cs.isInactive(loc.context);
|
|
1779
|
+
inactive.path = path;
|
|
1780
|
+
vstatus.value = await cs.getStatus(loc.context);
|
|
1781
|
+
await this.worker.listDisplaysFromCodeSystem(displays, cs, loc.context);
|
|
1782
|
+
return result;
|
|
1783
|
+
}
|
|
1784
|
+
return result;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
|
|
1788
|
+
excludeInactives() {
|
|
1789
|
+
return this.valueSet.jsonObj.compose && this.valueSet.jsonObj.compose.inactive != undefined && !this.valueSet.jsonObj.compose.inactive;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
function addToPath(path, name) {
|
|
1795
|
+
if (!path) {
|
|
1796
|
+
return name;
|
|
1797
|
+
} else {
|
|
1798
|
+
return path + '.' + name;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
|
|
1803
|
+
function Unknown_Code_in_VersionSCT(url) {
|
|
1804
|
+
if (url === 'http://snomed.info/sct') {
|
|
1805
|
+
return 'Unknown_Code_in_Version_SCT';
|
|
1806
|
+
} else {
|
|
1807
|
+
return 'Unknown_Code_in_Version';
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
function SCTVersion(url, ver) {
|
|
1812
|
+
if (url !== 'http://snomed.info/sct' || !ver) {
|
|
1813
|
+
return '';
|
|
1814
|
+
} else {
|
|
1815
|
+
let result = 'unknown';
|
|
1816
|
+
let s = ver.split('/');
|
|
1817
|
+
if (s.length >= 5) {
|
|
1818
|
+
if (s[4] === '900000000000207008') result = 'International Edition';
|
|
1819
|
+
else if (s[4] === '449081005') result = 'International Spanish Edition';
|
|
1820
|
+
else if (s[4] === '11000221109') result = 'Argentinian Edition';
|
|
1821
|
+
else if (s[4] === '32506021000036107') result = 'Australian Edition (with drug extension)';
|
|
1822
|
+
else if (s[4] === '11000234105') result = 'Austrian Edition';
|
|
1823
|
+
else if (s[4] === '11000172109') result = 'Belgian Edition';
|
|
1824
|
+
else if (s[4] === '20621000087109') result = 'Canadian English Edition';
|
|
1825
|
+
else if (s[4] === '20611000087101') result = 'Canadian Canadian French Edition';
|
|
1826
|
+
else if (s[4] === '11000279109') result = 'Czech Edition';
|
|
1827
|
+
else if (s[4] === '554471000005108') result = 'Danish Edition';
|
|
1828
|
+
else if (s[4] === '11000181102') result = 'Estonian Edition';
|
|
1829
|
+
else if (s[4] === '11000229106') result = 'Finnish Edition';
|
|
1830
|
+
else if (s[4] === '11000274103') result = 'German Edition';
|
|
1831
|
+
else if (s[4] === '1121000189102') result = 'Indian Edition';
|
|
1832
|
+
else if (s[4] === '827022005') result = 'IPS Terminology';
|
|
1833
|
+
else if (s[4] === '11000220105') result = 'Irish Edition';
|
|
1834
|
+
else if (s[4] === '11000146104') result = 'Netherlands Edition';
|
|
1835
|
+
else if (s[4] === '21000210109') result = 'New Zealand Edition';
|
|
1836
|
+
else if (s[4] === '51000202101') result = 'Norwegian Edition';
|
|
1837
|
+
else if (s[4] === '11000267109') result = 'Republic of Korea Edition (South Korea)';
|
|
1838
|
+
else if (s[4] === '900000001000122104') result = 'Spanish National Edition';
|
|
1839
|
+
else if (s[4] === '45991000052106') result = 'Swedish Edition';
|
|
1840
|
+
else if (s[4] === '2011000195101') result = 'Swiss Edition';
|
|
1841
|
+
else if (s[4] === '83821000000107') result = 'UK Edition';
|
|
1842
|
+
else if (s[4] === '999000021000000109') result = 'UK Clinical Edition';
|
|
1843
|
+
else if (s[4] === '5631000179106') result = 'Uruguay Edition';
|
|
1844
|
+
else if (s[4] === '21000325107') result = 'Chilean Edition';
|
|
1845
|
+
else if (s[4] === '731000124108') result = 'US Edition';
|
|
1846
|
+
else if (s[4] === '5991000124107') result = 'US Edition (with ICD-10-CM maps)';
|
|
1847
|
+
}
|
|
1848
|
+
return result;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
|
|
1853
|
+
function toText(st, sep) {
|
|
1854
|
+
if (st === null || st.length === 0) {
|
|
1855
|
+
return '';
|
|
1856
|
+
} else {
|
|
1857
|
+
let result = st[0];
|
|
1858
|
+
for (let i = 1; i < st.length; i++) {
|
|
1859
|
+
result = result + sep + st[i];
|
|
1860
|
+
}
|
|
1861
|
+
return result;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
class ValidateWorker extends TerminologyWorker {
|
|
1866
|
+
|
|
1867
|
+
/**
|
|
1868
|
+
* @param {OperationContext} opContext - Operation context
|
|
1869
|
+
* @param {Logger} log - Logger instance
|
|
1870
|
+
* @param {Provider} provider - Provider for code systems and resources
|
|
1871
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
1872
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
1873
|
+
*/
|
|
1874
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
1875
|
+
super(opContext, log, provider, languages, i18n);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
/**
|
|
1879
|
+
* Get operation name
|
|
1880
|
+
* @returns {string}
|
|
1881
|
+
*/
|
|
1882
|
+
opName() {
|
|
1883
|
+
return 'validate-code';
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// ========== Entry Points ==========
|
|
1887
|
+
|
|
1888
|
+
/**
|
|
1889
|
+
* Handle a type-level CodeSystem $validate-code request
|
|
1890
|
+
* GET/POST /CodeSystem/$validate-code
|
|
1891
|
+
*/
|
|
1892
|
+
async handleCodeSystem(req, res) {
|
|
1893
|
+
try {
|
|
1894
|
+
const params = this.buildParameters(req);
|
|
1895
|
+
this.addHttpParams(req, params);
|
|
1896
|
+
this.log.debug('CodeSystem $validate-code with params:', params);
|
|
1897
|
+
|
|
1898
|
+
let result = await this.handleCodeSystemInner(params);
|
|
1899
|
+
|
|
1900
|
+
return res.status(200).json(result);
|
|
1901
|
+
|
|
1902
|
+
} catch (error) {
|
|
1903
|
+
this.log.error(error);
|
|
1904
|
+
console.error(error);
|
|
1905
|
+
if (error instanceof Issue) {
|
|
1906
|
+
if (error.isHandleAsOO()) {
|
|
1907
|
+
let oo = new OperationOutcome();
|
|
1908
|
+
oo.addIssue(error);
|
|
1909
|
+
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
1910
|
+
} else {
|
|
1911
|
+
// this is actually handled in the inner method
|
|
1912
|
+
}
|
|
1913
|
+
} else {
|
|
1914
|
+
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
1915
|
+
'error', error.issueCode || 'exception', error.message));
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
async handleCodeSystemInner(params, req) {
|
|
1922
|
+
let coded;
|
|
1923
|
+
let mode;
|
|
1924
|
+
|
|
1925
|
+
try {
|
|
1926
|
+
// Handle tx-resource and cache-id parameters
|
|
1927
|
+
this.setupAdditionalResources(params);
|
|
1928
|
+
|
|
1929
|
+
let txp = new TxParameters(this.languages, this.i18n, true);
|
|
1930
|
+
txp.readParams(params);
|
|
1931
|
+
|
|
1932
|
+
// Extract coded value
|
|
1933
|
+
mode = {mode: null};
|
|
1934
|
+
coded = this.extractCodedValue(params, true, mode);
|
|
1935
|
+
if (!coded) {
|
|
1936
|
+
throw new Issue('error', 'invalid', null, null, 'Unable to find code to validate (looked for coding | codeableConcept | code in parameters =codingX:Coding)', null, 400);
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// Get the CodeSystem - from parameter or by url
|
|
1940
|
+
const codeSystem = await this.resolveCodeSystem(params, txp, coded?.coding?.[0] ?? null, mode);
|
|
1941
|
+
if (!codeSystem) {
|
|
1942
|
+
throw new Issue('error', 'invalid', null, null, 'No CodeSystem specified - provide url parameter or codeSystem resource', null, 400);
|
|
1943
|
+
}
|
|
1944
|
+
if (codeSystem.contentMode() == 'supplement') {
|
|
1945
|
+
throw new Issue('error', 'invalid', this.systemPath(mode), 'CODESYSTEM_CS_NO_SUPPLEMENT', this.opContext.i18n.translate('CODESYSTEM_CS_NO_SUPPLEMENT', txp.HTTPLanguages, [codeSystem.vurl()]), "invalid-data");
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// Perform validation
|
|
1949
|
+
const result = await this.doValidationCS(coded, codeSystem, txp, mode);
|
|
1950
|
+
if (req) {
|
|
1951
|
+
req.logInfo = this.usedSources.join("|") + txp.logInfo();
|
|
1952
|
+
}
|
|
1953
|
+
return result;
|
|
1954
|
+
} catch (error) {
|
|
1955
|
+
this.log.error(error);
|
|
1956
|
+
if (error instanceof Issue && !error.isHandleAsOO()) {
|
|
1957
|
+
return this.handlePrepareError(error, coded, mode.mode);
|
|
1958
|
+
} else {
|
|
1959
|
+
throw error;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
}
|
|
1966
|
+
systemPath(mode) {
|
|
1967
|
+
switch (mode.mode) {
|
|
1968
|
+
case 'code': return 'system';
|
|
1969
|
+
case 'coding': return 'Coding.system';
|
|
1970
|
+
default: return 'CodeableConcept.coding[0].system';
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/**
|
|
1975
|
+
* Handle an instance-level CodeSystem $validate-code request
|
|
1976
|
+
* GET/POST /CodeSystem/{id}/$validate-code
|
|
1977
|
+
*/
|
|
1978
|
+
async handleCodeSystemInstance(req, res) {
|
|
1979
|
+
try {
|
|
1980
|
+
const {id} = req.params;
|
|
1981
|
+
const params = this.buildParameters(req);
|
|
1982
|
+
this.log.debug(`CodeSystem/${id}/$validate-code with params:`, params);
|
|
1983
|
+
|
|
1984
|
+
// Handle tx-resource and cache-id parameters
|
|
1985
|
+
this.setupAdditionalResources(params);
|
|
1986
|
+
|
|
1987
|
+
let txp = new TxParameters(this.languages, this.i18n, true);
|
|
1988
|
+
txp.readParams(params);
|
|
1989
|
+
|
|
1990
|
+
// Get the CodeSystem by id
|
|
1991
|
+
const codeSystem = await this.provider.getCodeSystemById(this.opContext, id);
|
|
1992
|
+
if (!codeSystem) {
|
|
1993
|
+
return res.status(404).json(this.operationOutcome('error', 'not-found',
|
|
1994
|
+
`CodeSystem/${id} not found`));
|
|
1995
|
+
}
|
|
1996
|
+
const csp = new FhirCodeSystemProvider(this.opContext, new CodeSystem(codeSystem), []);
|
|
1997
|
+
|
|
1998
|
+
// Extract coded value
|
|
1999
|
+
let mode = { mode : null }
|
|
2000
|
+
const coded = this.extractCodedValue(params, true, mode);
|
|
2001
|
+
if (!coded) {
|
|
2002
|
+
return res.status(400).json(this.operationOutcome('error', 'invalid',
|
|
2003
|
+
'Unable to find code to validate (looked for coding | codeableConcept | code in parameters =codingX:Coding)'));
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
// Perform validation
|
|
2007
|
+
const result = await this.doValidationCS(coded, csp, txp, mode);
|
|
2008
|
+
req.logInfo = this.usedSources.join("|") + txp.logInfo();
|
|
2009
|
+
return res.json(result);
|
|
2010
|
+
|
|
2011
|
+
} catch (error) {
|
|
2012
|
+
this.log.error(error);
|
|
2013
|
+
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
2014
|
+
'error', error.issueCode || 'exception', error.message));
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
/**
|
|
2019
|
+
* Handle a type-level ValueSet $validate-code request
|
|
2020
|
+
* GET/POST /ValueSet/$validate-code
|
|
2021
|
+
*/
|
|
2022
|
+
async handleValueSet(req, res) {
|
|
2023
|
+
try {
|
|
2024
|
+
const params = this.buildParameters(req);
|
|
2025
|
+
this.addHttpParams(req, params);
|
|
2026
|
+
this.log.debug('ValueSet $validate-code with params:', params);
|
|
2027
|
+
|
|
2028
|
+
const result = await this.handleValueSetInner(params, req);
|
|
2029
|
+
return res.json(result);
|
|
2030
|
+
|
|
2031
|
+
} catch (error) {
|
|
2032
|
+
this.log.error(error);
|
|
2033
|
+
if (error instanceof Issue) {
|
|
2034
|
+
let op = new OperationOutcome();
|
|
2035
|
+
op.addIssue(error);
|
|
2036
|
+
return res.status(error.statusCode || 500).json(op.jsonObj);
|
|
2037
|
+
} else {
|
|
2038
|
+
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
2039
|
+
'error', error.issueCode || 'exception', error.message));
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
async handleValueSetInner(params, req) {
|
|
2045
|
+
// Handle tx-resource and cache-id parameters
|
|
2046
|
+
this.setupAdditionalResources(params);
|
|
2047
|
+
|
|
2048
|
+
let txp = new TxParameters(this.languages, this.i18n, true);
|
|
2049
|
+
txp.readParams(params);
|
|
2050
|
+
|
|
2051
|
+
// Get the ValueSet - from parameter or by url
|
|
2052
|
+
const valueSet = await this.resolveValueSet(params, txp);
|
|
2053
|
+
if (!valueSet) {
|
|
2054
|
+
throw new Issue("error", "invalid", null, null, 'No ValueSet specified - provide url parameter or valueSet resource', null, 400);
|
|
2055
|
+
}
|
|
2056
|
+
// Extract coded value
|
|
2057
|
+
|
|
2058
|
+
let mode = { mode : null };
|
|
2059
|
+
const coded = this.extractCodedValue(params, false, mode);
|
|
2060
|
+
if (!coded) {
|
|
2061
|
+
throw new Issue("error", "invalid", null, null, 'Unable to find code to validate (looked for coding | codeableConcept | code in parameters =codingX:Coding)', null, 400);
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// Perform validation
|
|
2065
|
+
let res = await this.doValidationVS(coded, valueSet, txp, mode.mode, mode.issuePath);
|
|
2066
|
+
if (req) {
|
|
2067
|
+
req.logInfo = this.usedSources.join("|") + txp.logInfo();
|
|
2068
|
+
}
|
|
2069
|
+
return res;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Handle an instance-level ValueSet $validate-code request
|
|
2073
|
+
* GET/POST /ValueSet/{id}/$validate-code
|
|
2074
|
+
*/
|
|
2075
|
+
async handleValueSetInstance(req, res) {
|
|
2076
|
+
try {
|
|
2077
|
+
const {id} = req.params;
|
|
2078
|
+
const params = this.buildParameters(req);
|
|
2079
|
+
this.log.debug(`ValueSet/${id}/$validate-code with params:`, params);
|
|
2080
|
+
|
|
2081
|
+
// Handle tx-resource and cache-id parameters
|
|
2082
|
+
this.setupAdditionalResources(params);
|
|
2083
|
+
|
|
2084
|
+
let txp = new TxParameters(this.languages, this.i18n, true);
|
|
2085
|
+
txp.readParams(params);
|
|
2086
|
+
|
|
2087
|
+
// Get the ValueSet by id
|
|
2088
|
+
const valueSet = await this.provider.getValueSetById(this.opContext, id);
|
|
2089
|
+
this.seeSourceVS(valueSet, id);
|
|
2090
|
+
if (!valueSet) {
|
|
2091
|
+
return res.status(404).json(this.operationOutcome('error', 'not-found',
|
|
2092
|
+
`ValueSet/${id} not found`));
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
// Extract coded value
|
|
2096
|
+
let mode = { mode : null };
|
|
2097
|
+
const coded = this.extractCodedValue(params, false, mode);
|
|
2098
|
+
if (!coded) {
|
|
2099
|
+
return res.status(400).json(this.operationOutcome('error', 'invalid',
|
|
2100
|
+
'Unable to find code to validate (looked for coding | codeableConcept | code in parameters =codingX:Coding)'));
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// Perform validation
|
|
2104
|
+
const result = await this.doValidationVS(coded, valueSet, txp, mode.mode, mode.issuePath);
|
|
2105
|
+
req.logInfo = this.usedSources.join("|")+txp.logInfo();
|
|
2106
|
+
return res.json(result);
|
|
2107
|
+
|
|
2108
|
+
} catch (error) {
|
|
2109
|
+
this.log.error(error);
|
|
2110
|
+
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
2111
|
+
'error', error.issueCode || 'exception', error.message));
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
// ========== Resource Resolution ==========
|
|
2116
|
+
|
|
2117
|
+
/**
|
|
2118
|
+
* Resolve the CodeSystem to validate against
|
|
2119
|
+
* @param {Object} params - Parameters resource
|
|
2120
|
+
* @param {string|null} id - Instance id (if instance-level request)
|
|
2121
|
+
* @returns {Object|null} CodeSystem resource (wrapper or JSON)
|
|
2122
|
+
*/
|
|
2123
|
+
async resolveCodeSystem(params, txParams, coded, mode) {
|
|
2124
|
+
// Check for codeSystem resource parameter
|
|
2125
|
+
const csResource = this.getResourceParam(params, 'codeSystem');
|
|
2126
|
+
if (csResource) {
|
|
2127
|
+
return csResource;
|
|
2128
|
+
}
|
|
2129
|
+
let path = coded == null ? null : mode.issuePath+".system";
|
|
2130
|
+
let fromCoded = false;
|
|
2131
|
+
// Check for url parameter
|
|
2132
|
+
let url = this.getStringParam(params, 'url');
|
|
2133
|
+
if (!url && coded.system) {
|
|
2134
|
+
fromCoded = true;
|
|
2135
|
+
url = coded.system;
|
|
2136
|
+
}
|
|
2137
|
+
if (!url) {
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
let issue = null;
|
|
2142
|
+
if (!isAbsoluteUrl(url)) {
|
|
2143
|
+
let m = this.i18n.translate('Terminology_TX_System_Relative', txParams.HTTPLanguages, [url]);
|
|
2144
|
+
issue = new Issue('error', 'invalid', path, 'Terminology_TX_System_Relative', m, 'invalid-data');
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
let version = this.getStringParam(params, 'version');
|
|
2148
|
+
if (!version && fromCoded) {
|
|
2149
|
+
version = coded.version;
|
|
2150
|
+
}
|
|
2151
|
+
version = this.determineVersionBase(url, version, txParams);
|
|
2152
|
+
|
|
2153
|
+
let supplements = this.loadSupplements(url, version);
|
|
2154
|
+
|
|
2155
|
+
// First check additional resources
|
|
2156
|
+
const fromAdditional = this.findInAdditionalResources(url, version, 'CodeSystem', false);
|
|
2157
|
+
if (fromAdditional) {
|
|
2158
|
+
return this.provider.createCodeSystemProvider(this.opContext, fromAdditional, supplements);
|
|
2159
|
+
} else {
|
|
2160
|
+
|
|
2161
|
+
let csp = await this.provider.getCodeSystemProvider(this.opContext, url, version, supplements);
|
|
2162
|
+
if (csp) {
|
|
2163
|
+
return csp;
|
|
2164
|
+
} else {
|
|
2165
|
+
let vs = await this.findValueSet(url, version);
|
|
2166
|
+
if (vs) {
|
|
2167
|
+
let msg = this.i18n.translate('Terminology_TX_System_ValueSet2', txParams.HTTPLanguages, [url]);
|
|
2168
|
+
throw new Issue('error', 'invalid', path, 'Terminology_TX_System_ValueSet2', msg, 'invalid-data');
|
|
2169
|
+
} else if (version) {
|
|
2170
|
+
let vl = await this.listVersions(url);
|
|
2171
|
+
if (vl.length == 0) {
|
|
2172
|
+
throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM_VERSION_NONE', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM_VERSION_NONE', this.opContext.HTTPLanguages, [url, version]), 'not-found').setUnknownSystem(url).addIssue(issue);
|
|
2173
|
+
} else {
|
|
2174
|
+
throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM_VERSION', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM_VERSION', this.opContext.HTTPLanguages, [url, version, this.presentVersionList(vl)]), 'not-found').setUnknownSystem(url + "|" + version).addIssue(issue);
|
|
2175
|
+
}
|
|
2176
|
+
} else {
|
|
2177
|
+
throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM', this.opContext.HTTPLanguages, [url]), 'not-found').setUnknownSystem(url).addIssue(issue);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
/**
|
|
2184
|
+
* Resolve the ValueSet to validate against
|
|
2185
|
+
* @param {Object} params - Parameters resource
|
|
2186
|
+
* @param {string|null} id - Instance id (if instance-level request)
|
|
2187
|
+
* @returns {Object|null} ValueSet resource (wrapper or JSON)
|
|
2188
|
+
*/
|
|
2189
|
+
async resolveValueSet(params, txParams) {
|
|
2190
|
+
// Check for valueSet resource parameter
|
|
2191
|
+
const vsResource = this.getResourceParam(params, 'valueSet');
|
|
2192
|
+
if (vsResource) {
|
|
2193
|
+
this.seeSourceVS(vsResource);
|
|
2194
|
+
return new ValueSet(vsResource);
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
// Check for url parameter
|
|
2198
|
+
const url = this.getStringParam(params, 'url');
|
|
2199
|
+
if (url) {
|
|
2200
|
+
const version = this.determineVersionBase(url, this.getStringParam(params, 'valueSetVersion'), txParams);
|
|
2201
|
+
|
|
2202
|
+
// First check additional resources
|
|
2203
|
+
const fromAdditional = this.findInAdditionalResources(url, version, 'ValueSet', false);
|
|
2204
|
+
if (fromAdditional) {
|
|
2205
|
+
return fromAdditional;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
let vs = await this.provider.findValueSet(this.opContext, url, version);
|
|
2209
|
+
this.seeSourceVS(vs, url);
|
|
2210
|
+
if (vs == null) {
|
|
2211
|
+
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', 400);
|
|
2212
|
+
} else {
|
|
2213
|
+
return vs;
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
return null;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// ========== Coded Value Extraction ==========
|
|
2221
|
+
|
|
2222
|
+
/**
|
|
2223
|
+
* Extract the coded value to validate as a CodeableConcept
|
|
2224
|
+
* @param {Object} params - Parameters resource
|
|
2225
|
+
* @param {string} mode - 'cs' for CodeSystem, 'vs' for ValueSet
|
|
2226
|
+
* @returns {Object|null} CodeableConcept or null
|
|
2227
|
+
*/
|
|
2228
|
+
extractCodedValue(params, isCs, mode) {
|
|
2229
|
+
// Priority 1: codeableConcept parameter
|
|
2230
|
+
const cc = this.getCodeableConceptParam(params, 'codeableConcept');
|
|
2231
|
+
if (cc) {
|
|
2232
|
+
mode.mode = 'codeableConcept';
|
|
2233
|
+
mode.issuePath = "CodeableConcept";
|
|
2234
|
+
return cc;
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// Priority 2: coding parameter
|
|
2238
|
+
const coding = this.getCodingParam(params, 'coding');
|
|
2239
|
+
if (coding) {
|
|
2240
|
+
mode.mode = 'coding';
|
|
2241
|
+
mode.issuePath = "Coding";
|
|
2242
|
+
return {coding: [coding]};
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// Priority 3: individual parameters (code required)
|
|
2246
|
+
const code = this.getStringParam(params, 'code');
|
|
2247
|
+
if (code) {
|
|
2248
|
+
mode.mode = 'code';
|
|
2249
|
+
mode.issuePath = "";
|
|
2250
|
+
// For CodeSystem mode: url/version
|
|
2251
|
+
// For ValueSet mode: system/systemVersion
|
|
2252
|
+
let system, version;
|
|
2253
|
+
if (isCs) {
|
|
2254
|
+
system = this.getStringParam(params, 'url');
|
|
2255
|
+
if (!system) {
|
|
2256
|
+
system = this.getStringParam(params, 'system');
|
|
2257
|
+
|
|
2258
|
+
}
|
|
2259
|
+
version = this.getStringParam(params, 'version');
|
|
2260
|
+
} else {
|
|
2261
|
+
system = this.getStringParam(params, 'system');
|
|
2262
|
+
version = this.getStringParam(params, 'systemVersion');
|
|
2263
|
+
}
|
|
2264
|
+
const display = this.getStringParam(params, 'display');
|
|
2265
|
+
|
|
2266
|
+
const codingObj = {code};
|
|
2267
|
+
if (system) codingObj.system = system;
|
|
2268
|
+
if (version) codingObj.version = version;
|
|
2269
|
+
if (display) codingObj.display = display;
|
|
2270
|
+
|
|
2271
|
+
return {coding: [codingObj]};
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
return null;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
// ========== Validation Logic ==========
|
|
2278
|
+
|
|
2279
|
+
/**
|
|
2280
|
+
* Perform CodeSystem validation
|
|
2281
|
+
* @param {Object} coded - CodeableConcept to validate
|
|
2282
|
+
* @param {Object} codeSystem - CodeSystem to validate against
|
|
2283
|
+
* @param {Object} params - Full parameters
|
|
2284
|
+
* @returns {Object} Parameters resource with result
|
|
2285
|
+
*/
|
|
2286
|
+
async doValidationCS(coded, codeSystem, params, mode) {
|
|
2287
|
+
this.deadCheck('doValidationCS');
|
|
2288
|
+
this.params = params;
|
|
2289
|
+
|
|
2290
|
+
let vs = this.makeVsForCS(codeSystem);
|
|
2291
|
+
|
|
2292
|
+
// Get parameters
|
|
2293
|
+
const abstractOk = this._getBoolParam(params, 'abstract', true);
|
|
2294
|
+
|
|
2295
|
+
// Create and prepare checker
|
|
2296
|
+
const checker = new ValueSetChecker(this, vs, params);
|
|
2297
|
+
|
|
2298
|
+
// Perform validation
|
|
2299
|
+
const result = await checker.checkCodeableConcept(mode.issuePath, coded, abstractOk, false, mode.mode);
|
|
2300
|
+
|
|
2301
|
+
// Add diagnostics if requested
|
|
2302
|
+
if (params.diagnostics) {
|
|
2303
|
+
result.jsonObj.parameter.push({name: 'diagnostics', valueString: this.opContext.diagnostics()});
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
return result.jsonObj;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
makeVsForCS(codeSystem) {
|
|
2310
|
+
let vs = {
|
|
2311
|
+
resourceType: "ValueSet",
|
|
2312
|
+
internallyDefined : true,
|
|
2313
|
+
status: 'active',
|
|
2314
|
+
url : codeSystem.valueSet() ? codeSystem.valueSet() : codeSystem.system()+"/vs",
|
|
2315
|
+
compose : {
|
|
2316
|
+
"include" : [{
|
|
2317
|
+
system: codeSystem.system()
|
|
2318
|
+
}]
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
if (codeSystem.version()) {
|
|
2322
|
+
vs.version = codeSystem.version();
|
|
2323
|
+
vs.compose.include[0].version = codeSystem.version();
|
|
2324
|
+
}
|
|
2325
|
+
return new ValueSet(vs);
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
/**
|
|
2329
|
+
* Perform ValueSet validation
|
|
2330
|
+
* @param {Object} coded - CodeableConcept to validate
|
|
2331
|
+
* @param {Object} valueSet - ValueSet to validate against
|
|
2332
|
+
* @param {Object} params - Full parameters
|
|
2333
|
+
* @returns {Object} Parameters resource with result
|
|
2334
|
+
*/
|
|
2335
|
+
async doValidationVS(coded, valueSet, params, mode, issuePath) {
|
|
2336
|
+
this.deadCheck('doValidationVS');
|
|
2337
|
+
this.params = params;
|
|
2338
|
+
|
|
2339
|
+
// Get parameters
|
|
2340
|
+
const abstractOk = this._getBoolParam(params, 'abstract', true);
|
|
2341
|
+
const inferSystem = this._getBoolParam(params, 'inferSystem', false) || (mode === 'code' && !coded.coding[0].system)
|
|
2342
|
+
|
|
2343
|
+
// Create and prepare checker
|
|
2344
|
+
const checker = new ValueSetChecker(this, valueSet, params);
|
|
2345
|
+
try {
|
|
2346
|
+
await checker.prepare();
|
|
2347
|
+
} catch (error) {
|
|
2348
|
+
this.log.error(error);
|
|
2349
|
+
if (!(error instanceof Issue) || error.isHandleAsOO()) {
|
|
2350
|
+
throw error;
|
|
2351
|
+
} else {
|
|
2352
|
+
return this.handlePrepareError(error, coded, mode);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
// Perform validation
|
|
2357
|
+
const result = await checker.checkCodeableConcept(issuePath, coded, abstractOk, inferSystem, mode);
|
|
2358
|
+
|
|
2359
|
+
// Add diagnostics if requested
|
|
2360
|
+
if (params.diagnostics) {
|
|
2361
|
+
result.jsonObj.parameter.push({name: 'diagnostics', valueString: this.opContext.diagnostics()});
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
return result.jsonObj;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
/**
|
|
2368
|
+
* Get a boolean parameter value
|
|
2369
|
+
* @private
|
|
2370
|
+
*/
|
|
2371
|
+
_getBoolParam(params, name, defaultValue) {
|
|
2372
|
+
if (!params?.parameter) return defaultValue;
|
|
2373
|
+
const p = params.parameter.find(param => param.name === name);
|
|
2374
|
+
if (!p) return defaultValue;
|
|
2375
|
+
if (p.valueBoolean !== undefined) return p.valueBoolean;
|
|
2376
|
+
if (p.valueString !== undefined) return p.valueString === 'true';
|
|
2377
|
+
return defaultValue;
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
/**
|
|
2381
|
+
* Find a ValueSet by URL
|
|
2382
|
+
* @param {string} url - ValueSet URL
|
|
2383
|
+
* @param {string} [version] - ValueSet version
|
|
2384
|
+
* @returns {Object|null} ValueSet resource or null
|
|
2385
|
+
*/
|
|
2386
|
+
async findValueSet(url, version = null) {
|
|
2387
|
+
// First check additional resources
|
|
2388
|
+
const found = this.findInAdditionalResources(url, version || '', 'ValueSet', false);
|
|
2389
|
+
if (found) {
|
|
2390
|
+
return found;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
// Then check provider
|
|
2394
|
+
return await this.provider.findValueSet(this.opContext, url, version);
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
/**
|
|
2398
|
+
* Get display text for a code (stub implementation for doValidationCS)
|
|
2399
|
+
* @private
|
|
2400
|
+
*/
|
|
2401
|
+
getDisplayForCode(code) {
|
|
2402
|
+
const displays = {
|
|
2403
|
+
'male': 'Male',
|
|
2404
|
+
'female': 'Female',
|
|
2405
|
+
'unknown': 'Unknown',
|
|
2406
|
+
'other': 'Other'
|
|
2407
|
+
};
|
|
2408
|
+
return displays[code] || code;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
/**
|
|
2412
|
+
* Build the validation result Parameters resource
|
|
2413
|
+
*/
|
|
2414
|
+
buildValidationResult(result, message, display, coded) {
|
|
2415
|
+
const parameters = {
|
|
2416
|
+
resourceType: 'Parameters',
|
|
2417
|
+
parameter: [
|
|
2418
|
+
{name: 'result', valueBoolean: result}
|
|
2419
|
+
]
|
|
2420
|
+
};
|
|
2421
|
+
|
|
2422
|
+
if (message) {
|
|
2423
|
+
parameters.parameter.push({name: 'message', valueString: message});
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
if (display && result) {
|
|
2427
|
+
parameters.parameter.push({name: 'display', valueString: display});
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
// Include the code that was validated
|
|
2431
|
+
if (coded.coding && coded.coding.length > 0) {
|
|
2432
|
+
const coding = coded.coding[0];
|
|
2433
|
+
if (coding.code) {
|
|
2434
|
+
parameters.parameter.push({name: 'code', valueCode: coding.code});
|
|
2435
|
+
}
|
|
2436
|
+
if (coding.system) {
|
|
2437
|
+
parameters.parameter.push({name: 'system', valueUri: coding.system});
|
|
2438
|
+
}
|
|
2439
|
+
if (coding.version) {
|
|
2440
|
+
parameters.parameter.push({name: 'version', valueString: coding.version});
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
return parameters;
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
/**
|
|
2448
|
+
* Build an OperationOutcome
|
|
2449
|
+
*/
|
|
2450
|
+
operationOutcome(severity, code, message) {
|
|
2451
|
+
return {
|
|
2452
|
+
resourceType: 'OperationOutcome',
|
|
2453
|
+
issue: [{
|
|
2454
|
+
severity,
|
|
2455
|
+
code,
|
|
2456
|
+
details: {
|
|
2457
|
+
text: message
|
|
2458
|
+
},
|
|
2459
|
+
diagnostics: message
|
|
2460
|
+
}]
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
|
|
2465
|
+
handlePrepareError(error, coded, mode) {
|
|
2466
|
+
let op = new OperationOutcome();
|
|
2467
|
+
op.addIssue(error);
|
|
2468
|
+
let p = new Parameters();
|
|
2469
|
+
p.addParamResource('issues', op.jsonObj);
|
|
2470
|
+
p.addParamBool('result', false);
|
|
2471
|
+
p.addParamStr('message', error.message);
|
|
2472
|
+
if (mode == 'codeableConcept') {
|
|
2473
|
+
p.addParam('codeableConcept', 'valueCodeableConcept', coded);
|
|
2474
|
+
} else if (coded.coding) {
|
|
2475
|
+
if (coded.coding[0].system) {
|
|
2476
|
+
p.addParamUri('system', coded.coding[0].system)
|
|
2477
|
+
}
|
|
2478
|
+
if (coded.coding[0].version) {
|
|
2479
|
+
p.addParamStr('version', coded.coding[0].version)
|
|
2480
|
+
}
|
|
2481
|
+
if (coded.coding[0].code) {
|
|
2482
|
+
p.addParamCode('code', coded.coding[0].code)
|
|
2483
|
+
}
|
|
2484
|
+
if (coded.coding[0].display) {
|
|
2485
|
+
p.addParamStr('display', coded.coding[0].display)
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
if (error.unknownSystem) {
|
|
2489
|
+
p.addParamCanonical("x-caused-by-unknown-system", error.unknownSystem);
|
|
2490
|
+
}
|
|
2491
|
+
return p.jsonObj;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
isValidating() {
|
|
2495
|
+
return true;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
module.exports = {
|
|
2501
|
+
ValidateWorker,
|
|
2502
|
+
ValueSetChecker,
|
|
2503
|
+
ValidationCheckMode,
|
|
2504
|
+
};
|