fhirsmith 0.6.0 → 0.7.1
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 +36 -0
- package/README.md +15 -4
- package/configurations/projector.json +21 -0
- package/configurations/readme.md +6 -0
- package/library/package-manager.js +0 -2
- package/library/version-utilities.js +85 -0
- package/package.json +1 -1
- package/packages/package-crawler.js +44 -9
- package/packages/packages.js +1 -0
- package/registry/crawler.js +35 -14
- package/registry/registry.js +3 -0
- package/server.js +4 -0
- package/tx/README.md +4 -4
- package/tx/cs/cs-loinc.js +5 -2
- package/tx/cs/cs-provider-api.js +25 -1
- package/tx/cs/cs-provider-list.js +2 -2
- package/tx/html/conceptmap-operations.liquid +19 -0
- package/tx/library/canonical-resource.js +6 -1
- package/tx/library/renderer.js +571 -2
- package/tx/library.js +127 -10
- package/tx/ocl/README.md +236 -0
- package/tx/ocl/cache/cache-paths.cjs +32 -0
- package/tx/ocl/cache/cache-paths.js +2 -0
- package/tx/ocl/cache/cache-utils.cjs +43 -0
- package/tx/ocl/cache/cache-utils.js +2 -0
- package/tx/ocl/cm-ocl.cjs +531 -0
- package/tx/ocl/cm-ocl.js +1 -105
- package/tx/ocl/cs-ocl.cjs +1779 -0
- package/tx/ocl/cs-ocl.js +1 -38
- package/tx/ocl/fingerprint/fingerprint.cjs +67 -0
- package/tx/ocl/fingerprint/fingerprint.js +2 -0
- package/tx/ocl/http/client.cjs +31 -0
- package/tx/ocl/http/client.js +2 -0
- package/tx/ocl/http/pagination.cjs +98 -0
- package/tx/ocl/http/pagination.js +2 -0
- package/tx/ocl/jobs/background-queue.cjs +200 -0
- package/tx/ocl/jobs/background-queue.js +2 -0
- package/tx/ocl/mappers/concept-mapper.cjs +66 -0
- package/tx/ocl/mappers/concept-mapper.js +2 -0
- package/tx/ocl/model/concept-filter-context.cjs +51 -0
- package/tx/ocl/model/concept-filter-context.js +2 -0
- package/tx/ocl/shared/constants.cjs +15 -0
- package/tx/ocl/shared/constants.js +2 -0
- package/tx/ocl/shared/patches.cjs +224 -0
- package/tx/ocl/shared/patches.js +2 -0
- package/tx/ocl/vs-ocl.cjs +1848 -0
- package/tx/ocl/vs-ocl.js +1 -104
- package/tx/operation-context.js +8 -1
- package/tx/params.js +24 -3
- package/tx/provider.js +47 -0
- package/tx/tx-html.js +49 -4
- package/tx/tx.js +8 -0
- package/tx/vs/vs-vsac.js +4 -3
- package/tx/workers/batch-validate.js +3 -2
- package/tx/workers/batch.js +3 -2
- package/tx/workers/expand.js +64 -9
- package/tx/workers/lookup.js +5 -4
- package/tx/workers/read.js +25 -9
- package/tx/workers/related.js +3 -2
- package/tx/workers/search.js +40 -10
- package/tx/workers/subsumes.js +3 -2
- package/tx/workers/translate.js +4 -3
- package/tx/workers/validate.js +143 -46
- package/tx/workers/worker.js +1 -7
package/tx/workers/read.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
|
|
7
7
|
const { TerminologyWorker } = require('./worker');
|
|
8
|
+
const {debugLog} = require("../operation-context");
|
|
8
9
|
|
|
9
10
|
class ReadWorker extends TerminologyWorker {
|
|
10
11
|
/**
|
|
@@ -46,14 +47,7 @@ class ReadWorker extends TerminologyWorker {
|
|
|
46
47
|
return await this.handleValueSet(req, res, id);
|
|
47
48
|
|
|
48
49
|
case 'ConceptMap':
|
|
49
|
-
return
|
|
50
|
-
resourceType: 'OperationOutcome',
|
|
51
|
-
issue: [{
|
|
52
|
-
severity: 'error',
|
|
53
|
-
code: 'not-supported',
|
|
54
|
-
diagnostics: 'ConceptMap read not yet implemented'
|
|
55
|
-
}]
|
|
56
|
-
});
|
|
50
|
+
return await this.handleConceptMap(req, res, id);
|
|
57
51
|
|
|
58
52
|
default:
|
|
59
53
|
return res.status(404).json({
|
|
@@ -67,7 +61,7 @@ class ReadWorker extends TerminologyWorker {
|
|
|
67
61
|
}
|
|
68
62
|
} catch (error) {
|
|
69
63
|
this.log.error(error);
|
|
70
|
-
|
|
64
|
+
debugLog(error);
|
|
71
65
|
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
72
66
|
return res.status(500).json({
|
|
73
67
|
resourceType: 'OperationOutcome',
|
|
@@ -151,6 +145,28 @@ class ReadWorker extends TerminologyWorker {
|
|
|
151
145
|
}
|
|
152
146
|
}
|
|
153
147
|
|
|
148
|
+
return res.status(404).json({
|
|
149
|
+
resourceType: 'OperationOutcome',
|
|
150
|
+
issue: [{
|
|
151
|
+
severity: 'error',
|
|
152
|
+
code: 'not-found',
|
|
153
|
+
diagnostics: `ValueSet/${id} not found`
|
|
154
|
+
}]
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Handle ConceptMap read
|
|
159
|
+
*/
|
|
160
|
+
async handleConceptMap(req, res, id) {
|
|
161
|
+
// Iterate through valueSetProviders in order
|
|
162
|
+
for (const cmsp of this.provider.conceptMapProviders) {
|
|
163
|
+
this.deadCheck('handleConceptMap-loop');
|
|
164
|
+
const vs = await cmsp.fetchConceptMapById(id);
|
|
165
|
+
if (vs) {
|
|
166
|
+
return res.json(vs.jsonObj);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
154
170
|
return res.status(404).json({
|
|
155
171
|
resourceType: 'OperationOutcome',
|
|
156
172
|
issue: [{
|
package/tx/workers/related.js
CHANGED
|
@@ -16,6 +16,7 @@ const ValueSet = require("../library/valueset");
|
|
|
16
16
|
const {ValueSetExpander} = require("./expand");
|
|
17
17
|
const {SearchFilterText} = require("../library/designations");
|
|
18
18
|
const {ArrayMatcher} = require("../../library/utilities");
|
|
19
|
+
const {debugLog} = require("../operation-context");
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class RelatedWorker extends TerminologyWorker {
|
|
@@ -49,7 +50,7 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
49
50
|
await this.handleTypeLevelRelated(req, res);
|
|
50
51
|
} catch (error) {
|
|
51
52
|
this.log.error(error);
|
|
52
|
-
|
|
53
|
+
debugLog(error);
|
|
53
54
|
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
54
55
|
const statusCode = error.statusCode || 500;
|
|
55
56
|
if (error instanceof Issue) {
|
|
@@ -84,7 +85,7 @@ class RelatedWorker extends TerminologyWorker {
|
|
|
84
85
|
await this.handleInstanceLevelRelated(req, res);
|
|
85
86
|
} catch (error) {
|
|
86
87
|
this.log.error(error);
|
|
87
|
-
|
|
88
|
+
debugLog(error);
|
|
88
89
|
req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
|
|
89
90
|
const statusCode = error.statusCode || 500;
|
|
90
91
|
const issueCode = error.issueCode || 'exception';
|
package/tx/workers/search.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const { TerminologyWorker } = require('./worker');
|
|
9
9
|
const {Utilities} = require("../../library/utilities");
|
|
10
|
+
const {debugLog} = require("../operation-context");
|
|
10
11
|
|
|
11
12
|
class SearchWorker extends TerminologyWorker {
|
|
12
13
|
/**
|
|
@@ -95,7 +96,7 @@ class SearchWorker extends TerminologyWorker {
|
|
|
95
96
|
|
|
96
97
|
case 'ConceptMap':
|
|
97
98
|
// Not implemented yet - return empty set
|
|
98
|
-
matches =
|
|
99
|
+
matches = await this.searchConceptMaps(params, elements);
|
|
99
100
|
break;
|
|
100
101
|
|
|
101
102
|
default:
|
|
@@ -114,7 +115,7 @@ class SearchWorker extends TerminologyWorker {
|
|
|
114
115
|
|
|
115
116
|
} catch (error) {
|
|
116
117
|
this.log.error(error);
|
|
117
|
-
|
|
118
|
+
debugLog(error);
|
|
118
119
|
req.logInfo = "error "+(error.msgId || error.className);
|
|
119
120
|
return res.status(500).json({
|
|
120
121
|
resourceType: 'OperationOutcome',
|
|
@@ -146,9 +147,7 @@ class SearchWorker extends TerminologyWorker {
|
|
|
146
147
|
|
|
147
148
|
for (const [key, cs] of this.provider.codeSystems) {
|
|
148
149
|
this.deadCheck('searchCodeSystems');
|
|
149
|
-
|
|
150
|
-
console.log("debug");
|
|
151
|
-
}
|
|
150
|
+
|
|
152
151
|
if (key == cs.vurl) {
|
|
153
152
|
const json = cs.jsonObj;
|
|
154
153
|
|
|
@@ -160,10 +159,6 @@ class SearchWorker extends TerminologyWorker {
|
|
|
160
159
|
// Check each search parameter for partial match
|
|
161
160
|
let isMatch = true;
|
|
162
161
|
for (const [param, searchValue] of Object.entries(searchParams)) {
|
|
163
|
-
// 'system' doesn't do anything for CodeSystem search
|
|
164
|
-
if (param === 'system') {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
162
|
|
|
168
163
|
// Map content-mode to content property
|
|
169
164
|
const jsonProp = param === 'content-mode' ? 'content' : param;
|
|
@@ -180,7 +175,7 @@ class SearchWorker extends TerminologyWorker {
|
|
|
180
175
|
isMatch = false;
|
|
181
176
|
break;
|
|
182
177
|
}
|
|
183
|
-
} else if (param === 'url') { // exact match
|
|
178
|
+
} else if (param === 'url' || param === 'system') { // exact match
|
|
184
179
|
const propValue = json.url;
|
|
185
180
|
if (propValue !== searchValue) {
|
|
186
181
|
isMatch = false;
|
|
@@ -240,6 +235,41 @@ class SearchWorker extends TerminologyWorker {
|
|
|
240
235
|
return allMatches;
|
|
241
236
|
}
|
|
242
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Search ConceptMaps by delegating to providers
|
|
240
|
+
*/
|
|
241
|
+
async searchConceptMaps(params, elements) {
|
|
242
|
+
const allMatches = [];
|
|
243
|
+
|
|
244
|
+
// Convert params object to array format expected by ValueSet providers
|
|
245
|
+
// Exclude control params (_offset, _count, _elements, _sort)
|
|
246
|
+
const searchParams = [];
|
|
247
|
+
let source = null;
|
|
248
|
+
for (const [key, value] of Object.entries(params)) {
|
|
249
|
+
if (!key.startsWith('_') && value && SearchWorker.ALLOWED_PARAMS.includes(key)) {
|
|
250
|
+
searchParams.push({ name: key, value: value });
|
|
251
|
+
}
|
|
252
|
+
if (key == 'source') {
|
|
253
|
+
source = value;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const cmsp of this.provider.conceptMapProviders) {
|
|
258
|
+
if (!source || source == cmsp.sourcePackage()) {
|
|
259
|
+
this.deadCheck('searchConceptMaps-providers');
|
|
260
|
+
const results = await cmsp.searchConceptMaps(searchParams, elements);
|
|
261
|
+
if (results && Array.isArray(results)) {
|
|
262
|
+
for (const vs of results) {
|
|
263
|
+
this.deadCheck('searchConceptMaps-results');
|
|
264
|
+
allMatches.push(vs.jsonObj || vs);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return allMatches;
|
|
271
|
+
}
|
|
272
|
+
|
|
243
273
|
/**
|
|
244
274
|
* Check if a value matches the search term (partial, case-insensitive)
|
|
245
275
|
*/
|
package/tx/workers/subsumes.js
CHANGED
|
@@ -12,6 +12,7 @@ const { FhirCodeSystemProvider } = require('../cs/cs-cs');
|
|
|
12
12
|
const {TxParameters} = require("../params");
|
|
13
13
|
const {Parameters} = require("../library/parameters");
|
|
14
14
|
const {Issue, OperationOutcome} = require("../library/operation-outcome");
|
|
15
|
+
const {debugLog} = require("../operation-context");
|
|
15
16
|
class SubsumesWorker extends TerminologyWorker {
|
|
16
17
|
/**
|
|
17
18
|
* @param {OperationContext} opContext - Operation context
|
|
@@ -43,7 +44,7 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
43
44
|
await this.handleTypeLevelSubsumes(req, res);
|
|
44
45
|
} catch (error) {
|
|
45
46
|
this.log.error(error);
|
|
46
|
-
|
|
47
|
+
debugLog(error);
|
|
47
48
|
req.logInfo = "error "+(error.msgId || error.className);
|
|
48
49
|
if (error instanceof Issue) {
|
|
49
50
|
let oo = new OperationOutcome();
|
|
@@ -67,7 +68,7 @@ class SubsumesWorker extends TerminologyWorker {
|
|
|
67
68
|
await this.handleInstanceLevelSubsumes(req, res);
|
|
68
69
|
} catch (error) {
|
|
69
70
|
this.log.error(error);
|
|
70
|
-
|
|
71
|
+
debugLog(error);
|
|
71
72
|
if (error instanceof Issue) {
|
|
72
73
|
let oo = new OperationOutcome();
|
|
73
74
|
oo.addIssue(error);
|
package/tx/workers/translate.js
CHANGED
|
@@ -12,6 +12,7 @@ const { TxParameters } = require('../params');
|
|
|
12
12
|
const { Parameters } = require('../library/parameters');
|
|
13
13
|
const { Issue, OperationOutcome } = require('../library/operation-outcome');
|
|
14
14
|
const {ConceptMap} = require("../library/conceptmap");
|
|
15
|
+
const {debugLog} = require("../operation-context");
|
|
15
16
|
|
|
16
17
|
class TranslateWorker extends TerminologyWorker {
|
|
17
18
|
/**
|
|
@@ -44,7 +45,7 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
44
45
|
await this.handleTypeLevelTranslate(req, res);
|
|
45
46
|
} catch (error) {
|
|
46
47
|
this.log.error(error);
|
|
47
|
-
|
|
48
|
+
debugLog(error);
|
|
48
49
|
if (error instanceof Issue) {
|
|
49
50
|
const oo = new OperationOutcome();
|
|
50
51
|
oo.addIssue(error);
|
|
@@ -67,7 +68,7 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
67
68
|
await this.handleInstanceLevelTranslate(req, res);
|
|
68
69
|
} catch (error) {
|
|
69
70
|
this.log.error(error);
|
|
70
|
-
|
|
71
|
+
debugLog(error);
|
|
71
72
|
if (error instanceof Issue) {
|
|
72
73
|
const oo = new OperationOutcome();
|
|
73
74
|
oo.addIssue(error);
|
|
@@ -410,7 +411,7 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
410
411
|
}
|
|
411
412
|
} catch (error) {
|
|
412
413
|
this.log.error(error);
|
|
413
|
-
|
|
414
|
+
debugLog(error);
|
|
414
415
|
result.push({
|
|
415
416
|
name: 'result',
|
|
416
417
|
valueBoolean: false
|
package/tx/workers/validate.js
CHANGED
|
@@ -23,6 +23,8 @@ const ValueSet = require("../library/valueset");
|
|
|
23
23
|
const {ValueSetExpander} = require("./expand");
|
|
24
24
|
const {FhirCodeSystemProvider} = require("../cs/cs-cs");
|
|
25
25
|
const {CodeSystem} = require("../library/codesystem");
|
|
26
|
+
const {VersionUtilities} = require("../../library/version-utilities");
|
|
27
|
+
const {debugLog} = require("../operation-context");
|
|
26
28
|
|
|
27
29
|
const DEV_IGNORE_VALUESET = false; // todo: what's going on with this (ported from pascal)
|
|
28
30
|
|
|
@@ -123,7 +125,7 @@ class ValueSetChecker {
|
|
|
123
125
|
}
|
|
124
126
|
} catch (error) {
|
|
125
127
|
this.log.error(error);
|
|
126
|
-
|
|
128
|
+
debugLog(error);
|
|
127
129
|
throw new Error('Exception expanding value set in order to infer system: ' + error.message);
|
|
128
130
|
}
|
|
129
131
|
return result;
|
|
@@ -444,7 +446,7 @@ class ValueSetChecker {
|
|
|
444
446
|
return await this.check(issuePath, system, version, code, null, unknownSystems, ver, inactive, normalForm, vstatus, it, op, null, null, contentMode, impliedSystem, ts, msgs, defLang);
|
|
445
447
|
}
|
|
446
448
|
|
|
447
|
-
async check(path, system, version, code, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, params, contentMode, impliedSystem, unkCodes, messages, defLang) {
|
|
449
|
+
async check(path, system, version, code, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, params, contentMode, impliedSystem, unkCodes, messages, defLang, display) {
|
|
448
450
|
defLang.value = new Language('en');
|
|
449
451
|
this.worker.opContext.addNote(this.valueSet, 'Check "' + this.worker.renderer.displayCoded(system, version, code) + '"', this.indentCount);
|
|
450
452
|
|
|
@@ -677,11 +679,28 @@ class ValueSetChecker {
|
|
|
677
679
|
|
|
678
680
|
if (Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose')) {
|
|
679
681
|
result = false;
|
|
680
|
-
|
|
682
|
+
let determinedVersion = undefined;
|
|
683
|
+
if (!version) {
|
|
684
|
+
// if we don't have a fixed version, and we have more than one possible version, we have to pick the version
|
|
685
|
+
// now, by looking to see which version we can find the value in, starting from the most revent.
|
|
686
|
+
let includes = (this.valueSet.jsonObj.compose.include || []).filter(inc => inc.system == system);
|
|
687
|
+
let vset = new Set(includes.map(inc => inc.version).filter(Boolean));
|
|
688
|
+
if (vset.size > 1) {
|
|
689
|
+
determinedVersion = await this.pickApplicableVersion(vset, system, code, display);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const includes = [...(this.valueSet.jsonObj.compose.include || [])];
|
|
693
|
+
includes.sort((a, b) => {
|
|
694
|
+
if (a.system === b.system && a.version && b.version) {
|
|
695
|
+
return -VersionUtilities.compareVersionsGeneral(a.version, b.version);
|
|
696
|
+
}
|
|
697
|
+
return 0;
|
|
698
|
+
});
|
|
699
|
+
for (let cc of includes) {
|
|
681
700
|
this.worker.deadCheck('check#2');
|
|
682
701
|
if (!cc.system) {
|
|
683
702
|
result = true;
|
|
684
|
-
} else if (cc.system === system || system === '%%null%%') {
|
|
703
|
+
} else if ((cc.system === system || system === '%%null%%') && (!determinedVersion || cc.version == determinedVersion) && this.useThisVersion(cc, version)) {
|
|
685
704
|
let v = await this.determineVersion(path, cc.system, cc.version, version, op, unknownSystems, messages);
|
|
686
705
|
let cs = await this.worker.findCodeSystem(system, v, this.params, ["complete", "fragment"], op,true, true, false, this.worker.requiredSupplements);
|
|
687
706
|
if (cs === null) {
|
|
@@ -720,12 +739,12 @@ class ValueSetChecker {
|
|
|
720
739
|
defLang.value = new Language(cs.defLang());
|
|
721
740
|
this.worker.opContext.addNote(this.valueSet, 'CodeSystem found: ' + this.worker.renderer.displayCoded(cs) + ' for ' + this.worker.renderer.displayCoded(cc.system, v), this.indentCount);
|
|
722
741
|
await this.checkCanonicalStatusCS(path, op, cs, this.valueSet);
|
|
723
|
-
ver.value = cs.version();
|
|
724
742
|
this.worker.checkSupplements(cs, cc, this.worker.requiredSupplements, this.worker.usedSupplements);
|
|
725
743
|
contentMode.value = cs.contentMode();
|
|
726
744
|
|
|
727
745
|
let msg = '';
|
|
728
746
|
if ((system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'in', cs, cc, code, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc, messages)) {
|
|
747
|
+
ver.value = cs.version();
|
|
729
748
|
result = true;
|
|
730
749
|
} else {
|
|
731
750
|
result = false;
|
|
@@ -766,7 +785,6 @@ class ValueSetChecker {
|
|
|
766
785
|
}
|
|
767
786
|
await this.checkCanonicalStatus(path, op, cs, this.valueSet);
|
|
768
787
|
this.worker.checkSupplements(cs, cc, this.worker.requiredSupplements, this.worker.usedSupplements);
|
|
769
|
-
ver.value = cs.version();
|
|
770
788
|
contentMode.value = cs.contentMode();
|
|
771
789
|
let msg = '';
|
|
772
790
|
excluded = (system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'not in', cs, cc, code, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc);
|
|
@@ -926,13 +944,13 @@ class ValueSetChecker {
|
|
|
926
944
|
if (inactive.value) {
|
|
927
945
|
result.AddParamBool('inactive', inactive.value);
|
|
928
946
|
if (vstatus.value && vstatus.value !== 'inactive') {
|
|
929
|
-
result.
|
|
947
|
+
result.addParamCode('status', vstatus.value);
|
|
930
948
|
}
|
|
931
949
|
let msg = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, coding.code]);
|
|
932
950
|
messages.push(msg);
|
|
933
951
|
op.addIssue(new Issue('warning', 'business-rule', path, 'INACTIVE_CONCEPT_FOUND', msg, 'code-comment'));
|
|
934
952
|
} else if (vstatus.value.toLowerCase() === 'deprecated') {
|
|
935
|
-
result.
|
|
953
|
+
result.addParamCode('status', vstatus.value);
|
|
936
954
|
let msg = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, coding.code]);
|
|
937
955
|
messages.push(msg);
|
|
938
956
|
op.addIssue(new Issue('warning', 'business-rule', path, 'DEPRECATED_CONCEPT_FOUND', msg, 'code-comment'));
|
|
@@ -1037,7 +1055,8 @@ class ValueSetChecker {
|
|
|
1037
1055
|
if (this.worker.opContext.usageTracker) {
|
|
1038
1056
|
this.worker.opContext.usageTracker.seeConcept(c.system, c.code);
|
|
1039
1057
|
}
|
|
1040
|
-
|
|
1058
|
+
let vsImpliedVersion = this.findVSVersionForSystem(c.system);
|
|
1059
|
+
const csd = await this.worker.findCodeSystem(c.system, vsImpliedVersion, this.params, ['complete', 'fragment'], false, true, false, false, this.worker.requiredSupplements);
|
|
1041
1060
|
this.worker.seeSourceProvider(csd, c.system);
|
|
1042
1061
|
this.worker.deadCheck('check-b#1');
|
|
1043
1062
|
let path;
|
|
@@ -1055,7 +1074,7 @@ class ValueSetChecker {
|
|
|
1055
1074
|
let ver = { value: '' };
|
|
1056
1075
|
let contentMode = { value: null };
|
|
1057
1076
|
let defLang = { value: null };
|
|
1058
|
-
let v = await this.check(path, c.system, c.version, c.code, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, result, contentMode, impliedSystem, ts, mt, defLang);
|
|
1077
|
+
let v = await this.check(path, c.system, c.version, c.code, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, result, contentMode, impliedSystem, ts, mt, defLang, c.display);
|
|
1059
1078
|
if (v === false) {
|
|
1060
1079
|
cause.value = 'code-invalid';
|
|
1061
1080
|
}
|
|
@@ -1329,13 +1348,18 @@ class ValueSetChecker {
|
|
|
1329
1348
|
if (inactive.value) {
|
|
1330
1349
|
result.addParamBool('inactive', inactive.value);
|
|
1331
1350
|
if (vstatus.value && vstatus.value !== 'inactive') {
|
|
1332
|
-
result.
|
|
1351
|
+
result.addParamCode('status', vstatus.value);
|
|
1352
|
+
}
|
|
1353
|
+
if (!['inactive', 'DISCOURAGED'].includes(vstatus.value)) {
|
|
1354
|
+
let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, ['inactive', tcode]);
|
|
1355
|
+
msg(m);
|
|
1356
|
+
op.addIssue(new Issue('warning', 'business-rule', inactive.path, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
|
|
1333
1357
|
}
|
|
1334
1358
|
let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
|
|
1335
1359
|
msg(m);
|
|
1336
1360
|
op.addIssue(new Issue('warning', 'business-rule', inactive.path, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
|
|
1337
1361
|
} else if (vstatus.value && vstatus.value.toLowerCase() === 'deprecated') {
|
|
1338
|
-
result.
|
|
1362
|
+
result.addParamCode('status', 'deprecated');
|
|
1339
1363
|
let m = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
|
|
1340
1364
|
msg(m);
|
|
1341
1365
|
op.addIssue(new Issue('warning', 'business-rule', issuePath, 'DEPRECATED_CONCEPT_FOUND', m, 'code-comment'));
|
|
@@ -1475,13 +1499,13 @@ class ValueSetChecker {
|
|
|
1475
1499
|
if (inactive.value) {
|
|
1476
1500
|
result.addParamBool('inactive', inactive.value);
|
|
1477
1501
|
if (vstatus.value && vstatus.value !== 'inactive') {
|
|
1478
|
-
result.
|
|
1502
|
+
result.addParamCode('status', vstatus.value);
|
|
1479
1503
|
}
|
|
1480
1504
|
let msg = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, code]);
|
|
1481
1505
|
messages.push(msg);
|
|
1482
1506
|
op.addIssue(new Issue('warning', 'business-rule', 'code', 'INACTIVE_CONCEPT_FOUND', msg, 'code-comment'));
|
|
1483
1507
|
} else if (vstatus.value.toLowerCase() === 'deprecated') {
|
|
1484
|
-
result.
|
|
1508
|
+
result.addParamCode('status', vstatus.value);
|
|
1485
1509
|
let msg = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, code]);
|
|
1486
1510
|
messages.push(msg);
|
|
1487
1511
|
op.addIssue(new Issue('warning', 'business-rule', 'code', 'DEPRECATED_CONCEPT_FOUND', msg, 'code-comment'));
|
|
@@ -1509,14 +1533,16 @@ class ValueSetChecker {
|
|
|
1509
1533
|
|
|
1510
1534
|
async checkConceptSet(path, role, cs, cset, code, displays, vs, message, inactive, normalForm, vstatus, op, vcc, messages) {
|
|
1511
1535
|
this.worker.opContext.addNote(vs, 'check code ' + role + ' ' + this.worker.renderer.displayValueSetInclude(cset) + ' at ' + path, this.indentCount);
|
|
1512
|
-
|
|
1536
|
+
if (role !== 'not in') {
|
|
1537
|
+
inactive.value = false;
|
|
1538
|
+
}
|
|
1513
1539
|
let result = false;
|
|
1514
1540
|
if (!cset.concept && !cset.filter) {
|
|
1515
1541
|
let loc = await cs.locate(code);
|
|
1516
1542
|
result = false;
|
|
1517
1543
|
if (loc.context == null) {
|
|
1518
1544
|
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+": "+loc.mesage, this.indentCount);
|
|
1519
|
-
if (!this.params.membershipOnly) {
|
|
1545
|
+
if (!this.params.membershipOnly && role !== 'not in') {
|
|
1520
1546
|
if (cs.contentMode() !== 'complete') {
|
|
1521
1547
|
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'));
|
|
1522
1548
|
result = true;
|
|
@@ -1548,16 +1574,18 @@ class ValueSetChecker {
|
|
|
1548
1574
|
|
|
1549
1575
|
if (!(this.params.abstractOk || !(await cs.isAbstract(loc.context)))) {
|
|
1550
1576
|
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
|
|
1551
|
-
if (!this.params.membershipOnly) {
|
|
1577
|
+
if (!this.params.membershipOnly && role !== 'not in') {
|
|
1552
1578
|
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'));
|
|
1553
1579
|
}
|
|
1554
1580
|
} else if (this.excludeInactives() && await cs.isInactive(loc.context)) {
|
|
1555
1581
|
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1556
|
-
|
|
1557
|
-
|
|
1582
|
+
if (role !== 'not in') {
|
|
1583
|
+
let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
1584
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
1585
|
+
messages.push(msg);
|
|
1586
|
+
}
|
|
1558
1587
|
result = false;
|
|
1559
|
-
|
|
1560
|
-
if (!this.params.membershipOnly) {
|
|
1588
|
+
if (!this.params.membershipOnly && role !== 'not in') {
|
|
1561
1589
|
inactive.value = true;
|
|
1562
1590
|
inactive.path = path;
|
|
1563
1591
|
if (inactive.value) {
|
|
@@ -1567,28 +1595,31 @@ class ValueSetChecker {
|
|
|
1567
1595
|
} else if (this.params.activeOnly && await cs.isInactive(loc.context)) {
|
|
1568
1596
|
this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
|
|
1569
1597
|
result = false;
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1598
|
+
if (role !== 'not in') {
|
|
1599
|
+
inactive.value = true;
|
|
1600
|
+
inactive.path = path;
|
|
1601
|
+
vstatus.value = await cs.getStatus(loc.context);
|
|
1602
|
+
let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
|
|
1603
|
+
messages.push(msg);
|
|
1604
|
+
op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
|
|
1605
|
+
}
|
|
1576
1606
|
} else {
|
|
1577
1607
|
result = true;
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1608
|
+
if (role !== 'not in') {
|
|
1609
|
+
inactive.value = await cs.isInactive(loc.context);
|
|
1610
|
+
inactive.path = path;
|
|
1611
|
+
vstatus.value = await cs.getStatus(loc.context);
|
|
1612
|
+
if (vcc !== null) {
|
|
1613
|
+
if (!vcc.coding) {
|
|
1614
|
+
vcc.coding = [];
|
|
1615
|
+
}
|
|
1616
|
+
vcc.coding.push({
|
|
1617
|
+
system: cs.system(),
|
|
1618
|
+
version: cs.version(),
|
|
1619
|
+
code: await cs.code(loc.context),
|
|
1620
|
+
display: displays.preferredDisplay(this.params.workingLanguages())
|
|
1621
|
+
});
|
|
1585
1622
|
}
|
|
1586
|
-
vcc.coding.push({
|
|
1587
|
-
system: cs.system(),
|
|
1588
|
-
version: cs.version(),
|
|
1589
|
-
code: await cs.code(loc.context),
|
|
1590
|
-
display: displays.preferredDisplay(this.params.workingLanguages())
|
|
1591
|
-
});
|
|
1592
1623
|
}
|
|
1593
1624
|
return result;
|
|
1594
1625
|
}
|
|
@@ -1739,6 +1770,72 @@ class ValueSetChecker {
|
|
|
1739
1770
|
return list.join(",");
|
|
1740
1771
|
}
|
|
1741
1772
|
|
|
1773
|
+
findVSVersionForSystem(system) {
|
|
1774
|
+
let set = new Set();
|
|
1775
|
+
for (let inc of this.valueSet.jsonObj.compose?.include || []) {
|
|
1776
|
+
if (inc.system == system && inc.version) {
|
|
1777
|
+
set.add(inc.version);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
let v = null;
|
|
1781
|
+
for (let t of set) {
|
|
1782
|
+
if (!v || VersionUtilities.compareVersionsGeneral(t, v) > 0) {
|
|
1783
|
+
v = t;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
return v;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
async pickApplicableVersion(vset, system, code, display) {
|
|
1790
|
+
let found = [];
|
|
1791
|
+
for (let v of vset) {
|
|
1792
|
+
let cs = await this.worker.findCodeSystem(system, v, this.params, ["complete", "fragment"], null, true, true, false, this.worker.requiredSupplements);
|
|
1793
|
+
if (cs != null) {
|
|
1794
|
+
let loc = await cs.locate(code);
|
|
1795
|
+
if (loc.context) {
|
|
1796
|
+
if (!display || await this.displayIsOk(cs, loc.context, display)) {
|
|
1797
|
+
found.push(v);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
// if it was found in none or all of them, we don't do anything
|
|
1803
|
+
if (found.length == vset.size) {
|
|
1804
|
+
return undefined;
|
|
1805
|
+
}
|
|
1806
|
+
if (found.length > 0) {
|
|
1807
|
+
let sorted = found.sort((a, b) => -VersionUtilities.compareVersionsGeneral(a, b));
|
|
1808
|
+
return sorted[0];
|
|
1809
|
+
} else { // well, none of them, we'll go with the latest
|
|
1810
|
+
let sorted = [...vset].sort((a, b) => -VersionUtilities.compareVersionsGeneral(a, b));
|
|
1811
|
+
return sorted[0];
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
async displayIsOk(cs, context, display) {
|
|
1816
|
+
if (display == await cs.display(context)) {
|
|
1817
|
+
return true;
|
|
1818
|
+
}
|
|
1819
|
+
const cds = new Designations(this.worker.i18n.languageDefinitions);
|
|
1820
|
+
await cs.designations(context, cds);
|
|
1821
|
+
return cds.designations.find(cd => cd.value == display);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
hasMatchForVersion(includes, version) {
|
|
1825
|
+
for (let inc of includes) {
|
|
1826
|
+
if (inc.version == version) {
|
|
1827
|
+
return true;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return false;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
useThisVersion(cc, version) {
|
|
1834
|
+
if (!version || cc.version == version) {
|
|
1835
|
+
return true;
|
|
1836
|
+
}
|
|
1837
|
+
return !this.hasMatchForVersion(this.valueSet.jsonObj.compose.include || [], version);
|
|
1838
|
+
}
|
|
1742
1839
|
}
|
|
1743
1840
|
|
|
1744
1841
|
function addToPath(path, name) {
|
|
@@ -1855,7 +1952,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
1855
1952
|
|
|
1856
1953
|
} catch (error) {
|
|
1857
1954
|
this.log.error(error);
|
|
1858
|
-
|
|
1955
|
+
debugLog(error);
|
|
1859
1956
|
if (error instanceof Issue) {
|
|
1860
1957
|
if (error.isHandleAsOO()) {
|
|
1861
1958
|
let oo = new OperationOutcome();
|
|
@@ -1914,7 +2011,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
1914
2011
|
return result;
|
|
1915
2012
|
} catch (error) {
|
|
1916
2013
|
this.log.error(error);
|
|
1917
|
-
|
|
2014
|
+
debugLog(error);
|
|
1918
2015
|
if (error instanceof Issue && !error.isHandleAsOO()) {
|
|
1919
2016
|
return await this.handlePrepareError(error, coded, mode.mode, txp);
|
|
1920
2017
|
} else {
|
|
@@ -1973,7 +2070,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
1973
2070
|
|
|
1974
2071
|
} catch (error) {
|
|
1975
2072
|
this.log.error(error);
|
|
1976
|
-
|
|
2073
|
+
debugLog(error);
|
|
1977
2074
|
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
1978
2075
|
'error', error.issueCode || 'exception', error.message));
|
|
1979
2076
|
}
|
|
@@ -1994,7 +2091,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
1994
2091
|
|
|
1995
2092
|
} catch (error) {
|
|
1996
2093
|
this.log.error(error);
|
|
1997
|
-
|
|
2094
|
+
debugLog(error);
|
|
1998
2095
|
if (error instanceof Issue) {
|
|
1999
2096
|
let op = new OperationOutcome();
|
|
2000
2097
|
op.addIssue(error);
|
|
@@ -2074,7 +2171,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2074
2171
|
|
|
2075
2172
|
} catch (error) {
|
|
2076
2173
|
this.log.error(error);
|
|
2077
|
-
|
|
2174
|
+
debugLog(error);
|
|
2078
2175
|
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
2079
2176
|
'error', error.issueCode || 'exception', error.message));
|
|
2080
2177
|
}
|
|
@@ -2314,7 +2411,7 @@ class ValidateWorker extends TerminologyWorker {
|
|
|
2314
2411
|
await checker.prepare();
|
|
2315
2412
|
} catch (error) {
|
|
2316
2413
|
this.log.error(error);
|
|
2317
|
-
|
|
2414
|
+
debugLog(error);
|
|
2318
2415
|
if (!(error instanceof Issue) || error.isHandleAsOO()) {
|
|
2319
2416
|
throw error;
|
|
2320
2417
|
} else {
|
package/tx/workers/worker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { TerminologyError
|
|
1
|
+
const { TerminologyError} = require('../operation-context');
|
|
2
2
|
const { CodeSystem } = require('../library/codesystem');
|
|
3
3
|
const ValueSet = require('../library/valueset');
|
|
4
4
|
const {VersionUtilities} = require("../../library/version-utilities");
|
|
@@ -900,12 +900,6 @@ class TerminologyWorker {
|
|
|
900
900
|
return true;
|
|
901
901
|
}
|
|
902
902
|
|
|
903
|
-
debugLog(error) {
|
|
904
|
-
if (isDebugging()) {
|
|
905
|
-
console.log(error);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
|
|
909
903
|
hasSupplement(cs, supplements) {
|
|
910
904
|
for (let t of supplements) {
|
|
911
905
|
if (t.vurl == cs.vurl) {
|