fhirsmith 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
|
@@ -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
|
+
};
|