fhirsmith 0.4.2 → 0.5.0

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 (92) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/library/cron-utilities.js +136 -0
  4. package/library/html-server.js +13 -29
  5. package/library/html.js +3 -8
  6. package/library/languages.js +160 -37
  7. package/library/package-manager.js +48 -1
  8. package/library/utilities.js +100 -19
  9. package/package.json +2 -2
  10. package/packages/package-crawler.js +6 -1
  11. package/packages/packages.js +38 -54
  12. package/publisher/publisher.js +19 -27
  13. package/registry/api.js +11 -10
  14. package/registry/crawler.js +31 -29
  15. package/registry/model.js +5 -26
  16. package/registry/registry.js +32 -41
  17. package/server.js +53 -5
  18. package/shl/shl.js +0 -18
  19. package/static/assets/js/statuspage.js +1 -9
  20. package/stats.js +39 -1
  21. package/token/token.js +14 -9
  22. package/translations/Messages.properties +2 -1
  23. package/tx/README.md +17 -6
  24. package/tx/cs/cs-api.js +19 -1
  25. package/tx/cs/cs-base.js +77 -0
  26. package/tx/cs/cs-country.js +46 -0
  27. package/tx/cs/cs-cpt.js +9 -5
  28. package/tx/cs/cs-cs.js +27 -13
  29. package/tx/cs/cs-lang.js +60 -22
  30. package/tx/cs/cs-loinc.js +69 -98
  31. package/tx/cs/cs-mimetypes.js +4 -0
  32. package/tx/cs/cs-ndc.js +6 -0
  33. package/tx/cs/cs-omop.js +16 -15
  34. package/tx/cs/cs-rxnorm.js +23 -1
  35. package/tx/cs/cs-snomed.js +283 -40
  36. package/tx/cs/cs-ucum.js +90 -70
  37. package/tx/importers/import-sct.module.js +371 -35
  38. package/tx/importers/readme.md +117 -7
  39. package/tx/library/bundle.js +5 -0
  40. package/tx/library/capabilitystatement.js +3 -142
  41. package/tx/library/codesystem.js +19 -173
  42. package/tx/library/conceptmap.js +4 -218
  43. package/tx/library/designations.js +14 -1
  44. package/tx/library/extensions.js +7 -0
  45. package/tx/library/namingsystem.js +3 -89
  46. package/tx/library/operation-outcome.js +8 -3
  47. package/tx/library/parameters.js +3 -2
  48. package/tx/library/renderer.js +10 -6
  49. package/tx/library/terminologycapabilities.js +3 -243
  50. package/tx/library/valueset.js +3 -235
  51. package/tx/library.js +100 -13
  52. package/tx/operation-context.js +23 -4
  53. package/tx/params.js +35 -38
  54. package/tx/provider.js +6 -5
  55. package/tx/sct/expressions.js +12 -3
  56. package/tx/tx-html.js +80 -89
  57. package/tx/tx.fhir.org.yml +6 -5
  58. package/tx/tx.js +163 -13
  59. package/tx/vs/vs-database.js +56 -39
  60. package/tx/vs/vs-package.js +21 -2
  61. package/tx/vs/vs-vsac.js +175 -39
  62. package/tx/workers/batch-validate.js +2 -0
  63. package/tx/workers/batch.js +2 -0
  64. package/tx/workers/expand.js +132 -112
  65. package/tx/workers/lookup.js +33 -14
  66. package/tx/workers/metadata.js +2 -2
  67. package/tx/workers/read.js +3 -2
  68. package/tx/workers/related.js +574 -0
  69. package/tx/workers/search.js +46 -9
  70. package/tx/workers/subsumes.js +13 -3
  71. package/tx/workers/translate.js +7 -3
  72. package/tx/workers/validate.js +258 -285
  73. package/tx/workers/worker.js +43 -39
  74. package/tx/xml/bundle-xml.js +237 -0
  75. package/tx/xml/xml-base.js +215 -64
  76. package/tx/xversion/xv-bundle.js +71 -0
  77. package/tx/xversion/xv-capabiliityStatement.js +137 -0
  78. package/tx/xversion/xv-codesystem.js +169 -0
  79. package/tx/xversion/xv-conceptmap.js +224 -0
  80. package/tx/xversion/xv-namingsystem.js +88 -0
  81. package/tx/xversion/xv-operationoutcome.js +27 -0
  82. package/tx/xversion/xv-parameters.js +87 -0
  83. package/tx/xversion/xv-resource.js +45 -0
  84. package/tx/xversion/xv-terminologyCapabilities.js +214 -0
  85. package/tx/xversion/xv-valueset.js +234 -0
  86. package/utilities/dev-proxy-server.js +126 -0
  87. package/utilities/explode-results.js +58 -0
  88. package/utilities/split-by-system.js +198 -0
  89. package/utilities/vsac-cs-fetcher.js +0 -0
  90. package/{windows-install.js → utilities/windows-install.js} +2 -0
  91. package/vcl/vcl.js +0 -18
  92. package/xig/xig.js +108 -99
@@ -43,7 +43,6 @@ class ValueSetChecker {
43
43
  worker;
44
44
  valueSet;
45
45
  params;
46
- requiredSupplements = [];
47
46
  others = new Map();
48
47
 
49
48
  constructor(worker, valueSet, params) {
@@ -74,20 +73,20 @@ class ValueSetChecker {
74
73
  checkCanonicalStatusFull(path, op, rtype, vurl, pid, status, standardsStatus, experimental, source) {
75
74
  if (op !== null) {
76
75
  if (standardsStatus === 'deprecated') {
77
- op.addIssue(new Issue('information', 'business-rule', '', 'MSG_DEPRECATED', this.worker.i18n.translate('MSG_DEPRECATED', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
76
+ op.addIssueIfNew(new Issue('information', 'business-rule', '', 'MSG_DEPRECATED', this.worker.i18n.translate('MSG_DEPRECATED', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
78
77
  } else if (standardsStatus === 'withdrawn') {
79
- op.addIssue(new Issue('information', 'business-rule', '', 'MSG_WITHDRAWN', this.worker.i18n.translate('MSG_WITHDRAWN', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
78
+ op.addIssueIfNew(new Issue('information', 'business-rule', '', 'MSG_WITHDRAWN', this.worker.i18n.translate('MSG_WITHDRAWN', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
80
79
  } else if (status === 'retired') {
81
- op.addIssue(new Issue('information', 'business-rule', '', 'MSG_RETIRED', this.worker.i18n.translate('MSG_RETIRED', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
80
+ op.addIssueIfNew(new Issue('information', 'business-rule', '', 'MSG_RETIRED', this.worker.i18n.translate('MSG_RETIRED', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
82
81
  } else if (source !== null) {
83
82
  if (experimental && !source.experimental) {
84
- op.addIssue(new Issue('information', 'business-rule', '', 'MSG_EXPERIMENTAL', this.worker.i18n.translate('MSG_EXPERIMENTAL', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
83
+ op.addIssueIfNew(new Issue('information', 'business-rule', '', 'MSG_EXPERIMENTAL', this.worker.i18n.translate('MSG_EXPERIMENTAL', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
85
84
  } else if ((status === 'draft' || standardsStatus === 'draft') &&
86
85
  !((source.status === 'draft') || (Extensions.readString(source, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status') === 'draft'))) {
87
86
  if (pid) {
88
- op.addIssue(new Issue('information', 'business-rule', '', 'MSG_DRAFT', this.worker.i18n.translate('MSG_DRAFT_SRC', this.params.HTTPLanguages, [vurl, pid, rtype]), 'status-check'), false);
87
+ op.addIssueIfNew(new Issue('information', 'business-rule', '', 'MSG_DRAFT', this.worker.i18n.translate('MSG_DRAFT_SRC', this.params.HTTPLanguages, [vurl, pid, rtype]), 'status-check'), false);
89
88
  } else {
90
- op.addIssue(new Issue('information', 'business-rule', '', 'MSG_DRAFT', this.worker.i18n.translate('MSG_DRAFT', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
89
+ op.addIssueIfNew(new Issue('information', 'business-rule', '', 'MSG_DRAFT', this.worker.i18n.translate('MSG_DRAFT', this.params.HTTPLanguages, [vurl, '', rtype]), 'status-check'), false);
91
90
  }
92
91
  }
93
92
  }
@@ -121,9 +120,10 @@ class ValueSetChecker {
121
120
  }
122
121
  }
123
122
  }
124
- } catch (e) {
125
- console.error(e);
126
- throw new Error('Exception expanding value set in order to infer system: ' + e.message);
123
+ } catch (error) {
124
+ this.log.error(error);
125
+ this.debugLog(error);
126
+ throw new Error('Exception expanding value set in order to infer system: ' + error.message);
127
127
  }
128
128
  return result;
129
129
  }
@@ -151,6 +151,7 @@ class ValueSetChecker {
151
151
  async determineSystem(opContext, code, systems, op) {
152
152
  let result = '';
153
153
  let needDoExpansion = false;
154
+ let mayNeedExpansion = false;
154
155
 
155
156
  for (let vsi of this.valueSet.jsonObj.compose.exclude || []) {
156
157
  if (vsi.valueSet || !vsi.system || vsi.filter) {
@@ -158,9 +159,12 @@ class ValueSetChecker {
158
159
  }
159
160
  }
160
161
  for (let vsi of this.valueSet.jsonObj.compose.include || []) {
161
- if (vsi.valueSet || !vsi.system || vsi.filter) {
162
+ if (vsi.valueSet || !vsi.system) {
162
163
  needDoExpansion = true;
163
164
  }
165
+ if (vsi.filter) {
166
+ mayNeedExpansion = true;
167
+ }
164
168
  }
165
169
 
166
170
  if (needDoExpansion) {
@@ -168,7 +172,7 @@ class ValueSetChecker {
168
172
  } else {
169
173
  for (let vsi of this.valueSet.jsonObj.compose.include) {
170
174
  this.worker.deadCheck('determineSystem');
171
- let cs = await this.worker.findCodeSystem(vsi.system, '', null, ['complete', 'fragment'], op,true);
175
+ let cs = await this.worker.findCodeSystem(vsi.system, '', null, ['complete', 'fragment'], op,true, false, false, this.worker.requiredSupplements);
172
176
  if (cs === null) {
173
177
  return '';
174
178
  }
@@ -192,7 +196,11 @@ class ValueSetChecker {
192
196
  if (!result) {
193
197
  result = vsi.system;
194
198
  } else if (result !== vsi.system) {
195
- return '';
199
+ if (mayNeedExpansion) {
200
+ result = await this.determineSystemFromExpansion(code, systems);
201
+ } else {
202
+ return '';
203
+ }
196
204
  }
197
205
  }
198
206
  }
@@ -212,7 +220,7 @@ class ValueSetChecker {
212
220
 
213
221
 
214
222
  let result;
215
- let csa = await this.worker.findCodeSystem(system, '', this.params, "*", op,true, false, true);
223
+ let csa = await this.worker.findCodeSystem(system, '', this.params, "*", op,true, false, true, this.worker.requiredSupplements);
216
224
 
217
225
  result = this.worker.determineVersionBase(system, versionVS, this.params);
218
226
 
@@ -231,7 +239,7 @@ class ValueSetChecker {
231
239
  }
232
240
  }
233
241
 
234
- let cs = await this.worker.findCodeSystem(system, result, this.params, ['complete', 'fragment'], op,true, false, false);
242
+ let cs = await this.worker.findCodeSystem(system, result, this.params, ['complete', 'fragment'], op,true, false, false, this.worker.requiredSupplements);
235
243
  if (cs !== null && cs.version() !== versionCoding && !cs.versionIsMoreDetailed(versionCoding, cs.version())) {
236
244
  let errLvl = 'error';
237
245
  let msg, mid;
@@ -252,7 +260,7 @@ class ValueSetChecker {
252
260
  if (errLvl === 'error') {
253
261
  messages.push(msg);
254
262
  }
255
- let cs2 = await this.worker.findCodeSystem(system, versionCoding, this.params, ['complete', 'fragment'], op, true, false, true);
263
+ let cs2 = await this.worker.findCodeSystem(system, versionCoding, this.params, ['complete', 'fragment'], op, true, false, true, this.worker.requiredSupplements);
256
264
  if (cs2 !== null) {
257
265
  cs2 = null;
258
266
  } else {
@@ -289,7 +297,7 @@ class ValueSetChecker {
289
297
 
290
298
  async prepare() {
291
299
  if (this.valueSet === null) {
292
- throw new Issue('error', 'not-found', null, null, 'Error Error: vs = nil');
300
+ throw new Issue('error', 'not-found', null, null, 'Error Error: vs = nil', null, 422);
293
301
  } else {
294
302
  this.seeValueSet();
295
303
  this.worker.opContext.addNote(this.valueSet, 'Analysing ' + this.valueSet.vurl + ' for validation purposes', this.indentCount);
@@ -300,11 +308,7 @@ class ValueSetChecker {
300
308
  this.worker.opContext.addNote(this.valueSet, 'Version Rules: ' + vrs, this.indentCount);
301
309
  }
302
310
  }
303
- this.requiredSupplements = [];
304
- for (let ext of Extensions.list(this.valueSet.jsonObj, 'http://hl7.org/fhir/StructureDefinition/valueset-supplement')) {
305
- this.requiredSupplements.push(getValuePrimitive(ext));
306
- }
307
- if (this.requiredSupplements.length > 0) {
311
+ if (this.worker.requiredSupplements.size > 0) {
308
312
  await this.checkSupplementsExist(this.valueSet);
309
313
  }
310
314
 
@@ -315,6 +319,7 @@ class ValueSetChecker {
315
319
 
316
320
  if (this.valueSet.jsonObj.compose) {
317
321
  Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose');
322
+ this.worker.checkNoLockedDate(this.valueSet.url, this.valueSet.jsonObj.compose)
318
323
  let i = 0;
319
324
  for (let cc of this.valueSet.jsonObj.compose.include || []) {
320
325
  await this.prepareConceptSet('include['+i+']', cc);
@@ -327,8 +332,10 @@ class ValueSetChecker {
327
332
  }
328
333
  }
329
334
  }
330
- if (this.requiredSupplements.length > 0) {
331
- throw new Issue('error', 'not-found', null, 'VALUESET_SUPPLEMENT_MISSING', this.worker.i18n.translatePlural(this.requiredSupplements.length, 'VALUESET_SUPPLEMENT_MISSING', this.params.HTTPLanguages, [this.requiredSupplements.join(',')])).handleAsOO(400);
335
+
336
+ const unused = new Set([...this.worker.requiredSupplements].filter(s => !this.worker.usedSupplements.has(s)));
337
+ if (unused.size > 0) {
338
+ 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);
332
339
  }
333
340
  }
334
341
 
@@ -343,7 +350,7 @@ class ValueSetChecker {
343
350
  if (!this.others.has(s)) {
344
351
  let other = await this.worker.findValueSet(s, '');
345
352
  if (other === null) {
346
- throw new Issue('error', 'not-found', null, 'Unable_to_resolve_value_Set_', this.worker.i18n.translate('Unable_to_resolve_value_Set_', this.params.HTTPLanguages, [s]), 'not-found');
353
+ throw new Issue('error', 'not-found', null, 'Unable_to_resolve_value_Set_', this.worker.i18n.translate('Unable_to_resolve_value_Set_', this.params.HTTPLanguages, [s]), 'not-found', 422);
347
354
  }
348
355
  let checker = new ValueSetChecker(this.worker, other, this.params);
349
356
  checker.indentCount = this.indentCount + 1;
@@ -353,12 +360,12 @@ class ValueSetChecker {
353
360
  }
354
361
  }
355
362
  let v = this.worker.determineVersionBase(cc.system, cc.version, this.params);
356
- let cs = await this.worker.findCodeSystem(cc.system, v, this.params, ['complete', 'fragment'], null, true, false, false);
363
+ let cs = await this.worker.findCodeSystem(cc.system, v, this.params, ['complete', 'fragment'], null, true, false, false, this.worker.requiredSupplements);
357
364
  if (cs !== null) {
358
365
  this.worker.opContext.addNote(this.valueSet, 'CodeSystem found: "' + this.worker.renderer.displayCoded(cs) + '"', this.indentCount);
359
- for (let i = this.requiredSupplements.length - 1; i >= 0; i--) {
360
- if (cs.hasSupplement(this.requiredSupplements[i])) {
361
- this.requiredSupplements.splice(i, 1);
366
+ for (const s of this.worker.requiredSupplements) {
367
+ if (cs.hasSupplement(s)) {
368
+ this.worker.usedSupplements.add(s);
362
369
  }
363
370
  }
364
371
  let i = 0;
@@ -415,9 +422,9 @@ class ValueSetChecker {
415
422
  }
416
423
  }
417
424
 
418
- async checkSimple(issuePath, system, version, code, abstractOk, inferSystem, op) {
425
+ async checkSimple(issuePath, system, version, code, op) {
419
426
  this.worker.opContext.clearContexts();
420
- if (inferSystem) {
427
+ if (this.params.inferSystem) {
421
428
  this.worker.opContext.addNote(this.valueSet, 'Validate "' + code + '" and infer system', this.indentCount);
422
429
  } else {
423
430
  this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(system, version, code) + '"', this.indentCount);
@@ -433,13 +440,14 @@ class ValueSetChecker {
433
440
  let contentMode = {value: null};
434
441
  let impliedSystem = {value: ''};
435
442
  let defLang = {value: null};
436
- return await this.check(issuePath, system, version, code, abstractOk, inferSystem, null, unknownSystems, ver, inactive, normalForm, vstatus, it, op, null, null, contentMode, impliedSystem, ts, msgs, defLang);
443
+ return await this.check(issuePath, system, version, code, null, unknownSystems, ver, inactive, normalForm, vstatus, it, op, null, null, contentMode, impliedSystem, ts, msgs, defLang);
437
444
  }
438
445
 
439
- async check(path, system, version, code, abstractOk, inferSystem, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, params, contentMode, impliedSystem, unkCodes, messages, defLang) {
446
+ async check(path, system, version, code, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, params, contentMode, impliedSystem, unkCodes, messages, defLang) {
440
447
  defLang.value = new Language('en');
441
448
  this.worker.opContext.addNote(this.valueSet, 'Check "' + this.worker.renderer.displayCoded(system, version, code) + '"', this.indentCount);
442
- if (!system && !inferSystem) {
449
+
450
+ if (!system && !this.params.inferSystem) {
443
451
  let msg = this.worker.i18n.translate('Coding_has_no_system__cannot_validate', this.params.HTTPLanguages, []);
444
452
  messages.push(msg);
445
453
  op.addIssue(new Issue('warning', 'invalid', path, 'Coding_has_no_system__cannot_validate', msg, 'invalid-data'));
@@ -455,7 +463,7 @@ class ValueSetChecker {
455
463
  op.addIssue(new Issue('warning', 'invalid', path, 'Coding_has_no_system__cannot_validate_NO_INFER', msg, 'invalid-data'));
456
464
  return false;
457
465
  }
458
- let cs = await this.worker.findCodeSystem(system, version, this.params, ['complete', 'fragment'], op,true);
466
+ let cs = await this.worker.findCodeSystem(system, version, this.params, ['complete', 'fragment'], op,true, false, false, this.worker.requiredSupplements);
459
467
  this.seeSourceProvider(cs, system);
460
468
  if (cs === null) {
461
469
  this.worker.opContext.addNote(this.valueSet, 'Didn\'t find CodeSystem "' + this.worker.renderer.displayCoded(system, version) + '"', this.indentCount);
@@ -469,7 +477,7 @@ class ValueSetChecker {
469
477
  op.addIssue(new Issue('error', 'invalid', addToPath(path, 'system'), 'Terminology_TX_System_ValueSet2', msg, 'invalid-data'));
470
478
  unknownSystems.add(system);
471
479
  } else {
472
- let css = await this.worker.findCodeSystem(system, version, this.params, ['supplement'], op,true);
480
+ let css = await this.worker.findCodeSystem(system, version, this.params, ['supplement'], op,true, false, false, this.worker.requiredSupplements);
473
481
  if (css !== null) {
474
482
  vss = null;
475
483
  let msg = this.worker.i18n.translate('CODESYSTEM_CS_NO_SUPPLEMENT', this.params.HTTPLanguages, [this.canonical(css.system(), css.version())]);
@@ -519,7 +527,7 @@ class ValueSetChecker {
519
527
  result = false;
520
528
  cause.value = 'code-invalid';
521
529
  this.worker.opContext.addNote(this.valueSet, 'Unknown code', this.indentCount);
522
- let msg = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]);
530
+ let msg = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system, cs.version()), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]);
523
531
  messages.push(msg);
524
532
  op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version', msg, 'invalid-code'));
525
533
  }
@@ -528,7 +536,7 @@ class ValueSetChecker {
528
536
  vcc.addCoding(cs.system(), cs.version(), await cs.code(ctxt), cs.display(ctxt, this.params.workingLanguages()));
529
537
  }
530
538
  cause.value = 'null';
531
- if (!(abstractOk || !(await cs.IsAbstract(ctxt)))) {
539
+ if (!(this.params.abstractOk || !(await cs.isAbstract(ctxt)))) {
532
540
  result = false;
533
541
  this.worker.opContext.addNote(this.valueSet, 'Abstract code when not allowed', this.indentCount);
534
542
  cause.value = 'business-rule';
@@ -565,7 +573,7 @@ class ValueSetChecker {
565
573
  }
566
574
  } else if (DEV_IGNORE_VALUESET) {
567
575
  // anyhow, we ignore the value set (at least for now)
568
- let cs = await this.worker.findCodeSystem(system, version, this.params, ['complete', 'fragment'], op, true, true, false);
576
+ let cs = await this.worker.findCodeSystem(system, version, this.params, ['complete', 'fragment'], op, true, true, false, this.worker.requiredSupplements);
569
577
  if (cs === null) {
570
578
  result = null;
571
579
  cause.value = 'not-found';
@@ -596,11 +604,11 @@ class ValueSetChecker {
596
604
  defLang.value = cs.defLang();
597
605
  this.checkCanonicalStatus(path, op, cs, this.valueSet);
598
606
  ver.value = cs.version();
599
- contentMode.value = cs.contentMode;
607
+ contentMode.value = cs.contentMode();
600
608
  let ctxt = cs.locate(code);
601
609
  if (ctxt.context === null) {
602
610
  unkCodes.push(system + '|' + version + '#' + code);
603
- if (cs.contentMode !== 'complete') {
611
+ if (cs.contentMode() !== 'complete') {
604
612
  result = true;
605
613
  cause.value = 'code-invalid';
606
614
  this.worker.opContext.addNote(this.valueSet, 'Not found in Incomplete Code System', this.indentCount);
@@ -611,14 +619,14 @@ class ValueSetChecker {
611
619
  result = false;
612
620
  cause.value = 'code-invalid';
613
621
  this.worker.opContext.addNote(this.valueSet, 'Unknown code', this.indentCount);
614
- let msg = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(system), this.params.HTTPLanguages, [code, system, version, SCTVersion(system, version)]);
622
+ let msg = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(system, version), this.params.HTTPLanguages, [code, system, version, SCTVersion(system, version)]);
615
623
  messages.push(msg);
616
624
  op.addIssue(new Issue('warning', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version', msg, 'invalid-code'));
617
625
  }
618
626
  } else {
619
627
  ctxt = ctxt.context;
620
628
  cause.value = 'null';
621
- if (!(abstractOk || !cs.IsAbstract(ctxt))) {
629
+ if (!(this.params.abstractOk || !(await cs.isAbstract(ctxt)))) {
622
630
  result = false;
623
631
  this.worker.opContext.addNote(this.valueSet, 'Abstract code when not allowed', this.indentCount);
624
632
  cause.value = 'business-rule';
@@ -640,7 +648,7 @@ class ValueSetChecker {
640
648
  }
641
649
  }
642
650
  } else {
643
- if (!system && inferSystem) {
651
+ if (!system && this.params.inferSystem) {
644
652
  let systems = new Set();
645
653
  system = await this.determineSystem(this.worker.opContext, code, systems, op);
646
654
  if (!system) {
@@ -661,8 +669,9 @@ class ValueSetChecker {
661
669
  }
662
670
  }
663
671
 
664
- if (this.requiredSupplements.length > 0) {
665
- throw new Issue('error', 'not-found', null, 'VALUESET_SUPPLEMENT_MISSING', this.worker.i18n.translatePlural(this.requiredSupplements.length, 'VALUESET_SUPPLEMENT_MISSING', this.params.HTTPLanguages, [this.requiredSupplements.join(',')])).handleAsOO(400);
672
+ const unused = new Set([...this.worker.requiredSupplements].filter(s => !this.worker.usedSupplements.has(s)));
673
+ if (unused.size > 0) {
674
+ 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);
666
675
  }
667
676
 
668
677
  if (Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose')) {
@@ -673,7 +682,7 @@ class ValueSetChecker {
673
682
  result = true;
674
683
  } else if (cc.system === system || system === '%%null%%') {
675
684
  let v = await this.determineVersion(path, cc.system, cc.version, version, op, unknownSystems, messages);
676
- let cs = await this.worker.findCodeSystem(system, v, this.params, ["complete", "fragment"], op,true, true, false);
685
+ let cs = await this.worker.findCodeSystem(system, v, this.params, ["complete", "fragment"], op,true, true, false, this.worker.requiredSupplements);
677
686
  if (cs === null) {
678
687
  this.worker.opContext.addNote(this.valueSet, 'CodeSystem not found: ' + this.worker.renderer.displayCoded(cc.system, v), this.indentCount);
679
688
  if (!this.params.membershipOnly) {
@@ -711,11 +720,11 @@ class ValueSetChecker {
711
720
  this.worker.opContext.addNote(this.valueSet, 'CodeSystem found: ' + this.worker.renderer.displayCoded(cs) + ' for ' + this.worker.renderer.displayCoded(cc.system, v), this.indentCount);
712
721
  await this.checkCanonicalStatusCS(path, op, cs, this.valueSet);
713
722
  ver.value = cs.version();
714
- this.worker.checkSupplements(cs, cc, this.requiredSupplements);
723
+ this.worker.checkSupplements(cs, cc, this.worker.requiredSupplements, this.worker.usedSupplements);
715
724
  contentMode.value = cs.contentMode();
716
725
 
717
726
  let msg = '';
718
- if ((system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'in', cs, cc, code, abstractOk, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc, messages)) {
727
+ if ((system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'in', cs, cc, code, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc, messages)) {
719
728
  result = true;
720
729
  } else {
721
730
  result = false;
@@ -736,7 +745,7 @@ class ValueSetChecker {
736
745
  }
737
746
  this.checkCanonicalStatus(path, op, checker.valueSet, this.valueSet);
738
747
  if (result === true) {
739
- result = await checker.check(path, system, version, code, abstractOk, inferSystem, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, params, contentMode, impliedSystem, unkCodes, messages, defLang);
748
+ result = await checker.check(path, system, version, code, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, params, contentMode, impliedSystem, unkCodes, messages, defLang);
740
749
  }
741
750
  }
742
751
  if (result === true) {
@@ -747,24 +756,24 @@ class ValueSetChecker {
747
756
  for (let cc of this.valueSet.jsonObj.compose.exclude || []) {
748
757
  this.worker.deadCheck('check#4');
749
758
  let excluded;
750
- if (cc.system) {
759
+ if (!cc.system) {
751
760
  excluded = true;
752
761
  } else {
753
- let cs = await this.worker.findCodeSystem(cc.system, cc.version, this.params, ['complete', 'fragment'], op,true, true, false);
762
+ let cs = await this.worker.findCodeSystem(cc.system, cc.version, this.params, ['complete', 'fragment'], op,true, true, false, this.worker.requiredSupplements);
754
763
  if (cs === null) {
755
764
  throw new Issue('error', 'unknown', null, null, 'No Match for ' + cc.system + '|' + cc.version);
756
765
  }
757
766
  await this.checkCanonicalStatus(path, op, cs, this.valueSet);
758
- this.worker.checkSupplements(cs, cc, this.requiredSupplements);
767
+ this.worker.checkSupplements(cs, cc, this.worker.requiredSupplements, this.worker.usedSupplements);
759
768
  ver.value = cs.version();
760
769
  contentMode.value = cs.contentMode();
761
770
  let msg = '';
762
- excluded = (system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'not in', cs, cc, code, abstractOk, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc);
771
+ excluded = (system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'not in', cs, cc, code, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc);
763
772
  if (msg) {
764
773
  messages.push(msg);
765
774
  }
766
775
  }
767
- for (let u of cc.valueSets || []) {
776
+ for (let u of cc.valueSet || []) {
768
777
  this.worker.deadCheck('check#5');
769
778
  let s = this.worker.pinValueSet(u);
770
779
  let checker = this.others.get(s);
@@ -772,7 +781,7 @@ class ValueSetChecker {
772
781
  throw new Issue('error', 'unknown', null, null, 'No Match for ' + cc.system + '|' + cc.version + ' in ' + Array.from(this.others.keys()).join(','));
773
782
  }
774
783
  this.checkCanonicalStatus(path, op, checker.valueSet, this.valueSet);
775
- excluded = excluded && (await checker.check(path, system, version, code, abstractOk, inferSystem, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, params, contentMode, impliedSystem, unkCodes, messages, defLang) === true);
784
+ excluded = excluded && (await checker.check(path, system, version, code, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, params, contentMode, impliedSystem, unkCodes, messages, defLang) === true);
776
785
  }
777
786
  if (excluded) {
778
787
  return false;
@@ -799,7 +808,7 @@ class ValueSetChecker {
799
808
  op.addIssueNoId('error', 'not-found', addToPath(path, 'version'), msg, 'vs-invalid');
800
809
  return false;
801
810
  }
802
- let cs = await this.worker.findCodeSystem(system, v, this.params, ['complete', 'fragment'], op, true, true, false);
811
+ let cs = await this.worker.findCodeSystem(system, v, this.params, ['complete', 'fragment'], op, true, true, false, this.worker.requiredSupplements);
803
812
  if (cs === null) {
804
813
  if (!this.params.membershipOnly) {
805
814
  let bAdd = true;
@@ -837,7 +846,7 @@ class ValueSetChecker {
837
846
  ver.value = cs.version();
838
847
  contentMode.value = cs.contentMode();
839
848
  let msg = '';
840
- if ((system === '%%null%%' || cs.system() === system) && await this.checkExpansion(path, cs, ccc, code, abstractOk, displays, this.valueSet, msg, inactive, vstatus, op)) {
849
+ if ((system === '%%null%%' || cs.system() === system) && await this.checkExpansion(path, cs, ccc, code, displays, this.valueSet, msg, inactive, vstatus, op)) {
841
850
  result = true;
842
851
  } else {
843
852
  result = false;
@@ -854,7 +863,7 @@ class ValueSetChecker {
854
863
  return result;
855
864
  }
856
865
 
857
- async checkCoding(issuePath, coding, abstractOk, inferSystem) {
866
+ async checkCoding(issuePath, coding) {
858
867
  let inactive = false;
859
868
  let path = issuePath;
860
869
  let unknownSystems = new Set();
@@ -863,7 +872,7 @@ class ValueSetChecker {
863
872
  let result = new Parameters();
864
873
 
865
874
  this.worker.opContext.clearContexts();
866
- if (inferSystem) {
875
+ if (this.params.inferSystem) {
867
876
  this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(coding) + '" and infer system', this.indentCount);
868
877
  } else {
869
878
  this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(coding) + '"', this.indentCount);
@@ -881,7 +890,7 @@ class ValueSetChecker {
881
890
  let impliedSystem = {value: ''};
882
891
  let defLang = {value: null};
883
892
 
884
- let ok = await this.check(path, coding.system, coding.version, coding.code, abstractOk, inferSystem, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, result, contentMode, impliedSystem, unkCodes, messages, defLang);
893
+ let ok = await this.check(path, coding.system, coding.version, coding.code, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, result, contentMode, impliedSystem, unkCodes, messages, defLang);
885
894
  if (ok === true) {
886
895
  result.AddParamBool('result', true);
887
896
  if ((cause.value === 'not-found' && contentMode.value !== 'complete') || contentMode.value === 'example') {
@@ -962,17 +971,17 @@ class ValueSetChecker {
962
971
  async checkSupplementsExist(vs) {
963
972
  for (let inc of vs.jsonObj.compose.include) {
964
973
  if (inc.system) {
965
- let cs = await this.worker.findCodeSystem(inc.system, inc.version, this.params, ['complete', 'fragment'], null,true);
974
+ let cs = await this.worker.findCodeSystem(inc.system, inc.version, this.params, ['complete', 'fragment'], null,true, false, false, this.worker.requiredSupplements);
966
975
  if (cs !== null) {
967
- await this.worker.checkSupplements(cs, null, this.requiredSupplements);
976
+ await this.worker.checkSupplements(cs, null, this.worker.requiredSupplements, this.worker.usedSupplements);
968
977
  }
969
978
  }
970
979
  }
971
980
  }
972
981
 
973
- async checkCodeableConcept(issuePath, code, abstractOk, inferSystem, mode) {
982
+ async checkCodeableConcept(issuePath, code, mode) {
974
983
  this.worker.opContext.clearContexts();
975
- if (inferSystem) {
984
+ if (this.params.inferSystem) {
976
985
  this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(code) + '" and infer system', this.indentCount);
977
986
  } else {
978
987
  this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(code) + '"', this.indentCount);
@@ -996,7 +1005,7 @@ class ValueSetChecker {
996
1005
  let tsys = '';
997
1006
  let tcode = '';
998
1007
  let tver = '';
999
- let vcc = {};
1008
+ let vcc = {}; // todo: VCC is an appendage, and useless. remove it
1000
1009
  if (code.text) {
1001
1010
  vcc.text = code.text;
1002
1011
  }
@@ -1023,10 +1032,9 @@ class ValueSetChecker {
1023
1032
  mt = [];
1024
1033
  let i = 0;
1025
1034
  let impliedSystem = { value: '' };
1026
- for (let c of code.coding) {
1027
- const csd = await this.worker.findCodeSystem(c.system, null, this.params, ['complete', 'fragment'], false, true);
1035
+ for (let c of code.coding || []) {
1036
+ const csd = await this.worker.findCodeSystem(c.system, null, this.params, ['complete', 'fragment'], false, true, false, false, this.worker.requiredSupplements);
1028
1037
  this.worker.seeSourceProvider(csd, c.system);
1029
-
1030
1038
  this.worker.deadCheck('check-b#1');
1031
1039
  let path;
1032
1040
  if (issuePath === 'CodeableConcept') {
@@ -1034,11 +1042,16 @@ class ValueSetChecker {
1034
1042
  } else {
1035
1043
  path = issuePath;
1036
1044
  }
1045
+ if (!c.code) {
1046
+ let msg = `Coding has no code for system ${c.system} and cannot be validated`;
1047
+ op.addIssue(new Issue('error', 'invalid', path, null, msg, 'invalid-data'));
1048
+ break;
1049
+ }
1037
1050
  list.clear();
1038
1051
  let ver = { value: '' };
1039
1052
  let contentMode = { value: null };
1040
1053
  let defLang = { value: null };
1041
- let v = await this.check(path, c.system, c.version, c.code, abstractOk, inferSystem, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, result, contentMode, impliedSystem, ts, mt, defLang);
1054
+ let v = await this.check(path, c.system, c.version, c.code, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, result, contentMode, impliedSystem, ts, mt, defLang);
1042
1055
  if (v === false) {
1043
1056
  cause.value = 'code-invalid';
1044
1057
  }
@@ -1053,6 +1066,9 @@ class ValueSetChecker {
1053
1066
  tcode = c.code;
1054
1067
  tver = c.version;
1055
1068
  }
1069
+ if (!tver && ver.value) {
1070
+ tver = ver.value;
1071
+ }
1056
1072
  let cc;
1057
1073
  if (!ws) {
1058
1074
  ws = "";
@@ -1113,7 +1129,7 @@ class ValueSetChecker {
1113
1129
  mt.push(m);
1114
1130
  op.addIssue(new Issue('error', 'invalid', p, 'Terminology_TX_System_Relative', m, 'invalid-data'));
1115
1131
  }
1116
- let prov = await this.worker.findCodeSystem(ws, c.version, this.params, ['complete', 'fragment'], op,true, true, false);
1132
+ let prov = await this.worker.findCodeSystem(ws, c.version, this.params, ['complete', 'fragment'], op,true, true, false, this.worker.requiredSupplements);
1117
1133
  if (prov === null) {
1118
1134
  let vss = await this.worker.findValueSet(ws, '');
1119
1135
  if (vss !== null) {
@@ -1123,7 +1139,7 @@ class ValueSetChecker {
1123
1139
  op.addIssue(new Issue('error', 'invalid', addToPath(path, 'system'), 'Terminology_TX_System_ValueSet2', m, 'invalid-data'));
1124
1140
  cause.value = 'invalid';
1125
1141
  } else {
1126
- let provS = await this.worker.findCodeSystem(ws, c.version, this.params, ['supplement'], op,true, true, false);
1142
+ let provS = await this.worker.findCodeSystem(ws, c.version, this.params, ['supplement'], op,true, true, false, this.worker.requiredSupplements);
1127
1143
  if (provS !== null) {
1128
1144
  vss = null;
1129
1145
  let m = this.worker.i18n.translate('CODESYSTEM_CS_NO_SUPPLEMENT', this.params.HTTPLanguages, [provS.vurl()]);
@@ -1131,7 +1147,7 @@ class ValueSetChecker {
1131
1147
  op.addIssue(new Issue('error', 'invalid', addToPath(path, 'system'), 'CODESYSTEM_CS_NO_SUPPLEMENT', m, 'invalid-data'));
1132
1148
  cause.value = 'invalid';
1133
1149
  } else {
1134
- let prov2 = await this.worker.findCodeSystem(ws, '', this.params, ['complete', 'fragment'], op,true, true, false);
1150
+ let prov2 = await this.worker.findCodeSystem(ws, '', this.params, ['complete', 'fragment'], op,true, true, false, this.worker.requiredSupplements);
1135
1151
  let bAdd = true;
1136
1152
  let m, mid, vn;
1137
1153
  if (prov2 === null && !c.version) {
@@ -1184,7 +1200,7 @@ class ValueSetChecker {
1184
1200
  ts.push(vs);
1185
1201
  let m;
1186
1202
  if (prov.contentMode() === 'complete') {
1187
- m = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(ws), this.params.HTTPLanguages, [c.code, ws, prov.version(), SCTVersion(ws, prov.version())]);
1203
+ m = this.worker.i18n.translate(Unknown_Code_in_VersionSCT(ws, prov.version()), this.params.HTTPLanguages, [c.code, ws, prov.version(), SCTVersion(ws, prov.version())]);
1188
1204
  cause.value = 'code-invalid';
1189
1205
  msg(m);
1190
1206
  op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version', m, 'invalid-code'), true);
@@ -1196,6 +1212,9 @@ class ValueSetChecker {
1196
1212
  }
1197
1213
  }
1198
1214
  } else {
1215
+ if (!c.version && mode == 'codeableConcept' && prov.version()) {
1216
+ c.version = prov.version();
1217
+ }
1199
1218
  await this.worker.listDisplaysFromCodeSystem(list, prov, ctxt.context);
1200
1219
  let pd = list.preferredDisplay(this.params.workingLanguages());
1201
1220
  if (pd) {
@@ -1230,7 +1249,7 @@ class ValueSetChecker {
1230
1249
  msg(m);
1231
1250
  op.addIssue(new Issue(severity, 'invalid', addToPath(path, 'display'), baseMsg, m, 'invalid-display'));
1232
1251
  }
1233
- if (prov.version()) {
1252
+ if (prov.version() && mode != 'codeableConcept') {
1234
1253
  result.addParamStr('version', prov.version());
1235
1254
  }
1236
1255
  }
@@ -1412,9 +1431,9 @@ class ValueSetChecker {
1412
1431
  }
1413
1432
  }
1414
1433
 
1415
- async checkSystemCode(issuePath, system, version, code, inferSystem) {
1434
+ async checkSystemCode(issuePath, system, version, code) {
1416
1435
  this.worker.opContext.clearContexts();
1417
- if (inferSystem) {
1436
+ if (this.params.inferSystem) {
1418
1437
  this.worker.opContext.addNote(this.valueSet, 'Validate "' + code + '" and infer system', this.indentCount);
1419
1438
  } else {
1420
1439
  this.worker.opContext.addNote(this.valueSet, 'Validate "' + this.worker.renderer.displayCoded(system, version, code) + '"', this.indentCount);
@@ -1435,7 +1454,7 @@ class ValueSetChecker {
1435
1454
  let impliedSystem = {value: ''};
1436
1455
  let defLang = {value: null};
1437
1456
 
1438
- let ok = await this.check(issuePath, system, version, code, true, inferSystem, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, result, contentMode, impliedSystem, unkCodes, messages, defLang);
1457
+ let ok = await this.check(issuePath, system, version, code, true, list, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, null, result, contentMode, impliedSystem, unkCodes, messages, defLang);
1439
1458
  if (ok === true) {
1440
1459
  result.AddParamBool('result', true);
1441
1460
  let pd = list.preferredDisplay(this.params.workingLanguages());
@@ -1484,7 +1503,7 @@ class ValueSetChecker {
1484
1503
  return result;
1485
1504
  }
1486
1505
 
1487
- async checkConceptSet(path, role, cs, cset, code, abstractOk, displays, vs, message, inactive, normalForm, vstatus, op, vcc, messages) {
1506
+ async checkConceptSet(path, role, cs, cset, code, displays, vs, message, inactive, normalForm, vstatus, op, vcc, messages) {
1488
1507
  this.worker.opContext.addNote(vs, 'check code ' + role + ' ' + this.worker.renderer.displayValueSetInclude(cset) + ' at ' + path, this.indentCount);
1489
1508
  inactive.value = false;
1490
1509
  let result = false;
@@ -1498,43 +1517,15 @@ class ValueSetChecker {
1498
1517
  op.addIssue(new Issue('warning', 'code-invalid', addToPath(path, 'code'), 'UNKNOWN_CODE_IN_FRAGMENT', this.worker.i18n.translate('UNKNOWN_CODE_IN_FRAGMENT', this.params.HTTPLanguages, [code, cs.system(), cs.version()]), 'invalid-code'));
1499
1518
  result = true;
1500
1519
  } else {
1501
- op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version',
1502
- this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system()), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]), 'invalid-code'));
1520
+ op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), cs.version() ? 'Unknown_Code_in_Version' : 'Unknown_Code_in',
1521
+ this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system(), cs.version()), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]), 'invalid-code'));
1503
1522
  }
1504
1523
  }
1505
1524
  if (loc.message && op) {
1506
1525
  op.addIssue(new Issue('information', 'code-invalid', addToPath(path, 'code'), null, loc.message, 'invalid-code'));
1507
1526
  }
1508
- } else if (!(abstractOk || !cs.IsAbstract(loc.context))) {
1509
- this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
1510
- if (!this.params.membershipOnly) {
1511
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1512
- }
1513
- } else if (this.excludeInactives() && await cs.isInactive(loc.context)) {
1514
- this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1515
- let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1516
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
1517
- result = false;
1518
- messages.push(msg);
1519
- if (!this.params.membershipOnly) {
1520
- inactive.value = true;
1521
- inactive.path = path;
1522
- if (inactive.value) {
1523
- vstatus.value = await cs.getStatus(loc.context);
1524
- }
1525
- }
1526
- } else if (this.params.activeOnly && await cs.isInactive(loc.context)) {
1527
- this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1528
- result = false;
1529
- inactive.value = true;
1530
- inactive.path = path;
1531
- vstatus.value = await cs.getStatus(loc.context);
1532
- let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1533
- messages.push(msg);
1534
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
1535
1527
  } else {
1536
1528
  this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1537
- result = true;
1538
1529
  if (await cs.code(loc.context) != code) {
1539
1530
  let msg;
1540
1531
  if (cs.version()) {
@@ -1550,22 +1541,53 @@ class ValueSetChecker {
1550
1541
  op.addIssue(new Issue('information', 'informational', addToPath(path, 'code'), null, msg, 'process-note'));
1551
1542
  }
1552
1543
  await this.worker.listDisplaysFromCodeSystem(displays, cs, loc.context);
1553
- inactive.value = await cs.isInactive(loc.context);
1554
- inactive.path = path;
1555
- vstatus.value = await cs.getStatus(loc.context);
1556
1544
 
1557
- if (vcc !== null) {
1558
- if (!vcc.coding) {
1559
- vcc.coding = [];
1545
+ if (!(this.params.abstractOk || !(await cs.isAbstract(loc.context)))) {
1546
+ this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
1547
+ if (!this.params.membershipOnly) {
1548
+ op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1549
+ }
1550
+ } else if (this.excludeInactives() && await cs.isInactive(loc.context)) {
1551
+ this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1552
+ let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1553
+ op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
1554
+ result = false;
1555
+ messages.push(msg);
1556
+ if (!this.params.membershipOnly) {
1557
+ inactive.value = true;
1558
+ inactive.path = path;
1559
+ if (inactive.value) {
1560
+ vstatus.value = await cs.getStatus(loc.context);
1561
+ }
1562
+ }
1563
+ } else if (this.params.activeOnly && await cs.isInactive(loc.context)) {
1564
+ this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1565
+ result = false;
1566
+ inactive.value = true;
1567
+ inactive.path = path;
1568
+ vstatus.value = await cs.getStatus(loc.context);
1569
+ let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1570
+ messages.push(msg);
1571
+ op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
1572
+ } else {
1573
+ result = true;
1574
+ inactive.value = await cs.isInactive(loc.context);
1575
+ inactive.path = path;
1576
+ vstatus.value = await cs.getStatus(loc.context);
1577
+
1578
+ if (vcc !== null) {
1579
+ if (!vcc.coding) {
1580
+ vcc.coding = [];
1581
+ }
1582
+ vcc.coding.push({
1583
+ system: cs.system(),
1584
+ version: cs.version(),
1585
+ code: await cs.code(loc.context),
1586
+ display: displays.preferredDisplay(this.params.workingLanguages())
1587
+ });
1560
1588
  }
1561
- vcc.coding.push({
1562
- system: cs.system(),
1563
- version: cs.version(),
1564
- code: await cs.code(loc.context),
1565
- display: displays.preferredDisplay(this.params.workingLanguages())
1566
- });
1589
+ return result;
1567
1590
  }
1568
- return result;
1569
1591
  }
1570
1592
  }
1571
1593
 
@@ -1579,7 +1601,7 @@ class ValueSetChecker {
1579
1601
  this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1580
1602
  await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
1581
1603
  this.worker.listDisplaysFromIncludeConcept(displays, cc, vs);
1582
- if (!(abstractOk || !cs.IsAbstract(loc))) {
1604
+ if (!(this.params.abstractOk || !(await cs.isAbstract(loc)))) {
1583
1605
  if (!this.params.membershipOnly) {
1584
1606
  op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1585
1607
  }
@@ -1627,149 +1649,62 @@ class ValueSetChecker {
1627
1649
  // }
1628
1650
  }
1629
1651
  let filters = await cs.executeFilters(prep);
1630
- if (filters) {
1631
- let ctxt = filters[0];
1632
- let loc = await cs.filterLocate(prep, ctxt, code);
1633
- if (loc != null && !(typeof loc === 'string')) {
1634
- await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
1635
- if (!(abstractOk || !cs.IsAbstract(loc))) {
1636
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
1637
- if (!this.params.membershipOnly) {
1638
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1639
- }
1640
- } else if (this.excludeInactives() && await cs.isInactive(loc)) {
1641
- result = false;
1642
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1643
- if (!this.params.membershipOnly) {
1644
- inactive.value = true;
1645
- inactive.path = path;
1646
- vstatus.value = await cs.getStatus(loc);
1647
- }
1648
- } else {
1649
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1650
- if (vcc !== null) {
1651
- if (!vcc.coding) { vcc.coding = []}
1652
- vcc.coding.push( { system : cs.system(), version: cs.version(), code: await cs.code(loc), display: displays.preferredDisplay(this.params.workingLanguages())});
1653
- }
1654
- result = true;
1655
- return result;
1652
+ if (!filters || filters.length == 0) {
1653
+ throw new Error("executing filters failed for " + cs.name());
1654
+ }
1655
+ let loc = await cs.filterLocate(prep, filters[0], code);
1656
+ if (loc != null && !(typeof loc === 'string') && filters.length > 1) {
1657
+ for (let i = 1; i < filters.length; i++) {
1658
+ if (!(await cs.filterCheck(prep, filters[i], loc))) {
1659
+ loc = null;
1660
+ break;
1656
1661
  }
1657
- } else if (loc != null) {
1658
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+ ": "+loc, this.indentCount);
1659
- messages.push(loc);
1660
- } else {
1661
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1662
1662
  }
1663
- } else {
1664
- result = true;
1665
- let i = 0;
1666
- for (let fc of cfl) {
1667
- this.worker.deadCheck('checkConceptSet#3');
1668
- if (fc.propertyerty === 'concept' && ["is-a", "descendent-of"].includes(fc.op)) {
1669
- let loc = await cs.locateIsA(code, fc.value, fc.op === "descendent-of");
1670
- if (loc !== null) {
1671
- await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
1672
- if (!(abstractOk || !cs.IsAbstract(loc))) {
1673
- this.worker.opContext.addNote(this.valueSet, 'Filter "' + fc.property + '' + fc.op + '' + fc.value + '": Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
1674
- if (!this.params.membershipOnly) {
1675
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1676
- }
1677
- } else {
1678
- this.worker.opContext.addNote(this.valueSet, 'Filter "' + fc.property + fc.op + fc.value + '": Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1679
- if (vcc !== null) {
1680
- vcc.addCoding(cs.system(), cs.version(), await cs.code(loc), displays.preferredDisplay(this.params.workingLanguages()));
1681
- }
1682
- result = true;
1683
- return result;
1684
- }
1685
- } else {
1686
- result = false;
1687
- this.worker.opContext.addNote(this.valueSet, 'Filter "' + fc.property + fc.op + fc.value + '": Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1688
- }
1689
- } else if (fc.property === 'concept' && fc.op === 'is-not-a') {
1690
- let loc = await cs.locateIsA(code, fc.value);
1691
- result = loc === null;
1692
- if (result) {
1693
- let msg;
1694
- loc = await cs.locate(code, null, msg);
1695
- if (loc !== null) {
1696
- await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
1697
- if (!(abstractOk || !cs.IsAbstract(loc))) {
1698
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + fc.property + fc.op + fc.value + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
1699
- if (!this.params.membershipOnly) {
1700
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1701
- }
1702
- } else if (this.excludeInactives() && await cs.isInactive(loc)) {
1703
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + fc.property + fc.op + fc.value + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1704
- result = false;
1705
- if (!this.params.membershipOnly) {
1706
- inactive.value = true;
1707
- inactive.path = path;
1708
- vstatus.value = await cs.getStatus(loc);
1709
- }
1710
- } else {
1711
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + fc.property + fc.op + fc.value + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1712
- if (vcc !== null) {
1713
- vcc.addCoding(cs.system(), cs.version(), await cs.code(loc), displays.preferredDisplay(this.params.workingLanguages()));
1714
- }
1715
- result = true;
1716
- return result;
1717
- }
1718
- }
1719
- } else {
1720
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + fc.property + fc.op + fc.value + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1721
- }
1722
- } else {
1723
- let ctxt = filters[i];
1724
- result = false;
1725
- let loc = await cs.filterLocate(prep, ctxt, code);
1726
- if (!(typeof loc === 'string')) {
1727
- await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
1728
- if (!(abstractOk || !cs.IsAbstract(loc))) {
1729
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
1730
- if (!this.params.membershipOnly) {
1731
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1732
- }
1733
- } else if (this.excludeInactives() && await cs.isInactive(loc)) {
1734
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1735
- result = false;
1736
- if (!this.params.membershipOnly) {
1737
- inactive.value = true;
1738
- inactive.path = path;
1739
- vstatus.value = await cs.getStatus(loc);
1740
- }
1741
- } else {
1742
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1743
- if (vcc !== null) {
1744
- vcc.addCoding(cs.system(), cs.version(), await cs.code(loc), displays.preferredDisplay(this.params.workingLanguages()));
1745
- }
1746
- result = true;
1747
- return result;
1748
- }
1749
- } else {
1750
- this.worker.opContext.addNote(this.valueSet, 'Filter ' + ctxt.summary + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+": "+loc, this.indentCount);
1751
- }
1663
+ }
1664
+ if (loc != null && !(typeof loc === 'string')) {
1665
+ await this.worker.listDisplaysFromCodeSystem(displays, cs, loc);
1666
+ if (!(this.params.abstractOk || !(await cs.isAbstract(loc)))) {
1667
+ this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
1668
+ if (!this.params.membershipOnly) {
1669
+ op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1752
1670
  }
1753
- if (!result) {
1754
- break;
1671
+ } else if (this.excludeInactives() && await cs.isInactive(loc)) {
1672
+ result = false;
1673
+ this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1674
+ if (!this.params.membershipOnly) {
1675
+ inactive.value = true;
1676
+ inactive.path = path;
1677
+ vstatus.value = await cs.getStatus(loc);
1755
1678
  }
1756
- i++;
1679
+ } else {
1680
+ this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1681
+ if (vcc !== null) {
1682
+ if (!vcc.coding) { vcc.coding = []}
1683
+ vcc.coding.push( { system : cs.system(), version: cs.version(), code: await cs.code(loc), display: displays.preferredDisplay(this.params.workingLanguages())});
1684
+ }
1685
+ result = true;
1686
+ return result;
1757
1687
  }
1688
+ } else if (loc != null) {
1689
+ this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+ ": "+loc, this.indentCount);
1690
+ messages.push(loc);
1691
+ } else {
1692
+ this.worker.opContext.addNote(this.valueSet, 'Filter ' + this.filterSummary(cset) + ': Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs), this.indentCount);
1758
1693
  }
1759
1694
  }
1760
1695
  return result;
1761
1696
  }
1762
1697
 
1763
- async checkExpansion(path, cs, cset, code, abstractOk, displays, vs, message, inactive, vstatus, op) {
1698
+ async checkExpansion(path, cs, cset, code, displays, vs, message, inactive, vstatus, op) {
1764
1699
  let result = false;
1765
1700
  let loc = await cs.locate(code, null, message);
1766
1701
  result = false;
1767
1702
  if (loc === null || loc.context == null) {
1768
1703
  if (!this.params.membershipOnly) {
1769
1704
  op.addIssue(new Issue('error', 'code-invalid', addToPath(path, 'code'), 'Unknown_Code_in_Version',
1770
- this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system()), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]), 'invalid-code'));
1705
+ this.worker.i18n.translate(Unknown_Code_in_VersionSCT(cs.system(), cs.version()), this.params.HTTPLanguages, [code, cs.system(), cs.version(), SCTVersion(cs.system(), cs.version())]), 'invalid-code'));
1771
1706
  }
1772
- } else if (!(abstractOk || !cs.IsAbstract(loc.context))) {
1707
+ } else if (!(this.params.abstractOk || !(await cs.isAbstract(loc.context)))) {
1773
1708
  if (!this.params.membershipOnly) {
1774
1709
  op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'ABSTRACT_CODE_NOT_ALLOWED', this.worker.i18n.translate('ABSTRACT_CODE_NOT_ALLOWED', this.params.HTTPLanguages, [cs.system(), code]), 'code-rule'));
1775
1710
  }
@@ -1789,6 +1724,17 @@ class ValueSetChecker {
1789
1724
  return this.valueSet.jsonObj.compose && this.valueSet.jsonObj.compose.inactive != undefined && !this.valueSet.jsonObj.compose.inactive;
1790
1725
  }
1791
1726
 
1727
+ filterSummary(cset) {
1728
+ let list = [];
1729
+ for (let filter of cset.filter) {
1730
+ let s = cset.filter.length > 1 ? "(" : "";
1731
+ s = filter.prop+" "+filter.op+" "+filter.value;
1732
+ s = s + (cset.filter.length > 1 ? ")" : "");
1733
+ list.push(s)
1734
+ }
1735
+ return list.join(",");
1736
+ }
1737
+
1792
1738
  }
1793
1739
 
1794
1740
  function addToPath(path, name) {
@@ -1800,11 +1746,13 @@ function addToPath(path, name) {
1800
1746
  }
1801
1747
 
1802
1748
 
1803
- function Unknown_Code_in_VersionSCT(url) {
1749
+ function Unknown_Code_in_VersionSCT(url, version) {
1804
1750
  if (url === 'http://snomed.info/sct') {
1805
1751
  return 'Unknown_Code_in_Version_SCT';
1806
- } else {
1752
+ } else if (version) {
1807
1753
  return 'Unknown_Code_in_Version';
1754
+ } else {
1755
+ return 'Unknown_Code_in';
1808
1756
  }
1809
1757
  }
1810
1758
 
@@ -1864,6 +1812,8 @@ function toText(st, sep) {
1864
1812
 
1865
1813
  class ValidateWorker extends TerminologyWorker {
1866
1814
 
1815
+ requiredSupplements = new Set();
1816
+ usedSupplements = new Set();
1867
1817
  /**
1868
1818
  * @param {OperationContext} opContext - Operation context
1869
1819
  * @param {Logger} log - Logger instance
@@ -1901,7 +1851,7 @@ class ValidateWorker extends TerminologyWorker {
1901
1851
 
1902
1852
  } catch (error) {
1903
1853
  this.log.error(error);
1904
- console.error(error);
1854
+ this.debugLog(error);
1905
1855
  if (error instanceof Issue) {
1906
1856
  if (error.isHandleAsOO()) {
1907
1857
  let oo = new OperationOutcome();
@@ -1922,27 +1872,34 @@ class ValidateWorker extends TerminologyWorker {
1922
1872
  let coded;
1923
1873
  let mode;
1924
1874
 
1925
- try {
1926
- // Handle tx-resource and cache-id parameters
1927
- this.setupAdditionalResources(params);
1875
+ // Handle tx-resource and cache-id parameters
1876
+ this.setupAdditionalResources(params);
1928
1877
 
1929
- let txp = new TxParameters(this.languages, this.i18n, true);
1930
- txp.readParams(params);
1878
+ let txp = new TxParameters(this.languages, this.i18n, true);
1879
+ txp.readParams(params);
1880
+ for (const item of txp.supplements) this.requiredSupplements.add(item);
1881
+
1882
+ try {
1931
1883
 
1932
1884
  // Extract coded value
1933
1885
  mode = {mode: null};
1934
1886
  coded = this.extractCodedValue(params, true, mode);
1935
1887
  if (!coded) {
1936
- throw new Issue('error', 'invalid', null, null, 'Unable to find code to validate (looked for coding | codeableConcept | code in parameters =codingX:Coding)', null, 400);
1888
+ throw new Issue('error', 'invalid', null, null, 'Unable to find code to validate (looked for coding | codeableConcept | code in parameters)', null, 400).handleAsOO(400);
1937
1889
  }
1938
1890
 
1939
1891
  // Get the CodeSystem - from parameter or by url
1940
1892
  const codeSystem = await this.resolveCodeSystem(params, txp, coded?.coding?.[0] ?? null, mode);
1941
1893
  if (!codeSystem) {
1942
- throw new Issue('error', 'invalid', null, null, 'No CodeSystem specified - provide url parameter or codeSystem resource', null, 400);
1894
+ if (!coded?.coding?.[0].system) {
1895
+ let msg = this.i18n.translate('Coding_has_no_system__cannot_validate', txp.HTTPLanguages, []);
1896
+ throw new Issue('warning', 'invalid', mode.issuePath, 'Coding_has_no_system__cannot_validate', msg, 'invalid-data');
1897
+ } else {
1898
+ throw new Issue('error', 'invalid', null, null, 'No CodeSystem specified - provide url parameter or codeSystem resource', null, 400);
1899
+ }
1943
1900
  }
1944
1901
  if (codeSystem.contentMode() == 'supplement') {
1945
- throw new Issue('error', 'invalid', this.systemPath(mode), 'CODESYSTEM_CS_NO_SUPPLEMENT', this.opContext.i18n.translate('CODESYSTEM_CS_NO_SUPPLEMENT', txp.HTTPLanguages, [codeSystem.vurl()]), "invalid-data");
1902
+ throw new Issue('error', 'invalid', this.systemPath(mode), 'CODESYSTEM_CS_NO_SUPPLEMENT', this.opContext.i18n.translate('CODESYSTEM_CS_NO_SUPPLEMENT', txp.HTTPLanguages, [codeSystem.vurl()]), "invalid-data", 400);
1946
1903
  }
1947
1904
 
1948
1905
  // Perform validation
@@ -1953,8 +1910,9 @@ class ValidateWorker extends TerminologyWorker {
1953
1910
  return result;
1954
1911
  } catch (error) {
1955
1912
  this.log.error(error);
1913
+ this.debugLog(error);
1956
1914
  if (error instanceof Issue && !error.isHandleAsOO()) {
1957
- return this.handlePrepareError(error, coded, mode.mode);
1915
+ return await this.handlePrepareError(error, coded, mode.mode, txp);
1958
1916
  } else {
1959
1917
  throw error;
1960
1918
  }
@@ -1986,11 +1944,12 @@ class ValidateWorker extends TerminologyWorker {
1986
1944
 
1987
1945
  let txp = new TxParameters(this.languages, this.i18n, true);
1988
1946
  txp.readParams(params);
1947
+ for (const item of txp.supplements) this.requiredSupplements.add(item);
1989
1948
 
1990
1949
  // Get the CodeSystem by id
1991
1950
  const codeSystem = await this.provider.getCodeSystemById(this.opContext, id);
1992
1951
  if (!codeSystem) {
1993
- return res.status(404).json(this.operationOutcome('error', 'not-found',
1952
+ return res.status(422).json(this.operationOutcome('error', 'not-found',
1994
1953
  `CodeSystem/${id} not found`));
1995
1954
  }
1996
1955
  const csp = new FhirCodeSystemProvider(this.opContext, new CodeSystem(codeSystem), []);
@@ -2010,6 +1969,7 @@ class ValidateWorker extends TerminologyWorker {
2010
1969
 
2011
1970
  } catch (error) {
2012
1971
  this.log.error(error);
1972
+ this.debugLog(error);
2013
1973
  return res.status(error.statusCode || 500).json(this.operationOutcome(
2014
1974
  'error', error.issueCode || 'exception', error.message));
2015
1975
  }
@@ -2030,6 +1990,7 @@ class ValidateWorker extends TerminologyWorker {
2030
1990
 
2031
1991
  } catch (error) {
2032
1992
  this.log.error(error);
1993
+ this.debugLog(error);
2033
1994
  if (error instanceof Issue) {
2034
1995
  let op = new OperationOutcome();
2035
1996
  op.addIssue(error);
@@ -2047,6 +2008,7 @@ class ValidateWorker extends TerminologyWorker {
2047
2008
 
2048
2009
  let txp = new TxParameters(this.languages, this.i18n, true);
2049
2010
  txp.readParams(params);
2011
+ for (const item of txp.supplements) this.requiredSupplements.add(item);
2050
2012
 
2051
2013
  // Get the ValueSet - from parameter or by url
2052
2014
  const valueSet = await this.resolveValueSet(params, txp);
@@ -2058,7 +2020,7 @@ class ValidateWorker extends TerminologyWorker {
2058
2020
  let mode = { mode : null };
2059
2021
  const coded = this.extractCodedValue(params, false, mode);
2060
2022
  if (!coded) {
2061
- throw new Issue("error", "invalid", null, null, 'Unable to find code to validate (looked for coding | codeableConcept | code in parameters =codingX:Coding)', null, 400);
2023
+ throw new Issue("error", "invalid", null, null, 'Unable to find code to validate (looked for coding | codeableConcept | code+system | code+inferSystem in parameters', null, 422);
2062
2024
  }
2063
2025
 
2064
2026
  // Perform validation
@@ -2083,12 +2045,13 @@ class ValidateWorker extends TerminologyWorker {
2083
2045
 
2084
2046
  let txp = new TxParameters(this.languages, this.i18n, true);
2085
2047
  txp.readParams(params);
2048
+ for (const item of txp.supplements) this.requiredSupplements.add(item);
2086
2049
 
2087
2050
  // Get the ValueSet by id
2088
2051
  const valueSet = await this.provider.getValueSetById(this.opContext, id);
2089
2052
  this.seeSourceVS(valueSet, id);
2090
2053
  if (!valueSet) {
2091
- return res.status(404).json(this.operationOutcome('error', 'not-found',
2054
+ return res.status(422).json(this.operationOutcome('error', 'not-found',
2092
2055
  `ValueSet/${id} not found`));
2093
2056
  }
2094
2057
 
@@ -2107,6 +2070,7 @@ class ValidateWorker extends TerminologyWorker {
2107
2070
 
2108
2071
  } catch (error) {
2109
2072
  this.log.error(error);
2073
+ this.debugLog(error);
2110
2074
  return res.status(error.statusCode || 500).json(this.operationOutcome(
2111
2075
  'error', error.issueCode || 'exception', error.message));
2112
2076
  }
@@ -2118,13 +2082,13 @@ class ValidateWorker extends TerminologyWorker {
2118
2082
  * Resolve the CodeSystem to validate against
2119
2083
  * @param {Object} params - Parameters resource
2120
2084
  * @param {string|null} id - Instance id (if instance-level request)
2121
- * @returns {Object|null} CodeSystem resource (wrapper or JSON)
2085
+ * @returns {CodeSystemProvider|null} CodeSystem resource (wrapper or JSON)
2122
2086
  */
2123
2087
  async resolveCodeSystem(params, txParams, coded, mode) {
2124
2088
  // Check for codeSystem resource parameter
2125
2089
  const csResource = this.getResourceParam(params, 'codeSystem');
2126
2090
  if (csResource) {
2127
- return csResource;
2091
+ return new FhirCodeSystemProvider(this.opContext, new CodeSystem(csResource), []); // todo: supplements
2128
2092
  }
2129
2093
  let path = coded == null ? null : mode.issuePath+".system";
2130
2094
  let fromCoded = false;
@@ -2150,7 +2114,7 @@ class ValidateWorker extends TerminologyWorker {
2150
2114
  }
2151
2115
  version = this.determineVersionBase(url, version, txParams);
2152
2116
 
2153
- let supplements = this.loadSupplements(url, version);
2117
+ let supplements = this.loadSupplements(url, version, this.requiredSupplements);
2154
2118
 
2155
2119
  // First check additional resources
2156
2120
  const fromAdditional = this.findInAdditionalResources(url, version, 'CodeSystem', false);
@@ -2169,12 +2133,12 @@ class ValidateWorker extends TerminologyWorker {
2169
2133
  } else if (version) {
2170
2134
  let vl = await this.listVersions(url);
2171
2135
  if (vl.length == 0) {
2172
- throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM_VERSION_NONE', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM_VERSION_NONE', this.opContext.HTTPLanguages, [url, version]), 'not-found').setUnknownSystem(url).addIssue(issue);
2136
+ throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM_VERSION_NONE', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM_VERSION_NONE', this.opContext.HTTPLanguages, [url, version]), 'not-found', 422).setUnknownSystem(url).addIssue(issue);
2173
2137
  } else {
2174
- throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM_VERSION', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM_VERSION', this.opContext.HTTPLanguages, [url, version, this.presentVersionList(vl)]), 'not-found').setUnknownSystem(url + "|" + version).addIssue(issue);
2138
+ throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM_VERSION', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM_VERSION', this.opContext.HTTPLanguages, [url, version, this.presentVersionList(vl)], 422), 'not-found').setUnknownSystem(url + "|" + version).addIssue(issue);
2175
2139
  }
2176
2140
  } else {
2177
- throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM', this.opContext.HTTPLanguages, [url]), 'not-found').setUnknownSystem(url).addIssue(issue);
2141
+ throw new Issue("error", "not-found", this.systemPath(mode), 'UNKNOWN_CODESYSTEM', this.opContext.i18n.translate('UNKNOWN_CODESYSTEM', this.opContext.HTTPLanguages, [url]), 'not-found', 422).setUnknownSystem(url).addIssue(issue);
2178
2142
  }
2179
2143
  }
2180
2144
  }
@@ -2208,7 +2172,7 @@ class ValidateWorker extends TerminologyWorker {
2208
2172
  let vs = await this.provider.findValueSet(this.opContext, url, version);
2209
2173
  this.seeSourceVS(vs, url);
2210
2174
  if (vs == null) {
2211
- throw new Issue('error', 'not-found', null, 'Unable_to_resolve_value_Set_', this.i18n.translate('Unable_to_resolve_value_Set_', params.HTTPLanguages, [url+(version ? "|"+version : "")]), 'not-found', 400);
2175
+ throw new Issue('error', 'not-found', null, 'Unable_to_resolve_value_Set_', this.i18n.translate('Unable_to_resolve_value_Set_', params.HTTPLanguages, [url+(version ? "|"+version : "")]), 'not-found', 422);
2212
2176
  } else {
2213
2177
  return vs;
2214
2178
  }
@@ -2263,6 +2227,9 @@ class ValidateWorker extends TerminologyWorker {
2263
2227
  }
2264
2228
  const display = this.getStringParam(params, 'display');
2265
2229
 
2230
+ if (!system && !this.getStringParam(params, 'inferSystem')) {
2231
+ return null;
2232
+ }
2266
2233
  const codingObj = {code};
2267
2234
  if (system) codingObj.system = system;
2268
2235
  if (version) codingObj.version = version;
@@ -2289,14 +2256,11 @@ class ValidateWorker extends TerminologyWorker {
2289
2256
 
2290
2257
  let vs = this.makeVsForCS(codeSystem);
2291
2258
 
2292
- // Get parameters
2293
- const abstractOk = this._getBoolParam(params, 'abstract', true);
2294
-
2295
2259
  // Create and prepare checker
2296
2260
  const checker = new ValueSetChecker(this, vs, params);
2297
2261
 
2298
2262
  // Perform validation
2299
- const result = await checker.checkCodeableConcept(mode.issuePath, coded, abstractOk, false, mode.mode);
2263
+ const result = await checker.checkCodeableConcept(mode.issuePath, coded, mode.mode);
2300
2264
 
2301
2265
  // Add diagnostics if requested
2302
2266
  if (params.diagnostics) {
@@ -2336,9 +2300,9 @@ class ValidateWorker extends TerminologyWorker {
2336
2300
  this.deadCheck('doValidationVS');
2337
2301
  this.params = params;
2338
2302
 
2339
- // Get parameters
2340
- const abstractOk = this._getBoolParam(params, 'abstract', true);
2341
- const inferSystem = this._getBoolParam(params, 'inferSystem', false) || (mode === 'code' && !coded.coding[0].system)
2303
+ for (let ext of Extensions.list(valueSet.jsonObj, 'http://hl7.org/fhir/StructureDefinition/valueset-supplement')) {
2304
+ this.requiredSupplements.add(getValuePrimitive(ext));
2305
+ }
2342
2306
 
2343
2307
  // Create and prepare checker
2344
2308
  const checker = new ValueSetChecker(this, valueSet, params);
@@ -2346,15 +2310,16 @@ class ValidateWorker extends TerminologyWorker {
2346
2310
  await checker.prepare();
2347
2311
  } catch (error) {
2348
2312
  this.log.error(error);
2313
+ this.debugLog(error);
2349
2314
  if (!(error instanceof Issue) || error.isHandleAsOO()) {
2350
2315
  throw error;
2351
2316
  } else {
2352
- return this.handlePrepareError(error, coded, mode);
2317
+ return await this.handlePrepareError(error, coded, mode, params);
2353
2318
  }
2354
2319
  }
2355
2320
 
2356
2321
  // Perform validation
2357
- const result = await checker.checkCodeableConcept(issuePath, coded, abstractOk, inferSystem, mode);
2322
+ const result = await checker.checkCodeableConcept(issuePath, coded, mode);
2358
2323
 
2359
2324
  // Add diagnostics if requested
2360
2325
  if (params.diagnostics) {
@@ -2462,14 +2427,16 @@ class ValidateWorker extends TerminologyWorker {
2462
2427
  }
2463
2428
 
2464
2429
 
2465
- handlePrepareError(error, coded, mode) {
2430
+ async handlePrepareError(error, coded, mode, txp) {
2466
2431
  let op = new OperationOutcome();
2467
2432
  op.addIssue(error);
2468
2433
  let p = new Parameters();
2469
2434
  p.addParamResource('issues', op.jsonObj);
2470
2435
  p.addParamBool('result', false);
2471
2436
  p.addParamStr('message', error.message);
2472
- if (mode == 'codeableConcept') {
2437
+ if (!mode || !coded) {
2438
+ // nothing to add
2439
+ } else if (mode == 'codeableConcept') {
2473
2440
  p.addParam('codeableConcept', 'valueCodeableConcept', coded);
2474
2441
  } else if (coded.coding) {
2475
2442
  if (coded.coding[0].system) {
@@ -2477,13 +2444,19 @@ class ValidateWorker extends TerminologyWorker {
2477
2444
  }
2478
2445
  if (coded.coding[0].version) {
2479
2446
  p.addParamStr('version', coded.coding[0].version)
2447
+ } else if (coded.coding[0].system) {
2448
+ try {
2449
+ let cs = await this.findCodeSystem(coded.coding[0].system, null, txp);
2450
+ if (cs && cs.version()) {
2451
+ p.addParamStr('version', cs.version());
2452
+ }
2453
+ } catch (e) {
2454
+ // nothing. not interested.
2455
+ }
2480
2456
  }
2481
2457
  if (coded.coding[0].code) {
2482
2458
  p.addParamCode('code', coded.coding[0].code)
2483
2459
  }
2484
- if (coded.coding[0].display) {
2485
- p.addParamStr('display', coded.coding[0].display)
2486
- }
2487
2460
  }
2488
2461
  if (error.unknownSystem) {
2489
2462
  p.addParamCanonical("x-caused-by-unknown-system", error.unknownSystem);