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.
Files changed (57) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +5 -1
  3. package/library/languages.js +10 -0
  4. package/package.json +1 -1
  5. package/packages/package-crawler.js +2 -2
  6. package/publisher/publisher.js +1 -1
  7. package/registry/registry.js +2 -2
  8. package/root-bare-template.html +1 -2
  9. package/security.md +3 -0
  10. package/server.js +100 -70
  11. package/stats.js +37 -6
  12. package/tx/cs/cs-api.js +8 -4
  13. package/tx/cs/cs-loinc.js +14 -2
  14. package/tx/cs/cs-omop.js +5 -3
  15. package/tx/cs/cs-rxnorm.js +18 -16
  16. package/tx/cs/cs-snomed.js +279 -6
  17. package/tx/data/cpt-fragment.db +0 -0
  18. package/tx/data/cs-de.json +186 -0
  19. package/tx/data/cs-extensions.json +92 -0
  20. package/tx/data/cs-simple.json +130 -0
  21. package/tx/data/cs-supplement.json +78 -0
  22. package/tx/data/lang.dat +49180 -0
  23. package/tx/data/languages.csv +191 -0
  24. package/tx/data/loinc-subset.txt +75 -0
  25. package/tx/data/omop-fragment.db +0 -0
  26. package/tx/data/readme.md +43 -0
  27. package/tx/data/regions.csv +273 -0
  28. package/tx/data/rxnorm-subset.txt +22 -0
  29. package/tx/data/snomed-subset.txt +47 -0
  30. package/tx/data/ucum-essence.xml +2059 -0
  31. package/tx/html/dash-metrics.liquid +147 -0
  32. package/tx/importers/import-rxnorm.module.js +4 -30
  33. package/tx/library/canonical-resource.js +8 -0
  34. package/tx/library/conceptmap.js +29 -1
  35. package/tx/library/designations.js +4 -8
  36. package/tx/library/extensions.js +4 -3
  37. package/tx/library/renderer.js +9 -9
  38. package/tx/ocl/cm-ocl.cjs +185 -65
  39. package/tx/ocl/cs-ocl.cjs +69 -50
  40. package/tx/ocl/jobs/background-queue.cjs +0 -8
  41. package/tx/ocl/mappers/concept-mapper.cjs +13 -3
  42. package/tx/ocl/shared/patches.cjs +1 -0
  43. package/tx/ocl/vs-ocl.cjs +137 -157
  44. package/tx/operation-context.js +3 -3
  45. package/tx/params.js +2 -2
  46. package/tx/provider.js +6 -3
  47. package/tx/sct/structures.js +6 -1
  48. package/tx/tx.fhir.org.yml +1 -1
  49. package/tx/vs/vs-database.js +107 -23
  50. package/tx/vs/vs-vsac.js +66 -19
  51. package/tx/workers/expand.js +10 -10
  52. package/tx/workers/related.js +2 -2
  53. package/tx/workers/search.js +2 -1
  54. package/tx/workers/translate.js +222 -33
  55. package/tx/workers/validate.js +13 -13
  56. package/tx/xversion/xv-parameters.js +54 -1
  57. package/xig/xig.js +171 -9
@@ -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('sourceCode')) {
129
- if (!params.has('sourceSystem')) {
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
- 'sourceSystem parameter is required when using sourceCode', null, 400);
139
+ 'codeableConcept must contain at least one coding', null, 400);
132
140
  }
133
- coding = {
134
- system: params.get('sourceSystem'),
135
- version: params.get('sourceVersion'),
136
- code: params.get('sourceCode')
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 (with system), sourceCoding, or sourceCodeableConcept', null, 400);
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 (params.has('targetSystem')) {
166
- targetSystem = params.get('targetSystem');
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
- await this.findConceptMapsInAdditionalResources(conceptMaps, coding.system, sourceScope, targetScope, targetSystem);
172
- await this.provider.findConceptMapForTranslation(this.opContext, conceptMaps, coding.system, sourceScope, targetScope, targetSystem);
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('sourceCode')) {
224
- if (!params.has('system')) {
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
- 'system parameter is required when using sourceCode', null, 400);
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: params.get('system'),
289
+ system,
230
290
  version: params.get('version'),
231
- code: params.get('sourceCode')
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
- translateUsingGroups(cm, coding, targetScope, targetSystem, params, output) {
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
- if (['null', 'equivalent', 'equal', 'wider', 'subsumes', 'narrower', 'specializes', 'inexact'].includes(map.relationship)) {
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: 'concept',
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
- if (map.comments) {
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.comments
456
+ valueString: map.comment
294
457
  });
295
458
  }
296
- for (const prod of map.products || []) {
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.uri,
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.translateUsingGroups(cm, coding, targetScope, targetSystem, params, result) || added;
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;
@@ -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.url, this.valueSet.jsonObj.compose)
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.vurl, '\'' + cc + '\'']);
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.vurl]);
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.vurl, codelist]);
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
- return jsonObj; // No conversion needed
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) {