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
package/tx/cs/cs-ndc.js
ADDED
|
@@ -0,0 +1,704 @@
|
|
|
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 {validateArrayParameter} = require("../../library/utilities");
|
|
6
|
+
|
|
7
|
+
class NdcConcept {
|
|
8
|
+
constructor(code, display, isPackage = false, key = null) {
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.display = display;
|
|
11
|
+
this.isPackage = isPackage;
|
|
12
|
+
this.key = key;
|
|
13
|
+
|
|
14
|
+
// Additional NDC-specific properties
|
|
15
|
+
this.productCode = null; // For packages, the related product code
|
|
16
|
+
this.code11 = null; // 11-digit version for packages
|
|
17
|
+
this.active = true;
|
|
18
|
+
this.properties = {}; // Store additional properties from database
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class NdcServices extends CodeSystemProvider {
|
|
23
|
+
constructor(opContext, supplements, db, lookupTables, packageCount, productCount, version) {
|
|
24
|
+
super(opContext, supplements);
|
|
25
|
+
this.db = db;
|
|
26
|
+
this._version = version;
|
|
27
|
+
this._lookupTables = lookupTables;
|
|
28
|
+
this._packageCount = packageCount;
|
|
29
|
+
this._productCount = productCount;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Clean up database connection when provider is destroyed
|
|
33
|
+
close() {
|
|
34
|
+
if (this.db) {
|
|
35
|
+
this.db.close();
|
|
36
|
+
this.db = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Metadata methods
|
|
41
|
+
system() {
|
|
42
|
+
return 'http://hl7.org/fhir/sid/ndc'; // NDC system URI
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
version() {
|
|
46
|
+
return this._version;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
description() {
|
|
50
|
+
return 'National Drug Code (NDC) Directory';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
name() {
|
|
54
|
+
return 'NDC Codes';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async totalCount() {
|
|
58
|
+
return this._packageCount + this._productCount;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
hasParents() {
|
|
62
|
+
return false; // No hierarchical relationships
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
hasAnyDisplays(languages) {
|
|
66
|
+
const langs = this._ensureLanguages(languages);
|
|
67
|
+
if (this._hasAnySupplementDisplays(langs)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return super.hasAnyDisplays(langs);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Core concept methods
|
|
74
|
+
async code(code) {
|
|
75
|
+
|
|
76
|
+
const ctxt = await this.#ensureContext(code);
|
|
77
|
+
return ctxt ? ctxt.code : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async display(code) {
|
|
81
|
+
|
|
82
|
+
const ctxt = await this.#ensureContext(code);
|
|
83
|
+
if (!ctxt) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check supplements first
|
|
88
|
+
let disp = this._displayFromSupplements(ctxt.code);
|
|
89
|
+
if (disp) {
|
|
90
|
+
return disp;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return ctxt.display ? ctxt.display.trim() : '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async definition(code) {
|
|
97
|
+
await this.#ensureContext(code);
|
|
98
|
+
return null; // No definitions provided in NDC
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async isAbstract(code) {
|
|
102
|
+
await this.#ensureContext(code);
|
|
103
|
+
return false; // No abstract concepts in NDC
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async isInactive(code) {
|
|
107
|
+
|
|
108
|
+
const ctxt = await this.#ensureContext(code);
|
|
109
|
+
return ctxt ? !ctxt.active : false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async isDeprecated(code) {
|
|
113
|
+
await this.#ensureContext(code);
|
|
114
|
+
return false; // NDC doesn't track deprecated status separately
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async designations(code, displays) {
|
|
118
|
+
const ctxt = await this.#ensureContext(code);
|
|
119
|
+
|
|
120
|
+
if (ctxt) {
|
|
121
|
+
// Add main display
|
|
122
|
+
if (ctxt.display) {
|
|
123
|
+
displays.addDesignation(true, 'active', 'en', CodeSystem.makeUseForDisplay(), ctxt.display.trim());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add supplement designations
|
|
127
|
+
this._listSupplementDesignations(ctxt.code, displays);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async extendLookup(ctxt, props, params) {
|
|
132
|
+
validateArrayParameter(props, 'props', String);
|
|
133
|
+
validateArrayParameter(params, 'params', Object);
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if (typeof ctxt === 'string') {
|
|
137
|
+
const located = await this.locate(ctxt);
|
|
138
|
+
if (!located.context) {
|
|
139
|
+
throw new Error(located.message);
|
|
140
|
+
}
|
|
141
|
+
ctxt = located.context;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!(ctxt instanceof NdcConcept)) {
|
|
145
|
+
throw new Error('Invalid context for NDC lookup');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Get full data for the concept
|
|
149
|
+
const fullData = await this.#getFullConceptData(ctxt);
|
|
150
|
+
|
|
151
|
+
// Add NDC-specific properties
|
|
152
|
+
if (!ctxt.isPackage) {
|
|
153
|
+
// Product properties
|
|
154
|
+
this.#addProperty(params, 'code-type', 'product');
|
|
155
|
+
this.#addProperty(params, 'description', fullData.display || '');
|
|
156
|
+
} else {
|
|
157
|
+
// Package properties
|
|
158
|
+
if (ctxt.code.includes('-')) {
|
|
159
|
+
this.#addProperty(params, 'code-type', '10-digit');
|
|
160
|
+
if (fullData.code11) {
|
|
161
|
+
this.#addProperty(params, 'synonym', fullData.code11);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
this.#addProperty(params, 'code-type', '11-digit');
|
|
165
|
+
if (fullData.originalCode) {
|
|
166
|
+
this.#addProperty(params, 'synonym', fullData.originalCode);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
this.#addProperty(params, 'description', fullData.display || '');
|
|
170
|
+
if (fullData.productCode) {
|
|
171
|
+
this.#addProperty(params, 'product', fullData.productCode);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Common properties
|
|
176
|
+
if (fullData.type && this._lookupTables.types.has(fullData.type)) {
|
|
177
|
+
this.#addProperty(params, 'type', this._lookupTables.types.get(fullData.type));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
this.#addProperty(params, 'active', fullData.active ? 'true' : 'false');
|
|
181
|
+
|
|
182
|
+
if (fullData.tradeName) {
|
|
183
|
+
this.#addProperty(params, 'trade-name', fullData.tradeName);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (fullData.doseForm && this._lookupTables.doseForms.has(fullData.doseForm)) {
|
|
187
|
+
this.#addProperty(params, 'dose-form', this._lookupTables.doseForms.get(fullData.doseForm));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (fullData.route && this._lookupTables.routes.has(fullData.route)) {
|
|
191
|
+
this.#addProperty(params, 'route', this._lookupTables.routes.get(fullData.route));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (fullData.company && this._lookupTables.organizations.has(fullData.company)) {
|
|
195
|
+
this.#addProperty(params, 'company', this._lookupTables.organizations.get(fullData.company));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (fullData.category) {
|
|
199
|
+
this.#addProperty(params, 'category', fullData.category);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (fullData.generics) {
|
|
203
|
+
this.#addProperty(params, 'generic', fullData.generics);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#addProperty(params, name, value) {
|
|
208
|
+
// This follows the FHIR Parameters structure for lookup responses
|
|
209
|
+
// Each property becomes a parameter with name='property' and sub-parameters
|
|
210
|
+
const property = {
|
|
211
|
+
name: 'property',
|
|
212
|
+
part: [
|
|
213
|
+
{ name: 'code', valueCode: name },
|
|
214
|
+
{ name: 'value', valueString: value }
|
|
215
|
+
]
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
params.push(property);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async #getFullConceptData(concept) {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
let sql, params;
|
|
224
|
+
|
|
225
|
+
if (concept.isPackage) {
|
|
226
|
+
sql = `
|
|
227
|
+
SELECT p.Code as PCode, pkg.Code, pkg.Code11, pkg.Active, pkg.Description,
|
|
228
|
+
p.TradeName, p.Suffix, p.Type, p.DoseForm, p.Route, p.Company,
|
|
229
|
+
p.Category, p.Generics
|
|
230
|
+
FROM NDCProducts p
|
|
231
|
+
JOIN NDCPackages pkg ON p.NDCKey = pkg.ProductKey
|
|
232
|
+
WHERE pkg.NDCKey = ?
|
|
233
|
+
`;
|
|
234
|
+
params = [concept.key];
|
|
235
|
+
} else {
|
|
236
|
+
sql = `
|
|
237
|
+
SELECT Code, TradeName, Suffix, Type, DoseForm, Route, Company,
|
|
238
|
+
Category, Generics, Active
|
|
239
|
+
FROM NDCProducts
|
|
240
|
+
WHERE NDCKey = ?
|
|
241
|
+
`;
|
|
242
|
+
params = [concept.key];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.db.get(sql, params, (err, row) => {
|
|
246
|
+
if (err) {
|
|
247
|
+
reject(err);
|
|
248
|
+
} else if (!row) {
|
|
249
|
+
resolve({});
|
|
250
|
+
} else {
|
|
251
|
+
const result = {
|
|
252
|
+
active: row.Active === 1,
|
|
253
|
+
tradeName: row.TradeName,
|
|
254
|
+
suffix: row.Suffix,
|
|
255
|
+
type: row.Type,
|
|
256
|
+
doseForm: row.DoseForm,
|
|
257
|
+
route: row.Route,
|
|
258
|
+
company: row.Company,
|
|
259
|
+
category: row.Category,
|
|
260
|
+
generics: row.Generics
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
if (concept.isPackage) {
|
|
264
|
+
result.productCode = row.PCode;
|
|
265
|
+
result.code11 = row.Code11;
|
|
266
|
+
result.originalCode = row.Code;
|
|
267
|
+
result.display = this.#packageDisplay(row);
|
|
268
|
+
} else {
|
|
269
|
+
result.display = this.#productDisplay(row);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
resolve(result);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
#productDisplay(row) {
|
|
279
|
+
const tradeName = row.TradeName || '';
|
|
280
|
+
const suffix = row.Suffix || '';
|
|
281
|
+
if (suffix) {
|
|
282
|
+
return `${tradeName} ${suffix} (product)`.trim();
|
|
283
|
+
}
|
|
284
|
+
return `${tradeName} (product)`.trim();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#packageDisplay(row) {
|
|
288
|
+
const tradeName = row.TradeName || '';
|
|
289
|
+
const suffix = row.Suffix || '';
|
|
290
|
+
const description = row.Description || '';
|
|
291
|
+
|
|
292
|
+
let display = tradeName;
|
|
293
|
+
if (suffix) {
|
|
294
|
+
display += ` ${suffix}`;
|
|
295
|
+
}
|
|
296
|
+
if (description) {
|
|
297
|
+
display += `, ${description}`;
|
|
298
|
+
}
|
|
299
|
+
display += ' (package)';
|
|
300
|
+
|
|
301
|
+
return display.replace(/\s+/g, ' ').trim();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async #ensureContext(code) {
|
|
305
|
+
if (!code) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
if (typeof code === 'string') {
|
|
309
|
+
const ctxt = await this.locate(code);
|
|
310
|
+
if (!ctxt.context) {
|
|
311
|
+
throw new Error(ctxt.message);
|
|
312
|
+
} else {
|
|
313
|
+
return ctxt.context;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (code instanceof NdcConcept) {
|
|
317
|
+
return code;
|
|
318
|
+
}
|
|
319
|
+
throw new Error("Unknown Type at #ensureContext: " + (typeof code));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Lookup methods
|
|
323
|
+
async locate(code) {
|
|
324
|
+
|
|
325
|
+
assert(!code || typeof code === 'string', 'code must be string');
|
|
326
|
+
if (!code) return { context: null, message: 'Empty code' };
|
|
327
|
+
|
|
328
|
+
// First try packages (both regular code and code11)
|
|
329
|
+
const packageResult = await this.#locateInPackages(code);
|
|
330
|
+
if (packageResult) {
|
|
331
|
+
return { context: packageResult, message: null };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Then try products
|
|
335
|
+
const productResult = await this.#locateInProducts(code);
|
|
336
|
+
if (productResult) {
|
|
337
|
+
return { context: productResult, message: null };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return { context: null, message: undefined };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async #locateInPackages(code) {
|
|
344
|
+
return new Promise((resolve, reject) => {
|
|
345
|
+
// Try both regular code and code11 formats
|
|
346
|
+
const sql = `
|
|
347
|
+
SELECT pkg.NDCKey, pkg.Code, pkg.Code11, p.TradeName, p.Suffix, pkg.Description,
|
|
348
|
+
p.Code as ProductCode, pkg.Active
|
|
349
|
+
FROM NDCPackages pkg
|
|
350
|
+
JOIN NDCProducts p ON pkg.ProductKey = p.NDCKey
|
|
351
|
+
WHERE pkg.Code = ? OR pkg.Code11 = ?
|
|
352
|
+
LIMIT 1
|
|
353
|
+
`;
|
|
354
|
+
|
|
355
|
+
this.db.get(sql, [code, code], (err, row) => {
|
|
356
|
+
if (err) {
|
|
357
|
+
reject(err);
|
|
358
|
+
} else if (row) {
|
|
359
|
+
const concept = new NdcConcept(code, this.#packageDisplay(row), true, row.NDCKey);
|
|
360
|
+
concept.productCode = row.ProductCode;
|
|
361
|
+
concept.code11 = row.Code11;
|
|
362
|
+
concept.active = row.Active === 1;
|
|
363
|
+
resolve(concept);
|
|
364
|
+
} else {
|
|
365
|
+
resolve(null);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async #locateInProducts(code) {
|
|
372
|
+
return new Promise((resolve, reject) => {
|
|
373
|
+
const sql = `
|
|
374
|
+
SELECT NDCKey, Code, TradeName, Suffix, Active
|
|
375
|
+
FROM NDCProducts
|
|
376
|
+
WHERE Code = ?
|
|
377
|
+
LIMIT 1
|
|
378
|
+
`;
|
|
379
|
+
|
|
380
|
+
this.db.get(sql, [code], (err, row) => {
|
|
381
|
+
if (err) {
|
|
382
|
+
reject(err);
|
|
383
|
+
} else if (row) {
|
|
384
|
+
const concept = new NdcConcept(code, this.#productDisplay(row), false, row.NDCKey);
|
|
385
|
+
concept.active = row.Active === 1;
|
|
386
|
+
resolve(concept);
|
|
387
|
+
} else {
|
|
388
|
+
resolve(null);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Filter support for code-type filtering
|
|
395
|
+
async doesFilter(prop, op, value) {
|
|
396
|
+
|
|
397
|
+
return prop === 'code-type' &&
|
|
398
|
+
op === '=' &&
|
|
399
|
+
['10-digit', '11-digit', 'product'].includes(value);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async filter(filterContext, prop, op, value) {
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
if (prop === 'code-type' && op === '=') {
|
|
406
|
+
const filter = { type: 'code-type', value: value };
|
|
407
|
+
filterContext.filters.push(filter);
|
|
408
|
+
return filter;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
throw new Error(`The filter "${prop} ${op} ${value}" is not supported for NDC`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async executeFilters(filterContext) {
|
|
415
|
+
|
|
416
|
+
return filterContext.filters;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async filterSize(filterContext, set) {
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
return new Promise((resolve, reject) => {
|
|
423
|
+
let sql;
|
|
424
|
+
|
|
425
|
+
switch (set.value) {
|
|
426
|
+
case 'product':
|
|
427
|
+
sql = 'SELECT COUNT(*) as count FROM NDCProducts';
|
|
428
|
+
break;
|
|
429
|
+
case '10-digit':
|
|
430
|
+
sql = "SELECT COUNT(*) as count FROM NDCPackages WHERE Code LIKE '%-%'";
|
|
431
|
+
break;
|
|
432
|
+
case '11-digit':
|
|
433
|
+
sql = "SELECT COUNT(*) as count FROM NDCPackages WHERE Code NOT LIKE '%-%'";
|
|
434
|
+
break;
|
|
435
|
+
default:
|
|
436
|
+
resolve(0);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
this.db.get(sql, (err, row) => {
|
|
441
|
+
if (err) reject(err);
|
|
442
|
+
else resolve(row.count);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
async filterMore(filterContext, set) {
|
|
448
|
+
|
|
449
|
+
if (!set._iterator) {
|
|
450
|
+
set._iterator = { offset: 0, hasMore: true };
|
|
451
|
+
}
|
|
452
|
+
return set._iterator.hasMore;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async filterConcept(filterContext, set) {
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
if (!set._iterator) {
|
|
459
|
+
set._iterator = { offset: 0, hasMore: true };
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return new Promise((resolve, reject) => {
|
|
463
|
+
let sql;
|
|
464
|
+
|
|
465
|
+
switch (set.value) {
|
|
466
|
+
case 'product':
|
|
467
|
+
sql = 'SELECT NDCKey, Code, TradeName, Suffix, Active FROM NDCProducts LIMIT 1 OFFSET ?';
|
|
468
|
+
break;
|
|
469
|
+
case '10-digit':
|
|
470
|
+
sql = `
|
|
471
|
+
SELECT pkg.NDCKey, pkg.Code, p.TradeName, p.Suffix, pkg.Description, pkg.Active
|
|
472
|
+
FROM NDCPackages pkg
|
|
473
|
+
JOIN NDCProducts p ON pkg.ProductKey = p.NDCKey
|
|
474
|
+
WHERE pkg.Code LIKE '%-%'
|
|
475
|
+
LIMIT 1 OFFSET ?
|
|
476
|
+
`;
|
|
477
|
+
break;
|
|
478
|
+
case '11-digit':
|
|
479
|
+
sql = `
|
|
480
|
+
SELECT pkg.NDCKey, pkg.Code, p.TradeName, p.Suffix, pkg.Description, pkg.Active
|
|
481
|
+
FROM NDCPackages pkg
|
|
482
|
+
JOIN NDCProducts p ON pkg.ProductKey = p.NDCKey
|
|
483
|
+
WHERE pkg.Code NOT LIKE '%-%'
|
|
484
|
+
LIMIT 1 OFFSET ?
|
|
485
|
+
`;
|
|
486
|
+
break;
|
|
487
|
+
default:
|
|
488
|
+
resolve(null);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
this.db.get(sql, [set._iterator.offset], (err, row) => {
|
|
493
|
+
if (err) {
|
|
494
|
+
reject(err);
|
|
495
|
+
} else if (row) {
|
|
496
|
+
set._iterator.offset++;
|
|
497
|
+
|
|
498
|
+
let concept;
|
|
499
|
+
if (set.value === 'product') {
|
|
500
|
+
concept = new NdcConcept(row.Code, this.#productDisplay(row), false, row.NDCKey);
|
|
501
|
+
} else {
|
|
502
|
+
concept = new NdcConcept(row.Code, this.#packageDisplay(row), true, row.NDCKey);
|
|
503
|
+
}
|
|
504
|
+
concept.active = row.Active === 1;
|
|
505
|
+
|
|
506
|
+
resolve(concept);
|
|
507
|
+
} else {
|
|
508
|
+
set._iterator.hasMore = false;
|
|
509
|
+
resolve(null);
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async filterLocate(filterContext, set, code) {
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
// First locate the code normally
|
|
519
|
+
const located = await this.locate(code);
|
|
520
|
+
if (!located.context) {
|
|
521
|
+
return located.message;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const concept = located.context;
|
|
525
|
+
|
|
526
|
+
// Check if it matches the filter
|
|
527
|
+
switch (set.value) {
|
|
528
|
+
case 'product':
|
|
529
|
+
return concept.isPackage ? 'Code is a package, not a product' : concept;
|
|
530
|
+
case '10-digit':
|
|
531
|
+
return (!concept.isPackage || !concept.code.includes('-')) ?
|
|
532
|
+
'Code is not a 10-digit package code' : concept;
|
|
533
|
+
case '11-digit':
|
|
534
|
+
return (!concept.isPackage || concept.code.includes('-')) ?
|
|
535
|
+
'Code is not an 11-digit package code' : concept;
|
|
536
|
+
default:
|
|
537
|
+
return 'Unknown filter type';
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async filterCheck(filterContext, set, concept) {
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
if (!(concept instanceof NdcConcept)) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
switch (set.value) {
|
|
549
|
+
case 'product':
|
|
550
|
+
return !concept.isPackage;
|
|
551
|
+
case '10-digit':
|
|
552
|
+
return concept.isPackage && concept.code.includes('-');
|
|
553
|
+
case '11-digit':
|
|
554
|
+
return concept.isPackage && !concept.code.includes('-');
|
|
555
|
+
default:
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Iterator methods - not supported for NDC
|
|
561
|
+
|
|
562
|
+
versionAlgorithm() {
|
|
563
|
+
return 'date';
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
class NdcServicesFactory extends CodeSystemFactoryProvider {
|
|
568
|
+
constructor(i18n, dbPath) {
|
|
569
|
+
super(i18n);
|
|
570
|
+
this.dbPath = dbPath;
|
|
571
|
+
this.uses = 0;
|
|
572
|
+
this._loaded = false;
|
|
573
|
+
this._lookupTables = null;
|
|
574
|
+
this._packageCount = null;
|
|
575
|
+
this._productCount = null;
|
|
576
|
+
this._version = null;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
system() {
|
|
580
|
+
return 'http://hl7.org/fhir/sid/ndc'; // NDC system URI
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
version() {
|
|
584
|
+
return this._version;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// eslint-disable-next-line no-unused-vars
|
|
588
|
+
async buildKnownValueSet(url, version) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async #ensureLoaded() {
|
|
593
|
+
if (!this._loaded) {
|
|
594
|
+
await this.load();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async load() {
|
|
599
|
+
// Use temporary database connection for loading
|
|
600
|
+
const tempDb = new sqlite3.Database(this.dbPath);
|
|
601
|
+
|
|
602
|
+
try {
|
|
603
|
+
// Load version
|
|
604
|
+
this._version = await new Promise((resolve, reject) => {
|
|
605
|
+
tempDb.get('SELECT Version FROM NDCVersion ORDER BY Version DESC LIMIT 1', (err, row) => {
|
|
606
|
+
if (err) reject(err);
|
|
607
|
+
else resolve(row ? row.Version : 'unknown');
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// Initialize lookup tables
|
|
612
|
+
this._lookupTables = {
|
|
613
|
+
types: new Map(),
|
|
614
|
+
organizations: new Map(),
|
|
615
|
+
doseForms: new Map(),
|
|
616
|
+
routes: new Map()
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// Load lookup tables
|
|
620
|
+
const tables = [
|
|
621
|
+
{ name: 'types', sql: 'SELECT NDCKey, Name FROM NDCProductTypes' },
|
|
622
|
+
{ name: 'organizations', sql: 'SELECT NDCKey, Name FROM NDCOrganizations' },
|
|
623
|
+
{ name: 'doseForms', sql: 'SELECT NDCKey, Name FROM NDCDoseForms' },
|
|
624
|
+
{ name: 'routes', sql: 'SELECT NDCKey, Name FROM NDCRoutes' }
|
|
625
|
+
];
|
|
626
|
+
|
|
627
|
+
for (const table of tables) {
|
|
628
|
+
await new Promise((resolve, reject) => {
|
|
629
|
+
tempDb.all(table.sql, (err, rows) => {
|
|
630
|
+
if (err) reject(err);
|
|
631
|
+
else {
|
|
632
|
+
const map = this._lookupTables[table.name];
|
|
633
|
+
rows.forEach(row => map.set(row.NDCKey, row.Name));
|
|
634
|
+
resolve();
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Load counts
|
|
641
|
+
this._packageCount = await new Promise((resolve, reject) => {
|
|
642
|
+
tempDb.get('SELECT COUNT(NDCKey) as count FROM NDCPackages', (err, row) => {
|
|
643
|
+
if (err) reject(err);
|
|
644
|
+
else resolve(row.count);
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
this._productCount = await new Promise((resolve, reject) => {
|
|
649
|
+
tempDb.get('SELECT COUNT(NDCKey) as count FROM NDCProducts', (err, row) => {
|
|
650
|
+
if (err) reject(err);
|
|
651
|
+
else resolve(row.count);
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
} finally {
|
|
656
|
+
tempDb.close();
|
|
657
|
+
}
|
|
658
|
+
this._loaded = true;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
defaultVersion() {
|
|
662
|
+
return this._version || 'unknown';
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
async build(opContext, supplements) {
|
|
666
|
+
|
|
667
|
+
this.recordUse();
|
|
668
|
+
|
|
669
|
+
// Create fresh database connection for this provider instance
|
|
670
|
+
const db = new sqlite3.Database(this.dbPath);
|
|
671
|
+
|
|
672
|
+
return new NdcServices(
|
|
673
|
+
opContext, supplements,
|
|
674
|
+
db,
|
|
675
|
+
this._lookupTables,
|
|
676
|
+
this._packageCount,
|
|
677
|
+
this._productCount,
|
|
678
|
+
this._version
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
useCount() {
|
|
683
|
+
return this.uses;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
recordUse() {
|
|
687
|
+
this.uses++;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
name() {
|
|
691
|
+
return 'NDC Codes';
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
id() {
|
|
696
|
+
return "ndc";
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
module.exports = {
|
|
701
|
+
NdcServices,
|
|
702
|
+
NdcServicesFactory,
|
|
703
|
+
NdcConcept
|
|
704
|
+
};
|