fhirsmith 0.3.0 → 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 (103) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +4 -2
  3. package/library/cron-utilities.js +136 -0
  4. package/library/folder-setup.js +6 -0
  5. package/library/html-server.js +13 -29
  6. package/library/html.js +3 -8
  7. package/library/languages.js +160 -37
  8. package/library/package-manager.js +48 -1
  9. package/library/utilities.js +100 -19
  10. package/package.json +2 -2
  11. package/packages/package-crawler.js +6 -1
  12. package/packages/packages.js +38 -54
  13. package/publisher/publisher.js +19 -27
  14. package/registry/api.js +11 -10
  15. package/registry/crawler.js +31 -29
  16. package/registry/model.js +5 -26
  17. package/registry/readme.md +1 -11
  18. package/registry/registry.js +32 -41
  19. package/server.js +53 -5
  20. package/shl/shl.js +0 -18
  21. package/static/assets/js/statuspage.js +1 -9
  22. package/stats.js +39 -1
  23. package/token/token.js +14 -9
  24. package/translations/Messages.properties +2 -1
  25. package/tx/README.md +17 -6
  26. package/tx/cs/cs-api.js +19 -1
  27. package/tx/cs/cs-base.js +77 -0
  28. package/tx/cs/cs-country.js +46 -0
  29. package/tx/cs/cs-cpt.js +9 -5
  30. package/tx/cs/cs-cs.js +27 -13
  31. package/tx/cs/cs-db.js +0 -13
  32. package/tx/cs/cs-lang.js +60 -22
  33. package/tx/cs/cs-loinc.js +69 -98
  34. package/tx/cs/cs-mimetypes.js +4 -0
  35. package/tx/cs/cs-ndc.js +6 -0
  36. package/tx/cs/cs-omop.js +16 -15
  37. package/tx/cs/cs-rxnorm.js +23 -1
  38. package/tx/cs/cs-snomed.js +283 -40
  39. package/tx/cs/cs-ucum.js +90 -70
  40. package/tx/importers/import-sct.module.js +371 -35
  41. package/tx/importers/readme.md +117 -7
  42. package/tx/library/bundle.js +5 -0
  43. package/tx/library/capabilitystatement.js +3 -142
  44. package/tx/library/codesystem.js +19 -173
  45. package/tx/library/conceptmap.js +4 -218
  46. package/tx/library/designations.js +14 -1
  47. package/tx/library/extensions.js +7 -0
  48. package/tx/library/namingsystem.js +3 -89
  49. package/tx/library/operation-outcome.js +8 -3
  50. package/tx/library/parameters.js +3 -2
  51. package/tx/library/renderer.js +10 -6
  52. package/tx/library/terminologycapabilities.js +3 -243
  53. package/tx/library/valueset.js +3 -235
  54. package/tx/library.js +100 -13
  55. package/tx/operation-context.js +23 -4
  56. package/tx/params.js +35 -38
  57. package/tx/provider.js +6 -5
  58. package/tx/sct/expressions.js +12 -3
  59. package/tx/tx-html.js +80 -89
  60. package/tx/tx.fhir.org.yml +6 -5
  61. package/tx/tx.js +163 -13
  62. package/tx/vs/vs-database.js +56 -39
  63. package/tx/vs/vs-package.js +21 -2
  64. package/tx/vs/vs-vsac.js +175 -39
  65. package/tx/workers/batch-validate.js +2 -0
  66. package/tx/workers/batch.js +2 -0
  67. package/tx/workers/expand.js +132 -112
  68. package/tx/workers/lookup.js +33 -14
  69. package/tx/workers/metadata.js +2 -2
  70. package/tx/workers/read.js +3 -2
  71. package/tx/workers/related.js +574 -0
  72. package/tx/workers/search.js +46 -9
  73. package/tx/workers/subsumes.js +13 -3
  74. package/tx/workers/translate.js +7 -3
  75. package/tx/workers/validate.js +258 -285
  76. package/tx/workers/worker.js +43 -39
  77. package/tx/xml/bundle-xml.js +237 -0
  78. package/tx/xml/xml-base.js +215 -64
  79. package/tx/xversion/xv-bundle.js +71 -0
  80. package/tx/xversion/xv-capabiliityStatement.js +137 -0
  81. package/tx/xversion/xv-codesystem.js +169 -0
  82. package/tx/xversion/xv-conceptmap.js +224 -0
  83. package/tx/xversion/xv-namingsystem.js +88 -0
  84. package/tx/xversion/xv-operationoutcome.js +27 -0
  85. package/tx/xversion/xv-parameters.js +87 -0
  86. package/tx/xversion/xv-resource.js +45 -0
  87. package/tx/xversion/xv-terminologyCapabilities.js +214 -0
  88. package/tx/xversion/xv-valueset.js +234 -0
  89. package/utilities/dev-proxy-server.js +126 -0
  90. package/utilities/explode-results.js +58 -0
  91. package/utilities/split-by-system.js +198 -0
  92. package/utilities/vsac-cs-fetcher.js +0 -0
  93. package/{windows-install.js → utilities/windows-install.js} +2 -0
  94. package/vcl/vcl.js +0 -18
  95. package/xig/xig.js +108 -99
  96. package/passwords.ini +0 -2
  97. package/registry/registry-data.json +0 -121015
  98. package/shl/private-key.pem +0 -5
  99. package/shl/public-key.pem +0 -18
  100. package/test-cache/vsac/vsac-valuesets.db +0 -0
  101. package/tx/dev.fhir.org.yml +0 -14
  102. package/tx/fixtures/test-cases-setup.json +0 -18
  103. package/tx/fixtures/test-cases.yml +0 -16
package/tx/cs/cs-omop.js CHANGED
@@ -1,9 +1,10 @@
1
1
  const sqlite3 = require('sqlite3').verbose();
2
2
  const assert = require('assert');
3
3
  const { CodeSystem } = require('../library/codesystem');
4
- const { CodeSystemProvider, FilterExecutionContext, CodeSystemFactoryProvider } = require('./cs-api');
4
+ const { FilterExecutionContext, CodeSystemFactoryProvider } = require('./cs-api');
5
5
  const {validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
6
6
  const {ConceptMap} = require("../library/conceptmap");
7
+ const {BaseCSServices} = require("./cs-base");
7
8
 
8
9
  class OMOPConcept {
9
10
  constructor(code, display, domain, conceptClass, standard, vocabulary) {
@@ -126,7 +127,7 @@ function getLang(langConcept) {
126
127
  return 'en'; // default
127
128
  }
128
129
 
129
- class OMOPServices extends CodeSystemProvider {
130
+ class OMOPServices extends BaseCSServices {
130
131
  constructor(opContext, supplements, db, sharedData) {
131
132
  super(opContext, supplements);
132
133
  this.db = db;
@@ -270,16 +271,16 @@ class OMOPServices extends CodeSystemProvider {
270
271
  }
271
272
 
272
273
  // Add basic properties
273
- if (this.#hasProp(props, 'domain-id', true)) {
274
+ if (this._hasProp(props, 'domain-id', true)) {
274
275
  this.#addCodeProperty(params, 'property', 'domain-id', ctxt.domain);
275
276
  }
276
- if (this.#hasProp(props, 'concept-class-id', true)) {
277
+ if (this._hasProp(props, 'concept-class-id', true)) {
277
278
  this.#addCodeProperty(params, 'property', 'concept-class-id', ctxt.conceptClass);
278
279
  }
279
- if (this.#hasProp(props, 'standard-concept', true)) {
280
+ if (this._hasProp(props, 'standard-concept', true)) {
280
281
  this.#addCodeProperty(params, 'property', 'standard-concept', ctxt.standard);
281
282
  }
282
- if (this.#hasProp(props, 'vocabulary-id', true)) {
283
+ if (this._hasProp(props, 'vocabulary-id', true)) {
283
284
  this.#addStringProperty(params, 'property', 'vocabulary-id', ctxt.vocabulary);
284
285
  }
285
286
 
@@ -304,26 +305,26 @@ class OMOPServices extends CodeSystemProvider {
304
305
  if (err) {
305
306
  reject(err);
306
307
  } else if (row) {
307
- if (this.#hasProp(props, 'concept-class-concept-id', true)) {
308
+ if (this._hasProp(props, 'concept-class-concept-id', true)) {
308
309
  this.#addCodeProperty(params, 'property', 'concept-class-concept-id', row.concept_class_id);
309
310
  }
310
- if (this.#hasProp(props, 'domain-concept-id', true)) {
311
+ if (this._hasProp(props, 'domain-concept-id', true)) {
311
312
  this.#addCodeProperty(params, 'property', 'domain-concept-id', row.domain_id);
312
313
  }
313
- if (this.#hasProp(props, 'valid-start-date', true) && row.valid_start_date) {
314
+ if (this._hasProp(props, 'valid-start-date', true) && row.valid_start_date) {
314
315
  this.#addDateProperty(params, 'property', 'valid-start-date', row.valid_start_date);
315
316
  }
316
- if (this.#hasProp(props, 'valid-end-date', true) && row.valid_end_date) {
317
+ if (this._hasProp(props, 'valid-end-date', true) && row.valid_end_date) {
317
318
  this.#addDateProperty(params, 'property', 'valid-end-date', row.valid_end_date);
318
319
  }
319
- if (this.#hasProp(props, 'source-concept-code', true) && row.concept_code && getUri(row.vocabulary_id)) {
320
+ if (this._hasProp(props, 'source-concept-code', true) && row.concept_code && getUri(row.vocabulary_id)) {
320
321
  this.#addCodingProperty(params, 'property', 'source-concept-code',
321
322
  getUriOrError(row.vocabulary_id), row.concept_code);
322
323
  }
323
- if (this.#hasProp(props, 'vocabulary-concept-id', true)) {
324
+ if (this._hasProp(props, 'vocabulary-concept-id', true)) {
324
325
  this.#addCodeProperty(params, 'property', 'vocabulary-concept-id', row.vocabulary_id);
325
326
  }
326
- if (this.#hasProp(props, 'invalid-reason', true) && row.invalid_reason) {
327
+ if (this._hasProp(props, 'invalid-reason', true) && row.invalid_reason) {
327
328
  this.#addStringProperty(params, 'property', 'invalid-reason', row.invalid_reason);
328
329
  }
329
330
  resolve();
@@ -353,7 +354,7 @@ class OMOPServices extends CodeSystemProvider {
353
354
  } else {
354
355
  for (const row of rows) {
355
356
  seenConcepts.add(row.concept_id);
356
- if (this.#hasProp(props, row.relationship_id, true)) {
357
+ if (this._hasProp(props, row.relationship_id, true)) {
357
358
  this.#addCodingProperty(params, 'property', row.relationship_id,
358
359
  this.system(), row.concept_id, row.concept_name);
359
360
  }
@@ -379,7 +380,7 @@ class OMOPServices extends CodeSystemProvider {
379
380
  } else {
380
381
  for (const row of rows) {
381
382
  if (!seenConcepts.has(row.concept_id)) {
382
- if (this.#hasProp(props, row.reverse_relationship_id, true)) {
383
+ if (this._hasProp(props, row.reverse_relationship_id, true)) {
383
384
  this.#addCodingProperty(params, 'property', row.reverse_relationship_id,
384
385
  this.system(), row.concept_id, row.concept_name);
385
386
  }
@@ -140,6 +140,28 @@ class RxNormServices extends CodeSystemProvider {
140
140
  return false; // RxNorm codes are not abstract
141
141
  }
142
142
 
143
+ async getStatus(context) {
144
+
145
+ const ctxt = await this.#ensureContext(context);
146
+
147
+ if (ctxt && ctxt.archived) {
148
+ return 'archived';
149
+ }
150
+
151
+ // Check suppress flag
152
+ return new Promise((resolve, reject) => {
153
+ const sql = `SELECT suppress FROM rxnconso WHERE ${this.getCodeField()} = ? AND SAB = ? AND TTY <> 'SY'`;
154
+
155
+ this.db.get(sql, [ctxt.code, this.getSAB()], (err, row) => {
156
+ if (err) {
157
+ reject(err);
158
+ } else {
159
+ resolve(row ? row.suppress === '1' ? 'suppressed' : null : null);
160
+ }
161
+ });
162
+ });
163
+ }
164
+
143
165
  async isInactive(context) {
144
166
 
145
167
  const ctxt = await this.#ensureContext(context);
@@ -527,7 +549,7 @@ class RxNormServices extends CodeSystemProvider {
527
549
  if (err) {
528
550
  reject(err);
529
551
  } else if (!row) {
530
- resolve(`Code ${code} is not in the specified filter`);
552
+ resolve(null);
531
553
  } else {
532
554
  const concept = new RxNormConcept(row[this.getCodeField()], row.STR);
533
555
  resolve(concept);
@@ -1,4 +1,4 @@
1
- const { CodeSystemProvider, CodeSystemContentMode, Designation, CodeSystemFactoryProvider} = require('./cs-api');
1
+ const { CodeSystemContentMode, CodeSystemFactoryProvider} = require('./cs-api');
2
2
  const {
3
3
  SnomedStrings, SnomedWords, SnomedStems, SnomedReferences,
4
4
  SnomedDescriptions, SnomedDescriptionIndex, SnomedConceptList,
@@ -9,6 +9,8 @@ const {
9
9
  SnomedExpressionServices, SnomedExpression, SnomedConcept,
10
10
  SnomedExpressionParser, NO_REFERENCE, SnomedServicesRenderOption
11
11
  } = require('../sct/expressions');
12
+ const {DesignationUse} = require("../library/designations");
13
+ const {BaseCSServices} = require("./cs-base");
12
14
 
13
15
  // Context kinds matching Pascal enum
14
16
  const SnomedProviderContextKind = {
@@ -75,6 +77,13 @@ class SnomedFilterContext {
75
77
  this.matches = [];
76
78
  this.members = [];
77
79
  this.descendants = [];
80
+ this.expressions = undefined; // special use
81
+ }
82
+ }
83
+
84
+ class SnomedPrep {
85
+ constructor() {
86
+ this.filters = [];
78
87
  }
79
88
  }
80
89
 
@@ -289,6 +298,19 @@ class SnomedServices {
289
298
  }
290
299
  }
291
300
 
301
+ getConceptRelationships(reference) {
302
+ try {
303
+ const concept = this.concepts.getConcept(reference);
304
+ const relRef = concept.outbounds;
305
+
306
+ if (relRef === 0) return [];
307
+
308
+ return this.refs.getReferences(relRef) || [];
309
+ } catch (error) {
310
+ return [];
311
+ }
312
+ }
313
+
292
314
  getConceptRefSet(conceptIndex, byName = false) {
293
315
  for (let i = 0; i < this.refSetIndex.count(); i++) {
294
316
  const refSet = this.refSetIndex.getReferenceSet(i);
@@ -352,7 +374,7 @@ class SnomedServices {
352
374
  const result = new SnomedFilterContext();
353
375
 
354
376
  // Simplified search - in full implementation would use stemming and word indexes
355
- const searchTerms = searchText.toLowerCase().split(/\s+/);
377
+ const searchTerms = searchText.filter.toLowerCase().split(/\s+/);
356
378
  const matches = [];
357
379
 
358
380
  // Search through all concepts
@@ -423,7 +445,7 @@ class SnomedServices {
423
445
  /**
424
446
  * SNOMED CT Code System Provider
425
447
  */
426
- class SnomedProvider extends CodeSystemProvider {
448
+ class SnomedProvider extends BaseCSServices {
427
449
  constructor(opContext, supplements, snomedServices) {
428
450
  super(opContext, supplements);
429
451
  this.sct = snomedServices;
@@ -501,7 +523,7 @@ class SnomedProvider extends CodeSystemProvider {
501
523
  if (disp) return disp;
502
524
 
503
525
  if (ctxt.isComplex()) {
504
- return this.sct.expressionServices.displayExpression(ctxt.expression);
526
+ return this.sct.expressionServices.renderExpression(ctxt.expression, SnomedServicesRenderOption.FillMissing);
505
527
  } else {
506
528
  return this.sct.getDisplayName(ctxt.getReference(), this.sct.defaultLanguage);
507
529
  }
@@ -552,7 +574,7 @@ class SnomedProvider extends CodeSystemProvider {
552
574
  // For complex expressions, just add the display
553
575
  const display = await this.display(context);
554
576
  if (display) {
555
- displays.addDesignation(true, 'active', new Designation('en-US', null, display));
577
+ displays.addDesignation(true, 'active', 'en-US', DesignationUse.PREFERRED, display);
556
578
  }
557
579
  } else {
558
580
  // Get all designations for the concept
@@ -602,8 +624,6 @@ class SnomedProvider extends CodeSystemProvider {
602
624
 
603
625
  // Lookup methods
604
626
  async locate(code) {
605
-
606
-
607
627
  if (!code) return { context: null, message: 'Empty code' };
608
628
 
609
629
  const conceptId = this.sct.stringToIdOrZero(code);
@@ -620,7 +640,7 @@ class SnomedProvider extends CodeSystemProvider {
620
640
  } catch (error) {
621
641
  return {
622
642
  context: null,
623
- message: `Code ${code} is not a valid SNOMED CT Term, and could not be parsed as an expression (${error.message})`
643
+ message: Number.isInteger(code) ? undefined : `Not a valid expression: ${error.message}`
624
644
  };
625
645
  }
626
646
  } else {
@@ -639,6 +659,19 @@ class SnomedProvider extends CodeSystemProvider {
639
659
  }
640
660
  }
641
661
 
662
+ async incompleteValidationMessage(context) {
663
+
664
+ const ctxt = await this.#ensureContext(context);
665
+
666
+ if (!ctxt) return null;
667
+
668
+ if (ctxt.isComplex()) {
669
+ return "The expression is grammatically correct and the concepts are valid, but the expression has not been checked against the SNOMED CT concept model (MRCM)";
670
+ } else {
671
+ return null;
672
+ }
673
+ }
674
+
642
675
  async locateIsA(code, parent, disallowParent = false) {
643
676
 
644
677
 
@@ -699,8 +732,6 @@ class SnomedProvider extends CodeSystemProvider {
699
732
  }
700
733
 
701
734
  async nextContext(iteratorContext) {
702
-
703
-
704
735
  if (iteratorContext.current >= iteratorContext.total) {
705
736
  return null;
706
737
  }
@@ -711,16 +742,74 @@ class SnomedProvider extends CodeSystemProvider {
711
742
  return SnomedExpressionContext.fromReference(key);
712
743
  }
713
744
 
714
- // Filter support
715
- async doesFilter(prop, op, value) {
716
-
745
+ async extendLookup(context, props, params) {
746
+ const ctxt = await this.#ensureContext(context);
747
+ if (ctxt) {
748
+ if (!(ctxt instanceof SnomedExpressionContext) || ctxt.expression?.concepts.length == 1) {
749
+ const parents = this.sct.getConceptParents(ctxt.getReference());
750
+ for (let parentRef of parents) {
751
+ const code = this.sct.getConceptId(parentRef);
752
+ const description = this.sct.getDisplayName(parentRef);
753
+ this._addCodeProperty(params, 'property', 'parent', code, null, description);
754
+ }
717
755
 
756
+ const children = this.sct.getConceptChildren(ctxt.getReference());
757
+ for (let childRef of children) {
758
+ const code = this.sct.getConceptId(childRef);
759
+ const description = this.sct.getDisplayName(childRef);
760
+ this._addCodeProperty(params, 'property', 'child', code, null, description);
761
+ }
762
+
763
+ const relationships = this.sct.getConceptRelationships(ctxt.getReference());
764
+ let set = new Set();
765
+ for (let relationshipRef of relationships) {
766
+ const relationship = this.sct.relationships.getRelationship(relationshipRef);
767
+ const relType = this.sct.getConceptId(relationship.relType);
768
+ if (relType != '116680003') {
769
+ const relTypeD = this.sct.getDisplayName(relationship.relType);
770
+ const code = this.sct.getConceptId(relationship.target);
771
+ const description = this.sct.getDisplayName(relationship.target);
772
+ if (!set.has(relType + ":" + code)) {
773
+ set.add(relType + ":" + code);
774
+ let p = this._addCodeProperty(params, 'property', relType, code, null, description);
775
+ p.part.push({name: 'code-display', valueString: relTypeD});
776
+ }
777
+ }
778
+ }
779
+ }
780
+ if (ctxt instanceof SnomedExpressionContext) {
781
+ // ignore concepts for now, but list refinements and refinement groups
782
+ for (const refinement of ctxt.expression.refinements) {
783
+ const codeA = refinement.name.code;
784
+ const codeB = refinement.value.describe();
785
+ const description = await this.display(codeB);
786
+ let p = this._addCodeProperty(params, 'property', codeA, codeB, null, description);
787
+ p.part.push({name: 'code-display', valueString: await this.display(codeA)});
788
+ }
789
+ for (const refinementGroup of ctxt.expression.refinementGroups) {
790
+ for (const refinement of refinementGroup.refinements) {
791
+ const codeA = refinement.name.code;
792
+ const codeB = refinement.value.describe();
793
+ const description = await this.display(codeB);
794
+ let p = this._addCodeProperty(params, 'property', codeA, codeB, null, description);
795
+ p.part.push({name: 'code-display', valueString: await this.display(codeA)});
796
+ }
797
+ }
798
+ }
799
+ }
800
+ }
801
+
802
+ // Filter support
803
+ async doesFilter(prop, op, value) {
718
804
  if (prop === 'concept') {
719
805
  const id = this.sct.stringToIdOrZero(value);
720
806
  if (id !== 0n && ['=', 'is-a', 'descendent-of', 'in'].includes(op)) {
721
807
  return this.sct.conceptExists(value);
722
808
  }
723
809
  }
810
+ if (prop == 'expressions' && op == '=' && ['true', 'false'].includes(value)) {
811
+ return true;
812
+ }
724
813
 
725
814
  return false;
726
815
  }
@@ -728,11 +817,10 @@ class SnomedProvider extends CodeSystemProvider {
728
817
  // eslint-disable-next-line no-unused-vars
729
818
  async getPrepContext(iterate) {
730
819
 
731
- return {}; // Simple filter context
820
+ return new SnomedPrep(); // Simple filter context
732
821
  }
733
822
 
734
823
  async filter(filterContext, prop, op, value) {
735
-
736
824
 
737
825
  if (prop === 'concept') {
738
826
  const id = this.sct.stringToIdOrZero(value);
@@ -741,30 +829,52 @@ class SnomedProvider extends CodeSystemProvider {
741
829
  }
742
830
 
743
831
  switch (op) {
744
- case '=':
745
- return this.sct.filterEquals(id);
746
- case 'is-a':
747
- return this.sct.filterIsA(id, true);
748
- case 'descendent-of':
749
- return this.sct.filterIsA(id, false);
750
- case 'in':
751
- return this.sct.filterIn(id);
832
+ case '=': {
833
+ filterContext.filters.push(this.sct.filterEquals(id));
834
+ return null;
835
+ }
836
+ case 'is-a': {
837
+ filterContext.filters.push(this.sct.filterIsA(id, true));
838
+ return null;
839
+ }
840
+ case 'descendent-of': {
841
+ filterContext.filters.push(this.sct.filterIsA(id, false));
842
+ return null;
843
+ }
844
+ case 'in': {
845
+ filterContext.filters.push(this.sct.filterIn(id));
846
+ return null;
847
+ }
752
848
  default:
753
849
  throw new Error(`Unsupported filter operation: ${op}`);
754
850
  }
755
851
  }
756
852
 
853
+ if (prop == 'expressions' && op == '=') {
854
+ const filter = new SnomedFilterContext();
855
+ filter.expressions = value == 'true';
856
+ filterContext.filters.push(filter);
857
+ return null;
858
+ }
859
+
757
860
  throw new Error(`Unsupported filter property: ${prop}`);
758
861
  }
759
862
 
760
863
  async executeFilters(filterContext) {
761
-
762
- return [filterContext];
864
+ return filterContext.filters;
763
865
  }
764
866
 
765
- async filterSize(filterContext, set) {
766
-
867
+ // eslint-disable-next-line no-unused-vars
868
+ async filtersNotClosed(filterContext) {
869
+ for (let filter of filterContext.filters) {
870
+ if (filter.expressions != undefined && !filter.expressions) {
871
+ return false;
872
+ }
873
+ }
874
+ return true;
875
+ }
767
876
 
877
+ async filterSize(filterContext, set) {
768
878
  if (set.matches && set.matches.length > 0) {
769
879
  return set.matches.length;
770
880
  } else if (set.members && set.members.length > 0) {
@@ -777,7 +887,6 @@ class SnomedProvider extends CodeSystemProvider {
777
887
  }
778
888
 
779
889
  async filterMore(filterContext, set) {
780
-
781
890
  set.cursor = set.cursor || 0;
782
891
 
783
892
  const size = await this.filterSize(filterContext, set);
@@ -785,8 +894,6 @@ class SnomedProvider extends CodeSystemProvider {
785
894
  }
786
895
 
787
896
  async filterConcept(filterContext, set) {
788
-
789
-
790
897
  const size = await this.filterSize(filterContext, set);
791
898
  if (set.cursor >= size) {
792
899
  return null;
@@ -808,7 +915,6 @@ class SnomedProvider extends CodeSystemProvider {
808
915
  }
809
916
 
810
917
  async filterLocate(filterContext, set, code) {
811
-
812
918
 
813
919
  const conceptResult = await this.locate(code);
814
920
  if (!conceptResult.context) {
@@ -816,9 +922,7 @@ class SnomedProvider extends CodeSystemProvider {
816
922
  }
817
923
 
818
924
  const ctxt = conceptResult.context;
819
- if (ctxt.isComplex()) {
820
- return 'Complex expressions not supported in filters';
821
- }
925
+
822
926
 
823
927
  const reference = ctxt.getReference();
824
928
  let found = false;
@@ -834,19 +938,18 @@ class SnomedProvider extends CodeSystemProvider {
834
938
  if (found) {
835
939
  return ctxt;
836
940
  } else {
837
- return `Code ${code} is not in the specified filter`;
941
+ return null;
838
942
  }
839
943
  }
840
944
 
841
945
  async filterCheck(filterContext, set, concept) {
842
-
843
-
844
946
  if (!(concept instanceof SnomedExpressionContext)) {
845
947
  return false;
846
948
  }
847
949
 
848
- if (concept.isComplex()) {
849
- return false;
950
+ if (set.expressions != undefined) {
951
+ let b = set.expressions || !concept.isComplex();
952
+ return b;
850
953
  }
851
954
 
852
955
  const reference = concept.getReference();
@@ -972,8 +1075,148 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider {
972
1075
  return null;
973
1076
  }
974
1077
  }
975
- // eslint-disable-next-line no-unused-vars
1078
+
1079
+
1080
+ /**
1081
+ * Build an implicit SNOMED CT ValueSet from a URL.
1082
+ *
1083
+ * Handles the following URL patterns:
1084
+ * http://snomed.info/sct?fhir_vs – all of SNOMED CT
1085
+ * http://snomed.info/sct?fhir_vs=refset – list of reference sets
1086
+ * http://snomed.info/sct?fhir_vs=refset/<id> – members of a reference set
1087
+ * http://snomed.info/sct?fhir_vs=isa/<id> – concept and descendants
1088
+ *
1089
+ * The URL may optionally include edition and/or version segments:
1090
+ * http://snomed.info/sct/<edition>?fhir_vs...
1091
+ * http://snomed.info/sct/<edition>/version/<ver>?fhir_vs...
1092
+ *
1093
+ * @param {string} url - The ValueSet URL to resolve
1094
+ * @returns {object|null} A FHIR ValueSet JSON object, or null if the URL is not recognised
1095
+ */
976
1096
  async buildKnownValueSet(url, version) {
1097
+ if (!url.startsWith("http://snomed.info/sct")) {
1098
+ return null;
1099
+ }
1100
+ if (version != null && !this.version().startsWith(version)) {
1101
+ return null;
1102
+ }
1103
+
1104
+ const URI_SNOMED = 'http://snomed.info/sct';
1105
+
1106
+ // Extract the query portion (?fhir_vs...) if this is a recognised SNOMED implicit VS URL
1107
+ let id = null;
1108
+ const qIdx = url.indexOf('?');
1109
+ if (qIdx === -1) {
1110
+ return null;
1111
+ }
1112
+
1113
+ if (url.startsWith('http://snomed.info/sct?fhir_vs') ||
1114
+ url.startsWith(`http://snomed.info/sct/${this.edition}?fhir_vs`) ||
1115
+ url.startsWith(`http://snomed.info/sct/${this.edition}/version/${this.version}?fhir_vs`)) {
1116
+ id = url.substring(qIdx);
1117
+ } else {
1118
+ return null;
1119
+ }
1120
+
1121
+ const now = new Date().toISOString();
1122
+
1123
+ if (id === '?fhir_vs=refset') {
1124
+ // List of all reference sets
1125
+ const concepts = [];
1126
+ for (let i = 0; i < this.refSetIndex.count; i++) {
1127
+ const code = this.refSetIndex.getReferenceSetCode(i);
1128
+ concepts.push({code: this.getConceptId(code)});
1129
+ }
1130
+ return {
1131
+ resourceType: 'ValueSet',
1132
+ url,
1133
+ status: 'active',
1134
+ version: this.versionDate,
1135
+ name: 'SNOMEDCTReferenceSetList',
1136
+ title: 'SNOMED CT Reference Set List',
1137
+ description: 'Reference Sets defined in this SNOMED-CT version',
1138
+ date: now,
1139
+ compose: {
1140
+ include: [{
1141
+ system: URI_SNOMED,
1142
+ concept: concepts,
1143
+ }],
1144
+ },
1145
+ };
1146
+ }
1147
+
1148
+ if (id === '?fhir_vs') {
1149
+ // All of SNOMED CT
1150
+ return {
1151
+ resourceType: 'ValueSet',
1152
+ url,
1153
+ status: 'active',
1154
+ version: this.versionDate,
1155
+ name: 'ALLSNOMEDCT',
1156
+ title: 'SNOMED CT Reference Set (All of SNOMED CT)',
1157
+ description: 'SNOMED CT Reference Set (All of SNOMED CT)',
1158
+ date: now,
1159
+ compose: {
1160
+ include: [{
1161
+ system: URI_SNOMED,
1162
+ }],
1163
+ },
1164
+ };
1165
+ }
1166
+
1167
+ if (id.startsWith('?fhir_vs=refset/')) {
1168
+ const refsetId = id.substring(16);
1169
+ if (!this.referenceSetExists(refsetId)) {
1170
+ return null;
1171
+ }
1172
+ return {
1173
+ resourceType: 'ValueSet',
1174
+ url,
1175
+ status: 'active',
1176
+ version: this.versionDate,
1177
+ name: 'SNOMEDCTRefSet' + refsetId,
1178
+ title: 'SNOMED CT Reference Set ' + refsetId,
1179
+ description: this.getDisplayName(refsetId, ''),
1180
+ date: now,
1181
+ compose: {
1182
+ include: [{
1183
+ system: URI_SNOMED,
1184
+ filter: [{
1185
+ property: 'concept',
1186
+ op: 'in',
1187
+ value: refsetId,
1188
+ }],
1189
+ }],
1190
+ },
1191
+ };
1192
+ }
1193
+
1194
+ if (id.startsWith('?fhir_vs=isa/')) {
1195
+ const conceptId = id.substring(13);
1196
+ if (!this.conceptExists(conceptId)) {
1197
+ return null;
1198
+ }
1199
+ return {
1200
+ resourceType: 'ValueSet',
1201
+ url,
1202
+ status: 'active',
1203
+ version: this.versionDate,
1204
+ name: 'SNOMEDCTConcept' + conceptId,
1205
+ title: 'SNOMED CT Concept ' + conceptId + ' and descendants',
1206
+ description: 'All Snomed CT concepts for ' + this.getDisplayName(conceptId, ''),
1207
+ date: now,
1208
+ compose: {
1209
+ include: [{
1210
+ system: URI_SNOMED,
1211
+ filter: [{
1212
+ property: 'concept',
1213
+ op: 'is-a',
1214
+ value: conceptId,
1215
+ }],
1216
+ }],
1217
+ },
1218
+ };
1219
+ }
977
1220
  return null;
978
1221
  }
979
1222