fhirsmith 0.3.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 +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
const { CodeSystemProvider, CodeSystemContentMode, Designation, CodeSystemFactoryProvider} = require('./cs-api');
|
|
2
|
+
const {
|
|
3
|
+
SnomedStrings, SnomedWords, SnomedStems, SnomedReferences,
|
|
4
|
+
SnomedDescriptions, SnomedDescriptionIndex, SnomedConceptList,
|
|
5
|
+
SnomedRelationshipList, SnomedReferenceSetMembers, SnomedReferenceSetIndex,
|
|
6
|
+
SnomedFileReader
|
|
7
|
+
} = require('../sct/structures');
|
|
8
|
+
const {
|
|
9
|
+
SnomedExpressionServices, SnomedExpression, SnomedConcept,
|
|
10
|
+
SnomedExpressionParser, NO_REFERENCE, SnomedServicesRenderOption
|
|
11
|
+
} = require('../sct/expressions');
|
|
12
|
+
|
|
13
|
+
// Context kinds matching Pascal enum
|
|
14
|
+
const SnomedProviderContextKind = {
|
|
15
|
+
CODE: 0,
|
|
16
|
+
EXPRESSION: 1
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SNOMED Expression Context - represents either a simple concept or complex expression
|
|
21
|
+
*/
|
|
22
|
+
class SnomedExpressionContext {
|
|
23
|
+
constructor(source = '', expression = null) {
|
|
24
|
+
this.source = source;
|
|
25
|
+
this.expression = expression;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static fromReference(reference) {
|
|
29
|
+
const expression = new SnomedExpression();
|
|
30
|
+
expression.concepts.push(new SnomedConcept(reference));
|
|
31
|
+
return new SnomedExpressionContext('', expression);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static fromCode(code, reference) {
|
|
35
|
+
const expression = new SnomedExpression();
|
|
36
|
+
const concept = new SnomedConcept(reference);
|
|
37
|
+
concept.code = code;
|
|
38
|
+
expression.concepts.push(concept);
|
|
39
|
+
return new SnomedExpressionContext(code, expression);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static fromExpression(source, expression) {
|
|
43
|
+
return new SnomedExpressionContext(source, expression);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
isComplex() {
|
|
47
|
+
return this.expression && this.expression.isComplex();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isSimple() {
|
|
51
|
+
return this.expression && this.expression.isSimple();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getReference() {
|
|
55
|
+
return this.expression && this.expression.concepts.length > 0
|
|
56
|
+
? this.expression.concepts[0].reference
|
|
57
|
+
: NO_REFERENCE;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getCode() {
|
|
61
|
+
if (this.source) return this.source;
|
|
62
|
+
return this.expression && this.expression.concepts.length > 0
|
|
63
|
+
? this.expression.concepts[0].code
|
|
64
|
+
: '';
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Filter context for SNOMED filtering operations
|
|
70
|
+
*/
|
|
71
|
+
class SnomedFilterContext {
|
|
72
|
+
constructor() {
|
|
73
|
+
this.ndx = 0;
|
|
74
|
+
this.cursor = 0;
|
|
75
|
+
this.matches = [];
|
|
76
|
+
this.members = [];
|
|
77
|
+
this.descendants = [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Core SNOMED services providing access to structures and expression processing
|
|
83
|
+
*/
|
|
84
|
+
class SnomedServices {
|
|
85
|
+
constructor(sharedData) {
|
|
86
|
+
// Core data structures
|
|
87
|
+
this.strings = new SnomedStrings(sharedData.strings);
|
|
88
|
+
this.words = new SnomedWords(sharedData.words);
|
|
89
|
+
this.stems = new SnomedStems(sharedData.stems);
|
|
90
|
+
this.refs = new SnomedReferences(sharedData.refs);
|
|
91
|
+
this.descriptions = new SnomedDescriptions(sharedData.desc);
|
|
92
|
+
this.descriptionIndex = new SnomedDescriptionIndex(sharedData.descRef);
|
|
93
|
+
this.concepts = new SnomedConceptList(sharedData.concept);
|
|
94
|
+
this.relationships = new SnomedRelationshipList(sharedData.rel);
|
|
95
|
+
this.refSetIndex = new SnomedReferenceSetIndex(sharedData.refSetIndex, sharedData.hasLangs);
|
|
96
|
+
this.refSetMembers = new SnomedReferenceSetMembers(sharedData.refSetMembers);
|
|
97
|
+
|
|
98
|
+
// Metadata
|
|
99
|
+
this.versionUri = sharedData.versionUri;
|
|
100
|
+
this.versionDate = sharedData.versionDate;
|
|
101
|
+
this.edition = sharedData.edition;
|
|
102
|
+
this.version = sharedData.version;
|
|
103
|
+
this.totalCount = this.concepts.count();
|
|
104
|
+
|
|
105
|
+
// Indexes and roots
|
|
106
|
+
this.isAIndex = sharedData.isAIndex;
|
|
107
|
+
this.activeRoots = sharedData.activeRoots;
|
|
108
|
+
this.inactiveRoots = sharedData.inactiveRoots;
|
|
109
|
+
this.defaultLanguage = sharedData.defaultLanguage;
|
|
110
|
+
this.isTesting = sharedData.isTesting;
|
|
111
|
+
|
|
112
|
+
// Expression services
|
|
113
|
+
this.expressionServices = new SnomedExpressionServices({
|
|
114
|
+
strings: this.strings,
|
|
115
|
+
words: this.words,
|
|
116
|
+
stems: this.stems,
|
|
117
|
+
refs: this.refs,
|
|
118
|
+
descriptions: this.descriptions,
|
|
119
|
+
descriptionIndex: this.descriptionIndex,
|
|
120
|
+
concepts: this.concepts,
|
|
121
|
+
relationships: this.relationships,
|
|
122
|
+
refSetMembers: this.refSetMembers,
|
|
123
|
+
refSetIndex: this.refSetIndex
|
|
124
|
+
}, this.isAIndex);
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
close() {
|
|
129
|
+
// Cleanup if needed
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getSystemUri() {
|
|
133
|
+
return 'http://snomed.info/sct';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getVersion() {
|
|
137
|
+
return this.versionUri;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getDescription() {
|
|
141
|
+
return `SNOMED CT ${getEditionName(this.edition)}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
name() {
|
|
145
|
+
return `SCT ${getEditionCode(this.edition)}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
stringToIdOrZero(str) {
|
|
149
|
+
try {
|
|
150
|
+
if (!str) return 0n;
|
|
151
|
+
return BigInt(str);
|
|
152
|
+
} catch {
|
|
153
|
+
return 0n;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
stringToId(str) {
|
|
158
|
+
return BigInt(str);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getConceptId(reference) {
|
|
162
|
+
try {
|
|
163
|
+
const concept = this.concepts.getConcept(reference);
|
|
164
|
+
return concept.identity.toString();
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return reference.toString();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
conceptExists(conceptId) {
|
|
171
|
+
const id = this.stringToIdOrZero(conceptId);
|
|
172
|
+
if (id === 0n) return false;
|
|
173
|
+
|
|
174
|
+
const result = this.concepts.findConcept(id);
|
|
175
|
+
return result.found;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
isActive(reference) {
|
|
179
|
+
try {
|
|
180
|
+
const concept = this.concepts.getConcept(reference);
|
|
181
|
+
// Check status flags - active concepts typically have status 0
|
|
182
|
+
return (concept.flags & 0x0F) === 0;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
isPrimitive(reference) {
|
|
189
|
+
try {
|
|
190
|
+
const concept = this.concepts.getConcept(reference);
|
|
191
|
+
// Check primitive flag
|
|
192
|
+
return (concept.flags & 0x10) !== 0;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return true; // Assume primitive if can't read
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
subsumes(parentRef, childRef) {
|
|
199
|
+
if (parentRef === childRef) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
// Get the closure (all descendants) for parent concept
|
|
205
|
+
const closureRef = this.concepts.getAllDesc(parentRef);
|
|
206
|
+
|
|
207
|
+
if (closureRef === 0 || closureRef === 0xFFFFFFFF) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const descendants = this.refs.getReferences(closureRef);
|
|
212
|
+
return descendants && descendants.includes(childRef);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
getDisplayName(reference = 0) {
|
|
219
|
+
try {
|
|
220
|
+
const concept = this.concepts.getConcept(reference);
|
|
221
|
+
const descriptionsRef = concept.descriptions;
|
|
222
|
+
|
|
223
|
+
if (descriptionsRef === 0) {
|
|
224
|
+
return '';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const descriptionIndices = this.refs.getReferences(descriptionsRef);
|
|
228
|
+
|
|
229
|
+
// Look for preferred term, then any active description
|
|
230
|
+
for (const descIndex of descriptionIndices) {
|
|
231
|
+
const description = this.descriptions.getDescription(descIndex);
|
|
232
|
+
if (description.active) {
|
|
233
|
+
const term = this.strings.getEntry(description.iDesc);
|
|
234
|
+
return term.trim();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return '';
|
|
239
|
+
} catch (error) {
|
|
240
|
+
return '';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
getConceptDescendants(reference) {
|
|
245
|
+
try {
|
|
246
|
+
const allDescRef = this.concepts.getAllDesc(reference);
|
|
247
|
+
if (allDescRef === 0 || allDescRef === 0xFFFFFFFF) {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
return this.refs.getReferences(allDescRef) || [];
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
getConceptChildren(reference) {
|
|
257
|
+
try {
|
|
258
|
+
const concept = this.concepts.getConcept(reference);
|
|
259
|
+
const inboundsRef = concept.inbounds;
|
|
260
|
+
|
|
261
|
+
if (inboundsRef === 0) return [];
|
|
262
|
+
|
|
263
|
+
const inbounds = this.refs.getReferences(inboundsRef);
|
|
264
|
+
const children = [];
|
|
265
|
+
|
|
266
|
+
for (const relIndex of inbounds) {
|
|
267
|
+
const rel = this.relationships.getRelationship(relIndex);
|
|
268
|
+
if (rel.active && rel.relType === this.isAIndex && rel.group === 0) {
|
|
269
|
+
children.push(rel.source);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return children;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
getConceptParents(reference) {
|
|
280
|
+
try {
|
|
281
|
+
const concept = this.concepts.getConcept(reference);
|
|
282
|
+
const parentsRef = concept.parents;
|
|
283
|
+
|
|
284
|
+
if (parentsRef === 0) return [];
|
|
285
|
+
|
|
286
|
+
return this.refs.getReferences(parentsRef) || [];
|
|
287
|
+
} catch (error) {
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
getConceptRefSet(conceptIndex, byName = false) {
|
|
293
|
+
for (let i = 0; i < this.refSetIndex.count(); i++) {
|
|
294
|
+
const refSet = this.refSetIndex.getReferenceSet(i);
|
|
295
|
+
if (refSet.definition === conceptIndex) {
|
|
296
|
+
return byName ? refSet.membersByName : refSet.membersByRef;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Filter support methods
|
|
303
|
+
filterEquals(id) {
|
|
304
|
+
const result = new SnomedFilterContext();
|
|
305
|
+
const conceptResult = this.concepts.findConcept(id);
|
|
306
|
+
|
|
307
|
+
if (!conceptResult.found) {
|
|
308
|
+
throw new Error(`The SNOMED CT Concept ${id} is not known`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
result.descendants = [conceptResult.index];
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
filterIsA(id, includeBase = true) {
|
|
316
|
+
const result = new SnomedFilterContext();
|
|
317
|
+
const conceptResult = this.concepts.findConcept(id);
|
|
318
|
+
|
|
319
|
+
if (!conceptResult.found) {
|
|
320
|
+
throw new Error(`The SNOMED CT Concept ${id} is not known`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const descendants = this.getConceptDescendants(conceptResult.index);
|
|
324
|
+
|
|
325
|
+
if (includeBase) {
|
|
326
|
+
result.descendants = [conceptResult.index, ...descendants];
|
|
327
|
+
} else {
|
|
328
|
+
result.descendants = descendants;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
filterIn(id) {
|
|
335
|
+
const result = new SnomedFilterContext();
|
|
336
|
+
const conceptResult = this.concepts.findConcept(id);
|
|
337
|
+
|
|
338
|
+
if (!conceptResult.found) {
|
|
339
|
+
throw new Error(`The SNOMED CT Concept ${id} is not known`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const refSetIndex = this.getConceptRefSet(conceptResult.index, false);
|
|
343
|
+
if (refSetIndex === 0) {
|
|
344
|
+
throw new Error(`The SNOMED CT Concept ${id} is not a reference set`);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
result.members = this.refSetMembers.getMembers(refSetIndex) || [];
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
searchFilter(searchText, includeInactive = false, exactMatch = false) {
|
|
352
|
+
const result = new SnomedFilterContext();
|
|
353
|
+
|
|
354
|
+
// Simplified search - in full implementation would use stemming and word indexes
|
|
355
|
+
const searchTerms = searchText.toLowerCase().split(/\s+/);
|
|
356
|
+
const matches = [];
|
|
357
|
+
|
|
358
|
+
// Search through all concepts
|
|
359
|
+
for (let i = 0; i < this.concepts.count(); i++) {
|
|
360
|
+
const conceptIndex = i * this.concepts.constructor.CONCEPT_SIZE;
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const concept = this.concepts.getConcept(conceptIndex);
|
|
364
|
+
if (!includeInactive && !this.isActive(conceptIndex)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const descriptionsRef = concept.descriptions;
|
|
369
|
+
if (descriptionsRef === 0) continue;
|
|
370
|
+
|
|
371
|
+
const descriptionIndices = this.refs.getReferences(descriptionsRef);
|
|
372
|
+
let matchFound = false;
|
|
373
|
+
let priority = 0;
|
|
374
|
+
|
|
375
|
+
for (const descIndex of descriptionIndices) {
|
|
376
|
+
const description = this.descriptions.getDescription(descIndex);
|
|
377
|
+
if (description.active) {
|
|
378
|
+
const term = this.strings.getEntry(description.iDesc).toLowerCase();
|
|
379
|
+
|
|
380
|
+
if (exactMatch) {
|
|
381
|
+
// All search terms must be present
|
|
382
|
+
matchFound = searchTerms.every(searchTerm => term.includes(searchTerm));
|
|
383
|
+
} else {
|
|
384
|
+
// Any search term can match
|
|
385
|
+
matchFound = searchTerms.some(searchTerm => term.includes(searchTerm));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (matchFound) {
|
|
389
|
+
// Calculate priority based on match quality
|
|
390
|
+
if (term === searchText.toLowerCase()) {
|
|
391
|
+
priority = 100; // Exact match
|
|
392
|
+
} else if (term.startsWith(searchText.toLowerCase())) {
|
|
393
|
+
priority = 50; // Prefix match
|
|
394
|
+
} else {
|
|
395
|
+
priority = 10; // Contains match
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (matchFound) {
|
|
403
|
+
matches.push({
|
|
404
|
+
index: conceptIndex,
|
|
405
|
+
term: concept.identity,
|
|
406
|
+
priority: priority
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
// Skip problematic concepts
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Sort by priority (descending)
|
|
416
|
+
matches.sort((a, b) => b.priority - a.priority);
|
|
417
|
+
|
|
418
|
+
result.matches = matches;
|
|
419
|
+
return result;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* SNOMED CT Code System Provider
|
|
425
|
+
*/
|
|
426
|
+
class SnomedProvider extends CodeSystemProvider {
|
|
427
|
+
constructor(opContext, supplements, snomedServices) {
|
|
428
|
+
super(opContext, supplements);
|
|
429
|
+
this.sct = snomedServices;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Metadata methods
|
|
433
|
+
system() {
|
|
434
|
+
return this.sct.getSystemUri();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
version() {
|
|
438
|
+
return this.sct.getVersion();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* @param {string} checkVersion - first version
|
|
444
|
+
* @param {string} actualVersion - second version
|
|
445
|
+
* @returns {boolean} True if actualVersion is more detailed than checkVersion (for SCT)
|
|
446
|
+
*/
|
|
447
|
+
versionIsMoreDetailed(checkVersion, actualVersion) {
|
|
448
|
+
return actualVersion && actualVersion.startsWith(checkVersion);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
description() {
|
|
452
|
+
return this.sct.getDescription();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
totalCount() {
|
|
456
|
+
return this.sct.totalCount;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
contentMode() {
|
|
460
|
+
return CodeSystemContentMode.Complete;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
hasParents() {
|
|
464
|
+
return true;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
hasAnyDisplays(languages) {
|
|
468
|
+
const langs = this._ensureLanguages(languages);
|
|
469
|
+
|
|
470
|
+
// Check supplements first
|
|
471
|
+
if (this._hasAnySupplementDisplays(langs)) {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// SNOMED has displays for English and other languages
|
|
476
|
+
return langs.isEnglishOrNothing();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Core concept methods
|
|
480
|
+
async code(context) {
|
|
481
|
+
|
|
482
|
+
const ctxt = await this.#ensureContext(context);
|
|
483
|
+
|
|
484
|
+
if (!ctxt) return null;
|
|
485
|
+
|
|
486
|
+
if (ctxt.isComplex()) {
|
|
487
|
+
return this.sct.expressionServices.renderExpression(ctxt.expression, SnomedServicesRenderOption.Minimal);
|
|
488
|
+
} else {
|
|
489
|
+
return ctxt.getCode() || this.sct.getConceptId(ctxt.getReference());
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async display(context) {
|
|
494
|
+
|
|
495
|
+
const ctxt = await this.#ensureContext(context);
|
|
496
|
+
|
|
497
|
+
if (!ctxt) return null;
|
|
498
|
+
|
|
499
|
+
// Check supplements first
|
|
500
|
+
let disp = this._displayFromSupplements(ctxt.getCode());
|
|
501
|
+
if (disp) return disp;
|
|
502
|
+
|
|
503
|
+
if (ctxt.isComplex()) {
|
|
504
|
+
return this.sct.expressionServices.displayExpression(ctxt.expression);
|
|
505
|
+
} else {
|
|
506
|
+
return this.sct.getDisplayName(ctxt.getReference(), this.sct.defaultLanguage);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async definition(context) {
|
|
511
|
+
await this.#ensureContext(context);
|
|
512
|
+
return null; // SNOMED doesn't provide definitions in this sense
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async isAbstract(context) {
|
|
516
|
+
await this.#ensureContext(context);
|
|
517
|
+
return false; // SNOMED concepts are not abstract
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async isInactive(context) {
|
|
521
|
+
|
|
522
|
+
const ctxt = await this.#ensureContext(context);
|
|
523
|
+
|
|
524
|
+
if (!ctxt || ctxt.isComplex()) return false;
|
|
525
|
+
|
|
526
|
+
return !this.sct.isActive(ctxt.getReference());
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async isDeprecated(context) {
|
|
530
|
+
await this.#ensureContext(context);
|
|
531
|
+
|
|
532
|
+
return false; // Handle via status if needed
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async getStatus(context) {
|
|
536
|
+
|
|
537
|
+
const ctxt = await this.#ensureContext(context);
|
|
538
|
+
|
|
539
|
+
if (!ctxt || ctxt.isComplex()) return null;
|
|
540
|
+
|
|
541
|
+
return this.sct.isActive(ctxt.getReference()) ? 'active' : 'inactive';
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async designations(context, displays) {
|
|
545
|
+
|
|
546
|
+
const ctxt = await this.#ensureContext(context);
|
|
547
|
+
|
|
548
|
+
if (ctxt) {
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
if (ctxt.isComplex()) {
|
|
552
|
+
// For complex expressions, just add the display
|
|
553
|
+
const display = await this.display(context);
|
|
554
|
+
if (display) {
|
|
555
|
+
displays.addDesignation(true, 'active', new Designation('en-US', null, display));
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
// Get all designations for the concept
|
|
559
|
+
try {
|
|
560
|
+
const concept = this.sct.concepts.getConcept(ctxt.getReference());
|
|
561
|
+
const descriptionsRef = concept.descriptions;
|
|
562
|
+
|
|
563
|
+
if (descriptionsRef !== 0) {
|
|
564
|
+
const descriptionIndices = this.sct.refs.getReferences(descriptionsRef);
|
|
565
|
+
|
|
566
|
+
for (const descIndex of descriptionIndices) {
|
|
567
|
+
const description = this.sct.descriptions.getDescription(descIndex);
|
|
568
|
+
const term = this.sct.strings.getEntry(description.iDesc).trim();
|
|
569
|
+
const langCode = this.getLanguageCode(description.lang);
|
|
570
|
+
const kind = this.sct.concepts.getConcept(description.kind);
|
|
571
|
+
const kid = String(kind.identity);
|
|
572
|
+
const kdesc = this.sct.getDisplayName(description.kind);
|
|
573
|
+
let use = { system: 'http://snomed.info/sct', code: kid, display : kdesc};
|
|
574
|
+
|
|
575
|
+
displays.addDesignation(false, description.active ? 'active' : 'inactive', langCode, use, term);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} catch (error) {
|
|
579
|
+
// Add basic designation if we can't read detailed descriptions
|
|
580
|
+
const display = this.sct.getDisplayName(ctxt.getReference());
|
|
581
|
+
if (display) {
|
|
582
|
+
displays.addDesignation(true, 'active','en-US', null, display);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Add supplement designations
|
|
587
|
+
this._listSupplementDesignations(ctxt.getCode(), displays);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
getLanguageCode(langIndex) {
|
|
593
|
+
const languageMap = {
|
|
594
|
+
1: 'en',
|
|
595
|
+
2: 'en-GB',
|
|
596
|
+
3: 'es',
|
|
597
|
+
4: 'fr',
|
|
598
|
+
5: 'de'
|
|
599
|
+
};
|
|
600
|
+
return languageMap[langIndex] || 'en';
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Lookup methods
|
|
604
|
+
async locate(code) {
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
if (!code) return { context: null, message: 'Empty code' };
|
|
608
|
+
|
|
609
|
+
const conceptId = this.sct.stringToIdOrZero(code);
|
|
610
|
+
|
|
611
|
+
if (conceptId === 0n) {
|
|
612
|
+
// Try parsing as expression
|
|
613
|
+
try {
|
|
614
|
+
const expression = new SnomedExpressionParser().parse(code);
|
|
615
|
+
this.sct.expressionServices.checkExpression(expression);
|
|
616
|
+
return {
|
|
617
|
+
context: SnomedExpressionContext.fromExpression(code, expression),
|
|
618
|
+
message: null
|
|
619
|
+
};
|
|
620
|
+
} catch (error) {
|
|
621
|
+
return {
|
|
622
|
+
context: null,
|
|
623
|
+
message: `Code ${code} is not a valid SNOMED CT Term, and could not be parsed as an expression (${error.message})`
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
const result = this.sct.concepts.findConcept(conceptId);
|
|
628
|
+
if (result.found) {
|
|
629
|
+
return {
|
|
630
|
+
context: SnomedExpressionContext.fromCode(code, result.index),
|
|
631
|
+
message: null
|
|
632
|
+
};
|
|
633
|
+
} else {
|
|
634
|
+
return {
|
|
635
|
+
context: null,
|
|
636
|
+
message: undefined
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async locateIsA(code, parent, disallowParent = false) {
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
const childId = this.sct.stringToIdOrZero(code);
|
|
646
|
+
const parentId = this.sct.stringToIdOrZero(parent);
|
|
647
|
+
|
|
648
|
+
if (childId === 0n || parentId === 0n) {
|
|
649
|
+
return { context: null, message: 'Invalid concept ID' };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const childResult = this.sct.concepts.findConcept(childId);
|
|
653
|
+
const parentResult = this.sct.concepts.findConcept(parentId);
|
|
654
|
+
|
|
655
|
+
if (!childResult.found || !parentResult.found) {
|
|
656
|
+
return { context: null, message: 'Concept not found' };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const subsumes = this.sct.subsumes(parentResult.index, childResult.index);
|
|
660
|
+
const allowedByParent = !disallowParent || (childResult.index !== parentResult.index);
|
|
661
|
+
|
|
662
|
+
if (subsumes && allowedByParent) {
|
|
663
|
+
return {
|
|
664
|
+
context: SnomedExpressionContext.fromCode(code, childResult.index),
|
|
665
|
+
message: null
|
|
666
|
+
};
|
|
667
|
+
} else {
|
|
668
|
+
return { context: null, message: 'Concept is not subsumed by parent' };
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Iterator methods
|
|
673
|
+
async iterator(context) {
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
if (!context) {
|
|
677
|
+
// Iterate all active root concepts
|
|
678
|
+
return {
|
|
679
|
+
context: null,
|
|
680
|
+
keys: this.sct.activeRoots.slice(),
|
|
681
|
+
current: 0,
|
|
682
|
+
total: this.sct.activeRoots.length
|
|
683
|
+
};
|
|
684
|
+
} else {
|
|
685
|
+
const ctxt = await this.#ensureContext(context);
|
|
686
|
+
if (!ctxt || ctxt.isComplex()) {
|
|
687
|
+
return { context: ctxt, keys: [], current: 0, total: 0 };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Get children of this concept
|
|
691
|
+
const children = this.sct.getConceptChildren(ctxt.getReference());
|
|
692
|
+
return {
|
|
693
|
+
context: ctxt,
|
|
694
|
+
keys: children,
|
|
695
|
+
current: 0,
|
|
696
|
+
total: children.length
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async nextContext(iteratorContext) {
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
if (iteratorContext.current >= iteratorContext.total) {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const key = iteratorContext.keys[iteratorContext.current];
|
|
709
|
+
iteratorContext.current++;
|
|
710
|
+
|
|
711
|
+
return SnomedExpressionContext.fromReference(key);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Filter support
|
|
715
|
+
async doesFilter(prop, op, value) {
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
if (prop === 'concept') {
|
|
719
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
720
|
+
if (id !== 0n && ['=', 'is-a', 'descendent-of', 'in'].includes(op)) {
|
|
721
|
+
return this.sct.conceptExists(value);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// eslint-disable-next-line no-unused-vars
|
|
729
|
+
async getPrepContext(iterate) {
|
|
730
|
+
|
|
731
|
+
return {}; // Simple filter context
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
async filter(filterContext, prop, op, value) {
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
if (prop === 'concept') {
|
|
738
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
739
|
+
if (id === 0n) {
|
|
740
|
+
throw new Error(`Invalid concept ID: ${value}`);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
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);
|
|
752
|
+
default:
|
|
753
|
+
throw new Error(`Unsupported filter operation: ${op}`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
throw new Error(`Unsupported filter property: ${prop}`);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
async executeFilters(filterContext) {
|
|
761
|
+
|
|
762
|
+
return [filterContext];
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async filterSize(filterContext, set) {
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
if (set.matches && set.matches.length > 0) {
|
|
769
|
+
return set.matches.length;
|
|
770
|
+
} else if (set.members && set.members.length > 0) {
|
|
771
|
+
return set.members.length;
|
|
772
|
+
} else if (set.descendants && set.descendants.length > 0) {
|
|
773
|
+
return set.descendants.length;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return 0;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async filterMore(filterContext, set) {
|
|
780
|
+
|
|
781
|
+
set.cursor = set.cursor || 0;
|
|
782
|
+
|
|
783
|
+
const size = await this.filterSize(filterContext, set);
|
|
784
|
+
return set.cursor < size;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
async filterConcept(filterContext, set) {
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
const size = await this.filterSize(filterContext, set);
|
|
791
|
+
if (set.cursor >= size) {
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
let key;
|
|
796
|
+
if (set.matches && set.matches.length > 0) {
|
|
797
|
+
key = set.matches[set.cursor].index;
|
|
798
|
+
} else if (set.members && set.members.length > 0) {
|
|
799
|
+
key = set.members[set.cursor].ref;
|
|
800
|
+
} else if (set.descendants && set.descendants.length > 0) {
|
|
801
|
+
key = set.descendants[set.cursor];
|
|
802
|
+
} else {
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
set.cursor++;
|
|
807
|
+
return SnomedExpressionContext.fromReference(key);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async filterLocate(filterContext, set, code) {
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
const conceptResult = await this.locate(code);
|
|
814
|
+
if (!conceptResult.context) {
|
|
815
|
+
return conceptResult.message;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const ctxt = conceptResult.context;
|
|
819
|
+
if (ctxt.isComplex()) {
|
|
820
|
+
return 'Complex expressions not supported in filters';
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const reference = ctxt.getReference();
|
|
824
|
+
let found = false;
|
|
825
|
+
|
|
826
|
+
if (set.matches && set.matches.length > 0) {
|
|
827
|
+
found = set.matches.some(m => m.index === reference);
|
|
828
|
+
} else if (set.members && set.members.length > 0) {
|
|
829
|
+
found = set.members.some(m => m.ref === reference);
|
|
830
|
+
} else if (set.descendants && set.descendants.length > 0) {
|
|
831
|
+
found = set.descendants.includes(reference);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (found) {
|
|
835
|
+
return ctxt;
|
|
836
|
+
} else {
|
|
837
|
+
return `Code ${code} is not in the specified filter`;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
async filterCheck(filterContext, set, concept) {
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
if (!(concept instanceof SnomedExpressionContext)) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (concept.isComplex()) {
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const reference = concept.getReference();
|
|
853
|
+
|
|
854
|
+
if (set.matches && set.matches.length > 0) {
|
|
855
|
+
return set.matches.some(m => m.index === reference);
|
|
856
|
+
} else if (set.members && set.members.length > 0) {
|
|
857
|
+
return set.members.some(m => m.ref === reference);
|
|
858
|
+
} else if (set.descendants && set.descendants.length > 0) {
|
|
859
|
+
return set.descendants.includes(reference);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
// Search filter
|
|
867
|
+
async searchFilter(filterContext, filter, sort) {
|
|
868
|
+
return this.sct.searchFilter(filter, false, sort);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Subsumption testing
|
|
872
|
+
async subsumesTest(codeA, codeB) {
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
try {
|
|
876
|
+
const exprA = new SnomedExpressionParser(this.sct.concepts).parse(codeA);
|
|
877
|
+
const exprB = new SnomedExpressionParser(this.sct.concepts).parse(codeB);
|
|
878
|
+
|
|
879
|
+
if (exprA.isSimple() && exprB.isSimple()) {
|
|
880
|
+
const refA = exprA.concepts[0].reference;
|
|
881
|
+
const refB = exprB.concepts[0].reference;
|
|
882
|
+
|
|
883
|
+
if (refA === refB) {
|
|
884
|
+
return 'equivalent';
|
|
885
|
+
} else if (this.sct.subsumes(refA, refB)) {
|
|
886
|
+
return 'subsumes';
|
|
887
|
+
} else if (this.sct.subsumes(refB, refA)) {
|
|
888
|
+
return 'subsumed-by';
|
|
889
|
+
} else {
|
|
890
|
+
return 'not-subsumed';
|
|
891
|
+
}
|
|
892
|
+
} else {
|
|
893
|
+
const b1 = this.sct.expressionServices.expressionSubsumes(exprA, exprB);
|
|
894
|
+
const b2 = this.sct.expressionServices.expressionSubsumes(exprB, exprA);
|
|
895
|
+
|
|
896
|
+
if (b1 && b2) {
|
|
897
|
+
return 'equivalent';
|
|
898
|
+
} else if (b1) {
|
|
899
|
+
return 'subsumes';
|
|
900
|
+
} else if (b2) {
|
|
901
|
+
return 'subsumed-by';
|
|
902
|
+
} else {
|
|
903
|
+
return 'not-subsumed';
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
} catch (error) {
|
|
907
|
+
throw new Error(`Error in subsumption test: ${error.message}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Helper methods
|
|
912
|
+
async #ensureContext(context) {
|
|
913
|
+
if (!context) {
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (typeof context === 'string') {
|
|
918
|
+
const result = await this.locate(context);
|
|
919
|
+
if (!result.context) {
|
|
920
|
+
throw new Error(result.message);
|
|
921
|
+
}
|
|
922
|
+
return result.context;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (context instanceof SnomedExpressionContext) {
|
|
926
|
+
return context;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
throw new Error(`Unknown type at #ensureContext: ${typeof context}`);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
versionAlgorithm() {
|
|
933
|
+
return 'url';
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
isNotClosed() {
|
|
937
|
+
return true;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
isDisplay(cd) {
|
|
941
|
+
return cd.use.system === this.system() &&
|
|
942
|
+
(cd.use.code === '900000000000013009' || cd.use.code === '900000000000003001');
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Factory for creating SNOMED services and providers
|
|
949
|
+
*/
|
|
950
|
+
class SnomedServicesFactory extends CodeSystemFactoryProvider {
|
|
951
|
+
constructor(i18n, filePath) {
|
|
952
|
+
super(i18n);
|
|
953
|
+
this.filePath = filePath;
|
|
954
|
+
this.uses = 0;
|
|
955
|
+
this._loaded = false;
|
|
956
|
+
this._sharedData = null;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
system() {
|
|
960
|
+
return 'http://snomed.info/sct';
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
version() {
|
|
964
|
+
return this._sharedData.versionUri;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
getPartialVersion() {
|
|
968
|
+
let ver = this.version();
|
|
969
|
+
if (ver.includes("/version")) {
|
|
970
|
+
return ver.substring(0, ver.indexOf("/version"));
|
|
971
|
+
} else {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// eslint-disable-next-line no-unused-vars
|
|
976
|
+
async buildKnownValueSet(url, version) {
|
|
977
|
+
return null;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async #ensureLoaded() {
|
|
981
|
+
if (!this._loaded) {
|
|
982
|
+
await this.load();
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
async load() {
|
|
987
|
+
const reader = new SnomedFileReader(this.filePath);
|
|
988
|
+
this._sharedData = await reader.loadSnomedData();
|
|
989
|
+
this.snomedServices = new SnomedServices(this._sharedData);
|
|
990
|
+
this._loaded = true;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
defaultVersion() {
|
|
994
|
+
return this._sharedData?.version || 'unknown';
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async build(opContext, supplements = []) {
|
|
998
|
+
await this.#ensureLoaded();
|
|
999
|
+
this.recordUse();
|
|
1000
|
+
return new SnomedProvider(opContext, supplements, this.snomedServices);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
useCount() {
|
|
1004
|
+
return this.uses;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
recordUse() {
|
|
1008
|
+
this.uses++;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
name() {
|
|
1013
|
+
return `SCT ${getEditionCode(this._sharedData.edition)}`;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
id() {
|
|
1017
|
+
return "SCT"+this.version();
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
|
|
1022
|
+
function getEditionName(edition) {
|
|
1023
|
+
const editionMap = {
|
|
1024
|
+
'900000000000207008': 'International Edition',
|
|
1025
|
+
'449081005': 'International Spanish Edition',
|
|
1026
|
+
'11000221109': 'Argentinian Edition',
|
|
1027
|
+
'32506021000036107': 'Australian Edition (with drug extension)',
|
|
1028
|
+
'11000234105': 'Austrian Edition',
|
|
1029
|
+
'11000172109': 'Belgian Edition',
|
|
1030
|
+
'20621000087109': 'Canadian English Edition',
|
|
1031
|
+
'20611000087101': 'Canadian Canadian French Edition',
|
|
1032
|
+
'554471000005108': 'Danish Edition',
|
|
1033
|
+
'11000279109': 'Czech Edition',
|
|
1034
|
+
'11000181102': 'Estonian Edition',
|
|
1035
|
+
'11000229106': 'Finnish Edition',
|
|
1036
|
+
'11000274103': 'German Edition',
|
|
1037
|
+
'1121000189102': 'Indian Edition',
|
|
1038
|
+
'827022005': 'IPS Terminology',
|
|
1039
|
+
'11000220105': 'Irish Edition',
|
|
1040
|
+
'11000146104': 'Netherlands Edition',
|
|
1041
|
+
'21000210109': 'New Zealand Edition',
|
|
1042
|
+
'51000202101': 'Norwegian Edition',
|
|
1043
|
+
'11000267109': 'Republic of Korea Edition (South Korea)',
|
|
1044
|
+
'900000001000122104': 'Spanish National Edition',
|
|
1045
|
+
'45991000052106': 'Swedish Edition',
|
|
1046
|
+
'2011000195101': 'Swiss Edition',
|
|
1047
|
+
'83821000000107': 'UK Edition',
|
|
1048
|
+
'999000021000000109': 'UK Clinical Edition',
|
|
1049
|
+
'5631000179106': 'Uruguay Edition',
|
|
1050
|
+
'731000124108': 'US Edition',
|
|
1051
|
+
'21000325107': 'Chilean Edition',
|
|
1052
|
+
'5991000124107': 'US Edition (with ICD-10-CM maps)'
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
return editionMap[edition] || 'Unknown Edition';
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function getEditionCode(edition) {
|
|
1059
|
+
const editionMap = {
|
|
1060
|
+
'900000000000207008': 'Intl',
|
|
1061
|
+
'449081005': 'es',
|
|
1062
|
+
'11000221109': 'AR-es',
|
|
1063
|
+
'32506021000036107': 'AU+',
|
|
1064
|
+
'11000234105': 'AT',
|
|
1065
|
+
'11000172109': 'BE',
|
|
1066
|
+
'20621000087109': 'CA-en',
|
|
1067
|
+
'20611000087101': 'CA-fr',
|
|
1068
|
+
'554471000005108': 'DK',
|
|
1069
|
+
'11000279109': 'CZ',
|
|
1070
|
+
'11000181102': 'ES',
|
|
1071
|
+
'11000229106': 'FI',
|
|
1072
|
+
'11000274103': 'DE',
|
|
1073
|
+
'1121000189102': 'IN',
|
|
1074
|
+
'827022005': 'IPS',
|
|
1075
|
+
'11000220105': 'IE',
|
|
1076
|
+
'11000146104': 'NL',
|
|
1077
|
+
'21000210109': 'NZ',
|
|
1078
|
+
'51000202101': 'NO',
|
|
1079
|
+
'11000267109': 'KR',
|
|
1080
|
+
'900000001000122104': 'ES-es',
|
|
1081
|
+
'45991000052106': 'SW',
|
|
1082
|
+
'2011000195101': 'CH',
|
|
1083
|
+
'83821000000107': 'UK',
|
|
1084
|
+
'999000021000000109': 'UK-Clinical',
|
|
1085
|
+
'5631000179106': 'UR',
|
|
1086
|
+
'731000124108': 'US',
|
|
1087
|
+
'21000325107': 'CL',
|
|
1088
|
+
'5991000124107': 'US+)'
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
return editionMap[edition] || 'Unknown Edition';
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
|
|
1095
|
+
module.exports = {
|
|
1096
|
+
SnomedProvider,
|
|
1097
|
+
SnomedServicesFactory,
|
|
1098
|
+
SnomedExpressionContext,
|
|
1099
|
+
SnomedServices,
|
|
1100
|
+
SnomedFilterContext,
|
|
1101
|
+
SnomedProviderContextKind
|
|
1102
|
+
};
|