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,601 @@
1
+ const { BaseTerminologyModule } = require('./tx-import-base');
2
+ const fs = require('fs');
3
+ const sqlite3 = require('sqlite3').verbose();
4
+ const path = require("path");
5
+ const readline = require("readline");
6
+
7
+ class UniiModule extends BaseTerminologyModule {
8
+ getName() {
9
+ return 'unii';
10
+ }
11
+
12
+ getDescription() {
13
+ return 'Unique Ingredient Identifier (UNII) from FDA';
14
+ }
15
+
16
+ getSupportedFormats() {
17
+ return ['txt', 'tsv'];
18
+ }
19
+
20
+ getEstimatedDuration() {
21
+ return '15-45 minutes (depending on file size)';
22
+ }
23
+
24
+ registerCommands(terminologyCommand, globalOptions) {
25
+ // Import command
26
+ terminologyCommand
27
+ .command('import')
28
+ .description('Import UNII data from tab-delimited file')
29
+ .option('-s, --source <file>', 'Source tab-delimited file')
30
+ .option('-d, --dest <file>', 'Destination SQLite database')
31
+ .option('-v, --version <version>', 'Data version identifier')
32
+ .option('-y, --yes', 'Skip confirmations')
33
+ .option('--no-indexes', 'Skip index creation for faster import')
34
+ .action(async (options) => {
35
+ await this.handleImportCommand({...globalOptions, ...options});
36
+ });
37
+
38
+ // Validate command
39
+ terminologyCommand
40
+ .command('validate')
41
+ .description('Validate UNII source file format')
42
+ .option('-s, --source <file>', 'Source file to validate')
43
+ .option('--sample <lines>', 'Number of lines to sample for validation', '100')
44
+ .action(async (options) => {
45
+ await this.handleValidateCommand({...globalOptions, ...options});
46
+ });
47
+
48
+ // Status command
49
+ terminologyCommand
50
+ .command('status')
51
+ .description('Show status of UNII database')
52
+ .option('-d, --dest <file>', 'Database file to check')
53
+ .action(async (options) => {
54
+ await this.handleStatusCommand({...globalOptions, ...options});
55
+ });
56
+ }
57
+
58
+ async handleImportCommand(options) {
59
+ // Gather configuration
60
+ const config = await this.gatherCommonConfig(options);
61
+
62
+ // UNII-specific configuration
63
+ if (!config.createIndexes && options.noIndexes) {
64
+ config.createIndexes = false;
65
+ }
66
+
67
+ // Show confirmation unless --yes is specified
68
+ if (!options.yes) {
69
+ const confirmed = await this.confirmImport(config);
70
+ if (!confirmed) {
71
+ this.logInfo('Import cancelled');
72
+ return;
73
+ }
74
+ }
75
+
76
+ // Run the import
77
+ await this.runImport(config);
78
+ }
79
+
80
+ async confirmImport(config) {
81
+ const inquirer = require('inquirer');
82
+ const chalk = require('chalk');
83
+
84
+ console.log(chalk.cyan(`\n📋 ${this.getName()} Import Configuration:`));
85
+ console.log(` Source: ${chalk.white(config.source)}`);
86
+ console.log(` Destination: ${chalk.white(config.dest)}`);
87
+ console.log(` Version: ${chalk.white(config.version)}`);
88
+ console.log(` Create Indexes: ${chalk.white(config.createIndexes ? 'Yes' : 'No')}`);
89
+ console.log(` Overwrite: ${chalk.white(config.overwrite ? 'Yes' : 'No')}`);
90
+ console.log(` Verbose: ${chalk.white(config.verbose ? 'Yes' : 'No')}`);
91
+
92
+ if (config.estimatedDuration) {
93
+ console.log(` Estimated Duration: ${chalk.white(config.estimatedDuration)}`);
94
+ }
95
+
96
+ const { confirmed } = await inquirer.prompt({
97
+ type: 'confirm',
98
+ name: 'confirmed',
99
+ message: 'Proceed with import?',
100
+ default: true
101
+ });
102
+
103
+ return confirmed;
104
+ }
105
+
106
+ async handleValidateCommand(options) {
107
+ if (!options.source) {
108
+ const answers = await require('inquirer').prompt({
109
+ type: 'input',
110
+ name: 'source',
111
+ message: 'Source file to validate:',
112
+ validate: (input) => input && fs.existsSync(input) ? true : 'File does not exist'
113
+ });
114
+ options.source = answers.source;
115
+ }
116
+
117
+ this.logInfo(`Validating UNII file: ${options.source}`);
118
+
119
+ try {
120
+ const stats = await this.validateUniiFile(options.source, parseInt(options.sample));
121
+
122
+ this.logSuccess('File validation passed');
123
+ console.log(` Lines: ${stats.totalLines}`);
124
+ console.log(` Estimated UNII codes: ${stats.estimatedCodes}`);
125
+ console.log(` Estimated descriptions: ${stats.estimatedDescriptions}`);
126
+ console.log(` Sample data format: ${stats.formatValid ? 'Valid' : 'Invalid'}`);
127
+
128
+ if (stats.warnings.length > 0) {
129
+ this.logWarning('Validation warnings:');
130
+ stats.warnings.forEach(warning => console.log(` ${warning}`));
131
+ }
132
+
133
+ } catch (error) {
134
+ this.logError(`Validation failed: ${error.message}`);
135
+ }
136
+ }
137
+
138
+ async handleStatusCommand(options) {
139
+ const dbPath = options.dest || './data/unii.db';
140
+
141
+ if (!fs.existsSync(dbPath)) {
142
+ this.logError(`Database not found: ${dbPath}`);
143
+ return;
144
+ }
145
+
146
+ this.logInfo(`Checking UNII database: ${dbPath}`);
147
+
148
+ try {
149
+ const stats = await this.getDatabaseStats(dbPath);
150
+
151
+ this.logSuccess('Database status:');
152
+ console.log(` Version: ${stats.version}`);
153
+ console.log(` UNII Codes: ${stats.uniiCount.toLocaleString()}`);
154
+ console.log(` Descriptions: ${stats.descCount.toLocaleString()}`);
155
+ console.log(` Database Size: ${stats.sizeGB.toFixed(2)} GB`);
156
+ console.log(` Last Modified: ${stats.lastModified}`);
157
+
158
+ } catch (error) {
159
+ this.logError(`Status check failed: ${error.message}`);
160
+ }
161
+ }
162
+
163
+ async validatePrerequisites(config) {
164
+ const baseValid = await super.validatePrerequisites(config);
165
+
166
+ // UNII-specific validation
167
+ try {
168
+ this.logInfo('Validating UNII file format...');
169
+ await this.validateUniiFile(config.source, 10);
170
+ this.logSuccess('UNII file format valid');
171
+ } catch (error) {
172
+ this.logError(`UNII file validation failed: ${error.message}`);
173
+ return false;
174
+ }
175
+
176
+ return baseValid;
177
+ }
178
+
179
+ async executeImport(config) {
180
+ this.logInfo('Starting UNII data migration...');
181
+
182
+ const migrator = new UniiDataMigrator();
183
+
184
+ // Create enhanced migrator with progress reporting
185
+ const enhancedMigrator = new UniiDataMigratorWithProgress(
186
+ migrator,
187
+ this,
188
+ config.verbose
189
+ );
190
+
191
+ await enhancedMigrator.migrate(
192
+ config.source,
193
+ config.dest,
194
+ config.version,
195
+ config.verbose
196
+ );
197
+
198
+ if (config.createIndexes) {
199
+ this.logInfo('Creating database indexes...');
200
+ await this.createIndexes(config.dest);
201
+ this.logSuccess('Indexes created');
202
+ }
203
+ }
204
+
205
+ async validateUniiFile(filePath, sampleLines = 100) {
206
+ const readline = require('readline');
207
+ const fileStream = fs.createReadStream(filePath);
208
+ const rl = readline.createInterface({
209
+ input: fileStream,
210
+ crlfDelay: Infinity
211
+ });
212
+
213
+ let lineCount = 0;
214
+ let sampleCount = 0;
215
+ let estimatedCodes = new Set();
216
+ let estimatedDescriptions = 0;
217
+ const warnings = [];
218
+ let formatValid = true;
219
+
220
+ for await (const line of rl) {
221
+ lineCount++;
222
+
223
+ if (sampleCount < sampleLines) {
224
+ // Validate format: should be tab-delimited with at least 3 columns
225
+ const cols = line.split('\t');
226
+
227
+ if (lineCount === 1) {
228
+ // Skip header if it looks like one
229
+ if (cols[0].toLowerCase().includes('display') ||
230
+ cols[0].toLowerCase().includes('name')) {
231
+ continue;
232
+ }
233
+ }
234
+
235
+ if (cols.length < 3) {
236
+ warnings.push(`Line ${lineCount}: Expected at least 3 columns, found ${cols.length}`);
237
+ if (sampleCount < 5) formatValid = false; // Only fail on early errors
238
+ } else {
239
+ const code = cols[2];
240
+ if (code && code.length === 10) {
241
+ estimatedCodes.add(code);
242
+ }
243
+ estimatedDescriptions++;
244
+ }
245
+
246
+ sampleCount++;
247
+ }
248
+ }
249
+
250
+ return {
251
+ totalLines: lineCount,
252
+ estimatedCodes: estimatedCodes.size,
253
+ estimatedDescriptions,
254
+ formatValid,
255
+ warnings
256
+ };
257
+ }
258
+
259
+ async getDatabaseStats(dbPath) {
260
+ const sqlite3 = require('sqlite3').verbose();
261
+ const db = new sqlite3.Database(dbPath);
262
+
263
+ return new Promise((resolve, reject) => {
264
+ const stats = {};
265
+
266
+ // Get version
267
+ db.get('SELECT Version FROM UniiVersion LIMIT 1', (err, row) => {
268
+ if (err) return reject(err);
269
+ stats.version = row ? row.Version : 'Unknown';
270
+
271
+ // Get counts
272
+ db.get('SELECT COUNT(*) as count FROM Unii', (err, row) => {
273
+ if (err) return reject(err);
274
+ stats.uniiCount = row.count;
275
+
276
+ db.get('SELECT COUNT(*) as count FROM UniiDesc', (err, row) => {
277
+ if (err) return reject(err);
278
+ stats.descCount = row.count;
279
+
280
+ // Get file stats
281
+ const fileStat = fs.statSync(dbPath);
282
+ stats.sizeGB = fileStat.size / (1024 * 1024 * 1024);
283
+ stats.lastModified = fileStat.mtime.toISOString();
284
+
285
+ db.close();
286
+ resolve(stats);
287
+ });
288
+ });
289
+ });
290
+ });
291
+ }
292
+
293
+ async createIndexes(dbPath) {
294
+ const sqlite3 = require('sqlite3').verbose();
295
+ const db = new sqlite3.Database(dbPath);
296
+
297
+ const indexes = [
298
+ 'CREATE INDEX IF NOT EXISTS idx_unii_code ON Unii(Code)',
299
+ 'CREATE INDEX IF NOT EXISTS idx_uniidesc_uniikey ON UniiDesc(UniiKey)',
300
+ 'CREATE INDEX IF NOT EXISTS idx_uniidesc_type ON UniiDesc(Type)'
301
+ ];
302
+
303
+ return new Promise((resolve, reject) => {
304
+ db.serialize(() => {
305
+ indexes.forEach(sql => {
306
+ db.run(sql, (err) => {
307
+ if (err) console.warn(`Index creation warning: ${err.message}`);
308
+ });
309
+ });
310
+ });
311
+
312
+ db.close((err) => {
313
+ if (err) reject(err);
314
+ else resolve();
315
+ });
316
+ });
317
+ }
318
+ }
319
+
320
+ class UniiDataMigrator {
321
+ /**
322
+ * Migrates UNII data from tab-delimited source file to SQLite database
323
+ * @param {string} sourceFile - Path to tab-delimited source file
324
+ * @param {string} destFile - Path to destination SQLite database file
325
+ * @param {string} version - Version string to store in database
326
+ * @param {boolean} verbose - Whether to log progress messages
327
+ * @returns {Promise<void>}
328
+ */
329
+ async migrate(sourceFile, destFile, version = 'unknown', verbose = true) {
330
+ if (verbose) console.log('Starting UNII data migration...');
331
+
332
+ // Ensure destination directory exists
333
+ const destDir = path.dirname(destFile);
334
+ if (!fs.existsSync(destDir)) {
335
+ fs.mkdirSync(destDir, { recursive: true });
336
+ }
337
+
338
+ // Remove existing database file if it exists
339
+ if (fs.existsSync(destFile)) {
340
+ fs.unlinkSync(destFile);
341
+ }
342
+
343
+ // Create new SQLite database
344
+ const db = new sqlite3.Database(destFile);
345
+
346
+ try {
347
+ // Create tables
348
+ await this.#createTables(db, version, verbose);
349
+
350
+ // Process source file
351
+ await this.#processSourceFile(db, sourceFile, verbose);
352
+
353
+ if (verbose) console.log('UNII data migration completed successfully');
354
+ } finally {
355
+ await this.#closeDatabase(db, verbose);
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Creates the required database tables
361
+ * @private
362
+ */
363
+ async #createTables(db, version, verbose = true) {
364
+ if (verbose) console.log('Creating database tables...');
365
+
366
+ return new Promise((resolve, reject) => {
367
+ db.serialize(() => {
368
+ // Create Unii table
369
+ db.run(`
370
+ CREATE TABLE Unii (
371
+ UniiKey INTEGER NOT NULL PRIMARY KEY,
372
+ Code TEXT(20) NOT NULL,
373
+ Display TEXT(255) NULL
374
+ )
375
+ `, (err) => {
376
+ if (err) return reject(err);
377
+ });
378
+
379
+ // Create UniiDesc table
380
+ db.run(`
381
+ CREATE TABLE UniiDesc (
382
+ UniiDescKey INTEGER NOT NULL PRIMARY KEY,
383
+ UniiKey INTEGER NOT NULL,
384
+ Type TEXT(20) NOT NULL,
385
+ Display TEXT(255) NULL
386
+ )
387
+ `, (err) => {
388
+ if (err) return reject(err);
389
+ });
390
+
391
+ // Create UniiVersion table
392
+ db.run(`
393
+ CREATE TABLE UniiVersion (
394
+ Version TEXT(20) NOT NULL
395
+ )
396
+ `, (err) => {
397
+ if (err) return reject(err);
398
+ });
399
+
400
+ // Insert version
401
+ db.run('INSERT INTO UniiVersion (Version) VALUES (?)', [version], (err) => {
402
+ if (err) return reject(err);
403
+ if (verbose) console.log('Database tables created');
404
+ resolve();
405
+ });
406
+ });
407
+ });
408
+ }
409
+
410
+ /**
411
+ * Processes the tab-delimited source file
412
+ * @private
413
+ */
414
+ async #processSourceFile(db, sourceFile, verbose = true) {
415
+ if (verbose) console.log('Processing source file:', sourceFile);
416
+
417
+ if (!fs.existsSync(sourceFile)) {
418
+ throw new Error(`Source file not found: ${sourceFile}`);
419
+ }
420
+
421
+ // Read all lines first using streaming (for memory efficiency)
422
+ const lines = [];
423
+ const fileStream = fs.createReadStream(sourceFile);
424
+ const rl = readline.createInterface({
425
+ input: fileStream,
426
+ crlfDelay: Infinity
427
+ });
428
+
429
+ await new Promise((resolve, reject) => {
430
+ rl.on('line', (line) => {
431
+ lines.push(line);
432
+ });
433
+ rl.on('close', resolve);
434
+ rl.on('error', reject);
435
+ });
436
+
437
+ if (lines.length === 0) {
438
+ throw new Error('Source file is empty');
439
+ }
440
+
441
+ if (verbose) console.log(`Read ${lines.length} lines, processing...`);
442
+
443
+ // Track processed codes and auto-increment keys
444
+ const codeMap = new Map(); // code -> UniiKey
445
+ let lastUniiKey = 0;
446
+ let lastUniiDescKey = 0;
447
+ let processedLines = 0;
448
+
449
+ const BATCH_SIZE = 1000;
450
+
451
+ // Process batches sequentially
452
+ for (let batchStart = 1; batchStart < lines.length; batchStart += BATCH_SIZE) {
453
+ const batchEnd = Math.min(batchStart + BATCH_SIZE, lines.length);
454
+
455
+ await new Promise((resolve, reject) => {
456
+ const insertUnii = db.prepare('INSERT INTO Unii (UniiKey, Code, Display) VALUES (?, ?, ?)');
457
+ const insertUniiDesc = db.prepare('INSERT INTO UniiDesc (UniiDescKey, UniiKey, Type, Display) VALUES (?, ?, ?, ?)');
458
+
459
+ db.serialize(() => {
460
+ db.run('BEGIN TRANSACTION');
461
+
462
+ for (let i = batchStart; i < batchEnd; i++) {
463
+ const line = lines[i].trim();
464
+ if (!line) continue; // Skip empty lines
465
+
466
+ const cols = line.split('\t');
467
+
468
+ // Need at least 3 columns (Display, Type, Code)
469
+ if (cols.length < 3) {
470
+ if (verbose) console.warn(`Skipping line ${i + 1}: insufficient columns`);
471
+ continue;
472
+ }
473
+
474
+ const display = cols[0] || '';
475
+ const type = cols[1] || '';
476
+ const code = cols[2] || '';
477
+
478
+ if (!code) {
479
+ if (verbose) console.warn(`Skipping line ${i + 1}: empty UNII code`);
480
+ continue;
481
+ }
482
+
483
+ // Get or create UniiKey for this code
484
+ let uniiKey = codeMap.get(code);
485
+ if (!uniiKey) {
486
+ lastUniiKey++;
487
+ uniiKey = lastUniiKey;
488
+ insertUnii.run(uniiKey, code, cols[3]);
489
+ codeMap.set(code, uniiKey);
490
+ }
491
+
492
+ lastUniiDescKey++;
493
+ insertUniiDesc.run(lastUniiDescKey, uniiKey, type, display);
494
+
495
+ processedLines++;
496
+ }
497
+
498
+ db.run('COMMIT', (err) => {
499
+ if (err) return reject(err);
500
+
501
+ insertUnii.finalize((err) => {
502
+ if (err) return reject(err);
503
+ insertUniiDesc.finalize((err) => {
504
+ if (err) return reject(err);
505
+ resolve();
506
+ });
507
+ });
508
+ });
509
+ });
510
+ });
511
+
512
+ if (processedLines % 10000 === 0) {
513
+ console.log(`Processed ${processedLines} lines...`);
514
+ }
515
+ }
516
+
517
+ if (verbose) {
518
+ console.log(`Processing completed. Total lines processed: ${processedLines}`);
519
+ console.log(`Unique UNII codes: ${codeMap.size}`);
520
+ console.log(`Total descriptions: ${lastUniiDescKey}`);
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Closes the database connection
526
+ * @private
527
+ */
528
+ async #closeDatabase(db, verbose = true) {
529
+ return new Promise((resolve) => {
530
+ db.close((err) => {
531
+ if (err && verbose) {
532
+ console.error('Error closing database:', err);
533
+ }
534
+ resolve();
535
+ });
536
+ });
537
+ }
538
+ }
539
+
540
+ // Enhanced migrator with progress reporting
541
+ class UniiDataMigratorWithProgress {
542
+ constructor(migrator, moduleInstance, verbose = true) {
543
+ this.migrator = migrator;
544
+ this.module = moduleInstance;
545
+ this.verbose = verbose;
546
+ }
547
+
548
+ async migrate(sourceFile, destFile, version, verbose) {
549
+ // Count total lines for progress bar
550
+ const totalLines = await this.countLines(sourceFile);
551
+
552
+ this.module.logInfo(`Processing ${totalLines.toLocaleString()} lines...`);
553
+ this.module.createProgressBar();
554
+ this.module.updateProgress(0, totalLines);
555
+
556
+ // Enhance the original migrator to report progress
557
+ const originalMigrator = this.migrator;
558
+ let processedLines = 0;
559
+
560
+ // Override the progress reporting in the original migrator
561
+ const originalConsoleLog = console.log;
562
+ console.log = (...args) => {
563
+ const message = args.join(' ');
564
+ if (message.includes('Processed') && message.includes('lines')) {
565
+ const match = message.match(/Processed (\d+) lines/);
566
+ if (match) {
567
+ processedLines = parseInt(match[1]);
568
+ this.module.updateProgress(processedLines);
569
+ }
570
+ }
571
+ if (verbose) originalConsoleLog(...args);
572
+ };
573
+
574
+ try {
575
+ await originalMigrator.migrate(sourceFile, destFile, version, verbose);
576
+ } finally {
577
+ console.log = originalConsoleLog;
578
+ this.module.stopProgress();
579
+ }
580
+ }
581
+
582
+ async countLines(filePath) {
583
+ return new Promise((resolve, reject) => {
584
+ let lineCount = 0;
585
+ const rl = require('readline').createInterface({
586
+ input: fs.createReadStream(filePath),
587
+ crlfDelay: Infinity
588
+ });
589
+
590
+ rl.on('line', () => lineCount++);
591
+ rl.on('close', () => resolve(lineCount));
592
+ rl.on('error', reject);
593
+ });
594
+ }
595
+ }
596
+
597
+
598
+ module.exports = {
599
+ UniiModule,
600
+ UniiDataMigrator
601
+ };