fhirsmith 0.9.5 → 0.9.7

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 (43) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/config-template.json +2 -1
  3. package/library/folder-content-loader.js +91 -0
  4. package/library/regex-utilities.js +49 -12
  5. package/npmprojector/npmprojector.js +2 -6
  6. package/package.json +2 -2
  7. package/publisher/publisher.js +105 -12
  8. package/registry/registry.js +6 -6
  9. package/server.js +6 -2
  10. package/test-scripts/repro-re2-wasm-leak.js +8 -7
  11. package/translations/Messages.properties +1 -1
  12. package/tx/cs/cs-cs.js +8 -0
  13. package/tx/cs/cs-loinc.js +13 -12
  14. package/tx/cs/cs-omop.js +24 -23
  15. package/tx/cs/cs-provider-list.js +2 -1
  16. package/tx/cs/cs-snomed.js +142 -59
  17. package/tx/cs/cs-unii.js +11 -11
  18. package/tx/data/snomed-testing.cache +0 -0
  19. package/tx/library/canonical-resource.js +4 -2
  20. package/tx/library/designations.js +27 -20
  21. package/tx/library/renderer.js +303 -22
  22. package/tx/library/ucum-types.js +4 -1
  23. package/tx/library.js +65 -21
  24. package/tx/operation-context.js +52 -23
  25. package/tx/params.js +36 -8
  26. package/tx/problems.js +0 -4
  27. package/tx/provider.js +7 -3
  28. package/tx/tx-html.js +7 -0
  29. package/tx/tx.js +24 -13
  30. package/tx/vs/vs-vsac.js +157 -9
  31. package/tx/workers/expand.js +100 -96
  32. package/tx/workers/lookup.js +6 -0
  33. package/tx/workers/read.js +1 -1
  34. package/tx/workers/translate.js +21 -29
  35. package/tx/workers/validate.js +18 -10
  36. package/tx/workers/worker.js +5 -1
  37. package/tx/xversion/xv-bundle.js +1 -2
  38. package/tx/xversion/xv-codesystem.js +5 -2
  39. package/tx/xversion/xv-parameters.js +4 -4
  40. package/tx/xversion/xv-resource.js +2 -2
  41. package/tx/xversion/xv-terminologyCapabilities.js +11 -6
  42. package/tx/xversion/xv-valueset.js +7 -7
  43. package/publisher/task-draft.js +0 -458
@@ -647,11 +647,11 @@ class ValueSetExpander {
647
647
  } else if (cs.contentMode() === 'supplement') {
648
648
  throw new Issue('error', 'business-rule', null, null, 'The code system definition for ' + cset.system + ' defines a supplement, so this expansion cannot be performed', 'invalid');
649
649
  } else if (cs.contentMode() === 'fragment') {
650
- this.addParamUri(exp, 'used-fragment', cs.system() + '|' + cs.version());
650
+ this.addParamUri(exp, 'used-fragment', cs.system()+ (cs.version() ? '|' + cs.version(): ""));
651
651
  Extensions.addBoolean(exp, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", true);
652
652
  Extensions.addString(exp, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed-reason","This extension is based on a fragment of the code system " + cset.system);
653
653
  } else {
654
- this.addParamUri(exp, cs.contentMode(), cs.system() + '|' + cs.version());
654
+ this.addParamUri(exp, cs.contentMode(), cs.system() + cs.system()+ (cs.version() ? '|' + cs.version(): ""));
655
655
  Extensions.addBoolean(exp, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", true);
656
656
  Extensions.addString(exp, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed-reason","This extension is based on a fragment of the code system " + cset.system);
657
657
  }
@@ -948,7 +948,7 @@ class ValueSetExpander {
948
948
  let vs = await this.worker.findValueSet(s, '', vsSrc);
949
949
  const ivs = new ImportedValueSet(await this.expandValueSet(s, '', vs, filter, notClosed));
950
950
  this.checkResourceCanonicalStatus(expansion, ivs.valueSet, this.valueSet);
951
- if (!vs.isContained) {
951
+ if (!vs.isContained && ivs.valueSet.vurl) {
952
952
  this.addParamUri(expansion, 'used-valueset', ivs.valueSet.vurl);
953
953
  }
954
954
  valueSets.push(ivs);
@@ -961,124 +961,128 @@ class ValueSetExpander {
961
961
  const cs = await this.worker.findCodeSystem(cset.system, cset.version, this.params, ['complete', 'fragment'], false,
962
962
  true, true, null, this.requiredSupplements);
963
963
 
964
- this.worker.checkSupplements(cs, cset, this.requiredSupplements, this.usedSupplements);
965
- this.checkResourceCanonicalStatus(expansion, cs, this.valueSet);
966
- const sv = this.canonical(await cs.system(), await cs.version());
967
- this.addParamUri(expansion, 'used-codesystem', sv);
964
+ if (cs == null) {
965
+ // nothing?
966
+ } else {
967
+ this.worker.checkSupplements(cs, cset, this.requiredSupplements, this.usedSupplements);
968
+ this.checkResourceCanonicalStatus(expansion, cs, this.valueSet);
969
+ const sv = this.canonical(await cs.system(), await cs.version());
970
+ this.addParamUri(expansion, 'used-codesystem', sv);
968
971
 
969
- for (const u of cset.valueSet || []) {
970
- this.worker.deadCheck('processCodes#3');
971
- const s = this.worker.pinValueSet(u);
972
- this.worker.opContext.log('import value set ' + s);
973
- let vs = await this.worker.findValueSet(s, '', vsSrc);
974
- const ivs = new ImportedValueSet(await this.expandValueSet(s, '', vs, filter, notClosed));
975
- this.checkResourceCanonicalStatus(expansion, ivs.valueSet, this.valueSet);
976
- if (!vs.isContained) {
977
- this.addParamUri(expansion, 'used-valueset', this.worker.makeVurl(ivs.valueSet));
972
+ for (const u of cset.valueSet || []) {
973
+ this.worker.deadCheck('processCodes#3');
974
+ const s = this.worker.pinValueSet(u);
975
+ this.worker.opContext.log('import value set ' + s);
976
+ let vs = await this.worker.findValueSet(s, '', vsSrc);
977
+ const ivs = new ImportedValueSet(await this.expandValueSet(s, '', vs, filter, notClosed));
978
+ this.checkResourceCanonicalStatus(expansion, ivs.valueSet, this.valueSet);
979
+ if (!vs.isContained) {
980
+ this.addParamUri(expansion, 'used-valueset', this.worker.makeVurl(ivs.valueSet));
981
+ }
982
+ valueSets.push(ivs);
978
983
  }
979
- valueSets.push(ivs);
980
- }
981
984
 
982
- if (!cset.concept && !cset.filter) {
983
- this.worker.opContext.log('handle system');
984
- if (!cset.valueSet) {
985
- if (!this.excludeSpecialCase) {
986
- // excluding a whole system - we don't list the codes in this case
987
- this.excludedSystems.add(cset.system + (this.doingVersion && cset.version ? '|' + cset.version : ''));
985
+ if (!cset.concept && !cset.filter) {
986
+ this.worker.opContext.log('handle system');
987
+ if (!cset.valueSet) {
988
+ if (!this.excludeSpecialCase) {
989
+ // excluding a whole system - we don't list the codes in this case
990
+ this.excludedSystems.add(cset.system + (this.doingVersion && cset.version ? '|' + cset.version : ''));
991
+ } else {
992
+ const iter = await cs.iteratorAll();
993
+ if (iter) {
994
+ let c = await cs.nextContext(iter);
995
+ while (c) {
996
+ this.worker.deadCheck('processCodes#3aa');
997
+ this.excludeCode(cs, cs.system(), cs.version(), await cs.code(c), expansion, valueSets, vsSrc.url);
998
+ c = await cs.nextContext(iter);
999
+ }
1000
+ }
1001
+ }
988
1002
  } else {
1003
+ if (cs.isNotClosed(filter)) {
1004
+ if (cs.specialEnumeration()) {
1005
+ Extensions.addBoolean(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", true);
1006
+ Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed-reason", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
1007
+ } else {
1008
+ throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly', null, 422).withDiagnostics(this.worker.opContext.diagnostics());
1009
+ }
1010
+ }
1011
+
989
1012
  const iter = await cs.iteratorAll();
990
1013
  if (iter) {
991
1014
  let c = await cs.nextContext(iter);
992
1015
  while (c) {
993
- this.worker.deadCheck('processCodes#3aa');
994
- this.excludeCode(cs, cs.system(), cs.version(), await cs.code(c), expansion, valueSets, vsSrc.url);
1016
+ this.worker.deadCheck('processCodes#3a');
1017
+ if (await this.passesFilters(cs, c, prep, filters, 0)) {
1018
+ this.excludeCode(cs, cs.system(), cs.version(), await cs.code(c), expansion, valueSets, vsSrc.url);
1019
+ }
995
1020
  c = await cs.nextContext(iter);
996
1021
  }
997
1022
  }
998
1023
  }
999
- } else {
1000
- if (cs.isNotClosed(filter)) {
1001
- if (cs.specialEnumeration()) {
1002
- Extensions.addBoolean(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", true);
1003
- Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed-reason", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
1004
- } else {
1005
- throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly', null, 422).withDiagnostics(this.worker.opContext.diagnostics());
1006
- }
1007
- }
1008
-
1009
- const iter = await cs.iteratorAll();
1010
- if (iter) {
1011
- let c = await cs.nextContext(iter);
1012
- while (c) {
1013
- this.worker.deadCheck('processCodes#3a');
1014
- if (await this.passesFilters(cs, c, prep, filters, 0)) {
1015
- this.excludeCode(cs, cs.system(), cs.version(), await cs.code(c), expansion, valueSets, vsSrc.url);
1016
- }
1017
- c = await cs.nextContext(iter);
1018
- }
1019
- }
1020
1024
  }
1021
- }
1022
1025
 
1023
- if (cset.concept) {
1024
- this.worker.opContext.log('iterate concepts');
1025
- const cds = new Designations(this.worker.i18n.languageDefinitions);
1026
- for (const cc of cset.concept) {
1027
- this.worker.deadCheck('processCodes#3');
1028
- cds.clear();
1029
- Extensions.checkNoModifiers(cc, 'ValueSetExpander.processCodes', 'set concept reference', vsSrc.vurl);
1030
- const cctxt = await cs.locate(cc.code, this.allAltCodes);
1031
- if (cctxt && cctxt.context && (!this.params.activeOnly || !await cs.isInactive(cctxt.context)) && await this.passesFilters(cs, cctxt.context, prep, filters, 0)) {
1032
- if (filter.passesDesignations(cds) || filter.passes(cc.code)) {
1033
- let ov = Extensions.readString(cc, 'http://hl7.org/fhir/StructureDefinition/itemWeight');
1034
- if (!ov) {
1035
- ov = await cs.itemWeight(cctxt.context);
1026
+ if (cset.concept) {
1027
+ this.worker.opContext.log('iterate concepts');
1028
+ const cds = new Designations(this.worker.i18n.languageDefinitions);
1029
+ for (const cc of cset.concept) {
1030
+ this.worker.deadCheck('processCodes#3');
1031
+ cds.clear();
1032
+ Extensions.checkNoModifiers(cc, 'ValueSetExpander.processCodes', 'set concept reference', vsSrc.vurl);
1033
+ const cctxt = await cs.locate(cc.code, this.allAltCodes);
1034
+ if (cctxt && cctxt.context && (!this.params.activeOnly || !await cs.isInactive(cctxt.context)) && await this.passesFilters(cs, cctxt.context, prep, filters, 0)) {
1035
+ if (filter.passesDesignations(cds) || filter.passes(cc.code)) {
1036
+ let ov = Extensions.readString(cc, 'http://hl7.org/fhir/StructureDefinition/itemWeight');
1037
+ if (!ov) {
1038
+ ov = await cs.itemWeight(cctxt.context);
1039
+ }
1040
+ this.excludeCode(cs, await cs.system(), await cs.version(), cc.code, expansion, valueSets, vsSrc.url);
1036
1041
  }
1037
- this.excludeCode(cs, await cs.system(), await cs.version(), cc.code, expansion, valueSets, vsSrc.url);
1038
1042
  }
1039
1043
  }
1040
1044
  }
1041
- }
1042
1045
 
1043
- if (cset.filter) {
1044
- this.worker.opContext.log('prep filters');
1045
- const prep = await cs.getPrepContext(true);
1046
- if (!filter.isNull) {
1047
- await cs.searchFilter(filter, prep, true);
1048
- }
1046
+ if (cset.filter) {
1047
+ this.worker.opContext.log('prep filters');
1048
+ const prep = await cs.getPrepContext(true);
1049
+ if (!filter.isNull) {
1050
+ await cs.searchFilter(filter, prep, true);
1051
+ }
1049
1052
 
1050
- if (cs.specialEnumeration()) {
1051
- await cs.specialFilter(prep, true);
1052
- Extensions.addBoolean(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", true);
1053
- Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed-reason", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
1054
- }
1053
+ if (cs.specialEnumeration()) {
1054
+ await cs.specialFilter(prep, true);
1055
+ Extensions.addBoolean(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", true);
1056
+ Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed-reason", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
1057
+ }
1055
1058
 
1056
- let first = true;
1057
- for (let fc of cset.filter) {
1058
- this.worker.deadCheck('processCodes#4a');
1059
- Extensions.checkNoModifiers(fc, 'ValueSetExpander.processCodes', 'filter', vsSrc.vurl);
1060
- await cs.filter(prep, first, fc.property, fc.op, fc.value);
1061
- first = false;
1062
- }
1059
+ let first = true;
1060
+ for (let fc of cset.filter) {
1061
+ this.worker.deadCheck('processCodes#4a');
1062
+ Extensions.checkNoModifiers(fc, 'ValueSetExpander.processCodes', 'filter', vsSrc.vurl);
1063
+ await cs.filter(prep, first, fc.property, fc.op, fc.value);
1064
+ first = false;
1065
+ }
1063
1066
 
1064
- this.worker.opContext.log('iterate filters');
1065
- const fset = await cs.executeFilters(prep);
1066
- if (await cs.filtersNotClosed(prep)) {
1067
- notClosed.value = true;
1068
- }
1069
- //let count = 0;
1070
- while (await cs.filterMore(prep, fset[0])) {
1071
- this.worker.deadCheck('processCodes#5');
1072
- const c = await cs.filterConcept(prep, fset[0]);
1073
- const ok = (!this.params.activeOnly || !await cs.isInactive(c)) && (await this.passesFilters(cs, c, prep, fset, 1));
1074
- if (ok) {
1075
- //count++;
1076
- if (this.passesImports(valueSets, await cs.system(), await cs.code(c), 0)) {
1077
- this.excludeCode(cs, await cs.system(), await cs.version(), await cs.code(c), expansion, null, vsSrc.url);
1067
+ this.worker.opContext.log('iterate filters');
1068
+ const fset = await cs.executeFilters(prep);
1069
+ if (await cs.filtersNotClosed(prep)) {
1070
+ notClosed.value = true;
1071
+ }
1072
+ //let count = 0;
1073
+ while (await cs.filterMore(prep, fset[0])) {
1074
+ this.worker.deadCheck('processCodes#5');
1075
+ const c = await cs.filterConcept(prep, fset[0]);
1076
+ const ok = (!this.params.activeOnly || !await cs.isInactive(c)) && (await this.passesFilters(cs, c, prep, fset, 1));
1077
+ if (ok) {
1078
+ //count++;
1079
+ if (this.passesImports(valueSets, await cs.system(), await cs.code(c), 0)) {
1080
+ this.excludeCode(cs, await cs.system(), await cs.version(), await cs.code(c), expansion, null, vsSrc.url);
1081
+ }
1078
1082
  }
1079
1083
  }
1084
+ this.worker.opContext.log('iterate filters finished');
1080
1085
  }
1081
- this.worker.opContext.log('iterate filters finished');
1082
1086
  }
1083
1087
  }
1084
1088
  }
@@ -374,6 +374,12 @@ class LookupWorker extends TerminologyWorker {
374
374
  });
375
375
  }
376
376
 
377
+ if (designation.status && designation.status !== 'active') {
378
+ designationParts.push({
379
+ name: 'status',
380
+ valueCode: designation.status
381
+ });
382
+ }
377
383
  designationParts.push({
378
384
  name: 'value',
379
385
  valueString: designation.value
@@ -179,7 +179,7 @@ class ReadWorker extends TerminologyWorker {
179
179
  issue: [{
180
180
  severity: 'error',
181
181
  code: 'not-found',
182
- diagnostics: `ValueSet/${id} not found`
182
+ diagnostics: `ConceptMap/${id} not found`
183
183
  }]
184
184
  });
185
185
  }
@@ -205,10 +205,8 @@ class TranslateWorker extends TerminologyWorker {
205
205
  targetSystem = params.get('targetSystem');
206
206
  }
207
207
  }
208
- let explicit = true;
209
208
  // If no explicit concept map, we need to find one based on source/target
210
209
  if (conceptMaps.length == 0) {
211
- explicit = false;
212
210
  if (reverse) {
213
211
  await this.findConceptMapsInAdditionalResources(conceptMaps,targetSystem, targetScope, sourceScope, coding.system);
214
212
  await this.provider.findConceptMapForTranslation(this.opContext, conceptMaps, targetSystem, targetScope, sourceScope, coding.system, coding.code);
@@ -222,7 +220,7 @@ class TranslateWorker extends TerminologyWorker {
222
220
  }
223
221
 
224
222
  // Perform the translation
225
- const result = await this.doTranslate(conceptMaps, coding, targetScope, targetSystem, txp, reverse, explicit);
223
+ const result = await this.doTranslate(conceptMaps, coding, targetScope, targetSystem, txp, reverse);
226
224
  return res.status(200).json(result);
227
225
  }
228
226
 
@@ -322,7 +320,7 @@ class TranslateWorker extends TerminologyWorker {
322
320
  return result;
323
321
  }
324
322
 
325
- translateUsingGroupsForwards(cm, coding, targetScope, targetSystem, params, output, explicit) {
323
+ translateUsingGroupsForwards(cm, coding, targetScope, targetSystem, params, output) {
326
324
  let result = false;
327
325
  const matches = cm.listTranslations(coding, targetScope, targetSystem);
328
326
  if (matches.length > 0) {
@@ -385,12 +383,10 @@ class TranslateWorker extends TerminologyWorker {
385
383
  part: productParts
386
384
  });
387
385
  }
388
- if (!explicit) {
389
- matchParts.push({
390
- name: 'originMap',
391
- valueCanonical: cm.vurl
392
- });
393
- }
386
+ matchParts.push({
387
+ name: 'originMap',
388
+ valueCanonical: cm.vurl
389
+ });
394
390
  output.push({
395
391
  name: 'match',
396
392
  part: matchParts
@@ -403,7 +399,7 @@ class TranslateWorker extends TerminologyWorker {
403
399
  return result;
404
400
  }
405
401
 
406
- translateUsingGroupsReverse(cm, coding, targetScope, targetSystem, params, output, explicit) {
402
+ translateUsingGroupsReverse(cm, coding, targetScope, targetSystem, params, output) {
407
403
  let result = false;
408
404
  const matches = cm.listTranslationsReverse(coding, targetScope, targetSystem);
409
405
  if (matches.length > 0) {
@@ -474,12 +470,10 @@ class TranslateWorker extends TerminologyWorker {
474
470
  part: productParts
475
471
  });
476
472
  }
477
- if (!explicit) {
478
- matchParts.push({
479
- name: 'originMap',
480
- valueCanonical: cm.vurl
481
- });
482
- }
473
+ matchParts.push({
474
+ name: 'originMap',
475
+ valueCanonical: cm.vurl
476
+ });
483
477
  output.push({
484
478
  name: 'match',
485
479
  part: matchParts
@@ -491,10 +485,11 @@ class TranslateWorker extends TerminologyWorker {
491
485
  return result;
492
486
  }
493
487
 
494
- async translateUsingCodeSystem(cm, coding, target, params, output, reverse, explicit) {
488
+ async translateUsingCodeSystem(cm, coding, target, params, output, reverse) {
495
489
  let result = false;
496
490
  const factory = cm.jsonObj.internalSource;
497
491
  let prov = await factory.build(this.opContext, []);
492
+ this.opContext.registerProvider(prov);
498
493
 
499
494
  output.push({
500
495
  name: 'used-system',
@@ -536,12 +531,10 @@ class TranslateWorker extends TerminologyWorker {
536
531
  valueString: t.message
537
532
  });
538
533
  }
539
- if (!explicit) {
540
- matchParts.push({
541
- name: 'originMap',
542
- valueCanonical: cm.vurl
543
- });
544
- }
534
+ matchParts.push({
535
+ name: 'originMap',
536
+ valueCanonical: cm.vurl
537
+ });
545
538
  output.push({
546
539
  name: 'match',
547
540
  part: matchParts
@@ -559,10 +552,9 @@ class TranslateWorker extends TerminologyWorker {
559
552
  * @param {string} targetSystem - Target code system (optional)
560
553
  * @param {Parameters} params - Full parameters object
561
554
  * @param {boolean} reverse - Full parameters object*
562
- * @param {boolean} explicit - If the concept map was named explicitly
563
555
  * @returns {Object} Parameters resource with translate result
564
556
  */
565
- async doTranslate(conceptMaps, coding, targetScope, targetSystem, params, reverse, explicit) {
557
+ async doTranslate(conceptMaps, coding, targetScope, targetSystem, params, reverse) {
566
558
  this.deadCheck('doTranslate');
567
559
 
568
560
  const result = [];
@@ -571,11 +563,11 @@ class TranslateWorker extends TerminologyWorker {
571
563
  let added = false;
572
564
  for (const cm of conceptMaps) {
573
565
  if (cm.jsonObj.internalSource) {
574
- added = await this.translateUsingCodeSystem(cm, coding, targetSystem, params, result, reverse, explicit) || added;
566
+ added = await this.translateUsingCodeSystem(cm, coding, targetSystem, params, result, reverse) || added;
575
567
  } else if (reverse) {
576
- added = this.translateUsingGroupsReverse(cm, coding, targetScope, targetSystem, params, result, reverse, explicit) || added;
568
+ added = this.translateUsingGroupsReverse(cm, coding, targetScope, targetSystem, params, result) || added;
577
569
  } else{
578
- added = this.translateUsingGroupsForwards(cm, coding, targetScope, targetSystem, params, result, reverse, explicit) || added;
570
+ added = this.translateUsingGroupsForwards(cm, coding, targetScope, targetSystem, params, result) || added;
579
571
  }
580
572
  }
581
573
  result.push({
@@ -124,7 +124,7 @@ class ValueSetChecker {
124
124
  }
125
125
  }
126
126
  } catch (error) {
127
- this.log.error(error);
127
+ this.worker.log.error(error);
128
128
  debugLog(error);
129
129
  throw new Error('Exception expanding value set in order to infer system: ' + error.message);
130
130
  }
@@ -467,7 +467,7 @@ class ValueSetChecker {
467
467
  return false;
468
468
  }
469
469
  let cs = await this.worker.findCodeSystem(system, version, this.params, ['complete', 'fragment'], op,true, false, false, this.worker.requiredSupplements);
470
- this.seeSourceProvider(cs, system);
470
+ this.worker.seeSourceProvider(cs, system);
471
471
  if (cs === null) {
472
472
  this.worker.opContext.addNote(this.valueSet, 'Didn\'t find CodeSystem "' + this.worker.renderer.displayCoded(system, version) + '"', this.indentCount);
473
473
  result = null;
@@ -563,7 +563,7 @@ class ValueSetChecker {
563
563
  }
564
564
  let msg = await cs.incompleteValidationMessage(ctxt.context, this.params.HTTPLanguages);
565
565
  if (msg) {
566
- op.addIssueNoId('information', 'informational', addToPath(path, 'code'), msg, 'process-note');
566
+ op.addIssue(new Issue('information', 'informational', addToPath(path, 'code'), null, msg, 'process-note'));
567
567
  }
568
568
  inactive.value = await cs.isInactive(ctxt.context);
569
569
  inactive.path = path;
@@ -824,7 +824,7 @@ class ValueSetChecker {
824
824
  } else {
825
825
  let msg = 'The code system "' + ccc.system + '" version "' + ccc.version + '" in the ValueSet expansion is different to the one in the value ("' + version + '")';
826
826
  messages.push(msg);
827
- op.addIssueNoId('error', 'not-found', addToPath(path, 'version'), msg, 'vs-invalid');
827
+ op.addIssue(new Issue('error', 'not-found', addToPath(path, 'version'), null, msg, 'vs-invalid'));
828
828
  return false;
829
829
  }
830
830
  let cs = await this.worker.findCodeSystem(system, v, this.params, ['complete', 'fragment'], op, true, true, false, this.worker.requiredSupplements);
@@ -1122,7 +1122,7 @@ class ValueSetChecker {
1122
1122
  if ((cause.value === 'not-found' && contentMode.value !== 'complete') || contentMode.value === 'example') {
1123
1123
  let m = 'The system ' + c.system + ' was found but did not contain enough information to properly validate the code "' + c.code + '" ("' + c.display + '") (mode = ' + contentMode.value + ')';
1124
1124
  msg(m);
1125
- op.addIssueNoId('warning', 'not-found', path, m, 'vs-invalid');
1125
+ op.addIssue(new Issue('warning', 'not-found', path, null, m, 'vs-invalid'));
1126
1126
  } else if (c.display && list.designations.length > 0) {
1127
1127
  await this.checkDisplays(list, defLang, c, msg, op, path);
1128
1128
  }
@@ -1350,14 +1350,18 @@ class ValueSetChecker {
1350
1350
  if (vstatus.value && vstatus.value !== 'inactive') {
1351
1351
  result.addParamCode('status', vstatus.value);
1352
1352
  }
1353
+ let mpath = inactive.path;
1354
+ if (!mpath) {
1355
+ mpath = 'code';
1356
+ }
1353
1357
  if (!['inactive', 'DISCOURAGED'].includes(vstatus.value)) {
1354
1358
  let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, ['inactive', tcode]);
1355
1359
  msg(m);
1356
- op.addIssue(new Issue('warning', 'business-rule', inactive.path, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
1360
+ op.addIssue(new Issue('warning', 'business-rule', mpath, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
1357
1361
  }
1358
1362
  let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
1359
1363
  msg(m);
1360
- op.addIssue(new Issue('warning', 'business-rule', inactive.path, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
1364
+ op.addIssue(new Issue('warning', 'business-rule', mpath, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
1361
1365
  } else if (vstatus.value && vstatus.value.toLowerCase() === 'deprecated') {
1362
1366
  result.addParamCode('status', 'deprecated');
1363
1367
  let m = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
@@ -1414,7 +1418,11 @@ class ValueSetChecker {
1414
1418
  m = this.worker.i18n.translate('NO_VALID_DISPLAY_AT_ALL', this.params.HTTPLanguages, [c.display, c.system, c.code]);
1415
1419
  mid = 'NO_VALID_DISPLAY_AT_ALL';
1416
1420
  } else {
1417
- if (ds === c.display) {
1421
+ // no displays in the requested language(s): fall back to checking against the
1422
+ // displays + designations in the code system's default language, not just the
1423
+ // single preferred display (see tests validation-simple-*-good-language-none)
1424
+ let hdDef = list.hasDisplay(this.params.workingLanguages(), defLang.value, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE);
1425
+ if (hdDef.found) {
1418
1426
  m = this.worker.i18n.translate('NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_OK', this.params.HTTPLanguages, [c.display, c.system, c.code, this.params.langSummary(), ds]);
1419
1427
  mid = 'NO_VALID_DISPLAY_FOUND_NONE_FOR_LANG_OK';
1420
1428
  severity = 'information';
@@ -1518,14 +1526,14 @@ class ValueSetChecker {
1518
1526
  } else if (ok === null) {
1519
1527
  result.AddParamBool('result', false);
1520
1528
  result.addParamStr('message', 'The system "' + system + '" is unknown so the /"' + code + '" cannot be confirmed to be in the value set ' + this.valueSet.name);
1521
- op.addIssueNoId('error', cause.value, 'code', 'The system "' + system + '" is unknown so the /"' + code + '" cannot be confirmed to be in the value set ' + this.valueSet.name, 'not-found');
1529
+ op.addIssue(new Issue('error', cause.value, 'code', null, 'The system "' + system + '" is unknown so the /"' + code + '" cannot be confirmed to be in the value set ' + this.valueSet.name, 'not-found'));
1522
1530
  for (let us of unknownSystems) {
1523
1531
  result.addParamCanonical('x-caused-by-unknown-system', us);
1524
1532
  }
1525
1533
  } else {
1526
1534
  result.AddParamBool('result', false);
1527
1535
  result.addParamStr('message', 'The system/code "' + system + '"/"' + code + '" is not in the value set ' + this.valueSet.name);
1528
- op.addIssueNoId('error', cause.value, 'code', 'The system/code "' + system + '"/"' + code + '" is not in the value set ' + this.valueSet.name, 'not-in-vs');
1536
+ op.addIssue(new Issue('error', cause.value, 'code', null, 'The system/code "' + system + '"/"' + code + '" is not in the value set ' + this.valueSet.name, 'not-in-vs'));
1529
1537
  if (cause.value) {
1530
1538
  result.AddParamCode('cause', cause.value);
1531
1539
  }
@@ -1,4 +1,4 @@
1
- const { TerminologyError} = require('../operation-context');
1
+ const { TerminologyError} = require('../library/errors');
2
2
  const { CodeSystem } = require('../library/codesystem');
3
3
  const ValueSet = require('../library/valueset');
4
4
  const {VersionUtilities} = require("../../library/version-utilities");
@@ -184,6 +184,10 @@ class TerminologyWorker {
184
184
  if (checkVer) {
185
185
  this.checkVersion(url, provider.version(), params, provider.versionAlgorithm(), op);
186
186
  }
187
+ // Track for lifecycle cleanup at end of request. Providers built from
188
+ // a factory open a fresh sqlite connection that needs releasing;
189
+ // resource-built providers and others without close() are no-ops.
190
+ this.opContext.registerProvider(provider);
187
191
  }
188
192
 
189
193
  return provider;
@@ -18,8 +18,7 @@ function bundleToR5(jsonObj, sourceVersion) {
18
18
  for (let be of jsonObj.entry || []) {
19
19
  convertResourceToR5(be.resource, sourceVersion);
20
20
  }
21
-
22
- throw new Error(`Unsupported FHIR version: ${sourceVersion}`);
21
+ return jsonObj;
23
22
  }
24
23
 
25
24
  /**
@@ -139,9 +139,12 @@ function codeSystemR5ToR3(r5Obj) {
139
139
  * @private
140
140
  */
141
141
  function isR5OnlyFilterOperator(operator) {
142
+ // Operators added in R5 that are not valid in R4 or earlier.
143
+ // NOTE: 'generalizes' is NOT R5-only — it is a valid R4 operator
144
+ // (filter-operator value set, 4.0.1), so it must not be stripped here.
142
145
  const r5OnlyOperators = [
143
- 'generalizes', // Added in R5
144
- // Add other R5-only operators as they're identified
146
+ 'child-of', // Added in R5
147
+ 'descendent-leaf', // Added in R5
145
148
  ];
146
149
  return r5OnlyOperators.includes(operator);
147
150
  }
@@ -18,7 +18,7 @@ function parametersToR5(jsonObj, sourceVersion) {
18
18
  }
19
19
 
20
20
  const {convertResourceToR5} = require("./xv-resource");
21
- for (let p of jsonObj.parameter) {
21
+ for (let p of jsonObj.parameter || []) {
22
22
  if (p.resource) {
23
23
  p.resource = convertResourceToR5(p.resource, sourceVersion);
24
24
  }
@@ -59,7 +59,7 @@ function parametersFromR5(r5Obj, targetVersion) {
59
59
  function parametersR5ToR4(r5Obj) {
60
60
  const {convertResourceFromR5} = require("./xv-resource");
61
61
 
62
- for (let p of r5Obj.parameter) {
62
+ for (let p of r5Obj.parameter || []) {
63
63
  if (p.resource) {
64
64
  p.resource = convertResourceFromR5(p.resource, "R4");
65
65
  }
@@ -71,7 +71,7 @@ function parametersR5ToR4(r5Obj) {
71
71
  }
72
72
 
73
73
  function convertResourceWithinR5(r5Obj) {
74
- for (let p of r5Obj.parameter) {
74
+ for (let p of r5Obj.parameter || []) {
75
75
  if (p.name == 'match') {
76
76
  fixMatchParameterfor5(p);
77
77
  }
@@ -135,7 +135,7 @@ function convertParameterR5ToR3(p) {
135
135
  function parametersR5ToR3(r5Obj) {
136
136
  const {convertResourceFromR5} = require("./xv-resource");
137
137
 
138
- for (let p of r5Obj.parameter) {
138
+ for (let p of r5Obj.parameter || []) {
139
139
  if (p.resource) {
140
140
  p.resource = convertResourceFromR5(p.resource, "R3");
141
141
  }
@@ -9,7 +9,7 @@ const {bundleFromR5, bundleToR5} = require("./xv-bundle");
9
9
 
10
10
 
11
11
  function convertResourceToR5(data, sourceVersion) {
12
- if (sourceVersion == "5.0" || !data.resourceType) {
12
+ if (!data || sourceVersion == "5.0" || !data.resourceType) {
13
13
  return data;
14
14
  }
15
15
  switch (data.resourceType) {
@@ -26,7 +26,7 @@ function convertResourceToR5(data, sourceVersion) {
26
26
  }
27
27
 
28
28
  function convertResourceFromR5(data, targetVersion) {
29
- if (targetVersion == "5.0" || !data.resourceType) {
29
+ if (!data || targetVersion == "5.0" || !data.resourceType) {
30
30
  return data;
31
31
  }
32
32
  switch (data.resourceType) {
@@ -15,17 +15,22 @@ function terminologyCapabilitiesToR5(jsonObj, sourceVersion) {
15
15
  }
16
16
 
17
17
  if (VersionUtilities.isR4Ver(sourceVersion)) {
18
+ const contentExtUrl = "http://hl7.org/fhir/5.0/StructureDefinition/extension-TerminologyCapabilities.codeSystem.content";
18
19
  for (const cs of jsonObj.codeSystem || []) {
19
- if (cs.content) {
20
- let cnt = Extensions.readString(cs, "http://hl7.org/fhir/5.0/StructureDefinition/extension-TerminologyCapabilities.codeSystem.content");
21
- if (cnt) {
22
- delete cs.extensions;
23
- cs.content = cnt;
20
+ // In R4 the content is carried only by the content extension (there is no
21
+ // native codeSystem.content), so read it from the extension directly.
22
+ const cnt = Extensions.readString(cs, contentExtUrl);
23
+ if (cnt) {
24
+ cs.content = cnt;
25
+ // Remove the now-redundant source extension (the field is `extension`,
26
+ // singular). Filter so any unrelated extensions are preserved.
27
+ cs.extension = (cs.extension || []).filter(e => e.url !== contentExtUrl);
28
+ if (cs.extension.length === 0) {
29
+ delete cs.extension;
24
30
  }
25
31
  }
26
32
  }
27
33
 
28
-
29
34
  return jsonObj;
30
35
  }
31
36