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,808 @@
|
|
|
1
|
+
const sqlite3 = require('sqlite3').verbose();
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const { CodeSystem } = require('../library/codesystem');
|
|
4
|
+
const { CodeSystemProvider, CodeSystemFactoryProvider } = require('./cs-api');
|
|
5
|
+
const {Designations} = require("../library/designations");
|
|
6
|
+
const {validateArrayParameter} = require("../../library/utilities");
|
|
7
|
+
|
|
8
|
+
// Context for RxNorm concepts
|
|
9
|
+
class RxNormConcept {
|
|
10
|
+
constructor(code, display) {
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.display = display;
|
|
13
|
+
this.others = []; // Array of alternative displays (SY terms, etc.)
|
|
14
|
+
this.archived = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Filter holder for query building and iteration
|
|
19
|
+
class RxNormFilterHolder {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.sql = '';
|
|
22
|
+
this.text = false; // Whether this is a text search filter
|
|
23
|
+
this.params = {}; // Parameters for the SQL query
|
|
24
|
+
this.cursor = 0;
|
|
25
|
+
this.results = null; // Will hold query results for iteration
|
|
26
|
+
this.executed = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Filter preparation context
|
|
31
|
+
class RxNormPrep {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.filters = [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Iterator context
|
|
38
|
+
class RxNormIteratorContext {
|
|
39
|
+
constructor(query, params = {}) {
|
|
40
|
+
this.query = query;
|
|
41
|
+
this.params = params;
|
|
42
|
+
this.cursor = 0;
|
|
43
|
+
this.results = null;
|
|
44
|
+
this.executed = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
more() {
|
|
48
|
+
return this.cursor < (this.results ? this.results.length : 0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
next() {
|
|
52
|
+
this.cursor++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class RxNormServices extends CodeSystemProvider {
|
|
57
|
+
constructor(opContext, supplements, db, sharedData, isNCI = false) {
|
|
58
|
+
super(opContext, supplements);
|
|
59
|
+
this.db = db;
|
|
60
|
+
this.isNCI = isNCI;
|
|
61
|
+
|
|
62
|
+
// Shared data from factory
|
|
63
|
+
this.dbVersion = sharedData.version;
|
|
64
|
+
this.rels = sharedData.rels;
|
|
65
|
+
this.reltypes = sharedData.reltypes;
|
|
66
|
+
this.totalCodeCount = sharedData.totalCodeCount;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
close() {
|
|
70
|
+
if (this.db) {
|
|
71
|
+
this.db.close();
|
|
72
|
+
this.db = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Metadata methods
|
|
77
|
+
system() {
|
|
78
|
+
return this.isNCI ? 'http://ncimeta.nci.nih.gov' : 'http://www.nlm.nih.gov/research/umls/rxnorm';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
version() {
|
|
82
|
+
return this.dbVersion;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
description() {
|
|
86
|
+
return this.isNCI ? 'NCI Metathesaurus' : 'RxNorm';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
name() {
|
|
90
|
+
return this.isNCI ? 'NCI' : 'RxNorm';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async totalCount() {
|
|
94
|
+
return this.totalCodeCount;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
getSAB() {
|
|
98
|
+
return this.isNCI ? 'NCI' : 'RXNORM';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getCodeField() {
|
|
102
|
+
return this.isNCI ? 'SCUI' : 'RXCUI';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
hasParents() {
|
|
106
|
+
return true; // RxNorm has relationships
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Core concept methods
|
|
110
|
+
async code(context) {
|
|
111
|
+
|
|
112
|
+
const ctxt = await this.#ensureContext(context);
|
|
113
|
+
return ctxt ? ctxt.code : null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async display(context) {
|
|
117
|
+
|
|
118
|
+
const ctxt = await this.#ensureContext(context);
|
|
119
|
+
if (!ctxt) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check supplements first
|
|
124
|
+
let disp = this._displayFromSupplements(ctxt.code);
|
|
125
|
+
if (disp) {
|
|
126
|
+
return disp;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return ctxt.display || '';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async definition(context) {
|
|
133
|
+
await this.#ensureContext(context);
|
|
134
|
+
return null; // RxNorm doesn't provide definitions
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async isAbstract(context) {
|
|
138
|
+
await this.#ensureContext(context);
|
|
139
|
+
|
|
140
|
+
return false; // RxNorm codes are not abstract
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async isInactive(context) {
|
|
144
|
+
|
|
145
|
+
const ctxt = await this.#ensureContext(context);
|
|
146
|
+
|
|
147
|
+
if (ctxt && ctxt.archived) {
|
|
148
|
+
return true;
|
|
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' : false);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async isDeprecated(context) {
|
|
166
|
+
|
|
167
|
+
const ctxt = await this.#ensureContext(context);
|
|
168
|
+
return ctxt ? ctxt.archived : false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async designations(context, displays) {
|
|
172
|
+
|
|
173
|
+
const ctxt = await this.#ensureContext(context);
|
|
174
|
+
|
|
175
|
+
if (ctxt) {
|
|
176
|
+
// Add main display
|
|
177
|
+
displays.addDesignation(true, 'active', 'en-US', CodeSystem.makeUseForDisplay(), ctxt.display);
|
|
178
|
+
|
|
179
|
+
// Add other displays
|
|
180
|
+
for (const other of ctxt.others) {
|
|
181
|
+
displays.addDesignation(false, 'active', 'en-US', null, other);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Add supplement designations
|
|
185
|
+
this._listSupplementDesignations(ctxt.code, displays);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async #ensureContext(context) {
|
|
190
|
+
if (!context) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
if (typeof context === 'string') {
|
|
194
|
+
const ctxt = await this.locate(context);
|
|
195
|
+
if (!ctxt.context) {
|
|
196
|
+
throw new Error(ctxt.message);
|
|
197
|
+
} else {
|
|
198
|
+
return ctxt.context;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (context instanceof RxNormConcept) {
|
|
202
|
+
return context;
|
|
203
|
+
}
|
|
204
|
+
throw new Error("Unknown Type at #ensureContext: " + (typeof context));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Lookup methods
|
|
208
|
+
async locate(code) {
|
|
209
|
+
|
|
210
|
+
assert(!code || typeof code === 'string', 'code must be string');
|
|
211
|
+
if (!code) return { context: null, message: 'Empty code' };
|
|
212
|
+
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
let sql = `SELECT STR, TTY FROM rxnconso WHERE ${this.getCodeField()} = ? AND SAB = ?`;
|
|
215
|
+
|
|
216
|
+
this.db.all(sql, [code, this.getSAB()], (err, rows) => {
|
|
217
|
+
if (err) {
|
|
218
|
+
reject(err);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (rows.length === 0) {
|
|
223
|
+
// Try archive
|
|
224
|
+
sql = `SELECT STR, TTY FROM RXNATOMARCHIVE WHERE ${this.getCodeField()} = ? AND SAB = ?`;
|
|
225
|
+
this.db.all(sql, [code, this.getSAB()], (err, archiveRows) => {
|
|
226
|
+
if (err) {
|
|
227
|
+
reject(err);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (archiveRows.length === 0) {
|
|
232
|
+
resolve({ context: null, message: undefined});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const concept = this.#createConceptFromRows(code, archiveRows, true);
|
|
237
|
+
resolve({ context: concept, message: null });
|
|
238
|
+
});
|
|
239
|
+
} else {
|
|
240
|
+
const concept = this.#createConceptFromRows(code, rows, false);
|
|
241
|
+
resolve({ context: concept, message: null });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#createConceptFromRows(code, rows, archived) {
|
|
248
|
+
const concept = new RxNormConcept(code);
|
|
249
|
+
concept.archived = archived;
|
|
250
|
+
|
|
251
|
+
for (const row of rows) {
|
|
252
|
+
if (row.TTY === 'SY' || concept.display && concept.display) {
|
|
253
|
+
concept.others.push(row.STR.trim());
|
|
254
|
+
} else {
|
|
255
|
+
concept.display = row.STR.trim();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return concept;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Iterator methods
|
|
263
|
+
async iterator(context) {
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
if (!context) {
|
|
267
|
+
// Iterate all codes
|
|
268
|
+
const query = `SELECT ${this.getCodeField()}, STR FROM rxnconso WHERE SAB = ? AND TTY <> 'SY' ORDER BY ${this.getCodeField()}`;
|
|
269
|
+
return new RxNormIteratorContext(query, { sab: this.getSAB() });
|
|
270
|
+
} else {
|
|
271
|
+
// No hierarchical iteration for specific contexts in this implementation
|
|
272
|
+
return new RxNormIteratorContext('', {});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async nextContext(iteratorContext) {
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
if (!iteratorContext.executed) {
|
|
280
|
+
await this.#executeIterator(iteratorContext);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!iteratorContext.more()) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const row = iteratorContext.results[iteratorContext.cursor];
|
|
288
|
+
iteratorContext.next();
|
|
289
|
+
|
|
290
|
+
const concept = new RxNormConcept(row[this.getCodeField()], row.STR);
|
|
291
|
+
return concept;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async #executeIterator(iteratorContext) {
|
|
295
|
+
return new Promise((resolve, reject) => {
|
|
296
|
+
this.db.all(iteratorContext.query, Object.values(iteratorContext.params), (err, rows) => {
|
|
297
|
+
if (err) {
|
|
298
|
+
reject(err);
|
|
299
|
+
} else {
|
|
300
|
+
iteratorContext.results = rows;
|
|
301
|
+
iteratorContext.executed = true;
|
|
302
|
+
resolve();
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Filter support
|
|
309
|
+
async doesFilter(prop, op, value) {
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
prop = prop.toUpperCase();
|
|
313
|
+
|
|
314
|
+
// TTY filters
|
|
315
|
+
if (prop === 'TTY' && ['=', 'in'].includes(op)) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// STY filter
|
|
320
|
+
if (prop === 'STY' && op === '=') {
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// SAB filter
|
|
325
|
+
if (prop === 'SAB' && op === '=') {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Relationship filters (REL values like 'SY', 'RN', etc.)
|
|
330
|
+
if (this.rels.includes(prop) && op === '=' && (value.startsWith('CUI:') || value.startsWith('AUI:'))) {
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Relationship type filters (RELA values)
|
|
335
|
+
if (this.reltypes.includes(prop) && op === '=' && (value.startsWith('CUI:') || value.startsWith('AUI:'))) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// eslint-disable-next-line no-unused-vars
|
|
343
|
+
async getPrepContext(iterate) {
|
|
344
|
+
return new RxNormPrep();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async filter(filterContext, prop, op, value) {
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
const filter = new RxNormFilterHolder();
|
|
351
|
+
prop = prop.toUpperCase();
|
|
352
|
+
|
|
353
|
+
let sql = '';
|
|
354
|
+
let params = {};
|
|
355
|
+
|
|
356
|
+
if (op === 'in' && prop === 'TTY') {
|
|
357
|
+
const values = value.split(',').map(v => v.trim()).filter(v => v);
|
|
358
|
+
const placeholders = values.map((_, i) => `$tty${i}`).join(',');
|
|
359
|
+
sql = `AND TTY IN (${placeholders})`;
|
|
360
|
+
values.forEach((val, i) => {
|
|
361
|
+
params[`tty${i}`] = this.#sqlWrapString(val);
|
|
362
|
+
});
|
|
363
|
+
} else if (op === '=') {
|
|
364
|
+
if (prop === 'STY') {
|
|
365
|
+
sql = `AND ${this.getCodeField()} IN (SELECT RXCUI FROM rxnsty WHERE TUI = $sty)`;
|
|
366
|
+
params.sty = this.#sqlWrapString(value);
|
|
367
|
+
} else if (prop === 'SAB') {
|
|
368
|
+
sql = `AND ${this.getCodeField()} IN (SELECT ${this.getCodeField()} FROM rxnconso WHERE SAB = $sab)`;
|
|
369
|
+
params.sab = this.#sqlWrapString(value);
|
|
370
|
+
} else if (prop === 'TTY') {
|
|
371
|
+
sql = `AND TTY = $tty`;
|
|
372
|
+
params.tty = this.#sqlWrapString(value);
|
|
373
|
+
} else if (this.rels.includes(prop)) {
|
|
374
|
+
if (value.startsWith('CUI:')) {
|
|
375
|
+
const cui = value.substring(4);
|
|
376
|
+
sql = `AND (${this.getCodeField()} IN (SELECT ${this.getCodeField()} FROM rxnconso WHERE RXCUI IN (SELECT RXCUI1 FROM rxnrel WHERE REL = $rel AND RXCUI2 = $cui2)))`;
|
|
377
|
+
params.rel = this.#sqlWrapString(prop);
|
|
378
|
+
params.cui2 = this.#sqlWrapString(cui);
|
|
379
|
+
} else if (value.startsWith('AUI:')) {
|
|
380
|
+
const aui = value.substring(4);
|
|
381
|
+
sql = `AND (${this.getCodeField()} IN (SELECT ${this.getCodeField()} FROM rxnconso WHERE RXAUI IN (SELECT RXAUI1 FROM rxnrel WHERE REL = $rel AND RXAUI2 = $aui2)))`;
|
|
382
|
+
params.rel = this.#sqlWrapString(prop);
|
|
383
|
+
params.aui2 = this.#sqlWrapString(aui);
|
|
384
|
+
}
|
|
385
|
+
} else if (this.reltypes.includes(prop)) {
|
|
386
|
+
if (value.startsWith('CUI:')) {
|
|
387
|
+
const cui = value.substring(4);
|
|
388
|
+
sql = `AND (${this.getCodeField()} IN (SELECT ${this.getCodeField()} FROM rxnconso WHERE RXCUI IN (SELECT RXCUI1 FROM rxnrel WHERE RELA = $rela AND RXCUI2 = $cui2)))`;
|
|
389
|
+
params.rela = this.#sqlWrapString(prop);
|
|
390
|
+
params.cui2 = this.#sqlWrapString(cui);
|
|
391
|
+
} else if (value.startsWith('AUI:')) {
|
|
392
|
+
const aui = value.substring(4);
|
|
393
|
+
sql = `AND (${this.getCodeField()} IN (SELECT ${this.getCodeField()} FROM rxnconso WHERE RXAUI IN (SELECT RXAUI1 FROM rxnrel WHERE RELA = $rela AND RXAUI2 = $aui2)))`;
|
|
394
|
+
params.rela = this.#sqlWrapString(prop);
|
|
395
|
+
params.aui2 = this.#sqlWrapString(aui);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (!sql) {
|
|
401
|
+
throw new Error(`Unknown filter "${prop} ${op} ${value}"`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
filter.sql = sql;
|
|
405
|
+
filter.params = params;
|
|
406
|
+
filterContext.filters.push(filter);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async searchFilter(filterContext, filter, sort) {
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
if (!filter || !filter.stems || filter.stems.length === 0) {
|
|
413
|
+
throw new Error('Invalid search filter');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (let i = 0; i < filter.stems.length; i++) {
|
|
417
|
+
const stem = filter.stems[i];
|
|
418
|
+
const rxnormFilter = new RxNormFilterHolder();
|
|
419
|
+
rxnormFilter.text = true;
|
|
420
|
+
rxnormFilter.sql = ` AND (${this.getCodeField()} = s${i}.CUI AND s${i}.stem LIKE $stem${i})`;
|
|
421
|
+
rxnormFilter.params[`stem${i}`] = this.#sqlWrapString(stem) + '%';
|
|
422
|
+
|
|
423
|
+
filterContext.filters.push(rxnormFilter);
|
|
424
|
+
}
|
|
425
|
+
if (sort) {
|
|
426
|
+
// TODO
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async executeFilters(filterContext) {
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
if (filterContext.filters.length === 0) {
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Build the complete query
|
|
438
|
+
let sql1 = '';
|
|
439
|
+
let sql2 = 'FROM rxnconso';
|
|
440
|
+
let allParams = {};
|
|
441
|
+
|
|
442
|
+
let stemIndex = 0;
|
|
443
|
+
|
|
444
|
+
// Add non-text filters first
|
|
445
|
+
for (const filter of filterContext.filters) {
|
|
446
|
+
if (!filter.text) {
|
|
447
|
+
sql1 += ' ' + filter.sql;
|
|
448
|
+
Object.assign(allParams, filter.params);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Add text search joins and filters
|
|
453
|
+
for (const filter of filterContext.filters) {
|
|
454
|
+
if (filter.text) {
|
|
455
|
+
sql2 += `, rxnstems as s${stemIndex}`;
|
|
456
|
+
const stemSql = filter.sql.replace(/s\d+/g, `s${stemIndex}`);
|
|
457
|
+
sql1 += ' ' + stemSql;
|
|
458
|
+
|
|
459
|
+
// Update parameter keys to match stem index
|
|
460
|
+
for (const [key, value] of Object.entries(filter.params)) {
|
|
461
|
+
const newKey = key.replace(/\d+/, stemIndex.toString());
|
|
462
|
+
allParams[newKey] = value;
|
|
463
|
+
}
|
|
464
|
+
stemIndex++;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const fullQuery = `SELECT ${this.getCodeField()}, STR ${sql2} WHERE SAB = $sab AND TTY <> 'SY' ${sql1}`;
|
|
469
|
+
allParams.sab = this.getSAB();
|
|
470
|
+
|
|
471
|
+
// Create a single filter holder with the combined query
|
|
472
|
+
const combinedFilter = new RxNormFilterHolder();
|
|
473
|
+
combinedFilter.sql = fullQuery;
|
|
474
|
+
combinedFilter.params = allParams;
|
|
475
|
+
|
|
476
|
+
return [combinedFilter];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async filterSize(filterContext, set) {
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
if (!set.executed) {
|
|
483
|
+
await this.#executeFilter(set);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return set.results ? set.results.length : 0;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async filterMore(filterContext, set) {
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
if (!set.executed) {
|
|
493
|
+
await this.#executeFilter(set);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return set.cursor < (set.results ? set.results.length : 0);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async filterConcept(filterContext, set) {
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
if (!set.executed) {
|
|
503
|
+
await this.#executeFilter(set);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (set.cursor >= set.results.length) {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const row = set.results[set.cursor];
|
|
511
|
+
set.cursor++;
|
|
512
|
+
|
|
513
|
+
const concept = new RxNormConcept(row[this.getCodeField()], row.STR);
|
|
514
|
+
return concept;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async filterLocate(filterContext, set, code) {
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
return new Promise((resolve, reject) => {
|
|
521
|
+
// Build query to check if code exists in filter
|
|
522
|
+
const checkQuery = `SELECT ${this.getCodeField()}, STR FROM rxnconso WHERE SAB = $sab AND TTY <> 'SY' AND ${this.getCodeField()} = $code ${set.sql.replace(/SELECT.*?FROM rxnconso/, '').replace(/WHERE SAB = \$sab AND TTY <> 'SY'/, '')}`;
|
|
523
|
+
|
|
524
|
+
const params = { ...set.params, code };
|
|
525
|
+
|
|
526
|
+
this.db.get(checkQuery, this.#buildParamArray(checkQuery, params), (err, row) => {
|
|
527
|
+
if (err) {
|
|
528
|
+
reject(err);
|
|
529
|
+
} else if (!row) {
|
|
530
|
+
resolve(`Code ${code} is not in the specified filter`);
|
|
531
|
+
} else {
|
|
532
|
+
const concept = new RxNormConcept(row[this.getCodeField()], row.STR);
|
|
533
|
+
resolve(concept);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async filterCheck(filterContext, set, concept) {
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
if (!(concept instanceof RxNormConcept)) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (!set.executed) {
|
|
547
|
+
await this.#executeFilter(set);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return set.results.some(row => row[this.getCodeField()] === concept.code);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async #executeFilter(filter) {
|
|
554
|
+
return new Promise((resolve, reject) => {
|
|
555
|
+
const paramArray = this.#buildParamArray(filter.sql, filter.params);
|
|
556
|
+
|
|
557
|
+
this.db.all(filter.sql, paramArray, (err, rows) => {
|
|
558
|
+
if (err) {
|
|
559
|
+
reject(err);
|
|
560
|
+
} else {
|
|
561
|
+
filter.results = rows;
|
|
562
|
+
filter.executed = true;
|
|
563
|
+
resolve();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Helper method to build parameter arrays for sqlite3
|
|
570
|
+
#buildParamArray(sql, params) {
|
|
571
|
+
const paramArray = [];
|
|
572
|
+
const paramOrder = [];
|
|
573
|
+
|
|
574
|
+
// Extract parameter names from SQL in order
|
|
575
|
+
const paramMatches = sql.match(/\$\w+/g) || [];
|
|
576
|
+
paramMatches.forEach(match => {
|
|
577
|
+
const paramName = match.substring(1); // Remove $
|
|
578
|
+
if (!paramOrder.includes(paramName)) {
|
|
579
|
+
paramOrder.push(paramName);
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Build array in correct order
|
|
584
|
+
paramOrder.forEach(paramName => {
|
|
585
|
+
if (Object.prototype.hasOwnProperty.call(params, paramName)) {
|
|
586
|
+
paramArray.push(params[paramName]);
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
return paramArray;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
#sqlWrapString(str) {
|
|
594
|
+
return str.replace(/'/g, "''");
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Subsumption testing
|
|
598
|
+
async subsumesTest(codeA, codeB) {
|
|
599
|
+
await this.#ensureContext(codeA);
|
|
600
|
+
await this.#ensureContext(codeB);
|
|
601
|
+
return 'not-subsumed'; // Not implemented yet
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Extension for lookup operation
|
|
605
|
+
async extendLookup(ctxt, props, params) {
|
|
606
|
+
validateArrayParameter(props, 'props', String);
|
|
607
|
+
validateArrayParameter(params, 'params', Object);
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
if (typeof ctxt === 'string') {
|
|
611
|
+
const located = await this.locate(ctxt);
|
|
612
|
+
if (!located.context) {
|
|
613
|
+
throw new Error(located.message);
|
|
614
|
+
}
|
|
615
|
+
ctxt = located.context;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (!(ctxt instanceof RxNormConcept)) {
|
|
619
|
+
throw new Error('Invalid context for RxNorm lookup');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Set abstract status
|
|
623
|
+
params.abstract = false;
|
|
624
|
+
|
|
625
|
+
// Add designations
|
|
626
|
+
const designations = new Designations(this.opContext.i18n.languageDefinitions);
|
|
627
|
+
await this.designations(ctxt, designations);
|
|
628
|
+
for (const designation of designations) {
|
|
629
|
+
this.#addProperty(params, 'designation', 'display', designation.value, designation.language);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
#addProperty(params, type, name, value, language = null) {
|
|
634
|
+
if (!params.parameter) {
|
|
635
|
+
params.parameter = [];
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const property = {
|
|
639
|
+
name: type,
|
|
640
|
+
part: [
|
|
641
|
+
{ name: 'code', valueCode: name },
|
|
642
|
+
{ name: 'value', valueString: value }
|
|
643
|
+
]
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
if (language) {
|
|
647
|
+
property.part.push({ name: 'language', valueCode: language });
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
params.parameter.push(property);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
versionAlgorithm() {
|
|
654
|
+
return 'date';
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
659
|
+
constructor(i18n, dbPath, isNCI = false) {
|
|
660
|
+
super(i18n);
|
|
661
|
+
this.dbPath = dbPath;
|
|
662
|
+
this.isNCI = isNCI;
|
|
663
|
+
this._loaded = false;
|
|
664
|
+
this._sharedData = null;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
system() {
|
|
668
|
+
return this.isNCI ? 'http://ncimeta.nci.nih.gov' : 'http://www.nlm.nih.gov/research/umls/rxnorm';
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
version() {
|
|
672
|
+
return this._sharedData.version;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// eslint-disable-next-line no-unused-vars
|
|
676
|
+
async buildKnownValueSet(url, version) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async #ensureLoaded() {
|
|
681
|
+
if (!this._loaded) {
|
|
682
|
+
await this.load();
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async load() {
|
|
687
|
+
const db = new sqlite3.Database(this.dbPath);
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
this._sharedData = {
|
|
691
|
+
version: '',
|
|
692
|
+
rels: [],
|
|
693
|
+
reltypes: [],
|
|
694
|
+
totalCodeCount: 0
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// Load version
|
|
698
|
+
this._sharedData.version = await this.#readVersion(db);
|
|
699
|
+
|
|
700
|
+
// Load relationship types
|
|
701
|
+
this._sharedData.rels = await this.#loadList(db, 'SELECT DISTINCT REL FROM RXNREL');
|
|
702
|
+
|
|
703
|
+
// Load relationship attributes
|
|
704
|
+
this._sharedData.reltypes = await this.#loadList(db, 'SELECT DISTINCT RELA FROM RXNREL');
|
|
705
|
+
|
|
706
|
+
// Get total count
|
|
707
|
+
const sab = this.isNCI ? 'NCI' : 'RXNORM';
|
|
708
|
+
this._sharedData.totalCodeCount = await this.#getCount(db, `SELECT COUNT(RXCUI) FROM rxnconso WHERE SAB = ? AND TTY <> 'SY'`, [sab]);
|
|
709
|
+
|
|
710
|
+
} finally {
|
|
711
|
+
db.close();
|
|
712
|
+
}
|
|
713
|
+
this._loaded = true;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
async #readVersion(db) {
|
|
717
|
+
return new Promise((resolve) => {
|
|
718
|
+
db.get('SELECT version FROM RXNVer', (err, row) => {
|
|
719
|
+
if (err || !row) {
|
|
720
|
+
// Fallback: try to extract version from database path
|
|
721
|
+
const dbDetails = this.dbPath;
|
|
722
|
+
let version = '??';
|
|
723
|
+
|
|
724
|
+
if (dbDetails.includes('.db')) {
|
|
725
|
+
let d = dbDetails.substring(0, dbDetails.indexOf('.db'));
|
|
726
|
+
if (d.includes('_')) {
|
|
727
|
+
d = d.substring(d.lastIndexOf('_') + 1);
|
|
728
|
+
}
|
|
729
|
+
if (/^\d+$/.test(d)) {
|
|
730
|
+
version = d;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
resolve(version);
|
|
734
|
+
} else {
|
|
735
|
+
resolve(row.version.toString());
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async #loadList(db, sql) {
|
|
742
|
+
return new Promise((resolve, reject) => {
|
|
743
|
+
db.all(sql, (err, rows) => {
|
|
744
|
+
if (err) {
|
|
745
|
+
reject(err);
|
|
746
|
+
} else {
|
|
747
|
+
resolve(rows.map(row => Object.values(row)[0]));
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
async #getCount(db, sql, params = []) {
|
|
754
|
+
return new Promise((resolve, reject) => {
|
|
755
|
+
db.get(sql, params, (err, row) => {
|
|
756
|
+
if (err) {
|
|
757
|
+
reject(err);
|
|
758
|
+
} else {
|
|
759
|
+
resolve(row ? Object.values(row)[0] : 0);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
defaultVersion() {
|
|
766
|
+
return this._sharedData?.version || 'unknown';
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
async build(opContext, supplements) {
|
|
770
|
+
await this.#ensureLoaded();
|
|
771
|
+
this.recordUse();
|
|
772
|
+
|
|
773
|
+
// Create fresh database connection for this provider instance
|
|
774
|
+
const db = new sqlite3.Database(this.dbPath);
|
|
775
|
+
|
|
776
|
+
return new RxNormServices(opContext, supplements, db, this._sharedData, this.isNCI);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
name() {
|
|
780
|
+
return this.isNCI ? 'NCI' : 'RxNorm';
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
id() {
|
|
784
|
+
return this.name();
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Specific RxNorm implementation
|
|
790
|
+
class RxNormServicesFactory extends RxNormTypeServicesFactory {
|
|
791
|
+
constructor(languageDefinitions, dbPath) {
|
|
792
|
+
super(languageDefinitions, dbPath, false);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// NCI Meta implementation
|
|
797
|
+
class NCIServicesFactory extends RxNormTypeServicesFactory {
|
|
798
|
+
constructor(languageDefinitions, dbPath) {
|
|
799
|
+
super(languageDefinitions, dbPath, true);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
module.exports = {
|
|
804
|
+
RxNormServices,
|
|
805
|
+
RxNormServicesFactory,
|
|
806
|
+
NCIServicesFactory,
|
|
807
|
+
RxNormConcept
|
|
808
|
+
};
|