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.
Files changed (277) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/FHIRsmith.png +0 -0
  3. package/README.md +277 -0
  4. package/config-template.json +144 -0
  5. package/library/folder-setup.js +58 -0
  6. package/library/html-server.js +166 -0
  7. package/library/html.js +835 -0
  8. package/library/i18nsupport.js +259 -0
  9. package/library/languages.js +779 -0
  10. package/library/logger-telnet.js +205 -0
  11. package/library/logger.js +279 -0
  12. package/library/package-manager.js +876 -0
  13. package/library/utilities.js +196 -0
  14. package/library/version-utilities.js +1056 -0
  15. package/npmprojector/config-example.json +13 -0
  16. package/npmprojector/indexer.js +394 -0
  17. package/npmprojector/npmprojector.js +395 -0
  18. package/npmprojector/readme.md +174 -0
  19. package/npmprojector/watcher.js +335 -0
  20. package/package.json +119 -0
  21. package/packages/package-crawler.js +846 -0
  22. package/packages/packages-template.html +126 -0
  23. package/packages/packages.js +2838 -0
  24. package/passwords.ini +2 -0
  25. package/publisher/publisher-template.html +208 -0
  26. package/publisher/publisher.js +2167 -0
  27. package/publisher/task-draft.js +458 -0
  28. package/registry/api.js +735 -0
  29. package/registry/crawler.js +637 -0
  30. package/registry/model.js +513 -0
  31. package/registry/readme.md +243 -0
  32. package/registry/registry-data.json +121015 -0
  33. package/registry/registry-template.html +126 -0
  34. package/registry/registry.js +1395 -0
  35. package/registry/test-runner.js +237 -0
  36. package/root-template.html +124 -0
  37. package/server.js +524 -0
  38. package/shl/private-key.pem +5 -0
  39. package/shl/public-key.pem +18 -0
  40. package/shl/shl.js +1125 -0
  41. package/shl/vhl.js +69 -0
  42. package/static/FHIRsmith128.png +0 -0
  43. package/static/FHIRsmith16.png +0 -0
  44. package/static/FHIRsmith32.png +0 -0
  45. package/static/FHIRsmith64.png +0 -0
  46. package/static/assets/css/bootstrap-fhir.css +5302 -0
  47. package/static/assets/css/bootstrap-glyphicons.css +2 -0
  48. package/static/assets/css/bootstrap.css +4097 -0
  49. package/static/assets/css/jquery-ui.css +523 -0
  50. package/static/assets/css/jquery-ui.structure.css +863 -0
  51. package/static/assets/css/jquery-ui.structure.min.css +5 -0
  52. package/static/assets/css/jquery-ui.theme.css +439 -0
  53. package/static/assets/css/jquery-ui.theme.min.css +5 -0
  54. package/static/assets/css/jquery.ui.all.css +7 -0
  55. package/static/assets/css/modules.css +18 -0
  56. package/static/assets/css/project.css +367 -0
  57. package/static/assets/css/pygments-manni.css +66 -0
  58. package/static/assets/css/tags.css +74 -0
  59. package/static/assets/css/xml.css +2 -0
  60. package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
  61. package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
  62. package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
  63. package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
  64. package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
  65. package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
  66. package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
  67. package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
  68. package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
  69. package/static/assets/ico/favicon.ico +0 -0
  70. package/static/assets/ico/favicon.png +0 -0
  71. package/static/assets/images/fhir-logo-www.png +0 -0
  72. package/static/assets/images/fhir-logo.png +0 -0
  73. package/static/assets/images/hl7-logo.png +0 -0
  74. package/static/assets/images/logo_ansinew.jpg +0 -0
  75. package/static/assets/images/search.png +0 -0
  76. package/static/assets/images/stripe.png +0 -0
  77. package/static/assets/images/target.png +0 -0
  78. package/static/assets/images/tx-registry-root.gif +0 -0
  79. package/static/assets/images/tx-registry.png +0 -0
  80. package/static/assets/images/tx-server.png +0 -0
  81. package/static/assets/images/tx-version.png +0 -0
  82. package/static/assets/js/bootstrap.min.js +6 -0
  83. package/static/assets/js/fhir-gw.js +259 -0
  84. package/static/assets/js/fhir.js +2 -0
  85. package/static/assets/js/html5shiv.js +8 -0
  86. package/static/assets/js/jcookie.js +96 -0
  87. package/static/assets/js/jquery-ui.min.js +6 -0
  88. package/static/assets/js/jquery.js +10716 -0
  89. package/static/assets/js/jquery.min.js +2 -0
  90. package/static/assets/js/jquery.ui.core.js +314 -0
  91. package/static/assets/js/jquery.ui.draggable.js +825 -0
  92. package/static/assets/js/jquery.ui.mouse.js +162 -0
  93. package/static/assets/js/jquery.ui.resizable.js +842 -0
  94. package/static/assets/js/jquery.ui.widget.js +268 -0
  95. package/static/assets/js/json2.js +487 -0
  96. package/static/assets/js/jtip.js +97 -0
  97. package/static/assets/js/respond.min.js +6 -0
  98. package/static/assets/js/statuspage.js +70 -0
  99. package/static/assets/js/xml.js +2 -0
  100. package/static/dist/js/bootstrap.js +1964 -0
  101. package/static/favicon.png +0 -0
  102. package/static/fhir.css +626 -0
  103. package/static/icon-fhir-16.png +0 -0
  104. package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  105. package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  106. package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
  107. package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  108. package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  109. package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  110. package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  111. package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  112. package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  113. package/static/images/ui-icons_222222_256x240.png +0 -0
  114. package/static/images/ui-icons_228ef1_256x240.png +0 -0
  115. package/static/images/ui-icons_ef8c08_256x240.png +0 -0
  116. package/static/images/ui-icons_ffd27a_256x240.png +0 -0
  117. package/static/images/ui-icons_ffffff_256x240.png +0 -0
  118. package/static/js/jquery.effects.blind.js +49 -0
  119. package/static/js/jquery.effects.bounce.js +78 -0
  120. package/static/js/jquery.effects.clip.js +54 -0
  121. package/static/js/jquery.effects.core.js +763 -0
  122. package/static/js/jquery.effects.drop.js +50 -0
  123. package/static/js/jquery.effects.explode.js +79 -0
  124. package/static/js/jquery.effects.fade.js +32 -0
  125. package/static/js/jquery.effects.fold.js +56 -0
  126. package/static/js/jquery.effects.highlight.js +50 -0
  127. package/static/js/jquery.effects.pulsate.js +51 -0
  128. package/static/js/jquery.effects.scale.js +178 -0
  129. package/static/js/jquery.effects.shake.js +57 -0
  130. package/static/js/jquery.effects.slide.js +50 -0
  131. package/static/js/jquery.effects.transfer.js +45 -0
  132. package/static/js/jquery.ui.accordion.js +611 -0
  133. package/static/js/jquery.ui.autocomplete.js +612 -0
  134. package/static/js/jquery.ui.button.js +416 -0
  135. package/static/js/jquery.ui.datepicker.js +1823 -0
  136. package/static/js/jquery.ui.dialog.js +878 -0
  137. package/static/js/jquery.ui.droppable.js +296 -0
  138. package/static/js/jquery.ui.position.js +252 -0
  139. package/static/js/jquery.ui.progressbar.js +109 -0
  140. package/static/js/jquery.ui.selectable.js +266 -0
  141. package/static/js/jquery.ui.slider.js +666 -0
  142. package/static/js/jquery.ui.sortable.js +1077 -0
  143. package/static/js/jquery.ui.tabs.js +758 -0
  144. package/stats.js +80 -0
  145. package/test-cache/vsac/vsac-valuesets.db +0 -0
  146. package/token/nginx_passport_setup.md +383 -0
  147. package/token/security_guide.md +294 -0
  148. package/token/token-template.html +330 -0
  149. package/token/token.js +1300 -0
  150. package/translations/Messages.properties +1510 -0
  151. package/translations/Messages_ar.properties +1399 -0
  152. package/translations/Messages_de.properties +836 -0
  153. package/translations/Messages_es.properties +737 -0
  154. package/translations/Messages_fr.properties +1 -0
  155. package/translations/Messages_ja.properties +893 -0
  156. package/translations/Messages_nl.properties +1357 -0
  157. package/translations/Messages_pt.properties +1302 -0
  158. package/translations/Messages_ru.properties +1 -0
  159. package/translations/Messages_uz.properties +1 -0
  160. package/translations/Messages_zh.properties +1 -0
  161. package/translations/rendering-phrases.properties +1128 -0
  162. package/translations/rendering-phrases_ar.properties +1091 -0
  163. package/translations/rendering-phrases_de.properties +6 -0
  164. package/translations/rendering-phrases_es.properties +6 -0
  165. package/translations/rendering-phrases_fr.properties +624 -0
  166. package/translations/rendering-phrases_ja.properties +21 -0
  167. package/translations/rendering-phrases_nl.properties +970 -0
  168. package/translations/rendering-phrases_pt.properties +1020 -0
  169. package/translations/rendering-phrases_ru.properties +1094 -0
  170. package/translations/rendering-phrases_uz.properties +1 -0
  171. package/translations/rendering-phrases_zh.properties +1 -0
  172. package/tx/README.md +418 -0
  173. package/tx/cm/cm-api.js +110 -0
  174. package/tx/cm/cm-database.js +735 -0
  175. package/tx/cm/cm-package.js +325 -0
  176. package/tx/cs/cs-api.js +789 -0
  177. package/tx/cs/cs-areacode.js +615 -0
  178. package/tx/cs/cs-country.js +1110 -0
  179. package/tx/cs/cs-cpt.js +785 -0
  180. package/tx/cs/cs-cs.js +1579 -0
  181. package/tx/cs/cs-currency.js +539 -0
  182. package/tx/cs/cs-db.js +1321 -0
  183. package/tx/cs/cs-hgvs.js +329 -0
  184. package/tx/cs/cs-lang.js +465 -0
  185. package/tx/cs/cs-loinc.js +1485 -0
  186. package/tx/cs/cs-mimetypes.js +238 -0
  187. package/tx/cs/cs-ndc.js +704 -0
  188. package/tx/cs/cs-omop.js +1025 -0
  189. package/tx/cs/cs-provider-api.js +43 -0
  190. package/tx/cs/cs-provider-list.js +37 -0
  191. package/tx/cs/cs-rxnorm.js +808 -0
  192. package/tx/cs/cs-snomed.js +1102 -0
  193. package/tx/cs/cs-ucum.js +514 -0
  194. package/tx/cs/cs-unii.js +271 -0
  195. package/tx/cs/cs-uri.js +218 -0
  196. package/tx/cs/cs-usstates.js +305 -0
  197. package/tx/dev.fhir.org.yml +14 -0
  198. package/tx/fixtures/test-cases-setup.json +18 -0
  199. package/tx/fixtures/test-cases.yml +16 -0
  200. package/tx/html/codesystem-operations.liquid +25 -0
  201. package/tx/html/home-metrics.liquid +247 -0
  202. package/tx/html/operations-form.liquid +148 -0
  203. package/tx/html/search-form.liquid +62 -0
  204. package/tx/html/tx-template.html +133 -0
  205. package/tx/html/valueset-operations.liquid +54 -0
  206. package/tx/importers/atc-to-fhir.js +316 -0
  207. package/tx/importers/import-loinc.module.js +1536 -0
  208. package/tx/importers/import-ndc.module.js +1088 -0
  209. package/tx/importers/import-rxnorm.module.js +898 -0
  210. package/tx/importers/import-sct.module.js +2457 -0
  211. package/tx/importers/import-unii.module.js +601 -0
  212. package/tx/importers/readme.md +453 -0
  213. package/tx/importers/subset-loinc.module.js +1081 -0
  214. package/tx/importers/subset-rxnorm.module.js +938 -0
  215. package/tx/importers/tx-import-base.js +351 -0
  216. package/tx/importers/tx-import-settings.js +310 -0
  217. package/tx/importers/tx-import.js +357 -0
  218. package/tx/library/canonical-resource.js +88 -0
  219. package/tx/library/capabilitystatement.js +292 -0
  220. package/tx/library/codesystem.js +774 -0
  221. package/tx/library/conceptmap.js +568 -0
  222. package/tx/library/designations.js +932 -0
  223. package/tx/library/errors.js +77 -0
  224. package/tx/library/extensions.js +117 -0
  225. package/tx/library/namingsystem.js +322 -0
  226. package/tx/library/operation-outcome.js +127 -0
  227. package/tx/library/parameters.js +105 -0
  228. package/tx/library/renderer.js +1559 -0
  229. package/tx/library/terminologycapabilities.js +418 -0
  230. package/tx/library/ucum-parsers.js +1029 -0
  231. package/tx/library/ucum-service.js +370 -0
  232. package/tx/library/ucum-types.js +1099 -0
  233. package/tx/library/valueset.js +543 -0
  234. package/tx/library.js +676 -0
  235. package/tx/ocl/cm-ocl.js +106 -0
  236. package/tx/ocl/cs-ocl.js +39 -0
  237. package/tx/ocl/vs-ocl.js +105 -0
  238. package/tx/operation-context.js +568 -0
  239. package/tx/params.js +613 -0
  240. package/tx/provider.js +403 -0
  241. package/tx/sct/ecl.js +1560 -0
  242. package/tx/sct/expressions.js +2077 -0
  243. package/tx/sct/structures.js +1396 -0
  244. package/tx/tx-html.js +1063 -0
  245. package/tx/tx.fhir.org.yml +39 -0
  246. package/tx/tx.js +927 -0
  247. package/tx/vs/vs-api.js +112 -0
  248. package/tx/vs/vs-database.js +786 -0
  249. package/tx/vs/vs-package.js +358 -0
  250. package/tx/vs/vs-vsac.js +366 -0
  251. package/tx/workers/batch-validate.js +129 -0
  252. package/tx/workers/batch.js +361 -0
  253. package/tx/workers/closure.js +32 -0
  254. package/tx/workers/expand.js +1845 -0
  255. package/tx/workers/lookup.js +407 -0
  256. package/tx/workers/metadata.js +467 -0
  257. package/tx/workers/operations.js +34 -0
  258. package/tx/workers/read.js +164 -0
  259. package/tx/workers/search.js +384 -0
  260. package/tx/workers/subsumes.js +334 -0
  261. package/tx/workers/translate.js +492 -0
  262. package/tx/workers/validate.js +2504 -0
  263. package/tx/workers/worker.js +904 -0
  264. package/tx/xml/capabilitystatement-xml.js +63 -0
  265. package/tx/xml/codesystem-xml.js +62 -0
  266. package/tx/xml/conceptmap-xml.js +65 -0
  267. package/tx/xml/namingsystem-xml.js +65 -0
  268. package/tx/xml/operationoutcome-xml.js +127 -0
  269. package/tx/xml/parameters-xml.js +312 -0
  270. package/tx/xml/terminologycapabilities-xml.js +64 -0
  271. package/tx/xml/valueset-xml.js +64 -0
  272. package/tx/xml/xml-base.js +603 -0
  273. package/vcl/vcl-parser.js +1098 -0
  274. package/vcl/vcl.js +253 -0
  275. package/windows-install.js +19 -0
  276. package/xig/xig-template.html +124 -0
  277. package/xig/xig.js +3049 -0
@@ -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
+ };