fhirsmith 0.4.2 → 0.5.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 (92) hide show
  1. package/CHANGELOG.md +24 -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 +89 -12
  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 +241 -230
@@ -200,6 +200,8 @@ class ValueSetExpander {
200
200
  params;
201
201
  excluded = new Set();
202
202
  hasExclusions = false;
203
+ requiredSupplements = new Set();
204
+ usedSupplements = new Set();
203
205
 
204
206
  constructor(worker, params) {
205
207
  this.worker = worker;
@@ -333,12 +335,13 @@ class ValueSetExpander {
333
335
 
334
336
  if (this.limitCount > 0 && this.fullList.length >= this.limitCount && !this.hasExclusions) {
335
337
  if (this.count > -1 && this.offset > -1 && this.count + this.offset > 0 && this.fullList.length >= this.count + this.offset) {
338
+ this.noTotal();
336
339
  throw new Issue('information', 'informational', null, null, null, null).setFinished();
337
340
  } else {
338
341
  if (!srcURL) {
339
342
  srcURL = '??';
340
343
  }
341
- throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [srcURL, '>' + this.limitCount]), null, 400).withDiagnostics(this.worker.opContext.diagnostics());
344
+ throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [srcURL, '>' + this.limitCount]), null, 422).withDiagnostics(this.worker.opContext.diagnostics());
342
345
  }
343
346
  }
344
347
 
@@ -448,7 +451,7 @@ class ValueSetExpander {
448
451
  if (cp.code === pn) {
449
452
  let vn = getValueName(cp);
450
453
  let v = cp[vn];
451
- this.defineProperty(expansion, n, this.getPropUrl(cs, pn), pn, vn, v);
454
+ this.defineProperty(expansion, n, this.getPropUrl(cs, pn, cp), pn, vn, v);
452
455
  }
453
456
  }
454
457
  }
@@ -486,7 +489,7 @@ class ValueSetExpander {
486
489
  if (!srcURL) {
487
490
  srcURL = '??';
488
491
  }
489
- throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [srcURL, '>' + this.limitCount]), null, 400).withDiagnostics(this.worker.opContext.diagnostics());
492
+ throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [srcURL, '>' + this.limitCount]), null, 422).withDiagnostics(this.worker.opContext.diagnostics());
490
493
  }
491
494
  }
492
495
 
@@ -504,7 +507,7 @@ class ValueSetExpander {
504
507
  this.excluded.add(system + '|' + version + '#' + code);
505
508
  }
506
509
 
507
- async checkCanExpandValueset(uri, version) {
510
+ async checkCanExpandValueSet(uri, version) {
508
511
  const vs = await this.worker.findValueSet(uri, version);
509
512
  if (vs == null) {
510
513
  if (!version && uri.includes('|')) {
@@ -512,9 +515,9 @@ class ValueSetExpander {
512
515
  uri = uri.substring(0, uri.indexOf('|'));
513
516
  }
514
517
  if (!version) {
515
- throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK', this.worker.i18n.translate('VS_EXP_IMPORT_UNK', this.params.httpLanguages, [uri]), 'unknown', 400);
518
+ throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK', this.worker.i18n.translate('VS_EXP_IMPORT_UNK', this.params.httpLanguages, [uri]), 'unknown', 422);
516
519
  } else {
517
- throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK_PINNED', this.worker.i18n.translate('VS_EXP_IMPORT_UNK_PINNED', this.params.httpLanguages, [uri, version]), 'not-found', 400);
520
+ throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK_PINNED', this.worker.i18n.translate('VS_EXP_IMPORT_UNK_PINNED', this.params.httpLanguages, [uri, version]), 'not-found', 422);
518
521
  }
519
522
  } else {
520
523
  this.worker.seeSourceVS(vs, uri);
@@ -526,16 +529,19 @@ class ValueSetExpander {
526
529
  let vs = await this.worker.findValueSet(uri, version);
527
530
  if (!vs) {
528
531
  if (version) {
529
- throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK_PINNED', this.worker.i18n.translate('VS_EXP_IMPORT_UNK_PINNED', this.params.httpLanguages, [uri, version]), "not-found", 400);
532
+ throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK_PINNED', this.worker.i18n.translate('VS_EXP_IMPORT_UNK_PINNED', this.params.httpLanguages, [uri, version]), "not-found", 422);
530
533
  } else if (uri.includes('|')) {
531
- throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK_PINNED', this.worker.i18n.translate('VS_EXP_IMPORT_UNK_PINNED', this.params.httpLanguages, [uri.substring(0, uri.indexOf("|")), uri.substring(uri.indexOf("|")+1)]), "not-found", 400);
534
+ throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK_PINNED', this.worker.i18n.translate('VS_EXP_IMPORT_UNK_PINNED', this.params.httpLanguages, [uri.substring(0, uri.indexOf("|")), uri.substring(uri.indexOf("|")+1)]), "not-found", 422);
532
535
  } else {
533
- throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK', this.worker.i18n.translate('VS_EXP_IMPORT_UNK', this.params.httpLanguages, [uri]), "not-found", 400);
536
+ throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK', this.worker.i18n.translate('VS_EXP_IMPORT_UNK', this.params.httpLanguages, [uri]), "not-found", 422);
534
537
  }
535
538
  }
536
539
  let worker = new ExpandWorker(this.worker.opContext, this.worker.log, this.worker.provider, this.worker.languages, this.worker.i18n);
537
540
  worker.additionalResources = this.worker.additionalResources;
538
- let expander = new ValueSetExpander(worker, this.params);
541
+ // we're going to let this one do more expansion for technical reasons
542
+ let paramsInner = this.params.clone();
543
+ paramsInner.limit = INTERNAL_LIMIT;
544
+ let expander = new ValueSetExpander(worker, paramsInner);
539
545
  let result = await expander.expand(vs, filter, false);
540
546
  if (result == null) {
541
547
  throw new Issue('error', 'not-found', null, 'VS_EXP_IMPORT_UNK', this.worker.i18n.translate('VS_EXP_IMPORT_UNK', this.params.httpLanguages, [uri]), 'unknown');
@@ -548,7 +554,7 @@ class ValueSetExpander {
548
554
 
549
555
  async importValueSet(vs, expansion, imports, offset) {
550
556
  this.canBeHierarchy = false;
551
- for (let p of vs.expansion.parameter) {
557
+ for (let p of vs.expansion.parameter || []) {
552
558
  let vn = getValueName(p);
553
559
  let v = getValuePrimitive(p);
554
560
  this.addParam(expansion, p.name, vn, v);
@@ -590,6 +596,7 @@ class ValueSetExpander {
590
596
  this.fullList.splice(idx, 1);
591
597
  }
592
598
  this.map.delete(s);
599
+ this.decTotal();
593
600
  }
594
601
  }
595
602
  }
@@ -601,7 +608,7 @@ class ValueSetExpander {
601
608
  for (const u of cset.valueSet || []) {
602
609
  this.worker.deadCheck('checkSource');
603
610
  const s = this.worker.pinValueSet(u);
604
- await this.checkCanExpandValueset(s, '');
611
+ await this.checkCanExpandValueSet(s, '');
605
612
  imp = true;
606
613
  }
607
614
 
@@ -615,7 +622,8 @@ class ValueSetExpander {
615
622
  }
616
623
 
617
624
  if (cset.system) {
618
- const cs = await this.worker.findCodeSystem(cset.system, cset.version, this.params, ['complete', 'fragment'], false, true, true, null);
625
+ const cs = await this.worker.findCodeSystem(cset.system, cset.version, this.params, ['complete', 'fragment'],
626
+ false, true, true, null, this.requiredSupplements);
619
627
  this.worker.seeSourceProvider(cs, cset.system);
620
628
  if (cs == null) {
621
629
  // nothing
@@ -625,27 +633,26 @@ class ValueSetExpander {
625
633
  throw new Issue('error', 'business-rule', null, null, 'The code system definition for ' + cset.system + ' has no content, so this expansion cannot be performed', 'invalid');
626
634
  } else if (cs.contentMode() === 'supplement') {
627
635
  throw new Issue('error', 'business-rule', null, null, 'The code system definition for ' + cset.system + ' defines a supplement, so this expansion cannot be performed', 'invalid');
628
- } else if (this.params.incompleteOK) {
629
- exp.addParamUri(cs.contentMode, cs.system + '|' + cs.version);
630
636
  } else {
631
- throw new Issue('error', 'business-rule', null, null, 'The code system definition for ' + cset.system + ' is a ' + cs.contentMode + ', so this expansion is not permitted unless the expansion parameter "incomplete-ok" has a value of "true"', 'invalid');
637
+ this.addParamUri(cs.contentMode(), cs.system + '|' + cs.version);
638
+ Extensions.addString(exp, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed",
639
+ "This extension is based on a fragment of the code system " + cset.system);
632
640
  }
633
641
  }
634
642
 
635
643
  if (!cset.concept && !cset.filter) {
636
- if (cs.specialEnumeration() && this.params.limitedExpansion) {
637
- this.checkCanExpandValueSet(cs.specialEnumeration(), '');
644
+ if (cs.specialEnumeration()) {
645
+ await this.checkCanExpandValueSet(cs.specialEnumeration(), '');
638
646
  } else if (filter.isNull) {
639
647
  if (cs.isNotClosed()) {
640
648
  if (cs.specialEnumeration()) {
641
- throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly. If an incomplete expansion is requested, a limited enumeration will be returned', null, 400).withDiagnostics(this.worker.opContext.diagnostics());
649
+ Extensions.addString(exp, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
642
650
  } else {
643
- throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly', null, 400).withDiagnostics(this.worker.opContext.diagnostics());
651
+ throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly', null, 422).withDiagnostics(this.worker.opContext.diagnostics());
644
652
  }
645
653
  }
646
-
647
- if (!imp && this.limitCount > 0 && cs.totalCount > this.limitCount && !this.params.limitedExpansion) {
648
- throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [srcURL, '>' + this.limitCount]), null, 400).withDiagnostics(this.worker.opContext.diagnostics());
654
+ if (!imp && this.limitCount > 0 && cs.totalCount > this.limitCount) {
655
+ throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [srcURL, '>' + this.limitCount]), null, 422).withDiagnostics(this.worker.opContext.diagnostics());
649
656
  }
650
657
  }
651
658
  }
@@ -677,13 +684,13 @@ class ValueSetExpander {
677
684
  } else {
678
685
  const filters = [];
679
686
  const prep = null;
680
- const cs = await this.worker.findCodeSystem(cset.system, cset.version, this.params, ['complete', 'fragment'], false, false, true, null);
687
+ const cs = await this.worker.findCodeSystem(cset.system, cset.version, this.params, ['complete', 'fragment'],
688
+ false, false, true, null, this.requiredSupplements);
681
689
 
682
690
  if (cs == null) {
683
691
  // nothing
684
692
  } else {
685
-
686
- this.worker.checkSupplements(cs, cset, this.requiredSupplements);
693
+ this.worker.checkSupplements(cs, cset, this.requiredSupplements, this.usedSupplements);
687
694
  this.checkProviderCanonicalStatus(expansion, cs, this.valueSet);
688
695
  const sv = this.canonical(await cs.system(), await cs.version());
689
696
  this.addParamUri(expansion, 'used-codesystem', sv);
@@ -705,26 +712,26 @@ class ValueSetExpander {
705
712
  }
706
713
 
707
714
  if (!cset.concept && !cset.filter) {
708
- if (cs.specialEnumeration() && this.params.limitedExpansion && filters.length === 0) {
715
+ if (cs.specialEnumeration() && filters.length === 0) {
709
716
  this.worker.opContext.log('import special value set ' + cs.specialEnumeration());
710
717
  const base = await this.expandValueSet(cs.specialEnumeration(), '', filter, notClosed);
711
- Extensions.addBoolean(expansion, 'http://hl7.org/fhir/StructureDefinition/valueset-toocostly', true);
718
+ Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
712
719
  await this.importValueSet(base, expansion, valueSets, 0);
713
720
  notClosed.value = true;
714
721
  } else if (filter.isNull) {
715
722
  this.worker.opContext.log('add whole code system');
716
723
  if (cs.isNotClosed()) {
717
724
  if (cs.specialEnumeration()) {
718
- throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly. If an incomplete expansion is requested, a limited enumeration will be returned', null, 400).withDiagnostics(this.worker.opContext.diagnostics());
719
-
725
+ Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
720
726
  } else {
721
- throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly', null, 400).withDiagnostics(this.worker.opContext.diagnostics());
727
+ throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly', null, 422).withDiagnostics(this.worker.opContext.diagnostics());
722
728
  }
729
+ notClosed.value = true;
723
730
  }
724
731
 
725
732
  const iter = await cs.iterator(null);
726
- if (valueSets.length === 0 && this.limitCount > 0 && (iter && iter.total > this.limitCount) && !this.params.limitedExpansion && this.offset < 0) {
727
- throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [vsSrc.vurl, '>' + this.limitCount]), null, 400).withDiagnostics(this.worker.opContext.diagnostics());
733
+ if (valueSets.length === 0 && this.limitCount > 0 && (iter && iter.total > this.limitCount) && this.offset < 0) {
734
+ throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [vsSrc.vurl, '>' + this.limitCount]), null, 422).withDiagnostics(this.worker.opContext.diagnostics());
728
735
 
729
736
  }
730
737
  let tcount = 0;
@@ -744,12 +751,12 @@ class ValueSetExpander {
744
751
  notClosed.value = true;
745
752
  }
746
753
  const prep = await cs.getPrepContext(true);
747
- const ctxt = await cs.searchFilter(filter, prep, false);
748
- await cs.prepare(prep);
754
+ const ctxt = await cs.searchFilter(prep, filter, false);
755
+ let set = await cs.executeFilters(prep);
749
756
  this.worker.opContext.log('iterate filters');
750
- while (await cs.filterMore(ctxt)) {
757
+ while (await cs.filterMore(ctxt, set)) {
751
758
  this.worker.deadCheck('processCodes#4');
752
- const c = await cs.filterConcept(ctxt);
759
+ const c = await cs.filterConcept(ctxt, set);
753
760
  if (await this.passesFilters(cs, c, prep, filters, 0)) {
754
761
  const cds = new Designations(this.worker.i18n.languageDefinitions);
755
762
  await this.listDisplaysFromProvider(cds, cs, c);
@@ -764,7 +771,7 @@ class ValueSetExpander {
764
771
  if (cset.concept) {
765
772
  this.worker.opContext.log('iterate concepts');
766
773
  const cds = new Designations(this.worker.i18n.languageDefinitions);
767
- let tcount = 0;
774
+
768
775
  for (const cc of cset.concept) {
769
776
  this.worker.deadCheck('processCodes#3');
770
777
  cds.clear();
@@ -774,17 +781,18 @@ class ValueSetExpander {
774
781
  await this.listDisplaysFromProvider(cds, cs, cctxt.context);
775
782
  this.listDisplaysFromIncludeConcept(cds, cc, vsSrc);
776
783
  if (filter.passesDesignations(cds) || filter.passes(cc.code)) {
777
- tcount++;
778
784
  let ov = Extensions.readString(cc, 'http://hl7.org/fhir/StructureDefinition/itemWeight');
779
785
  if (!ov) {
780
786
  ov = await cs.itemWeight(cctxt.context);
781
787
  }
782
- await this.includeCode(cs, null, cs.system(), cs.version(), cc.code, await cs.isAbstract(cctxt.context), await cs.isInactive(cctxt.context), await cs.isDeprecated(cctxt.context), await cs.getStatus(cctxt.context), cds,
788
+ let added = await this.includeCode(cs, null, cs.system(), cs.version(), cc.code, await cs.isAbstract(cctxt.context), await cs.isInactive(cctxt.context), await cs.isDeprecated(cctxt.context), await cs.getStatus(cctxt.context), cds,
783
789
  await cs.definition(cctxt.context), ov, expansion, valueSets, await cs.extensions(cctxt.context), cc.extension, await cs.properties(cctxt.context), null, excludeInactive, vsSrc.url);
790
+ if (added) {
791
+ this.addToTotal();
792
+ }
784
793
  }
785
794
  }
786
795
  }
787
- this.addToTotal(tcount);
788
796
  this.worker.opContext.log('iterate concepts done');
789
797
  }
790
798
 
@@ -797,8 +805,7 @@ class ValueSetExpander {
797
805
  }
798
806
 
799
807
  if (cs.specialEnumeration()) {
800
- await cs.specialFilter(prep, true);
801
- Extensions.addBoolean(expansion, 'http://hl7.org/fhir/StructureDefinition/valueset-toocostly', true);
808
+ Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
802
809
  notClosed.value = true;
803
810
  }
804
811
 
@@ -815,12 +822,10 @@ class ValueSetExpander {
815
822
  const fset = await cs.executeFilters(prep);
816
823
  if (await cs.filtersNotClosed(prep)) {
817
824
  notClosed.value = true;
818
- }
819
- if (fset.length === 1 && !excludeInactive && !this.params.activeOnly) {
820
- this.addToTotal(await cs.filterSize(prep, fset[0]));
825
+ } else if (fset.length === 1 && !excludeInactive && !this.params.activeOnly) {
826
+ // this.addToTotal(await cs.filterSize(prep, fset[0]));
821
827
  }
822
828
 
823
- // let count = 0;
824
829
  this.worker.opContext.log('iterate filters');
825
830
  while (await cs.filterMore(prep, fset[0])) {
826
831
  this.worker.deadCheck('processCodes#5');
@@ -837,16 +842,17 @@ class ValueSetExpander {
837
842
  } else {
838
843
  this.canBeHierarchy = false;
839
844
  }
840
- await this.includeCode(cs, parent, await cs.system(), await cs.version(), await cs.code(c), await cs.isAbstract(c), await cs.isInactive(c),
845
+ let added = await this.includeCode(cs, parent, await cs.system(), await cs.version(), await cs.code(c), await cs.isAbstract(c), await cs.isInactive(c),
841
846
  await cs.isDeprecated(c), await cs.getStatus(c), cds, await cs.definition(c), await cs.itemWeight(c),
842
847
  expansion, null, await cs.extensions(c), null, await cs.properties(c), null, excludeInactive, vsSrc.url);
843
-
848
+ if (added) {
849
+ this.addToTotal();
850
+ }
844
851
  }
845
852
  }
846
853
  }
847
854
  this.worker.opContext.log('iterate filters done');
848
855
  }
849
-
850
856
  }
851
857
  }
852
858
  }
@@ -900,9 +906,10 @@ class ValueSetExpander {
900
906
  } else {
901
907
  const filters = [];
902
908
  const prep = null;
903
- const cs = await this.worker.findCodeSystem(cset.system, cset.version, this.params, ['complete', 'fragment'], false, true, true, null);
909
+ const cs = await this.worker.findCodeSystem(cset.system, cset.version, this.params, ['complete', 'fragment'], false,
910
+ true, true, null, this.requiredSupplements);
904
911
 
905
- this.worker.checkSupplements(cs, cset, this.requiredSupplements);
912
+ this.worker.checkSupplements(cs, cset, this.requiredSupplements, this.usedSupplements);
906
913
  this.checkResourceCanonicalStatus(expansion, cs, this.valueSet);
907
914
  const sv = this.canonical(await cs.system(), await cs.version());
908
915
  this.addParamUri(expansion, 'used-codesystem', sv);
@@ -924,7 +931,7 @@ class ValueSetExpander {
924
931
 
925
932
  if (!cset.concept && !cset.filter) {
926
933
  this.opContext.log('handle system');
927
- if (cs.specialEnumeration() && this.params.limitedExpansion && filters.length === 0) {
934
+ if (cs.specialEnumeration() && filters.length === 0) {
928
935
  const base = await this.expandValueSet(cs.specialEnumeration(), '', filter, notClosed);
929
936
  Extensions.addBoolean(expansion, 'http://hl7.org/fhir/StructureDefinition/valueset-toocostly', true);
930
937
  this.excludeValueSet(base, expansion, valueSets, 0);
@@ -932,15 +939,15 @@ class ValueSetExpander {
932
939
  } else if (filter.isNull) {
933
940
  if (cs.isNotClosed(filter)) {
934
941
  if (cs.specialEnumeration()) {
935
- throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly. If an incomplete expansion is requested, a limited enumeration will be returned', null, 400).withDiagnostics(this.worker.opContext.diagnostics());
942
+ Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
936
943
  } else {
937
- throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system + '" has a grammar, and cannot be enumerated directly', null, 400).withDiagnostics(this.worker.opContext.diagnostics());
944
+ throw new Issue("error", "too-costly", null, null, 'The code System "' + cs.system() + '" has a grammar, and cannot be enumerated directly', null, 422).withDiagnostics(this.worker.opContext.diagnostics());
938
945
  }
939
946
  }
940
947
 
941
948
  const iter = await cs.getIterator(null);
942
- if (valueSets.length === 0 && this.limitCount > 0 && iter.count > this.limitCount && !this.params.limitedExpansion) {
943
- throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [vsSrc.url, '>' + this.limitCount]), null, 400).withDiagnostics(this.worker.opContext.diagnostics());
949
+ if (valueSets.length === 0 && this.limitCount > 0 && iter.count > this.limitCount) {
950
+ throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [vsSrc.url, '>' + this.limitCount]), null, 422).withDiagnostics(this.worker.opContext.diagnostics());
944
951
  }
945
952
  while (iter.more()) {
946
953
  this.worker.deadCheck('processCodes#3a');
@@ -996,8 +1003,7 @@ class ValueSetExpander {
996
1003
 
997
1004
  if (cs.specialEnumeration()) {
998
1005
  await cs.specialFilter(prep, true);
999
- Extensions.addBoolean(expansion, 'http://hl7.org/fhir/StructureDefinition/valueset-toocostly', true);
1000
- notClosed.value = true;
1006
+ Extensions.addString(expansion, "http://hl7.org/fhir/StructureDefinition/valueset-unclosed", 'The code System "' + cs.system() + " has a grammar and so has infinite members. This extension is based on " + cs.specialEnumeration());
1001
1007
  }
1002
1008
 
1003
1009
  for (let fc of cset.filter) {
@@ -1145,7 +1151,7 @@ class ValueSetExpander {
1145
1151
  this.valueSet = source;
1146
1152
 
1147
1153
  const result = structuredClone(source.jsonObj);
1148
- result.id = '';
1154
+ result.id = undefined;
1149
1155
  let table = null;
1150
1156
  let div_ = null;
1151
1157
 
@@ -1160,9 +1166,9 @@ class ValueSetExpander {
1160
1166
  result.text = undefined;
1161
1167
  }
1162
1168
 
1163
- this.requiredSupplements = [];
1169
+ for (let s of this.params.supplements) this.requiredSupplements.add(s);
1164
1170
  for (const ext of Extensions.list(source.jsonObj, 'http://hl7.org/fhir/StructureDefinition/valueset-supplement')) {
1165
- this.requiredSupplements.push(getValuePrimitive(ext));
1171
+ this.requiredSupplements.add(getValuePrimitive(ext));
1166
1172
  }
1167
1173
 
1168
1174
  if (result.expansion) {
@@ -1181,13 +1187,14 @@ class ValueSetExpander {
1181
1187
  this.fullList = [];
1182
1188
  this.canBeHierarchy = !this.params.excludeNested;
1183
1189
 
1184
- this.limitCount = INTERNAL_LIMIT;
1185
1190
  if (this.params.limit <= 0) {
1186
1191
  if (!filter.isNull) {
1187
1192
  this.limitCount = UPPER_LIMIT_TEXT;
1188
1193
  } else {
1189
1194
  this.limitCount = UPPER_LIMIT_NO_TEXT;
1190
1195
  }
1196
+ } else {
1197
+ this.limitCount = Math.min(this.params.limit, INTERNAL_LIMIT);
1191
1198
  }
1192
1199
  this.offset = this.params.offset;
1193
1200
  this.count = this.params.count;
@@ -1205,9 +1212,6 @@ class ValueSetExpander {
1205
1212
  this.addParamStr(exp, 'filter', filter.filter);
1206
1213
  }
1207
1214
 
1208
- if (this.params.hasLimitedExpansion) {
1209
- this.addParamBool(exp, 'limitedExpansion', this.params.limitedExpansion);
1210
- }
1211
1215
  if (this.params.DisplayLanguages) {
1212
1216
  this.addParamCode(exp, 'displayLanguage', this.params.DisplayLanguages.asString(true));
1213
1217
  } else if (this.params.HTTPLanguages) {
@@ -1256,12 +1260,14 @@ class ValueSetExpander {
1256
1260
  let notClosed = { value : false};
1257
1261
 
1258
1262
  try {
1259
- if (source.jsonObj.compose && Extensions.checkNoModifiers(source.jsonObj.compose, 'ValueSetExpander.Expand', 'compose')) {
1263
+ if (source.jsonObj.compose && Extensions.checkNoModifiers(source.jsonObj.compose, 'ValueSetExpander.Expand', 'compose')
1264
+ && this.worker.checkNoLockedDate(source.url, source.jsonObj.compose)) {
1260
1265
  await this.handleCompose(source, filter, exp, notClosed);
1261
1266
  }
1262
1267
 
1263
- if (this.requiredSupplements.length > 0) {
1264
- throw new Issue('error', 'not-found', null, 'VALUESET_SUPPLEMENT_MISSING', this.worker.opContext.i18n.translatePlural(this.requiredSupplements.length, 'VALUESET_SUPPLEMENT_MISSING', this.params.httpLanguages, [this.requiredSupplements.join(', ')]), 'not-found', 400);
1268
+ const unused = new Set([...this.requiredSupplements].filter(s => !this.usedSupplements.has(s)));
1269
+ if (unused.size > 0) {
1270
+ 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);
1265
1271
  }
1266
1272
  } catch (e) {
1267
1273
  if (e instanceof Issue) {
@@ -1270,13 +1276,9 @@ class ValueSetExpander {
1270
1276
  if (this.totalStatus === 'uninitialised') {
1271
1277
  this.totalStatus = 'off';
1272
1278
  } else if (e.toocostly) {
1273
- if (this.params.limitedExpansion) {
1274
- Extensions.addBoolean(exp, 'http://hl7.org/fhir/StructureDefinition/valueset-toocostly', 'value', true);
1275
- if (table != null) {
1276
- div_.p().style('color: Maroon').tx(e.message);
1277
- }
1278
- } else {
1279
- throw e;
1279
+ Extensions.addBoolean(exp, 'http://hl7.org/fhir/StructureDefinition/valueset-toocostly', 'value', true);
1280
+ if (table != null) {
1281
+ div_.p().style('color: Maroon').tx(e.message);
1280
1282
  }
1281
1283
  } else {
1282
1284
  // nothing- swallow it
@@ -1293,7 +1295,12 @@ class ValueSetExpander {
1293
1295
 
1294
1296
  let list;
1295
1297
  if (notClosed.value) {
1296
- Extensions.addBoolean(exp, 'http://hl7.org/fhir/StructureDefinition/valueset-unclosed', true);
1298
+ if (!Extensions.has(exp, 'http://hl7.org/fhir/StructureDefinition/valueset-unclosed')) {
1299
+ Extensions.addBoolean(exp, 'http://hl7.org/fhir/StructureDefinition/valueset-unclosed', true);
1300
+ }
1301
+ if (this.totalStatus === 'set' && this.total > -1) {
1302
+ exp.total = this.total;
1303
+ }
1297
1304
  list = this.fullList;
1298
1305
  for (const c of this.fullList) {
1299
1306
  c.contains = undefined;
@@ -1310,7 +1317,7 @@ class ValueSetExpander {
1310
1317
  exp.total = this.fullList.length;
1311
1318
  }
1312
1319
 
1313
- if (this.canBeHierarchy && (this.count <= 0 || this.count > this.fullList.length)) {
1320
+ if (this.canBeHierarchy && (this.count < 0 || this.count > this.fullList.length)) {
1314
1321
  list = this.rootList;
1315
1322
  } else {
1316
1323
  list = this.fullList;
@@ -1322,7 +1329,7 @@ class ValueSetExpander {
1322
1329
 
1323
1330
  if (this.offset + this.count < 0 && this.fullList.length > this.limit) {
1324
1331
  this.log.log('Operation took too long @ expand (' + this.constructor.name + ')');
1325
- throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [source.vurl, '>' + this.limit]), null, 400).withDiagnostics(this.worker.opContext.diagnostics());
1332
+ throw new Issue("error", "too-costly", null, 'VALUESET_TOO_COSTLY', this.worker.i18n.translate('VALUESET_TOO_COSTLY', this.params.httpLanguages, [source.vurl, '>' + this.limit]), null, 422).withDiagnostics(this.worker.opContext.diagnostics());
1326
1333
  } else {
1327
1334
  let t = 0;
1328
1335
  let o = 0;
@@ -1331,7 +1338,7 @@ class ValueSetExpander {
1331
1338
  const c = list[i];
1332
1339
  if (this.map.has(this.keyC(c))) {
1333
1340
  o++;
1334
- if (o > this.offset && (this.count <= 0 || t < this.count)) {
1341
+ if (o > this.offset && (this.count < 0 || t < this.count)) {
1335
1342
  t++;
1336
1343
  if (!exp.contains) {
1337
1344
  exp.contains = [];
@@ -1462,22 +1469,25 @@ class ValueSetExpander {
1462
1469
  if (value === undefined || value == null) {
1463
1470
  return;
1464
1471
  }
1465
- if (!expansion.property) {
1466
- expansion.property = [];
1467
- }
1468
- let pd = expansion.property.find(t1 => t1.uri == url || t1.code == code);
1469
- if (!pd) {
1470
- pd = {};
1471
- expansion.property.push(pd);
1472
- pd.uri = url;
1473
- pd.code = code;
1474
- } else if (!pd.uri) {
1475
- pd.uri = url
1476
- }
1477
- if (pd.uri != url) {
1478
- throw new Error('URL mismatch on expansion: ' + pd.uri + ' vs ' + url + ' for code ' + code);
1479
- } else {
1480
- code = pd.code;
1472
+ // we only define it if the code system has a definition
1473
+ if (url) {
1474
+ if (!expansion.property) {
1475
+ expansion.property = [];
1476
+ }
1477
+ let pd = expansion.property.find(t1 => t1.uri == url || t1.code == code);
1478
+ if (!pd) {
1479
+ pd = {};
1480
+ expansion.property.push(pd);
1481
+ pd.uri = url;
1482
+ pd.code = code;
1483
+ } else if (!pd.uri) {
1484
+ pd.uri = url
1485
+ }
1486
+ if (pd.uri != url) {
1487
+ throw new Error('URL mismatch on expansion: ' + pd.uri + ' vs ' + url + ' for code ' + code);
1488
+ } else {
1489
+ code = pd.code;
1490
+ }
1481
1491
  }
1482
1492
 
1483
1493
  if (!contains.property) {
@@ -1492,19 +1502,29 @@ class ValueSetExpander {
1492
1502
  pdv[valueName] = value;
1493
1503
  }
1494
1504
 
1495
- addToTotal(t) {
1505
+ addToTotal(t = 1) {
1496
1506
  if (this.total > -1 && this.totalStatus != "off") {
1497
1507
  this.total = this.total + t;
1498
1508
  this.totalStatus = 'set';
1499
1509
  }
1500
1510
  }
1501
1511
 
1512
+ decTotal(t= 1) {
1513
+ if (this.total > -1 && this.totalStatus != "off") {
1514
+ this.total = this.total - t;
1515
+ this.totalStatus = 'set';
1516
+ }
1517
+ }
1518
+
1502
1519
  noTotal() {
1503
1520
  this.total = -1;
1504
1521
  this.totalStatus = 'off';
1505
1522
  }
1506
1523
 
1507
- getPropUrl(cs, pn) {
1524
+ getPropUrl(cs, pn, cp) {
1525
+ if (cp.definition?.uri) {
1526
+ return cp.definition.uri;
1527
+ }
1508
1528
  for (let p of cs.propertyDefinitions()) {
1509
1529
  if (pn == p.code) {
1510
1530
  return p.uri;
@@ -1513,10 +1533,10 @@ class ValueSetExpander {
1513
1533
  return undefined;
1514
1534
  }
1515
1535
 
1516
-
1517
1536
  }
1518
1537
 
1519
1538
  class ExpandWorker extends TerminologyWorker {
1539
+
1520
1540
  /**
1521
1541
  * @param {OperationContext} opContext - Operation context
1522
1542
  * @param {Logger} log - Logger instance
@@ -1546,16 +1566,17 @@ class ExpandWorker extends TerminologyWorker {
1546
1566
  try {
1547
1567
  await this.handleTypeLevelExpand(req, res);
1548
1568
  } catch (error) {
1549
- req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
1550
1569
  this.log.error(error);
1570
+ this.debugLog(error);
1571
+ req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
1551
1572
  const statusCode = error.statusCode || 500;
1552
1573
  if (error instanceof Issue) {
1553
1574
  let oo = new OperationOutcome();
1554
1575
  oo.addIssue(error);
1555
- return res.status(error.statusCode || 500).json(this.fixForVersion(oo.jsonObj));
1576
+ return res.status(error.statusCode || 500).json(oo.jsonObj);
1556
1577
  } else {
1557
1578
  const issueCode = error.issueCode || 'exception';
1558
- return res.status(statusCode).json(this.fixForVersion({
1579
+ return res.status(statusCode).json({
1559
1580
  resourceType: 'OperationOutcome',
1560
1581
  issue: [{
1561
1582
  severity: 'error',
@@ -1565,7 +1586,7 @@ class ExpandWorker extends TerminologyWorker {
1565
1586
  },
1566
1587
  diagnostics: error.message
1567
1588
  }]
1568
- }));
1589
+ });
1569
1590
  }
1570
1591
  }
1571
1592
  }
@@ -1580,11 +1601,12 @@ class ExpandWorker extends TerminologyWorker {
1580
1601
  try {
1581
1602
  await this.handleInstanceLevelExpand(req, res);
1582
1603
  } catch (error) {
1583
- req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
1584
1604
  this.log.error(error);
1605
+ this.debugLog(error);
1606
+ req.logInfo = this.usedSources.join("|")+" - error"+(error.msgId ? " "+error.msgId : "");
1585
1607
  const statusCode = error.statusCode || 500;
1586
1608
  const issueCode = error.issueCode || 'exception';
1587
- return res.status(statusCode).json(this.fixForVersion({
1609
+ return res.status(statusCode).json({
1588
1610
  resourceType: 'OperationOutcome',
1589
1611
  issue: [{
1590
1612
  severity: 'error',
@@ -1594,7 +1616,7 @@ class ExpandWorker extends TerminologyWorker {
1594
1616
  },
1595
1617
  diagnostics: error.message
1596
1618
  }]
1597
- }));
1619
+ });
1598
1620
  }
1599
1621
  }
1600
1622
 
@@ -1667,7 +1689,7 @@ class ExpandWorker extends TerminologyWorker {
1667
1689
  valueSet = await this.findValueSet(url, version);
1668
1690
  this.seeSourceVS(valueSet, url);
1669
1691
  if (!valueSet) {
1670
- return res.status(404).json(this.operationOutcome('error', 'not-found',
1692
+ return res.status(422).json(this.operationOutcome('error', 'not-found',
1671
1693
  version ? `ValueSet not found: ${url} version ${version}` : `ValueSet not found: ${url}`));
1672
1694
  }
1673
1695
  }
@@ -1675,7 +1697,7 @@ class ExpandWorker extends TerminologyWorker {
1675
1697
  // Perform the expansion
1676
1698
  const result = await this.doExpand(valueSet, txp, logExtraOutput);
1677
1699
  req.logInfo = this.usedSources.join("|")+txp.logInfo();
1678
- return res.json(this.fixForVersion(result));
1700
+ return res.json(result);
1679
1701
  }
1680
1702
 
1681
1703
  /**
@@ -1691,7 +1713,7 @@ class ExpandWorker extends TerminologyWorker {
1691
1713
  const valueSet = await this.provider.getValueSetById(this.opContext, id);
1692
1714
 
1693
1715
  if (!valueSet) {
1694
- return res.status(404).json(this.operationOutcome('error', 'not-found',
1716
+ return res.status(422).json(this.operationOutcome('error', 'not-found',
1695
1717
  `ValueSet/${id} not found`));
1696
1718
  }
1697
1719
 
@@ -1725,7 +1747,7 @@ class ExpandWorker extends TerminologyWorker {
1725
1747
  // Perform the expansion
1726
1748
  const result = await this.doExpand(valueSet, txp, logExtraOutput);
1727
1749
  req.logInfo = this.usedSources.join("|")+txp.logInfo();
1728
- return res.json(this.fixForVersion(result));
1750
+ return res.json(result);
1729
1751
  }
1730
1752
 
1731
1753
  // Note: setupAdditionalResources, queryToParameters, formToParameters,
@@ -1791,8 +1813,6 @@ class ExpandWorker extends TerminologyWorker {
1791
1813
  }
1792
1814
 
1793
1815
  const filter = new SearchFilterText(params.filter);
1794
- //txResources = processAdditionalResources(context, manager, nil, params);
1795
- // Create expander and run expansion
1796
1816
  const expander = new ValueSetExpander(this, params);
1797
1817
  expander.logExtraOutput = logExtraOutput;
1798
1818
  return await expander.expand(valueSet, filter);