/** * Data Import Script * Imports test data from DAT and SHT files into PostgreSQL database */ const fs = require('fs'); const path = require('path'); const db = require('./db'); const { parseMultilineFile, extractTestStation } = require('../parsers/multiline'); const { parseCsvFile } = require('../parsers/csvline'); const { parseShtFile } = require('../parsers/shtfile'); // Data source paths const TEST_PATH = 'C:/Shares/test'; const RECOVERY_PATH = 'C:/Shares/Recovery-TEST'; const HISTLOGS_PATH = path.join(TEST_PATH, 'Ate/HISTLOGS'); // Log types and their parsers const LOG_TYPES = { 'DSCLOG': { parser: 'multiline', ext: '.DAT' }, '5BLOG': { parser: 'multiline', ext: '.DAT' }, '8BLOG': { parser: 'multiline', ext: '.DAT' }, 'PWRLOG': { parser: 'multiline', ext: '.DAT' }, 'SCTLOG': { parser: 'multiline', ext: '.DAT' }, 'VASLOG': { parser: 'multiline', ext: '.DAT' }, '7BLOG': { parser: 'csvline', ext: '.DAT' } }; // Find all files of a specific type in a directory function findFiles(dir, pattern, recursive = true) { const results = []; try { if (!fs.existsSync(dir)) return results; const items = fs.readdirSync(dir, { withFileTypes: true }); for (const item of items) { const fullPath = path.join(dir, item.name); if (item.isDirectory() && recursive) { results.push(...findFiles(fullPath, pattern, recursive)); } else if (item.isFile()) { if (pattern.test(item.name)) { results.push(fullPath); } } } } catch (err) { // Ignore permission errors } return results; } // Parse records from a file (sync -- file I/O only) function parseFile(filePath, logType, parser) { const testStation = extractTestStation(filePath); switch (parser) { case 'multiline': return parseMultilineFile(filePath, logType, testStation); case 'csvline': return parseCsvFile(filePath, testStation); case 'shtfile': return parseShtFile(filePath, testStation); default: return []; } } // Batch insert records into PostgreSQL async function insertBatch(txClient, records) { let imported = 0; for (const record of records) { try { const result = await txClient.execute( `INSERT INTO test_records (log_type, model_number, serial_number, test_date, test_station, overall_result, raw_data, source_file) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (log_type, model_number, serial_number, test_date, test_station) DO UPDATE SET raw_data = EXCLUDED.raw_data, overall_result = EXCLUDED.overall_result`, [ record.log_type, record.model_number, record.serial_number, record.test_date, record.test_station, record.overall_result, record.raw_data, record.source_file ] ); if (result.rowCount > 0) imported++; } catch (err) { // Constraint error - skip } } return imported; } // Import records from a file async function importFile(txClient, filePath, logType, parser) { let records = []; try { records = parseFile(filePath, logType, parser); const imported = await insertBatch(txClient, records); return { total: records.length, imported }; } catch (err) { console.error(`Error importing ${filePath}: ${err.message}`); return { total: 0, imported: 0 }; } } // Import from HISTLOGS (master consolidated logs) async function importHistlogs(txClient) { console.log('\n=== Importing from HISTLOGS ==='); let totalImported = 0; let totalRecords = 0; for (const [logType, config] of Object.entries(LOG_TYPES)) { const logDir = path.join(HISTLOGS_PATH, logType); if (!fs.existsSync(logDir)) { console.log(` ${logType}: directory not found`); continue; } const files = findFiles(logDir, new RegExp(`\\${config.ext}$`, 'i'), false); console.log(` ${logType}: found ${files.length} files`); for (const file of files) { const { total, imported } = await importFile(txClient, file, logType, config.parser); totalRecords += total; totalImported += imported; } } console.log(` HISTLOGS total: ${totalImported} records imported (${totalRecords} parsed)`); return totalImported; } // Import from test station logs async function importStationLogs(txClient, basePath, label) { console.log(`\n=== Importing from ${label} ===`); let totalImported = 0; let totalRecords = 0; const stationPattern = /^TS-\d+[LR]?$/i; let stations = []; try { const items = fs.readdirSync(basePath, { withFileTypes: true }); stations = items .filter(i => i.isDirectory() && stationPattern.test(i.name)) .map(i => i.name); } catch (err) { console.log(` Error reading ${basePath}: ${err.message}`); return 0; } console.log(` Found stations: ${stations.join(', ')}`); for (const station of stations) { const logsDir = path.join(basePath, station, 'LOGS'); if (!fs.existsSync(logsDir)) continue; for (const [logType, config] of Object.entries(LOG_TYPES)) { const logDir = path.join(logsDir, logType); if (!fs.existsSync(logDir)) continue; const files = findFiles(logDir, new RegExp(`\\${config.ext}$`, 'i'), false); for (const file of files) { const { total, imported } = await importFile(txClient, file, logType, config.parser); totalRecords += total; totalImported += imported; } } } // Also import SHT files const shtFiles = findFiles(basePath, /\.SHT$/i, true); console.log(` Found ${shtFiles.length} SHT files`); for (const file of shtFiles) { const { total, imported } = await importFile(txClient, file, 'SHT', 'shtfile'); totalRecords += total; totalImported += imported; } console.log(` ${label} total: ${totalImported} records imported (${totalRecords} parsed)`); return totalImported; } // Import from Recovery-TEST backups (newest first) async function importRecoveryBackups(txClient) { console.log('\n=== Importing from Recovery-TEST backups ==='); if (!fs.existsSync(RECOVERY_PATH)) { console.log(' Recovery-TEST directory not found'); return 0; } const backups = fs.readdirSync(RECOVERY_PATH, { withFileTypes: true }) .filter(i => i.isDirectory() && /^\d{2}-\d{2}-\d{2}$/.test(i.name)) .map(i => i.name) .sort() .reverse(); console.log(` Found backup dates: ${backups.join(', ')}`); let totalImported = 0; for (const backup of backups) { const backupPath = path.join(RECOVERY_PATH, backup); const imported = await importStationLogs(txClient, backupPath, `Recovery-TEST/${backup}`); totalImported += imported; } return totalImported; } // Main import function async function runImport() { console.log('========================================'); console.log('Test Data Import'); console.log('========================================'); console.log(`Start time: ${new Date().toISOString()}`); let grandTotal = 0; await db.transaction(async (txClient) => { grandTotal += await importHistlogs(txClient); grandTotal += await importRecoveryBackups(txClient); grandTotal += await importStationLogs(txClient, TEST_PATH, 'test'); }); const stats = await db.queryOne('SELECT COUNT(*) as count FROM test_records'); console.log('\n========================================'); console.log('Import Complete'); console.log('========================================'); console.log(`Total records in database: ${stats.count}`); console.log(`End time: ${new Date().toISOString()}`); await db.close(); } // Import a single file (for incremental imports from sync) async function importSingleFile(filePath) { console.log(`Importing: ${filePath}`); let logType = null; let parser = null; for (const [type, config] of Object.entries(LOG_TYPES)) { if (filePath.includes(type)) { logType = type; parser = config.parser; break; } } if (!logType) { if (/\.SHT$/i.test(filePath)) { logType = 'SHT'; parser = 'shtfile'; } else { console.log(` Unknown log type for: ${filePath}`); return { total: 0, imported: 0 }; } } let result; await db.transaction(async (txClient) => { result = await importFile(txClient, filePath, logType, parser); }); console.log(` Imported ${result.imported} of ${result.total} records`); return result; } // Import multiple files (for batch incremental imports) async function importFiles(filePaths) { console.log(`\n========================================`); console.log(`Incremental Import: ${filePaths.length} files`); console.log(`========================================`); let totalImported = 0; let totalRecords = 0; await db.transaction(async (txClient) => { for (const filePath of filePaths) { let logType = null; let parser = null; for (const [type, config] of Object.entries(LOG_TYPES)) { if (filePath.includes(type)) { logType = type; parser = config.parser; break; } } if (!logType) { if (/\.SHT$/i.test(filePath)) { logType = 'SHT'; parser = 'shtfile'; } else { console.log(` Skipping unknown type: ${filePath}`); continue; } } const { total, imported } = await importFile(txClient, filePath, logType, parser); totalRecords += total; totalImported += imported; console.log(` ${path.basename(filePath)}: ${imported}/${total} records`); } }); console.log(`\nTotal: ${totalImported} records imported (${totalRecords} parsed)`); // Export datasheets for newly imported records if (totalImported > 0) { try { const { loadAllSpecs } = require('../parsers/spec-reader'); const { exportNewRecords } = require('./export-datasheets'); const specMap = loadAllSpecs(); await exportNewRecords(specMap, filePaths); } catch (err) { console.error(`[EXPORT] Datasheet export failed: ${err.message}`); } } return { total: totalRecords, imported: totalImported }; } // Run if called directly if (require.main === module) { const args = process.argv.slice(2); if (args.length > 0 && args[0] === '--file') { const files = args.slice(1); if (files.length === 0) { console.log('Usage: node import.js --file [file2] ...'); process.exit(1); } importFiles(files).then(() => db.close()).catch(console.error); } else if (args.length > 0 && args[0] === '--help') { console.log('Usage:'); console.log(' node import.js Full import from all sources'); console.log(' node import.js --file Import specific file(s)'); process.exit(0); } else { runImport().catch(console.error); } } module.exports = { runImport, importSingleFile, importFiles };