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