fhirsmith 0.8.0 → 0.8.3

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.
@@ -148,6 +148,32 @@ class ConceptMap extends CanonicalResource {
148
148
  }
149
149
  return result;
150
150
  }
151
+
152
+ listTranslationsReverse(coding, targetScope, sourceSystem) {
153
+ let result = [];
154
+ let vurl = VersionUtilities.vurl(coding.system, coding.version);
155
+
156
+ let all = this.canonicalMatches(targetScope, this.targetScope);
157
+ for (const g of this.jsonObj.group || []) {
158
+ const targetOk = this.canonicalMatches(vurl, g.target);
159
+ const sourceOk = !sourceSystem || this.canonicalMatches(sourceSystem, g.source);
160
+ if (all || (sourceOk && targetOk)) {
161
+ for (const em of g.element || []) {
162
+ for (const tm of em.target || []) {
163
+ if (tm.code === coding.code) {
164
+ let match = {
165
+ group: g,
166
+ match: em,
167
+ target: tm
168
+ };
169
+ result.push(match);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ return result;
176
+ }
151
177
  /**
152
178
  * Gets the source scope (R5) or source system (R3/R4)
153
179
  * @returns {string|undefined} Source scope/system
@@ -29,7 +29,7 @@ const Extensions = {
29
29
  }
30
30
  },
31
31
 
32
- checkNoModifiers(element, place, name) {
32
+ checkNoModifiers(element, place, name, resource) {
33
33
  if (!element) {
34
34
  return;
35
35
  }
@@ -41,11 +41,12 @@ const Extensions = {
41
41
  for (const extension of element.modifierExtension) {
42
42
  urls.add(extension.url);
43
43
  }
44
+ const resId = resource ? resource : "";
44
45
  const urlList = [...urls].join('\', \'');
45
46
  if (urls.size > 1) {
46
- throw new Issue("error", "business-rule", null, null, 'Cannot process resource at "' + name + '" due to the presence of modifier extensions '+urlList);
47
+ throw new Issue("error", "business-rule", null, null, 'Cannot process resource '+resId+' at "' + name + '" due to the presence of modifier extensions '+urlList);
47
48
  } else {
48
- throw new Issue("error", "business-rule", null, null, 'Cannot process resource at "' + name + '" due to the presence of the modifier extension '+urlList);
49
+ throw new Issue("error", "business-rule", null, null, 'Cannot process resource '+resId+' at "' + name + '" due to the presence of the modifier extension '+urlList);
49
50
  }
50
51
  }
51
52
  return true;
package/tx/params.js CHANGED
@@ -114,11 +114,11 @@ class TxParameters {
114
114
  break;
115
115
  }
116
116
  case 'force-valueset-version': {
117
- this.seeVersionRule().push(getValuePrimitive(p), true, 'override');
117
+ this.seeVersionRule(getValuePrimitive(p), true, 'override');
118
118
  break;
119
119
  }
120
120
  case 'check-valueset-version': {
121
- this.seeVersionRule().push(getValuePrimitive(p), true, 'check');
121
+ this.seeVersionRule(getValuePrimitive(p), true, 'check');
122
122
  break;
123
123
  }
124
124
 
package/tx/provider.js CHANGED
@@ -228,7 +228,10 @@ class Provider {
228
228
  for (let csp of this.codeSystemFactories.values()) {
229
229
  if (!uris.has(csp.system())) {
230
230
  uris.add(csp.system());
231
- await csp.findImplicitConceptMap(url, version);
231
+ let cm = await csp.findImplicitConceptMap(url, version);
232
+ if (cm) {
233
+ return cm;
234
+ }
232
235
  }
233
236
  }
234
237
  }
@@ -1177,7 +1177,7 @@ class SnomedReferenceSetIndex {
1177
1177
  }
1178
1178
  }
1179
1179
 
1180
- return 0; // Not found
1180
+ return -1; // Not found
1181
1181
  }
1182
1182
 
1183
1183
  count() {
@@ -604,7 +604,7 @@ class ValueSetExpander {
604
604
 
605
605
  async checkSource(cset, exp, filter, srcURL, ts, vsInfo) {
606
606
  this.worker.deadCheck('checkSource');
607
- Extensions.checkNoModifiers(cset, 'ValueSetExpander.checkSource', 'set');
607
+ Extensions.checkNoModifiers(cset, 'ValueSetExpander.checkSource', 'set', srcURL);
608
608
  let imp = false;
609
609
  for (const u of cset.valueSet || []) {
610
610
  this.worker.deadCheck('checkSource');
@@ -682,7 +682,7 @@ class ValueSetExpander {
682
682
  this.worker.deadCheck('processCodes#1');
683
683
  const valueSets = [];
684
684
 
685
- Extensions.checkNoModifiers(cset, 'ValueSetExpander.processCodes', 'set');
685
+ Extensions.checkNoModifiers(cset, 'ValueSetExpander.processCodes', 'set', vsSrc.vurl);
686
686
 
687
687
  if (cset.valueSet || cset.concept || (cset.filter || []).length > 1) {
688
688
  this.canBeHierarchy = false;
@@ -792,7 +792,7 @@ class ValueSetExpander {
792
792
  for (const cc of cset.concept) {
793
793
  this.worker.deadCheck('processCodes#3');
794
794
  cds.clear();
795
- Extensions.checkNoModifiers(cc, 'ValueSetExpander.processCodes', 'set concept reference');
795
+ Extensions.checkNoModifiers(cc, 'ValueSetExpander.processCodes', 'set concept reference', vsSrc.vurl);
796
796
  const cctxt = await cs.locate(cc.code, this.allAltCodes);
797
797
  if (cctxt && cctxt.context && (!this.params.activeOnly || !await cs.isInactive(cctxt.context)) && await this.passesFilters(cs, cctxt.context, prep, filters, 0)) {
798
798
  await this.listDisplaysFromProvider(cds, cs, cctxt.context);
@@ -834,7 +834,7 @@ class ValueSetExpander {
834
834
  if (!fc.value) {
835
835
  throw new Issue('error', 'invalid', path + ".filter[" + i + "]", '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]), 'vs-invalid', 400);
836
836
  }
837
- Extensions.checkNoModifiers(fc, 'ValueSetExpander.processCodes', 'filter');
837
+ Extensions.checkNoModifiers(fc, 'ValueSetExpander.processCodes', 'filter', vsSrc.vurl);
838
838
  await cs.filter(prep, fc.property, fc.op, fc.value);
839
839
  }
840
840
 
@@ -891,7 +891,7 @@ class ValueSetExpander {
891
891
  this.worker.deadCheck('processCodes#1');
892
892
  const valueSets = [];
893
893
 
894
- Extensions.checkNoModifiers(cset, 'ValueSetExpander.processCodes', 'set');
894
+ Extensions.checkNoModifiers(cset, 'ValueSetExpander.processCodes', 'set', vsSrc.vurl);
895
895
 
896
896
  if (cset.valueSet || cset.concept || (cset.filter || []).length > 1) {
897
897
  this.canBeHierarchy = false;
@@ -978,7 +978,7 @@ class ValueSetExpander {
978
978
  for (const cc of cset.concept) {
979
979
  this.worker.deadCheck('processCodes#3');
980
980
  cds.clear();
981
- Extensions.checkNoModifiers(cc, 'ValueSetExpander.processCodes', 'set concept reference');
981
+ Extensions.checkNoModifiers(cc, 'ValueSetExpander.processCodes', 'set concept reference', vsSrc.vurl);
982
982
  const cctxt = await cs.locate(cc.code, this.allAltCodes);
983
983
  if (cctxt && cctxt.context && (!this.params.activeOnly || !await cs.isInactive(cctxt)) && await this.passesFilters(cs, cctxt, prep, filters, 0)) {
984
984
  if (filter.passesDesignations(cds) || filter.passes(cc.code)) {
@@ -1007,7 +1007,7 @@ class ValueSetExpander {
1007
1007
 
1008
1008
  for (let fc of cset.filter) {
1009
1009
  this.worker.deadCheck('processCodes#4a');
1010
- Extensions.checkNoModifiers(fc, 'ValueSetExpander.processCodes', 'filter');
1010
+ Extensions.checkNoModifiers(fc, 'ValueSetExpander.processCodes', 'filter', vsSrc.vurl);
1011
1011
  await cs.filter(prep, fc.property, fc.op, fc.value);
1012
1012
  }
1013
1013
 
@@ -1157,8 +1157,8 @@ class ValueSetExpander {
1157
1157
  this.totalStatus = 'uninitialised';
1158
1158
  this.total = 0;
1159
1159
 
1160
- Extensions.checkNoImplicitRules(source,'ValueSetExpander.Expand', 'ValueSet');
1161
- Extensions.checkNoModifiers(source,'ValueSetExpander.Expand', 'ValueSet');
1160
+ Extensions.checkNoImplicitRules(source,'ValueSetExpander.Expand', 'ValueSet', source.vurl);
1161
+ Extensions.checkNoModifiers(source,'ValueSetExpander.Expand', 'ValueSet', source.vurl);
1162
1162
  this.worker.seeValueSet(source, this.params);
1163
1163
  this.valueSet = source;
1164
1164
 
@@ -1272,7 +1272,7 @@ class ValueSetExpander {
1272
1272
 
1273
1273
  let vsInfo = this.scanValueSet(source.jsonObj.compose);
1274
1274
  try {
1275
- if (source.jsonObj.compose && Extensions.checkNoModifiers(source.jsonObj.compose, 'ValueSetExpander.Expand', 'compose')
1275
+ if (source.jsonObj.compose && Extensions.checkNoModifiers(source.jsonObj.compose, 'ValueSetExpander.Expand', 'compose', source.vurl)
1276
1276
  && this.worker.checkNoLockedDate(source.url, source.jsonObj.compose)) {
1277
1277
  await this.handleCompose(source, filter, exp, notClosed, vsInfo);
1278
1278
  }
@@ -205,12 +205,12 @@ class RelatedWorker extends TerminologyWorker {
205
205
  if (!thisC) {
206
206
  return this.makeOutcome("indeterminate", `The ValueSet ${thisVS.vurl} has no compose`);
207
207
  }
208
- Extensions.checkNoModifiers(thisC, 'RelatedWorker.doRelated', 'compose')
208
+ Extensions.checkNoModifiers(thisC, 'RelatedWorker.doRelated', 'compose', thisVS.vurl)
209
209
  this.checkNoLockedDate(thisVS.vurl, thisC);
210
210
  if (!otherC) {
211
211
  return this.makeOutcome("indeterminate", `The ValueSet ${otherVS.vurl} has no compose`);
212
212
  }
213
- Extensions.checkNoModifiers(otherC, 'RelatedWorker.doRelated', 'compose')
213
+ Extensions.checkNoModifiers(otherC, 'RelatedWorker.doRelated', 'compose', otherVS.vurl)
214
214
  this.checkNoLockedDate(otherVS.vurl, otherC);
215
215
 
216
216
  let systems = new Map(); // tracks whether they are version dependent or not
@@ -113,6 +113,7 @@ 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
118
119
  // Accept both R5 names (sourceCoding, sourceCodeableConcept, sourceCode/sourceSystem)
@@ -145,10 +146,32 @@ class TranslateWorker extends TerminologyWorker {
145
146
  'system parameter is required when using code/sourceCode', null, 400);
146
147
  }
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');
148
171
  coding = { system, version, code };
149
172
  } else {
150
173
  throw new Issue('error', 'invalid', null, null,
151
- '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);
152
175
  }
153
176
 
154
177
  // Get the concept map
@@ -173,21 +196,33 @@ class TranslateWorker extends TerminologyWorker {
173
196
  if (params.has('targetScope')) {
174
197
  targetScope = params.get('targetScope');
175
198
  }
176
- if (params.has('targetSystem')) {
177
- 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
+ }
178
207
  }
179
-
208
+ let explicit = true;
180
209
  // If no explicit concept map, we need to find one based on source/target
181
210
  if (conceptMaps.length == 0) {
182
- await this.findConceptMapsInAdditionalResources(conceptMaps, coding.system, sourceScope, targetScope, targetSystem);
183
- await this.provider.findConceptMapForTranslation(this.opContext, conceptMaps, coding.system, sourceScope, targetScope, targetSystem, coding.code);
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
+ }
184
219
  if (conceptMaps.length == 0) {
185
220
  throw new Issue('error', 'not-found', null, null, 'No suitable ConceptMaps found for the specified source and target', null, 404);
186
221
  }
187
222
  }
188
223
 
189
224
  // Perform the translation
190
- const result = await this.doTranslate(conceptMaps, coding, targetScope, targetSystem, txp);
225
+ const result = await this.doTranslate(conceptMaps, coding, targetScope, targetSystem, txp, reverse, explicit);
191
226
  return res.status(200).json(result);
192
227
  }
193
228
 
@@ -287,7 +322,7 @@ class TranslateWorker extends TerminologyWorker {
287
322
  return result;
288
323
  }
289
324
 
290
- translateUsingGroups(cm, coding, targetScope, targetSystem, params, output) {
325
+ translateUsingGroupsForwards(cm, coding, targetScope, targetSystem, params, output, explicit) {
291
326
  let result = false;
292
327
  const matches = cm.listTranslations(coding, targetScope, targetSystem);
293
328
  if (matches.length > 0) {
@@ -295,7 +330,13 @@ class TranslateWorker extends TerminologyWorker {
295
330
  const g = match.group;
296
331
  const em = match.match;
297
332
  for (const map of em.target || []) {
298
- 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) {
299
340
  result = true;
300
341
 
301
342
  const outcome = {
@@ -303,22 +344,119 @@ class TranslateWorker extends TerminologyWorker {
303
344
  code: map.code
304
345
  };
305
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)) {
306
433
  const matchParts = [];
307
434
  matchParts.push({
308
- name: 'concept',
435
+ name: 'source',
309
436
  valueCoding: outcome
310
437
  });
438
+ matchParts.push({
439
+ name: 'concept',
440
+ valueCoding: t
441
+ });
311
442
  matchParts.push({
312
443
  name: 'relationship',
313
444
  valueCode: map.relationship
314
445
  });
315
- 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) {
316
454
  matchParts.push({
317
455
  name: 'message',
318
- valueString: map.comments
456
+ valueString: map.comment
319
457
  });
320
458
  }
321
- for (const prod of map.products || []) {
459
+ for (const prod of map.product || []) {
322
460
  const productParts = [];
323
461
  productParts.push({
324
462
  name: 'element',
@@ -336,6 +474,12 @@ class TranslateWorker extends TerminologyWorker {
336
474
  part: productParts
337
475
  });
338
476
  }
477
+ if (!explicit) {
478
+ matchParts.push({
479
+ name: 'sourceMap',
480
+ valueCanonical: cm.vurl
481
+ });
482
+ }
339
483
  output.push({
340
484
  name: 'match',
341
485
  part: matchParts
@@ -347,7 +491,7 @@ class TranslateWorker extends TerminologyWorker {
347
491
  return result;
348
492
  }
349
493
 
350
- async translateUsingCodeSystem(cm, coding, target, params, output) {
494
+ async translateUsingCodeSystem(cm, coding, target, params, output, reverse, explicit) {
351
495
  let result = false;
352
496
  const factory = cm.jsonObj.internalSource;
353
497
  let prov = await factory.build(this.opContext, []);
@@ -357,7 +501,7 @@ class TranslateWorker extends TerminologyWorker {
357
501
  valueUri: prov.system() + '|' + prov.version()
358
502
  });
359
503
 
360
- let translations = await prov.getTranslations(coding, target);
504
+ let translations = await prov.getTranslations(cm, coding, target, reverse);
361
505
 
362
506
  if (translations.length > 0) {
363
507
  result = true;
@@ -371,7 +515,7 @@ class TranslateWorker extends TerminologyWorker {
371
515
  }
372
516
 
373
517
  const outcome = {
374
- system: t.uri,
518
+ system: t.system,
375
519
  code: t.code,
376
520
  version: t.version,
377
521
  display: t.display
@@ -392,6 +536,12 @@ class TranslateWorker extends TerminologyWorker {
392
536
  valueString: t.message
393
537
  });
394
538
  }
539
+ if (!explicit) {
540
+ matchParts.push({
541
+ name: 'sourceMap',
542
+ valueCanonical: cm.vurl
543
+ });
544
+ }
395
545
  output.push({
396
546
  name: 'match',
397
547
  part: matchParts
@@ -408,9 +558,11 @@ class TranslateWorker extends TerminologyWorker {
408
558
  * @param {string} targetScope - Target value set scope (optional)
409
559
  * @param {string} targetSystem - Target code system (optional)
410
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
411
563
  * @returns {Object} Parameters resource with translate result
412
564
  */
413
- async doTranslate(conceptMaps, coding, targetScope, targetSystem, params) {
565
+ async doTranslate(conceptMaps, coding, targetScope, targetSystem, params, reverse, explicit) {
414
566
  this.deadCheck('doTranslate');
415
567
 
416
568
  const result = [];
@@ -419,9 +571,11 @@ class TranslateWorker extends TerminologyWorker {
419
571
  let added = false;
420
572
  for (const cm of conceptMaps) {
421
573
  if (cm.jsonObj.internalSource) {
422
- added = await this.translateUsingCodeSystem(cm, coding, targetSystem, params, result) || added;
423
- } else {
424
- 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;
425
579
  }
426
580
  }
427
581
  result.push({
@@ -517,6 +671,16 @@ class TranslateWorker extends TerminologyWorker {
517
671
  }
518
672
  }
519
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
+ }
520
684
  }
521
685
 
522
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;
@@ -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) {