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.
- package/CHANGELOG.md +24 -0
- package/README.md +4 -2
- package/library/cron-utilities.js +136 -0
- package/library/folder-setup.js +6 -0
- package/library/html-server.js +13 -29
- package/library/html.js +3 -8
- package/library/languages.js +160 -37
- package/library/package-manager.js +48 -1
- package/library/utilities.js +100 -19
- package/package.json +2 -2
- package/packages/package-crawler.js +6 -1
- package/packages/packages.js +38 -54
- package/publisher/publisher.js +19 -27
- package/registry/api.js +11 -10
- package/registry/crawler.js +31 -29
- package/registry/model.js +5 -26
- package/registry/readme.md +1 -11
- package/registry/registry.js +32 -41
- package/server.js +53 -5
- package/shl/shl.js +0 -18
- package/static/assets/js/statuspage.js +1 -9
- package/stats.js +39 -1
- package/token/token.js +14 -9
- package/translations/Messages.properties +2 -1
- package/tx/README.md +17 -6
- package/tx/cs/cs-api.js +19 -1
- package/tx/cs/cs-base.js +77 -0
- package/tx/cs/cs-country.js +46 -0
- package/tx/cs/cs-cpt.js +9 -5
- package/tx/cs/cs-cs.js +27 -13
- package/tx/cs/cs-db.js +0 -13
- package/tx/cs/cs-lang.js +60 -22
- package/tx/cs/cs-loinc.js +69 -98
- package/tx/cs/cs-mimetypes.js +4 -0
- package/tx/cs/cs-ndc.js +6 -0
- package/tx/cs/cs-omop.js +16 -15
- package/tx/cs/cs-rxnorm.js +23 -1
- package/tx/cs/cs-snomed.js +283 -40
- package/tx/cs/cs-ucum.js +90 -70
- package/tx/importers/import-sct.module.js +371 -35
- package/tx/importers/readme.md +117 -7
- package/tx/library/bundle.js +5 -0
- package/tx/library/capabilitystatement.js +3 -142
- package/tx/library/codesystem.js +19 -173
- package/tx/library/conceptmap.js +4 -218
- package/tx/library/designations.js +14 -1
- package/tx/library/extensions.js +7 -0
- package/tx/library/namingsystem.js +3 -89
- package/tx/library/operation-outcome.js +8 -3
- package/tx/library/parameters.js +3 -2
- package/tx/library/renderer.js +10 -6
- package/tx/library/terminologycapabilities.js +3 -243
- package/tx/library/valueset.js +3 -235
- package/tx/library.js +100 -13
- package/tx/operation-context.js +23 -4
- package/tx/params.js +35 -38
- package/tx/provider.js +6 -5
- package/tx/sct/expressions.js +12 -3
- package/tx/tx-html.js +80 -89
- package/tx/tx.fhir.org.yml +6 -5
- package/tx/tx.js +163 -13
- package/tx/vs/vs-database.js +56 -39
- package/tx/vs/vs-package.js +21 -2
- package/tx/vs/vs-vsac.js +175 -39
- package/tx/workers/batch-validate.js +2 -0
- package/tx/workers/batch.js +2 -0
- package/tx/workers/expand.js +132 -112
- package/tx/workers/lookup.js +33 -14
- package/tx/workers/metadata.js +2 -2
- package/tx/workers/read.js +3 -2
- package/tx/workers/related.js +574 -0
- package/tx/workers/search.js +46 -9
- package/tx/workers/subsumes.js +13 -3
- package/tx/workers/translate.js +7 -3
- package/tx/workers/validate.js +258 -285
- package/tx/workers/worker.js +43 -39
- package/tx/xml/bundle-xml.js +237 -0
- package/tx/xml/xml-base.js +215 -64
- package/tx/xversion/xv-bundle.js +71 -0
- package/tx/xversion/xv-capabiliityStatement.js +137 -0
- package/tx/xversion/xv-codesystem.js +169 -0
- package/tx/xversion/xv-conceptmap.js +224 -0
- package/tx/xversion/xv-namingsystem.js +88 -0
- package/tx/xversion/xv-operationoutcome.js +27 -0
- package/tx/xversion/xv-parameters.js +87 -0
- package/tx/xversion/xv-resource.js +45 -0
- package/tx/xversion/xv-terminologyCapabilities.js +214 -0
- package/tx/xversion/xv-valueset.js +234 -0
- package/utilities/dev-proxy-server.js +126 -0
- package/utilities/explode-results.js +58 -0
- package/utilities/split-by-system.js +198 -0
- package/utilities/vsac-cs-fetcher.js +0 -0
- package/{windows-install.js → utilities/windows-install.js} +2 -0
- package/vcl/vcl.js +0 -18
- package/xig/xig.js +108 -99
- package/passwords.ini +0 -2
- package/registry/registry-data.json +0 -121015
- package/shl/private-key.pem +0 -5
- package/shl/public-key.pem +0 -18
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/tx/dev.fhir.org.yml +0 -14
- package/tx/fixtures/test-cases-setup.json +0 -18
- 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 {
|
|
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
|
|
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
|
|
274
|
+
if (this._hasProp(props, 'domain-id', true)) {
|
|
274
275
|
this.#addCodeProperty(params, 'property', 'domain-id', ctxt.domain);
|
|
275
276
|
}
|
|
276
|
-
if (this
|
|
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
|
|
280
|
+
if (this._hasProp(props, 'standard-concept', true)) {
|
|
280
281
|
this.#addCodeProperty(params, 'property', 'standard-concept', ctxt.standard);
|
|
281
282
|
}
|
|
282
|
-
if (this
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/tx/cs/cs-rxnorm.js
CHANGED
|
@@ -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(
|
|
552
|
+
resolve(null);
|
|
531
553
|
} else {
|
|
532
554
|
const concept = new RxNormConcept(row[this.getCodeField()], row.STR);
|
|
533
555
|
resolve(concept);
|
package/tx/cs/cs-snomed.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
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
|
|
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.
|
|
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',
|
|
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:
|
|
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
|
-
|
|
715
|
-
|
|
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
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
case '
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
849
|
-
|
|
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
|
-
|
|
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
|
|