fhirsmith 0.7.6 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +5 -1
- package/library/languages.js +10 -0
- package/package.json +1 -1
- package/packages/package-crawler.js +2 -2
- package/publisher/publisher.js +1 -1
- package/registry/registry.js +2 -2
- package/root-bare-template.html +1 -2
- package/security.md +3 -0
- package/server.js +100 -70
- package/stats.js +37 -6
- package/tx/cs/cs-api.js +8 -4
- package/tx/cs/cs-loinc.js +14 -2
- package/tx/cs/cs-omop.js +5 -3
- package/tx/cs/cs-rxnorm.js +18 -16
- package/tx/cs/cs-snomed.js +279 -6
- package/tx/data/cpt-fragment.db +0 -0
- package/tx/data/cs-de.json +186 -0
- package/tx/data/cs-extensions.json +92 -0
- package/tx/data/cs-simple.json +130 -0
- package/tx/data/cs-supplement.json +78 -0
- package/tx/data/lang.dat +49180 -0
- package/tx/data/languages.csv +191 -0
- package/tx/data/loinc-subset.txt +75 -0
- package/tx/data/omop-fragment.db +0 -0
- package/tx/data/readme.md +43 -0
- package/tx/data/regions.csv +273 -0
- package/tx/data/rxnorm-subset.txt +22 -0
- package/tx/data/snomed-subset.txt +47 -0
- package/tx/data/ucum-essence.xml +2059 -0
- package/tx/html/dash-metrics.liquid +147 -0
- package/tx/importers/import-rxnorm.module.js +4 -30
- package/tx/library/canonical-resource.js +8 -0
- package/tx/library/conceptmap.js +29 -1
- package/tx/library/designations.js +4 -8
- package/tx/library/extensions.js +4 -3
- package/tx/library/renderer.js +9 -9
- package/tx/ocl/cm-ocl.cjs +185 -65
- package/tx/ocl/cs-ocl.cjs +69 -50
- package/tx/ocl/jobs/background-queue.cjs +0 -8
- package/tx/ocl/mappers/concept-mapper.cjs +13 -3
- package/tx/ocl/shared/patches.cjs +1 -0
- package/tx/ocl/vs-ocl.cjs +137 -157
- package/tx/operation-context.js +3 -3
- package/tx/params.js +2 -2
- package/tx/provider.js +6 -3
- package/tx/sct/structures.js +6 -1
- package/tx/tx.fhir.org.yml +1 -1
- package/tx/vs/vs-database.js +107 -23
- package/tx/vs/vs-vsac.js +66 -19
- package/tx/workers/expand.js +10 -10
- package/tx/workers/related.js +2 -2
- package/tx/workers/search.js +2 -1
- package/tx/workers/translate.js +222 -33
- package/tx/workers/validate.js +13 -13
- package/tx/xversion/xv-parameters.js +54 -1
- package/xig/xig.js +171 -9
package/tx/workers/translate.js
CHANGED
|
@@ -113,10 +113,15 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
113
113
|
let targetScope = null;
|
|
114
114
|
let sourceScope = null;
|
|
115
115
|
let targetSystem = null;
|
|
116
|
+
let reverse = false;
|
|
116
117
|
|
|
117
118
|
// Get the source coding
|
|
119
|
+
// Accept both R5 names (sourceCoding, sourceCodeableConcept, sourceCode/sourceSystem)
|
|
120
|
+
// and R4 names (coding, codeableConcept, code/system) as aliases
|
|
118
121
|
if (params.has('sourceCoding')) {
|
|
119
122
|
coding = params.get('sourceCoding');
|
|
123
|
+
} else if (params.has('coding')) {
|
|
124
|
+
coding = params.get('coding');
|
|
120
125
|
} else if (params.has('sourceCodeableConcept')) {
|
|
121
126
|
const cc = params.get('sourceCodeableConcept');
|
|
122
127
|
if (cc.coding && cc.coding.length > 0) {
|
|
@@ -125,19 +130,48 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
125
130
|
throw new Issue('error', 'invalid', null, null,
|
|
126
131
|
'sourceCodeableConcept must contain at least one coding', null, 400);
|
|
127
132
|
}
|
|
128
|
-
} else if (params.has('
|
|
129
|
-
|
|
133
|
+
} else if (params.has('codeableConcept')) {
|
|
134
|
+
const cc = params.get('codeableConcept');
|
|
135
|
+
if (cc.coding && cc.coding.length > 0) {
|
|
136
|
+
coding = cc.coding[0];
|
|
137
|
+
} else {
|
|
130
138
|
throw new Issue('error', 'invalid', null, null,
|
|
131
|
-
'
|
|
139
|
+
'codeableConcept must contain at least one coding', null, 400);
|
|
132
140
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
} else if (params.has('sourceCode') || params.has('code')) {
|
|
142
|
+
const code = params.has('sourceCode') ? params.get('sourceCode') : params.get('code');
|
|
143
|
+
const system = params.has('sourceSystem') ? params.get('sourceSystem') : params.get('system');
|
|
144
|
+
if (!system) {
|
|
145
|
+
throw new Issue('error', 'invalid', null, null,
|
|
146
|
+
'system parameter is required when using code/sourceCode', null, 400);
|
|
147
|
+
}
|
|
148
|
+
const version = params.has('sourceVersion') ? params.get('sourceVersion') : params.get('version');
|
|
149
|
+
coding = {system, version, code};
|
|
150
|
+
} else if (params.has('targetCoding')) {
|
|
151
|
+
reverse = true;
|
|
152
|
+
coding = params.get('targetCoding');
|
|
153
|
+
} else if (params.has('targetCodeableConcept')) {
|
|
154
|
+
reverse = true;
|
|
155
|
+
const cc = params.get('targetCodeableConcept');
|
|
156
|
+
if (cc.coding && cc.coding.length > 0) {
|
|
157
|
+
coding = cc.coding[0]; // Use first coding
|
|
158
|
+
} else {
|
|
159
|
+
throw new Issue('error', 'invalid', null, null,
|
|
160
|
+
'sourceCodeableConcept must contain at least one coding', null, 400);
|
|
161
|
+
}
|
|
162
|
+
} else if (params.has('targetCode')) {
|
|
163
|
+
reverse = true;
|
|
164
|
+
const code = params.get('targetCode');
|
|
165
|
+
const system = params.get('targetSystem');
|
|
166
|
+
if (!system) {
|
|
167
|
+
throw new Issue('error', 'invalid', null, null,
|
|
168
|
+
'targetSystem parameter is required when using targetCode', null, 400);
|
|
169
|
+
}
|
|
170
|
+
const version = params.get('targetVersion');
|
|
171
|
+
coding = { system, version, code };
|
|
138
172
|
} else {
|
|
139
173
|
throw new Issue('error', 'invalid', null, null,
|
|
140
|
-
'Must provide sourceCode
|
|
174
|
+
'Must provide sourceCode+(source)system, sourceCoding, or sourceCodeableConcept, or targetCode+targetSystem), targetCoding, or targetCodeableConcept', null, 400);
|
|
141
175
|
}
|
|
142
176
|
|
|
143
177
|
// Get the concept map
|
|
@@ -162,21 +196,33 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
162
196
|
if (params.has('targetScope')) {
|
|
163
197
|
targetScope = params.get('targetScope');
|
|
164
198
|
}
|
|
165
|
-
if (
|
|
166
|
-
|
|
199
|
+
if (reverse) {
|
|
200
|
+
if (params.has('sourceSystem')) {
|
|
201
|
+
targetSystem = params.get('sourceSystem');
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
if (params.has('targetSystem')) {
|
|
205
|
+
targetSystem = params.get('targetSystem');
|
|
206
|
+
}
|
|
167
207
|
}
|
|
168
|
-
|
|
208
|
+
let explicit = true;
|
|
169
209
|
// If no explicit concept map, we need to find one based on source/target
|
|
170
210
|
if (conceptMaps.length == 0) {
|
|
171
|
-
|
|
172
|
-
|
|
211
|
+
explicit = false;
|
|
212
|
+
if (reverse) {
|
|
213
|
+
await this.findConceptMapsInAdditionalResources(conceptMaps,targetSystem, targetScope, sourceScope, coding.system);
|
|
214
|
+
await this.provider.findConceptMapForTranslation(this.opContext, conceptMaps, targetSystem, targetScope, sourceScope, coding.system, coding.code);
|
|
215
|
+
} else {
|
|
216
|
+
await this.findConceptMapsInAdditionalResources(conceptMaps, coding.system, sourceScope, targetScope, targetSystem);
|
|
217
|
+
await this.provider.findConceptMapForTranslation(this.opContext, conceptMaps, coding.system, sourceScope, targetScope, targetSystem, coding.code);
|
|
218
|
+
}
|
|
173
219
|
if (conceptMaps.length == 0) {
|
|
174
220
|
throw new Issue('error', 'not-found', null, null, 'No suitable ConceptMaps found for the specified source and target', null, 404);
|
|
175
221
|
}
|
|
176
222
|
}
|
|
177
223
|
|
|
178
224
|
// Perform the translation
|
|
179
|
-
const result = await this.doTranslate(conceptMaps, coding, targetScope, targetSystem, txp);
|
|
225
|
+
const result = await this.doTranslate(conceptMaps, coding, targetScope, targetSystem, txp, reverse, explicit);
|
|
180
226
|
return res.status(200).json(result);
|
|
181
227
|
}
|
|
182
228
|
|
|
@@ -208,10 +254,14 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
208
254
|
txp.readParams(params.jsonObj);
|
|
209
255
|
|
|
210
256
|
// Get the source coding
|
|
257
|
+
// Accept both R5 names (sourceCoding, sourceCodeableConcept, sourceCode)
|
|
258
|
+
// and R4 names (coding, codeableConcept, code) as aliases
|
|
211
259
|
let coding = null;
|
|
212
260
|
|
|
213
261
|
if (params.has('sourceCoding')) {
|
|
214
262
|
coding = params.get('sourceCoding');
|
|
263
|
+
} else if (params.has('coding')) {
|
|
264
|
+
coding = params.get('coding');
|
|
215
265
|
} else if (params.has('sourceCodeableConcept')) {
|
|
216
266
|
const cc = params.get('sourceCodeableConcept');
|
|
217
267
|
if (cc.coding && cc.coding.length > 0) {
|
|
@@ -220,15 +270,25 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
220
270
|
throw new Issue('error', 'invalid', null, null,
|
|
221
271
|
'sourceCodeableConcept must contain at least one coding', null, 400);
|
|
222
272
|
}
|
|
223
|
-
} else if (params.has('
|
|
224
|
-
|
|
273
|
+
} else if (params.has('codeableConcept')) {
|
|
274
|
+
const cc = params.get('codeableConcept');
|
|
275
|
+
if (cc.coding && cc.coding.length > 0) {
|
|
276
|
+
coding = cc.coding[0];
|
|
277
|
+
} else {
|
|
225
278
|
throw new Issue('error', 'invalid', null, null,
|
|
226
|
-
'
|
|
279
|
+
'codeableConcept must contain at least one coding', null, 400);
|
|
280
|
+
}
|
|
281
|
+
} else if (params.has('sourceCode') || params.has('code')) {
|
|
282
|
+
const code = params.has('sourceCode') ? params.get('sourceCode') : params.get('code');
|
|
283
|
+
const system = params.has('system') ? params.get('system') : null;
|
|
284
|
+
if (!system) {
|
|
285
|
+
throw new Issue('error', 'invalid', null, null,
|
|
286
|
+
'system parameter is required when using code/sourceCode', null, 400);
|
|
227
287
|
}
|
|
228
288
|
coding = {
|
|
229
|
-
system
|
|
289
|
+
system,
|
|
230
290
|
version: params.get('version'),
|
|
231
|
-
code
|
|
291
|
+
code
|
|
232
292
|
};
|
|
233
293
|
} else {
|
|
234
294
|
throw new Issue('error', 'invalid', null, null,
|
|
@@ -262,7 +322,7 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
262
322
|
return result;
|
|
263
323
|
}
|
|
264
324
|
|
|
265
|
-
|
|
325
|
+
translateUsingGroupsForwards(cm, coding, targetScope, targetSystem, params, output, explicit) {
|
|
266
326
|
let result = false;
|
|
267
327
|
const matches = cm.listTranslations(coding, targetScope, targetSystem);
|
|
268
328
|
if (matches.length > 0) {
|
|
@@ -270,7 +330,13 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
270
330
|
const g = match.group;
|
|
271
331
|
const em = match.match;
|
|
272
332
|
for (const map of em.target || []) {
|
|
273
|
-
|
|
333
|
+
let ok = false;
|
|
334
|
+
if (map.equivalence) { // R4 mode
|
|
335
|
+
ok = ['null', 'relatedto', 'equivalent', 'equal', 'wider', 'subsumes', 'narrower', 'specializes', 'inexact'].includes(map.equivalence);
|
|
336
|
+
} else {
|
|
337
|
+
ok = ['null', 'related-to', 'equivalent', 'source-is-narrower-than-target', 'source-is-broader-than-target'].includes(map.relationship);
|
|
338
|
+
}
|
|
339
|
+
if (ok) {
|
|
274
340
|
result = true;
|
|
275
341
|
|
|
276
342
|
const outcome = {
|
|
@@ -278,22 +344,119 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
278
344
|
code: map.code
|
|
279
345
|
};
|
|
280
346
|
|
|
347
|
+
if (!this.hasMatch(output, outcome)) {
|
|
348
|
+
const matchParts = [];
|
|
349
|
+
matchParts.push({
|
|
350
|
+
name: 'concept',
|
|
351
|
+
valueCoding: outcome
|
|
352
|
+
});
|
|
353
|
+
matchParts.push({
|
|
354
|
+
name: 'relationship',
|
|
355
|
+
valueCode: map.relationship
|
|
356
|
+
});
|
|
357
|
+
// equivalence vs relationship will be sorted out in the version transform for parameters
|
|
358
|
+
if (map.equivalence) {
|
|
359
|
+
matchParts.push({
|
|
360
|
+
name: 'equivalence',
|
|
361
|
+
valueCode: map.equivalence
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
if (map.comment) {
|
|
365
|
+
matchParts.push({
|
|
366
|
+
name: 'message',
|
|
367
|
+
valueString: map.comment
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
for (const prod of map.product || []) {
|
|
371
|
+
const productParts = [];
|
|
372
|
+
productParts.push({
|
|
373
|
+
name: 'element',
|
|
374
|
+
valueString: prod.property
|
|
375
|
+
});
|
|
376
|
+
productParts.push({
|
|
377
|
+
name: 'concept',
|
|
378
|
+
valueCoding: {
|
|
379
|
+
system: prod.system,
|
|
380
|
+
code: prod.value
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
matchParts.push({
|
|
384
|
+
name: 'product',
|
|
385
|
+
part: productParts
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
if (!explicit) {
|
|
389
|
+
matchParts.push({
|
|
390
|
+
name: 'sourceMap',
|
|
391
|
+
valueCanonical: cm.vurl
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
output.push({
|
|
395
|
+
name: 'match',
|
|
396
|
+
part: matchParts
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
translateUsingGroupsReverse(cm, coding, targetScope, targetSystem, params, output, explicit) {
|
|
407
|
+
let result = false;
|
|
408
|
+
const matches = cm.listTranslationsReverse(coding, targetScope, targetSystem);
|
|
409
|
+
if (matches.length > 0) {
|
|
410
|
+
for (let match of matches) {
|
|
411
|
+
const g = match.group;
|
|
412
|
+
const em = match.match;
|
|
413
|
+
const map = match.target;
|
|
414
|
+
let ok = false;
|
|
415
|
+
if (map.equivalence) { // R4 mode
|
|
416
|
+
ok = ['null', 'relatedto', 'equivalent', 'equal', 'wider', 'subsumes', 'narrower', 'specializes', 'inexact'].includes(map.equivalence);
|
|
417
|
+
} else {
|
|
418
|
+
ok = ['null', 'related-to', 'equivalent', 'source-is-narrower-than-target', 'source-is-broader-than-target'].includes(map.relationship);
|
|
419
|
+
}
|
|
420
|
+
if (ok) {
|
|
421
|
+
result = true;
|
|
422
|
+
|
|
423
|
+
const outcome = {
|
|
424
|
+
system: g.source,
|
|
425
|
+
code: em.code
|
|
426
|
+
};
|
|
427
|
+
const t = {
|
|
428
|
+
system: g.target,
|
|
429
|
+
code: coding.code
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (!this.hasMatch(output, outcome)) {
|
|
281
433
|
const matchParts = [];
|
|
282
434
|
matchParts.push({
|
|
283
|
-
name: '
|
|
435
|
+
name: 'source',
|
|
284
436
|
valueCoding: outcome
|
|
285
437
|
});
|
|
438
|
+
matchParts.push({
|
|
439
|
+
name: 'concept',
|
|
440
|
+
valueCoding: t
|
|
441
|
+
});
|
|
286
442
|
matchParts.push({
|
|
287
443
|
name: 'relationship',
|
|
288
444
|
valueCode: map.relationship
|
|
289
445
|
});
|
|
290
|
-
|
|
446
|
+
// equivalence vs relationship will be sorted out in the version transform for parameters
|
|
447
|
+
if (map.equivalence) {
|
|
448
|
+
matchParts.push({
|
|
449
|
+
name: 'equivalence',
|
|
450
|
+
valueCode: map.equivalence
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
if (map.comment) {
|
|
291
454
|
matchParts.push({
|
|
292
455
|
name: 'message',
|
|
293
|
-
valueString: map.
|
|
456
|
+
valueString: map.comment
|
|
294
457
|
});
|
|
295
458
|
}
|
|
296
|
-
for (const prod of map.
|
|
459
|
+
for (const prod of map.product || []) {
|
|
297
460
|
const productParts = [];
|
|
298
461
|
productParts.push({
|
|
299
462
|
name: 'element',
|
|
@@ -311,6 +474,12 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
311
474
|
part: productParts
|
|
312
475
|
});
|
|
313
476
|
}
|
|
477
|
+
if (!explicit) {
|
|
478
|
+
matchParts.push({
|
|
479
|
+
name: 'sourceMap',
|
|
480
|
+
valueCanonical: cm.vurl
|
|
481
|
+
});
|
|
482
|
+
}
|
|
314
483
|
output.push({
|
|
315
484
|
name: 'match',
|
|
316
485
|
part: matchParts
|
|
@@ -322,7 +491,7 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
322
491
|
return result;
|
|
323
492
|
}
|
|
324
493
|
|
|
325
|
-
async translateUsingCodeSystem(cm, coding, target, params, output) {
|
|
494
|
+
async translateUsingCodeSystem(cm, coding, target, params, output, reverse, explicit) {
|
|
326
495
|
let result = false;
|
|
327
496
|
const factory = cm.jsonObj.internalSource;
|
|
328
497
|
let prov = await factory.build(this.opContext, []);
|
|
@@ -332,7 +501,7 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
332
501
|
valueUri: prov.system() + '|' + prov.version()
|
|
333
502
|
});
|
|
334
503
|
|
|
335
|
-
let translations = await prov.getTranslations(coding, target);
|
|
504
|
+
let translations = await prov.getTranslations(cm, coding, target, reverse);
|
|
336
505
|
|
|
337
506
|
if (translations.length > 0) {
|
|
338
507
|
result = true;
|
|
@@ -346,7 +515,7 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
346
515
|
}
|
|
347
516
|
|
|
348
517
|
const outcome = {
|
|
349
|
-
system: t.
|
|
518
|
+
system: t.system,
|
|
350
519
|
code: t.code,
|
|
351
520
|
version: t.version,
|
|
352
521
|
display: t.display
|
|
@@ -367,6 +536,12 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
367
536
|
valueString: t.message
|
|
368
537
|
});
|
|
369
538
|
}
|
|
539
|
+
if (!explicit) {
|
|
540
|
+
matchParts.push({
|
|
541
|
+
name: 'sourceMap',
|
|
542
|
+
valueCanonical: cm.vurl
|
|
543
|
+
});
|
|
544
|
+
}
|
|
370
545
|
output.push({
|
|
371
546
|
name: 'match',
|
|
372
547
|
part: matchParts
|
|
@@ -383,9 +558,11 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
383
558
|
* @param {string} targetScope - Target value set scope (optional)
|
|
384
559
|
* @param {string} targetSystem - Target code system (optional)
|
|
385
560
|
* @param {Parameters} params - Full parameters object
|
|
561
|
+
* @param {boolean} reverse - Full parameters object*
|
|
562
|
+
* @param {boolean} explicit - If the concept map was named explicitly
|
|
386
563
|
* @returns {Object} Parameters resource with translate result
|
|
387
564
|
*/
|
|
388
|
-
async doTranslate(conceptMaps, coding, targetScope, targetSystem, params) {
|
|
565
|
+
async doTranslate(conceptMaps, coding, targetScope, targetSystem, params, reverse, explicit) {
|
|
389
566
|
this.deadCheck('doTranslate');
|
|
390
567
|
|
|
391
568
|
const result = [];
|
|
@@ -394,9 +571,11 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
394
571
|
let added = false;
|
|
395
572
|
for (const cm of conceptMaps) {
|
|
396
573
|
if (cm.jsonObj.internalSource) {
|
|
397
|
-
added = await this.translateUsingCodeSystem(cm, coding, targetSystem, params, result) || added;
|
|
398
|
-
} else {
|
|
399
|
-
added = this.
|
|
574
|
+
added = await this.translateUsingCodeSystem(cm, coding, targetSystem, params, result, reverse, explicit) || added;
|
|
575
|
+
} else if (reverse) {
|
|
576
|
+
added = this.translateUsingGroupsReverse(cm, coding, targetScope, targetSystem, params, result, reverse, explicit) || added;
|
|
577
|
+
} else{
|
|
578
|
+
added = this.translateUsingGroupsForwards(cm, coding, targetScope, targetSystem, params, result, reverse, explicit) || added;
|
|
400
579
|
}
|
|
401
580
|
}
|
|
402
581
|
result.push({
|
|
@@ -492,6 +671,16 @@ class TranslateWorker extends TerminologyWorker {
|
|
|
492
671
|
}
|
|
493
672
|
}
|
|
494
673
|
}
|
|
674
|
+
|
|
675
|
+
hasMatch(output, outcome) {
|
|
676
|
+
for (let o of output) {
|
|
677
|
+
let c = o.part.find(x => x.name === 'concept');
|
|
678
|
+
if (c.valueCoding.code === outcome.code && c.valueCoding.system === outcome.system) {
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
495
684
|
}
|
|
496
685
|
|
|
497
686
|
module.exports = TranslateWorker;
|
package/tx/workers/validate.js
CHANGED
|
@@ -316,21 +316,21 @@ class ValueSetChecker {
|
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
Extensions.checkNoImplicitRules(this.valueSet, 'ValueSetChecker.prepare', 'ValueSet');
|
|
319
|
-
Extensions.checkNoModifiers(this.valueSet, 'ValueSetChecker.prepare', 'ValueSet');
|
|
319
|
+
Extensions.checkNoModifiers(this.valueSet, 'ValueSetChecker.prepare', 'ValueSet', this.valueSet.vurl);
|
|
320
320
|
|
|
321
321
|
this.allValueSet = this.valueSet.url === 'http://hl7.org/fhir/ValueSet/@all';
|
|
322
322
|
|
|
323
323
|
if (this.valueSet.jsonObj.compose) {
|
|
324
|
-
Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose');
|
|
325
|
-
this.worker.checkNoLockedDate(this.valueSet.
|
|
324
|
+
Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose', this.valueSet.vurl);
|
|
325
|
+
this.worker.checkNoLockedDate(this.valueSet.vurl, this.valueSet.jsonObj.compose)
|
|
326
326
|
let i = 0;
|
|
327
327
|
for (let cc of this.valueSet.jsonObj.compose.include || []) {
|
|
328
|
-
await this.prepareConceptSet('include['+i+']', cc);
|
|
328
|
+
await this.prepareConceptSet('include['+i+']', cc, this.valueSet);
|
|
329
329
|
i++;
|
|
330
330
|
}
|
|
331
331
|
i = 0;
|
|
332
332
|
for (let cc of this.valueSet.jsonObj.compose.exclude || []) {
|
|
333
|
-
await this.prepareConceptSet('exclude['+i+']', cc);
|
|
333
|
+
await this.prepareConceptSet('exclude['+i+']', cc, this.valueSet);
|
|
334
334
|
i++;
|
|
335
335
|
}
|
|
336
336
|
}
|
|
@@ -342,9 +342,9 @@ class ValueSetChecker {
|
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
async prepareConceptSet(desc, cc) {
|
|
345
|
+
async prepareConceptSet(desc, cc, vs) {
|
|
346
346
|
this.worker.deadCheck('prepareConceptSet');
|
|
347
|
-
Extensions.checkNoModifiers(cc, 'ValueSetChecker.prepare', desc);
|
|
347
|
+
Extensions.checkNoModifiers(cc, 'ValueSetChecker.prepare', desc, vs.vurl);
|
|
348
348
|
this.worker.opContext.addNote(this.valueSet, 'Prepare ' + desc + ': "' + this.worker.renderer.displayValueSetInclude(cc) + '"', this.indentCount);
|
|
349
349
|
if (cc.valueSet) {
|
|
350
350
|
for (let u of cc.valueSet) {
|
|
@@ -374,7 +374,7 @@ class ValueSetChecker {
|
|
|
374
374
|
let i = 0;
|
|
375
375
|
for (let ccf of cc.filter || []) {
|
|
376
376
|
this.worker.deadCheck('prepareConceptSet#2');
|
|
377
|
-
Extensions.checkNoModifiers(ccf, 'ValueSetChecker.prepare', desc + '.filter');
|
|
377
|
+
Extensions.checkNoModifiers(ccf, 'ValueSetChecker.prepare', desc + '.filter', this.valueSet.vurl);
|
|
378
378
|
if (!ccf.value) {
|
|
379
379
|
throw new Issue('error', 'invalid', "ValueSet.compose."+desc+".filter["+i+"]", 'UNABLE_TO_HANDLE_SYSTEM_FILTER_WITH_NO_VALUE',
|
|
380
380
|
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);
|
|
@@ -677,7 +677,7 @@ class ValueSetChecker {
|
|
|
677
677
|
throw new Issue('error', 'not-found', null, 'VALUESET_SUPPLEMENT_MISSING', this.worker.i18n.translatePlural(unused.size, 'VALUESET_SUPPLEMENT_MISSING', this.params.HTTPLanguages, [[...unused].join(',')]), 'not-found').handleAsOO(422);
|
|
678
678
|
}
|
|
679
679
|
|
|
680
|
-
if (Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose')) {
|
|
680
|
+
if (Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose', this.valueSet.vurl)) {
|
|
681
681
|
result = false;
|
|
682
682
|
let determinedVersion = undefined;
|
|
683
683
|
if (!version) {
|
|
@@ -807,7 +807,7 @@ class ValueSetChecker {
|
|
|
807
807
|
}
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
|
-
} else if (Extensions.checkNoModifiers(this.valueSet.jsonObj.expansion, 'ValueSetChecker.prepare', 'ValueSet.expansion')) {
|
|
810
|
+
} else if (Extensions.checkNoModifiers(this.valueSet.jsonObj.expansion, 'ValueSetChecker.prepare', 'ValueSet.expansion', this.valueSet.vurl)) {
|
|
811
811
|
let ccc = this.valueSet.findContains(system, version, code);
|
|
812
812
|
if (ccc === null) {
|
|
813
813
|
result = false;
|
|
@@ -1107,7 +1107,7 @@ class ValueSetChecker {
|
|
|
1107
1107
|
codelist = !codelist ? '\'' + cc + '\'' : codelist + ', \'' + cc + '\'';
|
|
1108
1108
|
|
|
1109
1109
|
if (v === false && !this.valueSet.jsonObj.internallyDefined && mode === 'codeableConcept') {
|
|
1110
|
-
let m = this.worker.i18n.translate('None_of_the_provided_codes_are_in_the_value_set_one', this.params.HTTPLanguages, ['', this.valueSet.
|
|
1110
|
+
let m = this.worker.i18n.translate('None_of_the_provided_codes_are_in_the_value_set_one', this.params.HTTPLanguages, ['', this.valueSet.vurlOrMsg, '\'' + cc + '\'']);
|
|
1111
1111
|
let p = issuePath + '.coding[' + i + '].code';
|
|
1112
1112
|
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'));
|
|
1113
1113
|
if (cause.value === 'null') {
|
|
@@ -1284,10 +1284,10 @@ class ValueSetChecker {
|
|
|
1284
1284
|
let mid, m, p;
|
|
1285
1285
|
if (mode === 'codeableConcept') {
|
|
1286
1286
|
mid = 'TX_GENERAL_CC_ERROR_MESSAGE';
|
|
1287
|
-
m = this.worker.i18n.translate('TX_GENERAL_CC_ERROR_MESSAGE', this.params.HTTPLanguages, [this.valueSet.
|
|
1287
|
+
m = this.worker.i18n.translate('TX_GENERAL_CC_ERROR_MESSAGE', this.params.HTTPLanguages, [this.valueSet.vurlOrMsg]);
|
|
1288
1288
|
} else {
|
|
1289
1289
|
mid = 'None_of_the_provided_codes_are_in_the_value_set_one';
|
|
1290
|
-
m = this.worker.i18n.translate('None_of_the_provided_codes_are_in_the_value_set_one', this.params.HTTPLanguages, ['', this.valueSet.
|
|
1290
|
+
m = this.worker.i18n.translate('None_of_the_provided_codes_are_in_the_value_set_one', this.params.HTTPLanguages, ['', this.valueSet.vurlOrMsg, codelist]);
|
|
1291
1291
|
}
|
|
1292
1292
|
|
|
1293
1293
|
if (mode === 'codeableConcept') {
|
|
@@ -10,7 +10,11 @@ const {VersionUtilities} = require("../../library/version-utilities");
|
|
|
10
10
|
|
|
11
11
|
function parametersToR5(jsonObj, sourceVersion) {
|
|
12
12
|
if (VersionUtilities.isR5Ver(sourceVersion)) {
|
|
13
|
-
|
|
13
|
+
if (jsonObj.parameter && jsonObj.parameter.find(p => p.name == 'match')) {
|
|
14
|
+
return convertResourceWithinR5(JSON.parse(JSON.stringify(jsonObj)));
|
|
15
|
+
} else {
|
|
16
|
+
return jsonObj; // No conversion needed
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
const {convertResourceFromR5} = require("./xv-resource");
|
|
@@ -59,8 +63,57 @@ function parametersR5ToR4(r5Obj) {
|
|
|
59
63
|
if (p.resource) {
|
|
60
64
|
p.resource = convertResourceFromR5(p.resource, "R4");
|
|
61
65
|
}
|
|
66
|
+
if (p.name == 'match') {
|
|
67
|
+
fixMatchParameterfor4(p);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return r5Obj;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function convertResourceWithinR5(r5Obj) {
|
|
74
|
+
for (let p of r5Obj.parameter) {
|
|
75
|
+
if (p.name == 'match') {
|
|
76
|
+
fixMatchParameterfor5(p);
|
|
77
|
+
}
|
|
62
78
|
}
|
|
63
79
|
return r5Obj;
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function fixMatchParameterfor5(p) {
|
|
84
|
+
if (p.part) {
|
|
85
|
+
p.part = p.part.filter(pp => pp.name !== 'equivalence');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function fixMatchParameterfor4(p) {
|
|
90
|
+
if (p.part) {
|
|
91
|
+
if (!p.part.find(pp => pp.name === 'equivalence')) {
|
|
92
|
+
let rel = p.part.find(pp => pp.name === 'relationship');
|
|
93
|
+
if (rel && rel.valueCode) {
|
|
94
|
+
let pp = {name: "equivalence"};
|
|
95
|
+
switch (rel.valueCode) {
|
|
96
|
+
case 'related-to':
|
|
97
|
+
pp.valueCode = 'relatedto';
|
|
98
|
+
break;
|
|
99
|
+
case 'equivalent':
|
|
100
|
+
pp.valueCode = 'equivalent';
|
|
101
|
+
break;
|
|
102
|
+
case 'source-is-narrower-than-target':
|
|
103
|
+
pp.valueCode = 'wider';
|
|
104
|
+
break;
|
|
105
|
+
case 'source-is-broader-than-target':
|
|
106
|
+
pp.valueCode = 'narrower';
|
|
107
|
+
break;
|
|
108
|
+
case 'not-related-to':
|
|
109
|
+
pp.valueCode = 'unmatched';
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
p.part.push(pp);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
p.part = p.part.filter(pp => pp.name !== 'relationship');
|
|
116
|
+
}
|
|
64
117
|
}
|
|
65
118
|
|
|
66
119
|
function convertParameterR5ToR3(p) {
|