fhirsmith 0.18.1 → 1.0.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/ACCESSIBILITY.md +119 -0
- package/CHANGELOG.md +190 -9
- package/CONFIGURATION.md +305 -0
- package/LICENSE +29 -0
- package/README.md +29 -122
- package/cluster.js +1220 -0
- package/cluster.md +707 -0
- package/config-template.json +77 -8
- package/extension-tracker/extension-tracker-template.html +5 -8
- package/extension-tracker/extension-tracker.js +0 -2
- package/folder/folder.js +7 -6
- package/ips/analysis.js +560 -0
- package/ips/ips-template.html +5 -8
- package/ips/ips.js +331 -33
- package/ips/jurisdictions.js +65 -0
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +131 -17
- package/ips/presenter.js +1 -1
- package/ips/profile-editor.js +283 -4
- package/ips/readme.md +170 -20
- package/ips/render.js +769 -109
- package/ips/scripts.js +46 -0
- package/ips/store.js +123 -11
- package/ips/structure.js +247 -0
- package/ips/terminology.js +1 -1
- package/library/body-limit.js +22 -0
- package/library/cache-generation.js +15 -0
- package/library/cgroup-memory.js +133 -1
- package/library/cluster-counts.js +121 -0
- package/library/cluster-events.js +153 -0
- package/library/cluster-metrics.js +193 -0
- package/library/cluster-status.js +468 -0
- package/library/config-reader.js +96 -0
- package/library/daily-report.js +100 -2
- package/library/html-server.js +27 -1
- package/library/i18nsupport.js +23 -1
- package/library/keep-alive.js +18 -0
- package/library/languages.js +17 -3
- package/library/logger.js +31 -0
- package/library/memory-guard.js +428 -0
- package/library/native-memory.js +52 -0
- package/library/npm-audit.js +51 -25
- package/library/openapi-doc.js +7 -5
- package/library/package-manager.js +8 -0
- package/library/role.js +197 -0
- package/library/server-credentials.js +169 -0
- package/library/sqlite-utilities.js +59 -0
- package/library/ssrf-guard.js +100 -0
- package/library/static-response-stats.js +16 -0
- package/library/version-utilities.js +50 -0
- package/library/worker-count.js +192 -0
- package/nginx.md +30 -11
- package/npmprojector/indexer.js +18 -10
- package/package.json +11 -8
- package/packages/feed-check-page.js +117 -0
- package/packages/feed-check.js +279 -0
- package/packages/npm-check.js +57 -3
- package/packages/npm-signing.js +219 -0
- package/packages/openapi.yaml +207 -1
- package/packages/package-crawler.js +322 -41
- package/packages/packages-template.html +6 -8
- package/packages/packages.js +604 -57
- package/packages/readme.md +113 -1
- package/publisher/publisher-template.html +2 -5
- package/publisher/publisher.js +121 -110
- package/publisher/readme.md +75 -0
- package/registry/api.js +179 -1
- package/registry/crawler.js +86 -108
- package/registry/documentation/api.md +68 -2
- package/registry/documentation/configuration.md +34 -0
- package/registry/documentation/internals.md +4 -1
- package/registry/documentation/readme.md +3 -2
- package/registry/model.js +8 -0
- package/registry/openapi.yaml +84 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +58 -6
- package/root-bare-template.html +7 -4
- package/root-template.html +5 -8
- package/server.js +398 -198
- package/shl/shl.js +38 -37
- package/static/a11y/a11y-check.js +832 -0
- package/static/a11y/axe.min.js +12 -0
- package/static/assets/css/bootstrap-fhir.css +15 -15
- package/static/assets/images/flags/au.svg +18 -0
- package/static/assets/images/flags/unknown.svg +11 -0
- package/static/assets/images/flags/us.svg +63 -0
- package/static/assets/images/icon_audit.png +0 -0
- package/static/assets/images/icon_clinical.png +0 -0
- package/static/assets/images/icon_document.png +0 -0
- package/static/assets/images/icon_external.png +0 -0
- package/static/assets/images/icon_medication.png +0 -0
- package/static/assets/images/icon_organization.png +0 -0
- package/static/assets/images/icon_other.png +0 -0
- package/static/assets/images/icon_person.png +0 -0
- package/static/assets/images/icon_section.png +0 -0
- package/stats-db.js +45 -0
- package/stats.js +197 -10
- package/testing/testing-template.html +5 -8
- package/token/nginx_passport_setup.md +1 -1
- package/token/security_guide.md +1 -1
- package/token/token-template.html +2 -5
- package/token/token.js +40 -36
- package/translations/Messages.properties +19 -0
- package/tx/README.md +158 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +50 -16
- package/tx/cs/cs-api.js +284 -19
- package/tx/cs/cs-api.md +41 -4
- package/tx/cs/cs-areacode.js +1 -1
- package/tx/cs/cs-country.js +17 -5
- package/tx/cs/cs-cpt.js +33 -16
- package/tx/cs/cs-cs.js +199 -24
- package/tx/cs/cs-currency.js +1 -1
- package/tx/cs/cs-icd11.js +21 -13
- package/tx/cs/cs-icf.js +22 -3
- package/tx/cs/cs-lang.js +2 -2
- package/tx/cs/cs-loinc.js +193 -67
- package/tx/cs/cs-mimetypes.js +12 -2
- package/tx/cs/cs-ndc.js +40 -25
- package/tx/cs/cs-omop.js +54 -29
- package/tx/cs/cs-rxnorm.js +300 -196
- package/tx/cs/cs-snomed.js +861 -95
- package/tx/cs/cs-ucum.js +14 -4
- package/tx/cs/cs-unii.js +18 -14
- package/tx/cs/cs-virtual.js +210 -0
- package/tx/ecosystem-resolver.js +413 -0
- package/tx/html/cluster-dash-metrics.liquid +26 -0
- package/tx/html/cluster-home-metrics.liquid +31 -0
- package/tx/html/cluster-metrics-script.liquid +251 -0
- package/tx/html/codesystem-operations.liquid +3 -3
- package/tx/html/ecl-panel.liquid +1 -1
- package/tx/html/external-metrics.liquid +17 -0
- package/tx/html/operations-form.liquid +17 -17
- package/tx/html/search-form.liquid +11 -11
- package/tx/html/status-line.liquid +58 -0
- package/tx/html/status-panel.liquid +140 -0
- package/tx/html/status-tasks.liquid +25 -0
- package/tx/html/tx-template.html +2 -5
- package/tx/importers/atc-to-fhir.js +17 -16
- package/tx/importers/export-icd11-codesystem.js +3 -2
- package/tx/importers/import-icd11.js +4 -3
- package/tx/importers/import-loinc.module.js +118 -56
- package/tx/importers/import-ndc.module.js +56 -44
- package/tx/importers/import-rxnorm.module.js +118 -79
- package/tx/importers/import-sct.module.js +125 -61
- package/tx/importers/import-unii.module.js +37 -36
- package/tx/importers/subset-loinc.module.js +49 -48
- package/tx/importers/subset-rxnorm.module.js +29 -28
- package/tx/importers/terminal.js +18 -0
- package/tx/importers/tx-import-base.js +13 -12
- package/tx/importers/tx-import-settings.js +5 -4
- package/tx/importers/tx-import.js +53 -52
- package/tx/library/canonical-resource.js +49 -0
- package/tx/library/codesystem.js +94 -5
- package/tx/library/designations.js +45 -2
- package/tx/library/extensions.js +13 -7
- package/tx/library/implicit-valuesets.js +66 -0
- package/tx/library/renderer.js +77 -21
- package/tx/library/sqlite-utilities.js +2 -0
- package/tx/library/tho-precedence.js +40 -0
- package/tx/library/ucum-parsers.js +11 -1
- package/tx/library/wait-for-cache.js +43 -0
- package/tx/library.js +321 -22
- package/tx/openapi-operations.js +6 -4
- package/tx/operation-context.js +460 -99
- package/tx/params.js +25 -0
- package/tx/problems.js +3 -5
- package/tx/provider.js +314 -67
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/documentation.md +45 -21
- package/tx/sct/ecl.js +81 -17
- package/tx/sct/editions.js +39 -0
- package/tx/sct/expressions.js +10 -1
- package/tx/sct/load-config.js +107 -0
- package/tx/sct/mmap.js +109 -0
- package/tx/sct/structures.js +175 -15
- package/tx/sct/text-index.js +5 -1
- package/tx/syndication/syndication-feed.js +2 -2
- package/tx/tx-html.js +44 -8
- package/tx/tx.fhir.org.yml +8 -8
- package/tx/tx.js +282 -80
- package/tx/usage-tracker.js +39 -11
- package/tx/vs/vs-api.js +19 -0
- package/tx/vs/vs-database.js +386 -62
- package/tx/vs/vs-package.js +44 -17
- package/tx/vs/vs-vsac.js +649 -101
- package/tx/workers/batch-validate.js +37 -10
- package/tx/workers/batch.js +6 -1
- package/tx/workers/cache-control.js +15 -4
- package/tx/workers/closure.js +156 -13
- package/tx/workers/compare.js +8 -5
- package/tx/workers/expand.js +1173 -289
- package/tx/workers/metadata.js +35 -0
- package/tx/workers/search.js +53 -1
- package/tx/workers/supplement-filters.js +145 -0
- package/tx/workers/translate.js +169 -4
- package/tx/workers/validate.js +223 -119
- package/tx/workers/worker.js +123 -31
- package/tx/xversion/xv-valueset.js +7 -4
- package/utilities/submit-test-report.js +3 -1
- package/utilities/tx-stress-test.js +482 -0
- package/vcl/readme.md +37 -0
- package/vcl/vcl-parser.js +15 -0
- package/xig/readme.md +52 -0
- package/xig/xig-template.html +5 -8
- package/xig/xig.js +238 -422
- package/changelog-template.md +0 -22
- package/registry/test-runner.js +0 -237
- package/release.sh +0 -80
- package/test-scripts/repro-re2-wasm-leak.js +0 -34
- package/tsconfig.eslint.json +0 -9
- package/tx/html/dash-metrics.liquid +0 -147
- package/tx/html/home-metrics.liquid +0 -225
package/tx/cs/cs-rxnorm.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
const sqlite3 = require('sqlite3').verbose();
|
|
2
2
|
const assert = require('assert');
|
|
3
3
|
const { CodeSystem } = require('../library/codesystem');
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
4
|
+
const { CodeSystemFactoryProvider } = require('./cs-api');
|
|
5
|
+
const { BaseCSServices } = require('./cs-base');
|
|
6
6
|
const {validateArrayParameter, formatDateMMDDYYYY} = require("../../library/utilities");
|
|
7
|
+
const Logger = require("../../library/logger");
|
|
8
|
+
const { guarded } = require('../library/sqlite-utilities');
|
|
7
9
|
|
|
8
10
|
// Context for RxNorm concepts
|
|
9
11
|
class RxNormConcept {
|
|
@@ -12,9 +14,43 @@ class RxNormConcept {
|
|
|
12
14
|
this.display = display;
|
|
13
15
|
this.others = []; // Array of alternative displays (SY terms, etc.)
|
|
14
16
|
this.archived = false;
|
|
17
|
+
this.suppress = null; // SUPPRESS of the display atom, when known: N, E, O (obsolete) or Y
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get obsolete() {
|
|
21
|
+
return this.suppress === 'O';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Which RXNORM atom gives a concept its display: the atom with its main term type (IN, SCD,
|
|
26
|
+
// BN...), or failing that a prescribable name, an entry term, or a tall man synonym. SY atoms
|
|
27
|
+
// are never the display. As an SQL expression that is lowest for the best atom
|
|
28
|
+
const DISPLAY_RANK_SQL = `CASE TTY WHEN 'PSN' THEN 1 WHEN 'ET' THEN 2 WHEN 'TMSY' THEN 3 WHEN 'SY' THEN 4 ELSE 0 END`;
|
|
29
|
+
function displayRank(tty) {
|
|
30
|
+
switch (tty) {
|
|
31
|
+
case 'PSN': return 1;
|
|
32
|
+
case 'ET': return 2;
|
|
33
|
+
case 'TMSY': return 3;
|
|
34
|
+
case 'SY': return 4;
|
|
35
|
+
default: return 0;
|
|
15
36
|
}
|
|
16
37
|
}
|
|
17
38
|
|
|
39
|
+
// RxNorm's own relationships are the only ones used, for now (filters and $lookup). RXNREL
|
|
40
|
+
// holds every source's (MTHSPL, VANDF...), and they are all imported
|
|
41
|
+
const RELATIONSHIP_SAB = 'RXNORM';
|
|
42
|
+
|
|
43
|
+
// indexes the provider needs beyond what older imports made (see also import-rxnorm.module.js)
|
|
44
|
+
const RXNORM_LOAD_INDEXES = [
|
|
45
|
+
'CREATE INDEX IF NOT EXISTS idx_rxnstems_cui_stem ON RXNSTEMS(CUI, stem)',
|
|
46
|
+
'CREATE INDEX IF NOT EXISTS idx_rxnrel_rxcui2 ON RXNREL(RXCUI2, SAB)',
|
|
47
|
+
'CREATE INDEX IF NOT EXISTS idx_rxnrel_rela_rxcui1 ON RXNREL(RELA, RXCUI1)',
|
|
48
|
+
'CREATE INDEX IF NOT EXISTS idx_rxnrel_rela_rxaui1 ON RXNREL(RELA, RXAUI1)',
|
|
49
|
+
'CREATE INDEX IF NOT EXISTS idx_rxnsty_rxcui ON RXNSTY(RXCUI)',
|
|
50
|
+
// locate() looks an unknown code up in the archive; without this, each miss scans the table
|
|
51
|
+
'CREATE INDEX IF NOT EXISTS idx_rxnatomarchive_rxcui_sab ON RXNATOMARCHIVE(RXCUI, SAB)'
|
|
52
|
+
];
|
|
53
|
+
|
|
18
54
|
// Filter holder for query building and iteration
|
|
19
55
|
class RxNormFilterHolder {
|
|
20
56
|
constructor() {
|
|
@@ -68,7 +104,7 @@ class RxNormIteratorContext {
|
|
|
68
104
|
}
|
|
69
105
|
}
|
|
70
106
|
|
|
71
|
-
class RxNormServices extends
|
|
107
|
+
class RxNormServices extends BaseCSServices {
|
|
72
108
|
constructor(opContext, supplements, db, sharedData, isNCI = false) {
|
|
73
109
|
super(opContext, supplements);
|
|
74
110
|
this.db = db;
|
|
@@ -82,10 +118,7 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
82
118
|
}
|
|
83
119
|
|
|
84
120
|
close() {
|
|
85
|
-
|
|
86
|
-
this.db.close();
|
|
87
|
-
this.db = null;
|
|
88
|
-
}
|
|
121
|
+
this.closeDb();
|
|
89
122
|
}
|
|
90
123
|
|
|
91
124
|
// Metadata methods
|
|
@@ -162,19 +195,7 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
162
195
|
if (ctxt && ctxt.archived) {
|
|
163
196
|
return 'archived';
|
|
164
197
|
}
|
|
165
|
-
|
|
166
|
-
// Check suppress flag
|
|
167
|
-
return new Promise((resolve, reject) => {
|
|
168
|
-
const sql = `SELECT suppress FROM rxnconso WHERE ${this.getCodeField()} = ? AND SAB = ? AND TTY <> 'SY'`;
|
|
169
|
-
|
|
170
|
-
this.db.get(sql, [ctxt.code, this.getSAB()], (err, row) => {
|
|
171
|
-
if (err) {
|
|
172
|
-
reject(err);
|
|
173
|
-
} else {
|
|
174
|
-
resolve(row ? row.suppress === '1' ? 'suppressed' : null : null);
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
});
|
|
198
|
+
return (await this.#isObsolete(ctxt)) ? 'obsolete' : null;
|
|
178
199
|
}
|
|
179
200
|
|
|
180
201
|
async isInactive(context) {
|
|
@@ -184,19 +205,24 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
184
205
|
if (ctxt && ctxt.archived) {
|
|
185
206
|
return true;
|
|
186
207
|
}
|
|
208
|
+
return this.#isObsolete(ctxt);
|
|
209
|
+
}
|
|
187
210
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
211
|
+
/**
|
|
212
|
+
* A concept is obsolete (inactive) when its display atom is: SUPPRESS = O
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
215
|
+
async #isObsolete(ctxt) {
|
|
216
|
+
if (!ctxt) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (ctxt.suppress == null) {
|
|
220
|
+
const row = await this.#dbGet(
|
|
221
|
+
`SELECT SUPPRESS, MIN(${DISPLAY_RANK_SQL}) AS rnk FROM rxnconso WHERE ${this.getCodeField()} = ? AND SAB = ? AND TTY <> 'SY'`,
|
|
222
|
+
[ctxt.code, this.getSAB()]);
|
|
223
|
+
ctxt.suppress = row && row.SUPPRESS ? row.SUPPRESS : 'N';
|
|
224
|
+
}
|
|
225
|
+
return ctxt.obsolete;
|
|
200
226
|
}
|
|
201
227
|
|
|
202
228
|
async isDeprecated(context) {
|
|
@@ -248,9 +274,9 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
248
274
|
if (!code) return { context: null, message: 'Empty code' };
|
|
249
275
|
|
|
250
276
|
return new Promise((resolve, reject) => {
|
|
251
|
-
let sql = `SELECT STR, TTY FROM rxnconso WHERE ${this.getCodeField()} = ? AND SAB = ?`;
|
|
277
|
+
let sql = `SELECT STR, TTY, SUPPRESS FROM rxnconso WHERE ${this.getCodeField()} = ? AND SAB = ?`;
|
|
252
278
|
|
|
253
|
-
this.db.all(sql, [code, this.getSAB()], (err, rows) => {
|
|
279
|
+
this.db.all(sql, [code, this.getSAB()], guarded(reject, (err, rows) => {
|
|
254
280
|
if (err) {
|
|
255
281
|
reject(err);
|
|
256
282
|
return;
|
|
@@ -259,7 +285,7 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
259
285
|
if (rows.length === 0) {
|
|
260
286
|
// Try archive
|
|
261
287
|
sql = `SELECT STR, TTY FROM RXNATOMARCHIVE WHERE ${this.getCodeField()} = ? AND SAB = ?`;
|
|
262
|
-
this.db.all(sql, [code, this.getSAB()], (err, archiveRows) => {
|
|
288
|
+
this.db.all(sql, [code, this.getSAB()], guarded(reject, (err, archiveRows) => {
|
|
263
289
|
if (err) {
|
|
264
290
|
reject(err);
|
|
265
291
|
return;
|
|
@@ -272,12 +298,12 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
272
298
|
|
|
273
299
|
const concept = this.#createConceptFromRows(code, archiveRows, true);
|
|
274
300
|
resolve({ context: concept, message: null });
|
|
275
|
-
});
|
|
301
|
+
}));
|
|
276
302
|
} else {
|
|
277
303
|
const concept = this.#createConceptFromRows(code, rows, false);
|
|
278
304
|
resolve({ context: concept, message: null });
|
|
279
305
|
}
|
|
280
|
-
});
|
|
306
|
+
}));
|
|
281
307
|
});
|
|
282
308
|
}
|
|
283
309
|
|
|
@@ -285,24 +311,68 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
285
311
|
const concept = new RxNormConcept(code);
|
|
286
312
|
concept.archived = archived;
|
|
287
313
|
|
|
314
|
+
// the display is the best ranked atom (see DISPLAY_RANK_SQL), the rest are other names
|
|
315
|
+
let best = null;
|
|
288
316
|
for (const row of rows) {
|
|
289
|
-
if (row.TTY
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
317
|
+
if (row.TTY !== 'SY' && (!best || displayRank(row.TTY) < displayRank(best.TTY))) {
|
|
318
|
+
best = row;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (best) {
|
|
322
|
+
concept.display = best.STR.trim();
|
|
323
|
+
if (!archived) {
|
|
324
|
+
concept.suppress = best.SUPPRESS || 'N';
|
|
293
325
|
}
|
|
294
326
|
}
|
|
327
|
+
for (const row of rows) {
|
|
328
|
+
const str = row.STR.trim();
|
|
329
|
+
if (row !== best && str !== concept.display && !concept.others.includes(str)) {
|
|
330
|
+
concept.others.push(str);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return concept;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The query for a set of concepts: one row per concept, with its display, whatever atoms
|
|
339
|
+
* the conditions matched on. Every condition is about the concept (its code), not the atom
|
|
340
|
+
* @param {string} conditions - SQL 'AND ...' conditions on rxnconso.<code field>
|
|
341
|
+
* @param {string} [from] - more tables to join (the text search stems)
|
|
342
|
+
* @returns {string}
|
|
343
|
+
* @private
|
|
344
|
+
*/
|
|
345
|
+
#conceptQuery(conditions, from = '') {
|
|
346
|
+
const cf = this.getCodeField();
|
|
347
|
+
return `SELECT rxnconso.${cf} AS ${cf}, STR, SUPPRESS, MIN(${DISPLAY_RANK_SQL}) AS rnk FROM rxnconso${from} ` +
|
|
348
|
+
`WHERE SAB = $sab AND TTY <> 'SY' ${conditions} GROUP BY rxnconso.${cf}`;
|
|
349
|
+
}
|
|
295
350
|
|
|
351
|
+
#conceptFromRow(row) {
|
|
352
|
+
const concept = new RxNormConcept(row[this.getCodeField()], row.STR);
|
|
353
|
+
concept.suppress = row.SUPPRESS || 'N';
|
|
296
354
|
return concept;
|
|
297
355
|
}
|
|
298
356
|
|
|
357
|
+
#dbGet(sql, params) {
|
|
358
|
+
return new Promise((resolve, reject) => {
|
|
359
|
+
this.db.get(sql, params, (err, row) => err ? reject(err) : resolve(row));
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
#dbAll(sql, params) {
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
this.db.all(sql, params, (err, rows) => err ? reject(err) : resolve(rows));
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
299
369
|
// Iterator methods
|
|
300
370
|
async iterator(context) {
|
|
301
371
|
|
|
302
372
|
|
|
303
373
|
if (!context) {
|
|
304
374
|
// Iterate all codes
|
|
305
|
-
const query =
|
|
375
|
+
const query = this.#conceptQuery('') + ` ORDER BY rxnconso.${this.getCodeField()}`;
|
|
306
376
|
return new RxNormIteratorContext(query, { sab: this.getSAB() }, this.totalCodeCount);
|
|
307
377
|
} else {
|
|
308
378
|
// No hierarchical iteration for specific contexts in this implementation. The expander
|
|
@@ -326,8 +396,7 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
326
396
|
const row = iteratorContext.results[iteratorContext.cursor];
|
|
327
397
|
iteratorContext.next();
|
|
328
398
|
|
|
329
|
-
|
|
330
|
-
return concept;
|
|
399
|
+
return this.#conceptFromRow(row);
|
|
331
400
|
}
|
|
332
401
|
|
|
333
402
|
async #executeIterator(iteratorContext) {
|
|
@@ -345,36 +414,42 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
345
414
|
}
|
|
346
415
|
|
|
347
416
|
// Filter support
|
|
348
|
-
|
|
349
|
-
|
|
417
|
+
//
|
|
418
|
+
// Every filter is a condition on the concept (rxnconso.<code field> IN ...), not on the atom,
|
|
419
|
+
// so a concept with several atoms (SCD + PSN + TMSY...) is one result, whichever atoms matched.
|
|
420
|
+
// Filters in one include are ANDed. TTY, STY, SAB, concept (or code), and the relationships
|
|
421
|
+
// (REL or RELA, with CUI: or AUI: values) all take = or in; in is the union
|
|
350
422
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
423
|
+
/**
|
|
424
|
+
* @returns {string[]} the values of an = or in filter
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
static #filterValues(op, value) {
|
|
428
|
+
if (op === 'in') {
|
|
429
|
+
return (value || '').split(',').map(v => v.trim()).filter(v => v);
|
|
356
430
|
}
|
|
431
|
+
return value ? [value.trim()] : [];
|
|
432
|
+
}
|
|
357
433
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
434
|
+
static #isRelValue(v) {
|
|
435
|
+
return v.startsWith('CUI:') || v.startsWith('AUI:');
|
|
436
|
+
}
|
|
362
437
|
|
|
363
|
-
|
|
364
|
-
if (
|
|
365
|
-
return
|
|
438
|
+
async doesFilter(prop, op, value) {
|
|
439
|
+
if (!['=', 'in'].includes(op)) {
|
|
440
|
+
return false;
|
|
366
441
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return true;
|
|
442
|
+
const values = RxNormServices.#filterValues(op, value);
|
|
443
|
+
if (values.length === 0) {
|
|
444
|
+
return false;
|
|
371
445
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (this.reltypes.includes(prop) && op === '=' && (value.startsWith('CUI:') || value.startsWith('AUI:'))) {
|
|
446
|
+
const propUC = prop.toUpperCase();
|
|
447
|
+
if (['TTY', 'STY', 'SAB'].includes(propUC) || prop === 'concept' || prop === 'code') {
|
|
375
448
|
return true;
|
|
376
449
|
}
|
|
377
|
-
|
|
450
|
+
if (this.rels.includes(prop) || this.reltypes.includes(prop)) {
|
|
451
|
+
return values.every(v => RxNormServices.#isRelValue(v));
|
|
452
|
+
}
|
|
378
453
|
return false;
|
|
379
454
|
}
|
|
380
455
|
|
|
@@ -384,55 +459,47 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
384
459
|
}
|
|
385
460
|
|
|
386
461
|
async filter(filterContext, forIteration, prop, op, value) {
|
|
387
|
-
|
|
388
|
-
|
|
389
462
|
const filter = new RxNormFilterHolder();
|
|
390
|
-
|
|
463
|
+
const propUC = prop.toUpperCase();
|
|
464
|
+
const cf = this.getCodeField();
|
|
465
|
+
const values = ['=', 'in'].includes(op) ? RxNormServices.#filterValues(op, value) : [];
|
|
466
|
+
// each filter gets its own parameter names, so two filters of the same kind in one
|
|
467
|
+
// include don't overwrite each other's values (or the SAB the whole query is about)
|
|
468
|
+
const pfx = `f${filterContext.filters.length}_`;
|
|
469
|
+
const params = {};
|
|
470
|
+
const list = (name, vals) => vals.map((v, i) => {
|
|
471
|
+
params[`${pfx}${name}${i}`] = v;
|
|
472
|
+
return `$${pfx}${name}${i}`;
|
|
473
|
+
}).join(', ');
|
|
391
474
|
|
|
392
475
|
let sql = '';
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
sql = `AND TTY IN (${placeholders})`;
|
|
399
|
-
values.forEach((val, i) => {
|
|
400
|
-
params[`tty${i}`] = this.#sqlWrapString(val);
|
|
401
|
-
});
|
|
402
|
-
} else if (op === '=') {
|
|
403
|
-
if (propUC === 'STY') {
|
|
404
|
-
sql = `AND ${this.getCodeField()} IN (SELECT RXCUI FROM rxnsty WHERE TUI = $sty)`;
|
|
405
|
-
params.sty = this.#sqlWrapString(value);
|
|
476
|
+
if (values.length > 0) {
|
|
477
|
+
if (propUC === 'TTY') {
|
|
478
|
+
sql = `AND rxnconso.${cf} IN (SELECT ${cf} FROM rxnconso WHERE SAB = $sab AND TTY IN (${list('tty', values)}))`;
|
|
479
|
+
} else if (propUC === 'STY') {
|
|
480
|
+
sql = `AND rxnconso.${cf} IN (SELECT RXCUI FROM rxnsty WHERE TUI IN (${list('sty', values)}))`;
|
|
406
481
|
} else if (propUC === 'SAB') {
|
|
407
|
-
sql = `AND
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
params.rel = this.#sqlWrapString(prop);
|
|
422
|
-
params.aui2 = this.#sqlWrapString(aui);
|
|
482
|
+
sql = `AND rxnconso.${cf} IN (SELECT ${cf} FROM rxnconso WHERE SAB IN (${list('sab', values)}))`;
|
|
483
|
+
} else if (prop === 'concept' || prop === 'code') {
|
|
484
|
+
sql = `AND rxnconso.${cf} IN (${list('code', values)})`;
|
|
485
|
+
} else if ((this.rels.includes(prop) || this.reltypes.includes(prop)) && values.every(v => RxNormServices.#isRelValue(v))) {
|
|
486
|
+
// RXNREL rows read 'RXCUI2 <RELA> RXCUI1': has_ingredient = CUI:161 is the concepts
|
|
487
|
+
// that have acetaminophen as an ingredient. Concept-level rows: RxNorm's own (SAB=RXNORM)
|
|
488
|
+
const field = this.rels.includes(prop) ? 'REL' : 'RELA';
|
|
489
|
+
params[`${pfx}rel`] = prop;
|
|
490
|
+
const cuis = values.filter(v => v.startsWith('CUI:')).map(v => v.substring(4));
|
|
491
|
+
const auis = values.filter(v => v.startsWith('AUI:')).map(v => v.substring(4));
|
|
492
|
+
const targets = [];
|
|
493
|
+
if (cuis.length > 0) {
|
|
494
|
+
params[`${pfx}rsab`] = RELATIONSHIP_SAB;
|
|
495
|
+
targets.push(`rxnconso.${cf} IN (SELECT RXCUI2 FROM rxnrel WHERE ${field} = $${pfx}rel AND SAB = $${pfx}rsab AND RXCUI1 IN (${list('cui', cuis)}))`);
|
|
423
496
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
params.rela = this.#sqlWrapString(prop);
|
|
429
|
-
params.cui2 = this.#sqlWrapString(cui);
|
|
430
|
-
} else if (value.startsWith('AUI:')) {
|
|
431
|
-
const aui = value.substring(4);
|
|
432
|
-
sql = `AND (${this.getCodeField()} IN (SELECT ${this.getCodeField()} FROM rxnconso WHERE RXAUI IN (SELECT RXAUI2 FROM rxnrel WHERE RELA = $rela AND RXAUI1 = $aui2)))`;
|
|
433
|
-
params.rela = this.#sqlWrapString(prop);
|
|
434
|
-
params.aui2 = this.#sqlWrapString(aui);
|
|
497
|
+
if (auis.length > 0) {
|
|
498
|
+
// Atom-level rows leave RXCUI2 empty, and most come from other sources (MMSL, MTHSPL...):
|
|
499
|
+
// the target atom's concept comes from rxnconso, and every source's rows count
|
|
500
|
+
targets.push(`rxnconso.${cf} IN (SELECT ${cf} FROM rxnconso WHERE RXAUI IN (SELECT RXAUI2 FROM rxnrel WHERE ${field} = $${pfx}rel AND RXAUI1 IN (${list('aui', auis)})))`);
|
|
435
501
|
}
|
|
502
|
+
sql = `AND (${targets.join(' OR ')})`;
|
|
436
503
|
}
|
|
437
504
|
}
|
|
438
505
|
|
|
@@ -475,9 +542,11 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
475
542
|
} else {
|
|
476
543
|
// Not in the stem table, so match the token against the text itself. Left as a
|
|
477
544
|
// plain (non-text) filter so that executeFilters does not join RXNSTEMS for it.
|
|
478
|
-
// Unindexed, but it only ever narrows
|
|
479
|
-
// and the tokens are alphanumeric, so there are no LIKE wildcards to escape.
|
|
480
|
-
|
|
545
|
+
// Unindexed, but it only ever narrows concepts the other tokens have already found,
|
|
546
|
+
// and the tokens are alphanumeric, so there are no LIKE wildcards to escape. Any of
|
|
547
|
+
// the concept's RxNorm names can match, as for the stems
|
|
548
|
+
const cf = this.getCodeField();
|
|
549
|
+
rxnormFilter.sql = ` AND EXISTS (SELECT 1 FROM rxnconso t WHERE t.${cf} = rxnconso.${cf} AND t.SAB = $sab AND t.STR LIKE $text${i})`;
|
|
481
550
|
rxnormFilter.params[`text${i}`] = '%' + ((filter.words && filter.words[i]) || stem) + '%';
|
|
482
551
|
}
|
|
483
552
|
|
|
@@ -530,12 +599,13 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
530
599
|
}
|
|
531
600
|
}
|
|
532
601
|
|
|
533
|
-
const fullQuery = `SELECT ${this.getCodeField()}, STR ${sql2} WHERE SAB = $sab AND TTY <> 'SY' ${sql1}`;
|
|
534
602
|
allParams.sab = this.getSAB();
|
|
535
603
|
|
|
536
604
|
// Create a single filter holder with the combined query
|
|
537
605
|
const combinedFilter = new RxNormFilterHolder();
|
|
538
|
-
combinedFilter.
|
|
606
|
+
combinedFilter.conditions = sql1;
|
|
607
|
+
combinedFilter.from = sql2.substring('FROM rxnconso'.length);
|
|
608
|
+
combinedFilter.sql = this.#conceptQuery(sql1, combinedFilter.from);
|
|
539
609
|
combinedFilter.params = allParams;
|
|
540
610
|
|
|
541
611
|
return [combinedFilter];
|
|
@@ -550,8 +620,6 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
550
620
|
}
|
|
551
621
|
|
|
552
622
|
async filterMore(filterContext, set) {
|
|
553
|
-
|
|
554
|
-
|
|
555
623
|
if (!set.executed) {
|
|
556
624
|
await this.#executeFilter(set);
|
|
557
625
|
}
|
|
@@ -560,8 +628,6 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
560
628
|
}
|
|
561
629
|
|
|
562
630
|
async filterConcept(filterContext, set) {
|
|
563
|
-
|
|
564
|
-
|
|
565
631
|
if (!set.executed) {
|
|
566
632
|
await this.#executeFilter(set);
|
|
567
633
|
}
|
|
@@ -572,36 +638,18 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
572
638
|
|
|
573
639
|
const row = set.results[set.cursor];
|
|
574
640
|
set.cursor++;
|
|
575
|
-
|
|
576
|
-
const concept = new RxNormConcept(row[this.getCodeField()], row.STR);
|
|
577
|
-
return concept;
|
|
641
|
+
return this.#conceptFromRow(row);
|
|
578
642
|
}
|
|
579
643
|
|
|
580
644
|
async filterLocate(filterContext, set, code) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const params = { ...set.params, code };
|
|
588
|
-
|
|
589
|
-
this.db.get(checkQuery, this.#buildParamArray(checkQuery, params), (err, row) => {
|
|
590
|
-
if (err) {
|
|
591
|
-
reject(err);
|
|
592
|
-
} else if (!row) {
|
|
593
|
-
resolve(null);
|
|
594
|
-
} else {
|
|
595
|
-
const concept = new RxNormConcept(row[this.getCodeField()], row.STR);
|
|
596
|
-
resolve(concept);
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
});
|
|
645
|
+
const cf = this.getCodeField();
|
|
646
|
+
const sql = this.#conceptQuery((set.conditions || '') + ` AND rxnconso.${cf} = $code`, set.from || '');
|
|
647
|
+
const params = { ...set.params, code };
|
|
648
|
+
const row = await this.#dbGet(sql, this.#buildParamArray(sql, params));
|
|
649
|
+
return row ? this.#conceptFromRow(row) : null;
|
|
600
650
|
}
|
|
601
651
|
|
|
602
652
|
async filterCheck(filterContext, set, concept) {
|
|
603
|
-
|
|
604
|
-
|
|
605
653
|
if (!(concept instanceof RxNormConcept)) {
|
|
606
654
|
return false;
|
|
607
655
|
}
|
|
@@ -609,8 +657,10 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
609
657
|
if (!set.executed) {
|
|
610
658
|
await this.#executeFilter(set);
|
|
611
659
|
}
|
|
612
|
-
|
|
613
|
-
|
|
660
|
+
if (!set.codes) {
|
|
661
|
+
set.codes = new Set(set.results.map(row => row[this.getCodeField()]));
|
|
662
|
+
}
|
|
663
|
+
return set.codes.has(concept.code);
|
|
614
664
|
}
|
|
615
665
|
|
|
616
666
|
async #executeFilter(filter) {
|
|
@@ -653,23 +703,31 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
653
703
|
return paramArray;
|
|
654
704
|
}
|
|
655
705
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Subsumption testing
|
|
706
|
+
// Subsumption: RxNorm codes have no subsumption relationships (no hierarchy), so two codes
|
|
707
|
+
// that both exist are not-subsumed (the caller deals with equal codes: equivalent)
|
|
661
708
|
async subsumesTest(codeA, codeB) {
|
|
662
709
|
await this.#ensureContext(codeA);
|
|
663
710
|
await this.#ensureContext(codeB);
|
|
664
|
-
return 'not-subsumed';
|
|
711
|
+
return 'not-subsumed';
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* The concept's properties for $expand: built by extendLookup, so that an expansion reports
|
|
716
|
+
* the same properties $lookup does (see CodeSystemProvider._propertiesFromLookup)
|
|
717
|
+
*/
|
|
718
|
+
async properties(context, names) {
|
|
719
|
+
return this._propertiesFromLookup(context, names);
|
|
665
720
|
}
|
|
666
721
|
|
|
667
722
|
// Extension for lookup operation
|
|
723
|
+
//
|
|
724
|
+
// TTY, STY and SAB are returned unless other properties are asked for; the relationships
|
|
725
|
+
// (one property per REL/RELA, valued with the bare RXCUI of the other concept, RxNorm's
|
|
726
|
+
// own relationships only) only when asked for, by name or with *
|
|
668
727
|
async extendLookup(ctxt, props, params) {
|
|
669
728
|
validateArrayParameter(props, 'props', String);
|
|
670
729
|
validateArrayParameter(params, 'params', Object);
|
|
671
730
|
|
|
672
|
-
|
|
673
731
|
if (typeof ctxt === 'string') {
|
|
674
732
|
const located = await this.locate(ctxt);
|
|
675
733
|
if (!located.context) {
|
|
@@ -681,36 +739,43 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
681
739
|
if (!(ctxt instanceof RxNormConcept)) {
|
|
682
740
|
throw new Error('Invalid context for RxNorm lookup');
|
|
683
741
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
params.abstract = false;
|
|
687
|
-
|
|
688
|
-
// Add designations
|
|
689
|
-
const designations = new Designations(this.opContext.i18n.languageDefinitions);
|
|
690
|
-
await this.designations(ctxt, designations);
|
|
691
|
-
for (const designation of designations) {
|
|
692
|
-
this.#addProperty(params, 'designation', 'display', designation.value, designation.language);
|
|
742
|
+
if (ctxt.archived) {
|
|
743
|
+
return;
|
|
693
744
|
}
|
|
694
|
-
}
|
|
695
745
|
|
|
696
|
-
|
|
697
|
-
if (
|
|
698
|
-
|
|
746
|
+
const cf = this.getCodeField();
|
|
747
|
+
if (this._hasProp(props, 'TTY', true)) {
|
|
748
|
+
const rows = await this.#dbAll(`SELECT DISTINCT TTY FROM rxnconso WHERE ${cf} = ? AND SAB = ? ORDER BY TTY`, [ctxt.code, this.getSAB()]);
|
|
749
|
+
for (const row of rows) {
|
|
750
|
+
this._addCodeProperty(params, 'property', 'TTY', row.TTY);
|
|
751
|
+
}
|
|
699
752
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
753
|
+
if (this._hasProp(props, 'STY', true)) {
|
|
754
|
+
const rows = await this.#dbAll('SELECT DISTINCT TUI FROM rxnsty WHERE RXCUI = ? ORDER BY TUI', [ctxt.code]);
|
|
755
|
+
for (const row of rows) {
|
|
756
|
+
this._addCodeProperty(params, 'property', 'STY', row.TUI);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (this._hasProp(props, 'SAB', true)) {
|
|
760
|
+
const rows = await this.#dbAll(`SELECT DISTINCT SAB FROM rxnconso WHERE ${cf} = ? ORDER BY SAB`, [ctxt.code]);
|
|
761
|
+
for (const row of rows) {
|
|
762
|
+
this._addCodeProperty(params, 'property', 'SAB', row.SAB);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
if (props && props.length > 0) {
|
|
766
|
+
const rows = await this.#dbAll(
|
|
767
|
+
'SELECT DISTINCT REL, RELA, RXCUI1 FROM rxnrel WHERE RXCUI2 = ? AND SAB = ? ORDER BY RELA, REL, RXCUI1',
|
|
768
|
+
[ctxt.code, RELATIONSHIP_SAB]);
|
|
769
|
+
const seen = new Set();
|
|
770
|
+
for (const row of rows) {
|
|
771
|
+
const name = row.RELA || row.REL;
|
|
772
|
+
const key = name + '|' + row.RXCUI1;
|
|
773
|
+
if (name && row.RXCUI1 && !seen.has(key) && this._hasProp(props, name, false)) {
|
|
774
|
+
seen.add(key);
|
|
775
|
+
this._addCodeProperty(params, 'property', name, row.RXCUI1);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
711
778
|
}
|
|
712
|
-
|
|
713
|
-
params.parameter.push(property);
|
|
714
779
|
}
|
|
715
780
|
|
|
716
781
|
versionAlgorithm() {
|
|
@@ -719,10 +784,15 @@ class RxNormServices extends CodeSystemProvider {
|
|
|
719
784
|
}
|
|
720
785
|
|
|
721
786
|
class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
722
|
-
|
|
787
|
+
/**
|
|
788
|
+
* @param {Object} [options] - {readOnly: true} in a tx-worker (cluster.md): the master
|
|
789
|
+
* makes the indexes; this process opens the file read-only
|
|
790
|
+
*/
|
|
791
|
+
constructor(i18n, dbPath, isNCI = false, options = {}) {
|
|
723
792
|
super(i18n);
|
|
724
793
|
this.dbPath = dbPath;
|
|
725
794
|
this.isNCI = isNCI;
|
|
795
|
+
this.readOnly = !!options.readOnly;
|
|
726
796
|
this._loaded = false;
|
|
727
797
|
this._sharedData = null;
|
|
728
798
|
}
|
|
@@ -746,14 +816,32 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
746
816
|
}
|
|
747
817
|
}
|
|
748
818
|
|
|
819
|
+
#open() {
|
|
820
|
+
return this.readOnly ? new sqlite3.Database(this.dbPath, sqlite3.OPEN_READONLY) : new sqlite3.Database(this.dbPath);
|
|
821
|
+
}
|
|
822
|
+
|
|
749
823
|
async load() {
|
|
750
|
-
const db =
|
|
824
|
+
const db = this.#open();
|
|
751
825
|
|
|
752
826
|
try {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
827
|
+
// indexes the importer makes now, for databases imported before it did. A one-off cost
|
|
828
|
+
// the first time an older database is loaded. A database that can't be written (read-only
|
|
829
|
+
// file or mount) is used as it is: the indexes only make lookups faster. A tx-worker
|
|
830
|
+
// leaves them to the master
|
|
831
|
+
for (const sql of this.readOnly ? [] : RXNORM_LOAD_INDEXES) {
|
|
832
|
+
await new Promise((resolve, reject) => {
|
|
833
|
+
db.run(sql, guarded(reject, err => {
|
|
834
|
+
if (err && err.code === 'SQLITE_READONLY') {
|
|
835
|
+
Logger.getInstance().child({module: 'tx-rxnorm'}).warn(`${this.dbPath} is read-only; lookups will be slower without: ${sql}`);
|
|
836
|
+
resolve();
|
|
837
|
+
} else if (err) {
|
|
838
|
+
reject(err);
|
|
839
|
+
} else {
|
|
840
|
+
resolve();
|
|
841
|
+
}
|
|
842
|
+
}));
|
|
843
|
+
});
|
|
844
|
+
}
|
|
757
845
|
|
|
758
846
|
this._sharedData = {
|
|
759
847
|
version: '',
|
|
@@ -773,7 +861,8 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
773
861
|
|
|
774
862
|
// Get total count
|
|
775
863
|
const sab = this.isNCI ? 'NCI' : 'RXNORM';
|
|
776
|
-
|
|
864
|
+
// concepts, not atoms
|
|
865
|
+
this._sharedData.totalCodeCount = await this.#getCount(db, `SELECT COUNT(DISTINCT RXCUI) FROM rxnconso WHERE SAB = ? AND TTY <> 'SY'`, [sab]);
|
|
777
866
|
|
|
778
867
|
} finally {
|
|
779
868
|
db.close();
|
|
@@ -782,8 +871,8 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
782
871
|
}
|
|
783
872
|
|
|
784
873
|
async #readVersion(db) {
|
|
785
|
-
return new Promise((resolve) => {
|
|
786
|
-
db.get('SELECT version FROM RXNVer', (err, row) => {
|
|
874
|
+
return new Promise((resolve, reject) => {
|
|
875
|
+
db.get('SELECT version FROM RXNVer', guarded(reject, (err, row) => {
|
|
787
876
|
if (err || !row) {
|
|
788
877
|
// Fallback: try to extract version from database path
|
|
789
878
|
const dbDetails = this.dbPath;
|
|
@@ -805,31 +894,31 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
805
894
|
} else {
|
|
806
895
|
resolve(row.version.toString());
|
|
807
896
|
}
|
|
808
|
-
});
|
|
897
|
+
}));
|
|
809
898
|
});
|
|
810
899
|
}
|
|
811
900
|
|
|
812
901
|
async #loadList(db, sql) {
|
|
813
902
|
return new Promise((resolve, reject) => {
|
|
814
|
-
db.all(sql, (err, rows) => {
|
|
903
|
+
db.all(sql, guarded(reject, (err, rows) => {
|
|
815
904
|
if (err) {
|
|
816
905
|
reject(err);
|
|
817
906
|
} else {
|
|
818
907
|
resolve(rows.map(row => Object.values(row)[0]));
|
|
819
908
|
}
|
|
820
|
-
});
|
|
909
|
+
}));
|
|
821
910
|
});
|
|
822
911
|
}
|
|
823
912
|
|
|
824
913
|
async #getCount(db, sql, params = []) {
|
|
825
914
|
return new Promise((resolve, reject) => {
|
|
826
|
-
db.get(sql, params, (err, row) => {
|
|
915
|
+
db.get(sql, params, guarded(reject, (err, row) => {
|
|
827
916
|
if (err) {
|
|
828
917
|
reject(err);
|
|
829
918
|
} else {
|
|
830
919
|
resolve(row ? Object.values(row)[0] : 0);
|
|
831
920
|
}
|
|
832
|
-
});
|
|
921
|
+
}));
|
|
833
922
|
});
|
|
834
923
|
}
|
|
835
924
|
|
|
@@ -842,7 +931,7 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
842
931
|
this.recordUse();
|
|
843
932
|
|
|
844
933
|
// Create fresh database connection for this provider instance
|
|
845
|
-
const db =
|
|
934
|
+
const db = this.#open();
|
|
846
935
|
|
|
847
936
|
return new RxNormServices(opContext, supplements, db, this._sharedData, this.isNCI);
|
|
848
937
|
}
|
|
@@ -855,6 +944,21 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
855
944
|
return this.name()+"-"+this.version();
|
|
856
945
|
}
|
|
857
946
|
|
|
947
|
+
/**
|
|
948
|
+
* RxNorm codes link to RxNav (as DataRenderer.getLinkForCode in org.hl7.fhir.core). The NCI
|
|
949
|
+
* code system's codes are NCI Thesaurus codes (the SCUIs of SAB NCI), which link to EVS
|
|
950
|
+
* Explore: the NCIt browser link core uses now lands on EVS Explore's home page.
|
|
951
|
+
*/
|
|
952
|
+
codeLink(context) {
|
|
953
|
+
if (!context || !context.code) {
|
|
954
|
+
return undefined;
|
|
955
|
+
}
|
|
956
|
+
const code = encodeURIComponent(context.code);
|
|
957
|
+
return this.isNCI
|
|
958
|
+
? `https://evsexplore.semantics.cancer.gov/evsexplore/concept/ncit/${code}`
|
|
959
|
+
: `https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm=${code}`;
|
|
960
|
+
}
|
|
961
|
+
|
|
858
962
|
describeVersion(version) {
|
|
859
963
|
try {
|
|
860
964
|
return formatDateMMDDYYYY(version);
|
|
@@ -866,15 +970,15 @@ class RxNormTypeServicesFactory extends CodeSystemFactoryProvider {
|
|
|
866
970
|
|
|
867
971
|
// Specific RxNorm implementation
|
|
868
972
|
class RxNormServicesFactory extends RxNormTypeServicesFactory {
|
|
869
|
-
constructor(languageDefinitions, dbPath) {
|
|
870
|
-
super(languageDefinitions, dbPath, false);
|
|
973
|
+
constructor(languageDefinitions, dbPath, options = {}) {
|
|
974
|
+
super(languageDefinitions, dbPath, false, options);
|
|
871
975
|
}
|
|
872
976
|
}
|
|
873
977
|
|
|
874
978
|
// NCI Meta implementation
|
|
875
979
|
class NCIServicesFactory extends RxNormTypeServicesFactory {
|
|
876
|
-
constructor(languageDefinitions, dbPath) {
|
|
877
|
-
super(languageDefinitions, dbPath, true);
|
|
980
|
+
constructor(languageDefinitions, dbPath, options = {}) {
|
|
981
|
+
super(languageDefinitions, dbPath, true, options);
|
|
878
982
|
}
|
|
879
983
|
}
|
|
880
984
|
|