fhirsmith 0.6.0 → 0.7.1

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 (64) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +15 -4
  3. package/configurations/projector.json +21 -0
  4. package/configurations/readme.md +6 -0
  5. package/library/package-manager.js +0 -2
  6. package/library/version-utilities.js +85 -0
  7. package/package.json +1 -1
  8. package/packages/package-crawler.js +44 -9
  9. package/packages/packages.js +1 -0
  10. package/registry/crawler.js +35 -14
  11. package/registry/registry.js +3 -0
  12. package/server.js +4 -0
  13. package/tx/README.md +4 -4
  14. package/tx/cs/cs-loinc.js +5 -2
  15. package/tx/cs/cs-provider-api.js +25 -1
  16. package/tx/cs/cs-provider-list.js +2 -2
  17. package/tx/html/conceptmap-operations.liquid +19 -0
  18. package/tx/library/canonical-resource.js +6 -1
  19. package/tx/library/renderer.js +571 -2
  20. package/tx/library.js +127 -10
  21. package/tx/ocl/README.md +236 -0
  22. package/tx/ocl/cache/cache-paths.cjs +32 -0
  23. package/tx/ocl/cache/cache-paths.js +2 -0
  24. package/tx/ocl/cache/cache-utils.cjs +43 -0
  25. package/tx/ocl/cache/cache-utils.js +2 -0
  26. package/tx/ocl/cm-ocl.cjs +531 -0
  27. package/tx/ocl/cm-ocl.js +1 -105
  28. package/tx/ocl/cs-ocl.cjs +1779 -0
  29. package/tx/ocl/cs-ocl.js +1 -38
  30. package/tx/ocl/fingerprint/fingerprint.cjs +67 -0
  31. package/tx/ocl/fingerprint/fingerprint.js +2 -0
  32. package/tx/ocl/http/client.cjs +31 -0
  33. package/tx/ocl/http/client.js +2 -0
  34. package/tx/ocl/http/pagination.cjs +98 -0
  35. package/tx/ocl/http/pagination.js +2 -0
  36. package/tx/ocl/jobs/background-queue.cjs +200 -0
  37. package/tx/ocl/jobs/background-queue.js +2 -0
  38. package/tx/ocl/mappers/concept-mapper.cjs +66 -0
  39. package/tx/ocl/mappers/concept-mapper.js +2 -0
  40. package/tx/ocl/model/concept-filter-context.cjs +51 -0
  41. package/tx/ocl/model/concept-filter-context.js +2 -0
  42. package/tx/ocl/shared/constants.cjs +15 -0
  43. package/tx/ocl/shared/constants.js +2 -0
  44. package/tx/ocl/shared/patches.cjs +224 -0
  45. package/tx/ocl/shared/patches.js +2 -0
  46. package/tx/ocl/vs-ocl.cjs +1848 -0
  47. package/tx/ocl/vs-ocl.js +1 -104
  48. package/tx/operation-context.js +8 -1
  49. package/tx/params.js +24 -3
  50. package/tx/provider.js +47 -0
  51. package/tx/tx-html.js +49 -4
  52. package/tx/tx.js +8 -0
  53. package/tx/vs/vs-vsac.js +4 -3
  54. package/tx/workers/batch-validate.js +3 -2
  55. package/tx/workers/batch.js +3 -2
  56. package/tx/workers/expand.js +64 -9
  57. package/tx/workers/lookup.js +5 -4
  58. package/tx/workers/read.js +25 -9
  59. package/tx/workers/related.js +3 -2
  60. package/tx/workers/search.js +40 -10
  61. package/tx/workers/subsumes.js +3 -2
  62. package/tx/workers/translate.js +4 -3
  63. package/tx/workers/validate.js +143 -46
  64. package/tx/workers/worker.js +1 -7
@@ -5,6 +5,7 @@
5
5
  //
6
6
 
7
7
  const { TerminologyWorker } = require('./worker');
8
+ const {debugLog} = require("../operation-context");
8
9
 
9
10
  class ReadWorker extends TerminologyWorker {
10
11
  /**
@@ -46,14 +47,7 @@ class ReadWorker extends TerminologyWorker {
46
47
  return await this.handleValueSet(req, res, id);
47
48
 
48
49
  case 'ConceptMap':
49
- return res.status(501).json({
50
- resourceType: 'OperationOutcome',
51
- issue: [{
52
- severity: 'error',
53
- code: 'not-supported',
54
- diagnostics: 'ConceptMap read not yet implemented'
55
- }]
56
- });
50
+ return await this.handleConceptMap(req, res, id);
57
51
 
58
52
  default:
59
53
  return res.status(404).json({
@@ -67,7 +61,7 @@ class ReadWorker extends TerminologyWorker {
67
61
  }
68
62
  } catch (error) {
69
63
  this.log.error(error);
70
- this.debugLog(error);
64
+ debugLog(error);
71
65
  req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
72
66
  return res.status(500).json({
73
67
  resourceType: 'OperationOutcome',
@@ -151,6 +145,28 @@ class ReadWorker extends TerminologyWorker {
151
145
  }
152
146
  }
153
147
 
148
+ return res.status(404).json({
149
+ resourceType: 'OperationOutcome',
150
+ issue: [{
151
+ severity: 'error',
152
+ code: 'not-found',
153
+ diagnostics: `ValueSet/${id} not found`
154
+ }]
155
+ });
156
+ }
157
+ /**
158
+ * Handle ConceptMap read
159
+ */
160
+ async handleConceptMap(req, res, id) {
161
+ // Iterate through valueSetProviders in order
162
+ for (const cmsp of this.provider.conceptMapProviders) {
163
+ this.deadCheck('handleConceptMap-loop');
164
+ const vs = await cmsp.fetchConceptMapById(id);
165
+ if (vs) {
166
+ return res.json(vs.jsonObj);
167
+ }
168
+ }
169
+
154
170
  return res.status(404).json({
155
171
  resourceType: 'OperationOutcome',
156
172
  issue: [{
@@ -16,6 +16,7 @@ const ValueSet = require("../library/valueset");
16
16
  const {ValueSetExpander} = require("./expand");
17
17
  const {SearchFilterText} = require("../library/designations");
18
18
  const {ArrayMatcher} = require("../../library/utilities");
19
+ const {debugLog} = require("../operation-context");
19
20
 
20
21
 
21
22
  class RelatedWorker extends TerminologyWorker {
@@ -49,7 +50,7 @@ class RelatedWorker extends TerminologyWorker {
49
50
  await this.handleTypeLevelRelated(req, res);
50
51
  } catch (error) {
51
52
  this.log.error(error);
52
- this.debugLog(error);
53
+ debugLog(error);
53
54
  req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
54
55
  const statusCode = error.statusCode || 500;
55
56
  if (error instanceof Issue) {
@@ -84,7 +85,7 @@ class RelatedWorker extends TerminologyWorker {
84
85
  await this.handleInstanceLevelRelated(req, res);
85
86
  } catch (error) {
86
87
  this.log.error(error);
87
- this.debugLog(error);
88
+ debugLog(error);
88
89
  req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
89
90
  const statusCode = error.statusCode || 500;
90
91
  const issueCode = error.issueCode || 'exception';
@@ -7,6 +7,7 @@
7
7
 
8
8
  const { TerminologyWorker } = require('./worker');
9
9
  const {Utilities} = require("../../library/utilities");
10
+ const {debugLog} = require("../operation-context");
10
11
 
11
12
  class SearchWorker extends TerminologyWorker {
12
13
  /**
@@ -95,7 +96,7 @@ class SearchWorker extends TerminologyWorker {
95
96
 
96
97
  case 'ConceptMap':
97
98
  // Not implemented yet - return empty set
98
- matches = [];
99
+ matches = await this.searchConceptMaps(params, elements);
99
100
  break;
100
101
 
101
102
  default:
@@ -114,7 +115,7 @@ class SearchWorker extends TerminologyWorker {
114
115
 
115
116
  } catch (error) {
116
117
  this.log.error(error);
117
- this.debugLog(error);
118
+ debugLog(error);
118
119
  req.logInfo = "error "+(error.msgId || error.className);
119
120
  return res.status(500).json({
120
121
  resourceType: 'OperationOutcome',
@@ -146,9 +147,7 @@ class SearchWorker extends TerminologyWorker {
146
147
 
147
148
  for (const [key, cs] of this.provider.codeSystems) {
148
149
  this.deadCheck('searchCodeSystems');
149
- if (cs.url == 'http://www.cms.gov/Medicare/Coding/HCPCSReleaseCodeSets') {
150
- console.log("debug");
151
- }
150
+
152
151
  if (key == cs.vurl) {
153
152
  const json = cs.jsonObj;
154
153
 
@@ -160,10 +159,6 @@ class SearchWorker extends TerminologyWorker {
160
159
  // Check each search parameter for partial match
161
160
  let isMatch = true;
162
161
  for (const [param, searchValue] of Object.entries(searchParams)) {
163
- // 'system' doesn't do anything for CodeSystem search
164
- if (param === 'system') {
165
- continue;
166
- }
167
162
 
168
163
  // Map content-mode to content property
169
164
  const jsonProp = param === 'content-mode' ? 'content' : param;
@@ -180,7 +175,7 @@ class SearchWorker extends TerminologyWorker {
180
175
  isMatch = false;
181
176
  break;
182
177
  }
183
- } else if (param === 'url') { // exact match
178
+ } else if (param === 'url' || param === 'system') { // exact match
184
179
  const propValue = json.url;
185
180
  if (propValue !== searchValue) {
186
181
  isMatch = false;
@@ -240,6 +235,41 @@ class SearchWorker extends TerminologyWorker {
240
235
  return allMatches;
241
236
  }
242
237
 
238
+ /**
239
+ * Search ConceptMaps by delegating to providers
240
+ */
241
+ async searchConceptMaps(params, elements) {
242
+ const allMatches = [];
243
+
244
+ // Convert params object to array format expected by ValueSet providers
245
+ // Exclude control params (_offset, _count, _elements, _sort)
246
+ const searchParams = [];
247
+ let source = null;
248
+ for (const [key, value] of Object.entries(params)) {
249
+ if (!key.startsWith('_') && value && SearchWorker.ALLOWED_PARAMS.includes(key)) {
250
+ searchParams.push({ name: key, value: value });
251
+ }
252
+ if (key == 'source') {
253
+ source = value;
254
+ }
255
+ }
256
+
257
+ for (const cmsp of this.provider.conceptMapProviders) {
258
+ if (!source || source == cmsp.sourcePackage()) {
259
+ this.deadCheck('searchConceptMaps-providers');
260
+ const results = await cmsp.searchConceptMaps(searchParams, elements);
261
+ if (results && Array.isArray(results)) {
262
+ for (const vs of results) {
263
+ this.deadCheck('searchConceptMaps-results');
264
+ allMatches.push(vs.jsonObj || vs);
265
+ }
266
+ }
267
+ }
268
+ }
269
+
270
+ return allMatches;
271
+ }
272
+
243
273
  /**
244
274
  * Check if a value matches the search term (partial, case-insensitive)
245
275
  */
@@ -12,6 +12,7 @@ const { FhirCodeSystemProvider } = require('../cs/cs-cs');
12
12
  const {TxParameters} = require("../params");
13
13
  const {Parameters} = require("../library/parameters");
14
14
  const {Issue, OperationOutcome} = require("../library/operation-outcome");
15
+ const {debugLog} = require("../operation-context");
15
16
  class SubsumesWorker extends TerminologyWorker {
16
17
  /**
17
18
  * @param {OperationContext} opContext - Operation context
@@ -43,7 +44,7 @@ class SubsumesWorker extends TerminologyWorker {
43
44
  await this.handleTypeLevelSubsumes(req, res);
44
45
  } catch (error) {
45
46
  this.log.error(error);
46
- this.debugLog(error);
47
+ debugLog(error);
47
48
  req.logInfo = "error "+(error.msgId || error.className);
48
49
  if (error instanceof Issue) {
49
50
  let oo = new OperationOutcome();
@@ -67,7 +68,7 @@ class SubsumesWorker extends TerminologyWorker {
67
68
  await this.handleInstanceLevelSubsumes(req, res);
68
69
  } catch (error) {
69
70
  this.log.error(error);
70
- this.debugLog(error);
71
+ debugLog(error);
71
72
  if (error instanceof Issue) {
72
73
  let oo = new OperationOutcome();
73
74
  oo.addIssue(error);
@@ -12,6 +12,7 @@ const { TxParameters } = require('../params');
12
12
  const { Parameters } = require('../library/parameters');
13
13
  const { Issue, OperationOutcome } = require('../library/operation-outcome');
14
14
  const {ConceptMap} = require("../library/conceptmap");
15
+ const {debugLog} = require("../operation-context");
15
16
 
16
17
  class TranslateWorker extends TerminologyWorker {
17
18
  /**
@@ -44,7 +45,7 @@ class TranslateWorker extends TerminologyWorker {
44
45
  await this.handleTypeLevelTranslate(req, res);
45
46
  } catch (error) {
46
47
  this.log.error(error);
47
- this.debugLog(error);
48
+ debugLog(error);
48
49
  if (error instanceof Issue) {
49
50
  const oo = new OperationOutcome();
50
51
  oo.addIssue(error);
@@ -67,7 +68,7 @@ class TranslateWorker extends TerminologyWorker {
67
68
  await this.handleInstanceLevelTranslate(req, res);
68
69
  } catch (error) {
69
70
  this.log.error(error);
70
- this.debugLog(error);
71
+ debugLog(error);
71
72
  if (error instanceof Issue) {
72
73
  const oo = new OperationOutcome();
73
74
  oo.addIssue(error);
@@ -410,7 +411,7 @@ class TranslateWorker extends TerminologyWorker {
410
411
  }
411
412
  } catch (error) {
412
413
  this.log.error(error);
413
- this.debugLog(error);
414
+ debugLog(error);
414
415
  result.push({
415
416
  name: 'result',
416
417
  valueBoolean: false
@@ -23,6 +23,8 @@ const ValueSet = require("../library/valueset");
23
23
  const {ValueSetExpander} = require("./expand");
24
24
  const {FhirCodeSystemProvider} = require("../cs/cs-cs");
25
25
  const {CodeSystem} = require("../library/codesystem");
26
+ const {VersionUtilities} = require("../../library/version-utilities");
27
+ const {debugLog} = require("../operation-context");
26
28
 
27
29
  const DEV_IGNORE_VALUESET = false; // todo: what's going on with this (ported from pascal)
28
30
 
@@ -123,7 +125,7 @@ class ValueSetChecker {
123
125
  }
124
126
  } catch (error) {
125
127
  this.log.error(error);
126
- this.debugLog(error);
128
+ debugLog(error);
127
129
  throw new Error('Exception expanding value set in order to infer system: ' + error.message);
128
130
  }
129
131
  return result;
@@ -444,7 +446,7 @@ class ValueSetChecker {
444
446
  return await this.check(issuePath, system, version, code, null, unknownSystems, ver, inactive, normalForm, vstatus, it, op, null, null, contentMode, impliedSystem, ts, msgs, defLang);
445
447
  }
446
448
 
447
- async check(path, system, version, code, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, params, contentMode, impliedSystem, unkCodes, messages, defLang) {
449
+ async check(path, system, version, code, displays, unknownSystems, ver, inactive, normalForm, vstatus, cause, op, vcc, params, contentMode, impliedSystem, unkCodes, messages, defLang, display) {
448
450
  defLang.value = new Language('en');
449
451
  this.worker.opContext.addNote(this.valueSet, 'Check "' + this.worker.renderer.displayCoded(system, version, code) + '"', this.indentCount);
450
452
 
@@ -677,11 +679,28 @@ class ValueSetChecker {
677
679
 
678
680
  if (Extensions.checkNoModifiers(this.valueSet.jsonObj.compose, 'ValueSetChecker.prepare', 'ValueSet.compose')) {
679
681
  result = false;
680
- for (let cc of this.valueSet.jsonObj.compose.include || []) {
682
+ let determinedVersion = undefined;
683
+ if (!version) {
684
+ // if we don't have a fixed version, and we have more than one possible version, we have to pick the version
685
+ // now, by looking to see which version we can find the value in, starting from the most revent.
686
+ let includes = (this.valueSet.jsonObj.compose.include || []).filter(inc => inc.system == system);
687
+ let vset = new Set(includes.map(inc => inc.version).filter(Boolean));
688
+ if (vset.size > 1) {
689
+ determinedVersion = await this.pickApplicableVersion(vset, system, code, display);
690
+ }
691
+ }
692
+ const includes = [...(this.valueSet.jsonObj.compose.include || [])];
693
+ includes.sort((a, b) => {
694
+ if (a.system === b.system && a.version && b.version) {
695
+ return -VersionUtilities.compareVersionsGeneral(a.version, b.version);
696
+ }
697
+ return 0;
698
+ });
699
+ for (let cc of includes) {
681
700
  this.worker.deadCheck('check#2');
682
701
  if (!cc.system) {
683
702
  result = true;
684
- } else if (cc.system === system || system === '%%null%%') {
703
+ } else if ((cc.system === system || system === '%%null%%') && (!determinedVersion || cc.version == determinedVersion) && this.useThisVersion(cc, version)) {
685
704
  let v = await this.determineVersion(path, cc.system, cc.version, version, op, unknownSystems, messages);
686
705
  let cs = await this.worker.findCodeSystem(system, v, this.params, ["complete", "fragment"], op,true, true, false, this.worker.requiredSupplements);
687
706
  if (cs === null) {
@@ -720,12 +739,12 @@ class ValueSetChecker {
720
739
  defLang.value = new Language(cs.defLang());
721
740
  this.worker.opContext.addNote(this.valueSet, 'CodeSystem found: ' + this.worker.renderer.displayCoded(cs) + ' for ' + this.worker.renderer.displayCoded(cc.system, v), this.indentCount);
722
741
  await this.checkCanonicalStatusCS(path, op, cs, this.valueSet);
723
- ver.value = cs.version();
724
742
  this.worker.checkSupplements(cs, cc, this.worker.requiredSupplements, this.worker.usedSupplements);
725
743
  contentMode.value = cs.contentMode();
726
744
 
727
745
  let msg = '';
728
746
  if ((system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'in', cs, cc, code, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc, messages)) {
747
+ ver.value = cs.version();
729
748
  result = true;
730
749
  } else {
731
750
  result = false;
@@ -766,7 +785,6 @@ class ValueSetChecker {
766
785
  }
767
786
  await this.checkCanonicalStatus(path, op, cs, this.valueSet);
768
787
  this.worker.checkSupplements(cs, cc, this.worker.requiredSupplements, this.worker.usedSupplements);
769
- ver.value = cs.version();
770
788
  contentMode.value = cs.contentMode();
771
789
  let msg = '';
772
790
  excluded = (system === '%%null%%' || cs.system() === system) && await this.checkConceptSet(path, 'not in', cs, cc, code, displays, this.valueSet, msg, inactive, normalForm, vstatus, op, vcc);
@@ -926,13 +944,13 @@ class ValueSetChecker {
926
944
  if (inactive.value) {
927
945
  result.AddParamBool('inactive', inactive.value);
928
946
  if (vstatus.value && vstatus.value !== 'inactive') {
929
- result.addParamStr('status', vstatus.value);
947
+ result.addParamCode('status', vstatus.value);
930
948
  }
931
949
  let msg = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, coding.code]);
932
950
  messages.push(msg);
933
951
  op.addIssue(new Issue('warning', 'business-rule', path, 'INACTIVE_CONCEPT_FOUND', msg, 'code-comment'));
934
952
  } else if (vstatus.value.toLowerCase() === 'deprecated') {
935
- result.addParamStr('status', vstatus.value);
953
+ result.addParamCode('status', vstatus.value);
936
954
  let msg = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, coding.code]);
937
955
  messages.push(msg);
938
956
  op.addIssue(new Issue('warning', 'business-rule', path, 'DEPRECATED_CONCEPT_FOUND', msg, 'code-comment'));
@@ -1037,7 +1055,8 @@ class ValueSetChecker {
1037
1055
  if (this.worker.opContext.usageTracker) {
1038
1056
  this.worker.opContext.usageTracker.seeConcept(c.system, c.code);
1039
1057
  }
1040
- const csd = await this.worker.findCodeSystem(c.system, null, this.params, ['complete', 'fragment'], false, true, false, false, this.worker.requiredSupplements);
1058
+ let vsImpliedVersion = this.findVSVersionForSystem(c.system);
1059
+ const csd = await this.worker.findCodeSystem(c.system, vsImpliedVersion, this.params, ['complete', 'fragment'], false, true, false, false, this.worker.requiredSupplements);
1041
1060
  this.worker.seeSourceProvider(csd, c.system);
1042
1061
  this.worker.deadCheck('check-b#1');
1043
1062
  let path;
@@ -1055,7 +1074,7 @@ class ValueSetChecker {
1055
1074
  let ver = { value: '' };
1056
1075
  let contentMode = { value: null };
1057
1076
  let defLang = { value: null };
1058
- 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);
1077
+ 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, c.display);
1059
1078
  if (v === false) {
1060
1079
  cause.value = 'code-invalid';
1061
1080
  }
@@ -1329,13 +1348,18 @@ class ValueSetChecker {
1329
1348
  if (inactive.value) {
1330
1349
  result.addParamBool('inactive', inactive.value);
1331
1350
  if (vstatus.value && vstatus.value !== 'inactive') {
1332
- result.addParamStr('status', vstatus.value);
1351
+ result.addParamCode('status', vstatus.value);
1352
+ }
1353
+ if (!['inactive', 'DISCOURAGED'].includes(vstatus.value)) {
1354
+ let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, ['inactive', tcode]);
1355
+ msg(m);
1356
+ op.addIssue(new Issue('warning', 'business-rule', inactive.path, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
1333
1357
  }
1334
1358
  let m = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
1335
1359
  msg(m);
1336
1360
  op.addIssue(new Issue('warning', 'business-rule', inactive.path, 'INACTIVE_CONCEPT_FOUND', m, 'code-comment'));
1337
1361
  } else if (vstatus.value && vstatus.value.toLowerCase() === 'deprecated') {
1338
- result.addParamStr('status', 'deprecated');
1362
+ result.addParamCode('status', 'deprecated');
1339
1363
  let m = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, tcode]);
1340
1364
  msg(m);
1341
1365
  op.addIssue(new Issue('warning', 'business-rule', issuePath, 'DEPRECATED_CONCEPT_FOUND', m, 'code-comment'));
@@ -1475,13 +1499,13 @@ class ValueSetChecker {
1475
1499
  if (inactive.value) {
1476
1500
  result.addParamBool('inactive', inactive.value);
1477
1501
  if (vstatus.value && vstatus.value !== 'inactive') {
1478
- result.addParamStr('status', vstatus.value);
1502
+ result.addParamCode('status', vstatus.value);
1479
1503
  }
1480
1504
  let msg = this.worker.i18n.translate('INACTIVE_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, code]);
1481
1505
  messages.push(msg);
1482
1506
  op.addIssue(new Issue('warning', 'business-rule', 'code', 'INACTIVE_CONCEPT_FOUND', msg, 'code-comment'));
1483
1507
  } else if (vstatus.value.toLowerCase() === 'deprecated') {
1484
- result.addParamStr('status', vstatus.value);
1508
+ result.addParamCode('status', vstatus.value);
1485
1509
  let msg = this.worker.i18n.translate('DEPRECATED_CONCEPT_FOUND', this.params.HTTPLanguages, [vstatus.value, code]);
1486
1510
  messages.push(msg);
1487
1511
  op.addIssue(new Issue('warning', 'business-rule', 'code', 'DEPRECATED_CONCEPT_FOUND', msg, 'code-comment'));
@@ -1509,14 +1533,16 @@ class ValueSetChecker {
1509
1533
 
1510
1534
  async checkConceptSet(path, role, cs, cset, code, displays, vs, message, inactive, normalForm, vstatus, op, vcc, messages) {
1511
1535
  this.worker.opContext.addNote(vs, 'check code ' + role + ' ' + this.worker.renderer.displayValueSetInclude(cset) + ' at ' + path, this.indentCount);
1512
- inactive.value = false;
1536
+ if (role !== 'not in') {
1537
+ inactive.value = false;
1538
+ }
1513
1539
  let result = false;
1514
1540
  if (!cset.concept && !cset.filter) {
1515
1541
  let loc = await cs.locate(code);
1516
1542
  result = false;
1517
1543
  if (loc.context == null) {
1518
1544
  this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" not found in ' + this.worker.renderer.displayCoded(cs)+": "+loc.mesage, this.indentCount);
1519
- if (!this.params.membershipOnly) {
1545
+ if (!this.params.membershipOnly && role !== 'not in') {
1520
1546
  if (cs.contentMode() !== 'complete') {
1521
1547
  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'));
1522
1548
  result = true;
@@ -1548,16 +1574,18 @@ class ValueSetChecker {
1548
1574
 
1549
1575
  if (!(this.params.abstractOk || !(await cs.isAbstract(loc.context)))) {
1550
1576
  this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is abstract', this.indentCount);
1551
- if (!this.params.membershipOnly) {
1577
+ if (!this.params.membershipOnly && role !== 'not in') {
1552
1578
  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'));
1553
1579
  }
1554
1580
  } else if (this.excludeInactives() && await cs.isInactive(loc.context)) {
1555
1581
  this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1556
- let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1557
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
1582
+ if (role !== 'not in') {
1583
+ let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1584
+ op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
1585
+ messages.push(msg);
1586
+ }
1558
1587
  result = false;
1559
- messages.push(msg);
1560
- if (!this.params.membershipOnly) {
1588
+ if (!this.params.membershipOnly && role !== 'not in') {
1561
1589
  inactive.value = true;
1562
1590
  inactive.path = path;
1563
1591
  if (inactive.value) {
@@ -1567,28 +1595,31 @@ class ValueSetChecker {
1567
1595
  } else if (this.params.activeOnly && await cs.isInactive(loc.context)) {
1568
1596
  this.worker.opContext.addNote(this.valueSet, 'Code "' + code + '" found in ' + this.worker.renderer.displayCoded(cs) + ' but is inactive', this.indentCount);
1569
1597
  result = false;
1570
- inactive.value = true;
1571
- inactive.path = path;
1572
- vstatus.value = await cs.getStatus(loc.context);
1573
- let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1574
- messages.push(msg);
1575
- op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
1598
+ if (role !== 'not in') {
1599
+ inactive.value = true;
1600
+ inactive.path = path;
1601
+ vstatus.value = await cs.getStatus(loc.context);
1602
+ let msg = this.worker.i18n.translate('STATUS_CODE_WARNING_CODE', this.params.HTTPLanguages, ['not active', code]);
1603
+ messages.push(msg);
1604
+ op.addIssue(new Issue('error', 'business-rule', addToPath(path, 'code'), 'STATUS_CODE_WARNING_CODE', msg, 'code-rule'));
1605
+ }
1576
1606
  } else {
1577
1607
  result = true;
1578
- inactive.value = await cs.isInactive(loc.context);
1579
- inactive.path = path;
1580
- vstatus.value = await cs.getStatus(loc.context);
1581
-
1582
- if (vcc !== null) {
1583
- if (!vcc.coding) {
1584
- vcc.coding = [];
1608
+ if (role !== 'not in') {
1609
+ inactive.value = await cs.isInactive(loc.context);
1610
+ inactive.path = path;
1611
+ vstatus.value = await cs.getStatus(loc.context);
1612
+ if (vcc !== null) {
1613
+ if (!vcc.coding) {
1614
+ vcc.coding = [];
1615
+ }
1616
+ vcc.coding.push({
1617
+ system: cs.system(),
1618
+ version: cs.version(),
1619
+ code: await cs.code(loc.context),
1620
+ display: displays.preferredDisplay(this.params.workingLanguages())
1621
+ });
1585
1622
  }
1586
- vcc.coding.push({
1587
- system: cs.system(),
1588
- version: cs.version(),
1589
- code: await cs.code(loc.context),
1590
- display: displays.preferredDisplay(this.params.workingLanguages())
1591
- });
1592
1623
  }
1593
1624
  return result;
1594
1625
  }
@@ -1739,6 +1770,72 @@ class ValueSetChecker {
1739
1770
  return list.join(",");
1740
1771
  }
1741
1772
 
1773
+ findVSVersionForSystem(system) {
1774
+ let set = new Set();
1775
+ for (let inc of this.valueSet.jsonObj.compose?.include || []) {
1776
+ if (inc.system == system && inc.version) {
1777
+ set.add(inc.version);
1778
+ }
1779
+ }
1780
+ let v = null;
1781
+ for (let t of set) {
1782
+ if (!v || VersionUtilities.compareVersionsGeneral(t, v) > 0) {
1783
+ v = t;
1784
+ }
1785
+ }
1786
+ return v;
1787
+ }
1788
+
1789
+ async pickApplicableVersion(vset, system, code, display) {
1790
+ let found = [];
1791
+ for (let v of vset) {
1792
+ let cs = await this.worker.findCodeSystem(system, v, this.params, ["complete", "fragment"], null, true, true, false, this.worker.requiredSupplements);
1793
+ if (cs != null) {
1794
+ let loc = await cs.locate(code);
1795
+ if (loc.context) {
1796
+ if (!display || await this.displayIsOk(cs, loc.context, display)) {
1797
+ found.push(v);
1798
+ }
1799
+ }
1800
+ }
1801
+ }
1802
+ // if it was found in none or all of them, we don't do anything
1803
+ if (found.length == vset.size) {
1804
+ return undefined;
1805
+ }
1806
+ if (found.length > 0) {
1807
+ let sorted = found.sort((a, b) => -VersionUtilities.compareVersionsGeneral(a, b));
1808
+ return sorted[0];
1809
+ } else { // well, none of them, we'll go with the latest
1810
+ let sorted = [...vset].sort((a, b) => -VersionUtilities.compareVersionsGeneral(a, b));
1811
+ return sorted[0];
1812
+ }
1813
+ }
1814
+
1815
+ async displayIsOk(cs, context, display) {
1816
+ if (display == await cs.display(context)) {
1817
+ return true;
1818
+ }
1819
+ const cds = new Designations(this.worker.i18n.languageDefinitions);
1820
+ await cs.designations(context, cds);
1821
+ return cds.designations.find(cd => cd.value == display);
1822
+ }
1823
+
1824
+ hasMatchForVersion(includes, version) {
1825
+ for (let inc of includes) {
1826
+ if (inc.version == version) {
1827
+ return true;
1828
+ }
1829
+ }
1830
+ return false;
1831
+ }
1832
+
1833
+ useThisVersion(cc, version) {
1834
+ if (!version || cc.version == version) {
1835
+ return true;
1836
+ }
1837
+ return !this.hasMatchForVersion(this.valueSet.jsonObj.compose.include || [], version);
1838
+ }
1742
1839
  }
1743
1840
 
1744
1841
  function addToPath(path, name) {
@@ -1855,7 +1952,7 @@ class ValidateWorker extends TerminologyWorker {
1855
1952
 
1856
1953
  } catch (error) {
1857
1954
  this.log.error(error);
1858
- this.debugLog(error);
1955
+ debugLog(error);
1859
1956
  if (error instanceof Issue) {
1860
1957
  if (error.isHandleAsOO()) {
1861
1958
  let oo = new OperationOutcome();
@@ -1914,7 +2011,7 @@ class ValidateWorker extends TerminologyWorker {
1914
2011
  return result;
1915
2012
  } catch (error) {
1916
2013
  this.log.error(error);
1917
- this.debugLog(error);
2014
+ debugLog(error);
1918
2015
  if (error instanceof Issue && !error.isHandleAsOO()) {
1919
2016
  return await this.handlePrepareError(error, coded, mode.mode, txp);
1920
2017
  } else {
@@ -1973,7 +2070,7 @@ class ValidateWorker extends TerminologyWorker {
1973
2070
 
1974
2071
  } catch (error) {
1975
2072
  this.log.error(error);
1976
- this.debugLog(error);
2073
+ debugLog(error);
1977
2074
  return res.status(error.statusCode || 500).json(this.operationOutcome(
1978
2075
  'error', error.issueCode || 'exception', error.message));
1979
2076
  }
@@ -1994,7 +2091,7 @@ class ValidateWorker extends TerminologyWorker {
1994
2091
 
1995
2092
  } catch (error) {
1996
2093
  this.log.error(error);
1997
- this.debugLog(error);
2094
+ debugLog(error);
1998
2095
  if (error instanceof Issue) {
1999
2096
  let op = new OperationOutcome();
2000
2097
  op.addIssue(error);
@@ -2074,7 +2171,7 @@ class ValidateWorker extends TerminologyWorker {
2074
2171
 
2075
2172
  } catch (error) {
2076
2173
  this.log.error(error);
2077
- this.debugLog(error);
2174
+ debugLog(error);
2078
2175
  return res.status(error.statusCode || 500).json(this.operationOutcome(
2079
2176
  'error', error.issueCode || 'exception', error.message));
2080
2177
  }
@@ -2314,7 +2411,7 @@ class ValidateWorker extends TerminologyWorker {
2314
2411
  await checker.prepare();
2315
2412
  } catch (error) {
2316
2413
  this.log.error(error);
2317
- this.debugLog(error);
2414
+ debugLog(error);
2318
2415
  if (!(error instanceof Issue) || error.isHandleAsOO()) {
2319
2416
  throw error;
2320
2417
  } else {
@@ -1,4 +1,4 @@
1
- const { TerminologyError, isDebugging} = require('../operation-context');
1
+ const { TerminologyError} = require('../operation-context');
2
2
  const { CodeSystem } = require('../library/codesystem');
3
3
  const ValueSet = require('../library/valueset');
4
4
  const {VersionUtilities} = require("../../library/version-utilities");
@@ -900,12 +900,6 @@ class TerminologyWorker {
900
900
  return true;
901
901
  }
902
902
 
903
- debugLog(error) {
904
- if (isDebugging()) {
905
- console.log(error);
906
- }
907
- }
908
-
909
903
  hasSupplement(cs, supplements) {
910
904
  for (let t of supplements) {
911
905
  if (t.vurl == cs.vurl) {