institutional-trader/backend/scripts/fixDatabaseDates.js

233 lines
7.8 KiB
JavaScript

import dotenv from 'dotenv';
import fs from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { Pool } from 'pg';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load environment variables
dotenv.config({ path: join(__dirname, '..', '.env') });
// Database configuration
const dbConfig = {
host: process.env.LOCAL_DB_HOST || '192.168.8.151',
port: parseInt(process.env.LOCAL_DB_PORT || '5432'),
user: process.env.LOCAL_DB_USER || 'postgres',
password: process.env.LOCAL_DB_PASSWORD || 'postgres',
database: process.env.LOCAL_DB_NAME || 'institutional_trader'
};
/**
* Execute SQL file or query
*/
async function executeSQL(query, params = []) {
const pool = new Pool(dbConfig);
try {
const result = await pool.query(query, params);
return result.rows;
} finally {
await pool.end();
}
}
/**
* Run analysis queries to show current state
*/
async function analyzeDatabase() {
console.log('\n📊 Analyzing database date/time formats...\n');
const queries = {
'CreatedDate Analysis': `
SELECT
COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_iso_date,
COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as slash_format,
COUNT(*) FILTER (WHERE "CreatedDate" IS NOT NULL AND "CreatedDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$' AND "CreatedDate"::text !~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as invalid_format,
COUNT(*) FILTER (WHERE "CreatedDate" IS NULL) as null_dates,
COUNT(*) as total_rows
FROM "OptionsFlow_monthly"
`,
'CreatedTime Analysis': `
SELECT
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}\\s+[AP]M$') as valid_with_space,
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}[AP]M$') as valid_no_space,
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}\\s+[AP]M$') as valid_minutes_only_space,
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}[AP]M$') as valid_minutes_only_no_space,
COUNT(*) FILTER (WHERE "CreatedTime" IS NOT NULL AND "CreatedTime"::text !~* '(AM|PM)') as no_ampm,
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}\\s*[AP]M$' AND "CreatedTime"::text !~ ':') as missing_colon,
COUNT(*) FILTER (WHERE "CreatedTime" IS NULL) as null_times,
COUNT(*) as total_rows
FROM "OptionsFlow_monthly"
`,
'ExpirationDate Analysis': `
SELECT
COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_iso_date,
COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as slash_format,
COUNT(*) FILTER (WHERE "ExpirationDate" IS NOT NULL AND "ExpirationDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$' AND "ExpirationDate"::text !~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as invalid_format,
COUNT(*) FILTER (WHERE "ExpirationDate" IS NULL) as null_dates,
COUNT(*) as total_rows
FROM "OptionsFlow_monthly"
`
};
for (const [name, query] of Object.entries(queries)) {
console.log(`\n${name}:`);
console.log('─'.repeat(50));
try {
const results = await executeSQL(query);
if (results.length > 0) {
console.table(results[0]);
}
} catch (error) {
console.error(`Error running ${name}:`, error.message);
}
}
}
/**
* Run the fix script
*/
async function runFix() {
console.log('\n🔧 Running date/time fixes...\n');
const sqlFile = join(__dirname, '..', 'database', 'fix_all_dates_and_times.sql');
const sql = fs.readFileSync(sqlFile, 'utf8');
const pool = new Pool(dbConfig);
try {
// Extract and execute UPDATE statements only (skip SELECT/verification)
const updateStatements = sql
.split(/;[\r\n]+/)
.map(s => s.trim())
.filter(s => {
const upper = s.toUpperCase().trim();
return upper.startsWith('UPDATE') && !upper.includes('-- Skip');
});
console.log(`Found ${updateStatements.length} UPDATE statements to execute...\n`);
for (let i = 0; i < updateStatements.length; i++) {
const statement = updateStatements[i];
try {
const result = await pool.query(statement);
console.log(`✅ Update ${i + 1}/${updateStatements.length}: ${result.rowCount || 0} rows affected`);
} catch (error) {
console.error(`❌ Error in update ${i + 1}:`, error.message);
// Continue with next statement
}
}
console.log('\n✅ All fix statements executed!\n');
} finally {
await pool.end();
}
}
/**
* Verify fixes
*/
async function verifyFixes() {
console.log('\n✅ Verifying fixes...\n');
const queries = {
'CreatedDate Verification': `
SELECT
COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_format,
COUNT(*) FILTER (WHERE "CreatedDate" IS NOT NULL AND "CreatedDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$') as still_invalid,
COUNT(*) FILTER (WHERE "CreatedDate" IS NULL) as null_count,
COUNT(*) as total_rows
FROM "OptionsFlow_monthly"
`,
'CreatedTime Verification': `
SELECT
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}\\s+[AP]M$') as valid_format,
COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}\\s+[AP]M$') as minutes_only_valid,
COUNT(*) FILTER (WHERE "CreatedTime" IS NOT NULL AND "CreatedTime"::text !~ '^\\d{1,2}:\\d{2}(:\\d{2})?\\s+[AP]M$') as still_invalid,
COUNT(*) FILTER (WHERE "CreatedTime" IS NULL) as null_count,
COUNT(*) as total_rows
FROM "OptionsFlow_monthly"
`,
'ExpirationDate Verification': `
SELECT
COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_format,
COUNT(*) FILTER (WHERE "ExpirationDate" IS NOT NULL AND "ExpirationDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$') as still_invalid,
COUNT(*) FILTER (WHERE "ExpirationDate" IS NULL) as null_count,
COUNT(*) as total_rows
FROM "OptionsFlow_monthly"
`
};
for (const [name, query] of Object.entries(queries)) {
console.log(`\n${name}:`);
console.log('─'.repeat(50));
try {
const results = await executeSQL(query);
if (results.length > 0) {
const row = results[0];
console.table(row);
// Show percentage
if (row.total_rows > 0) {
const validPct = ((row.valid_format || 0) / row.total_rows * 100).toFixed(2);
const invalidPct = ((row.still_invalid || 0) / row.total_rows * 100).toFixed(2);
console.log(` Valid: ${validPct}% | Invalid: ${invalidPct}%`);
}
}
} catch (error) {
console.error(`Error running ${name}:`, error.message);
}
}
}
/**
* Main function
*/
async function main() {
const args = process.argv.slice(2);
const command = args[0] || 'all';
console.log('🗄️ Database Date/Time Fix Script\n');
console.log(`Connecting to: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}\n`);
try {
if (command === 'analyze' || command === 'all') {
await analyzeDatabase();
}
if (command === 'fix' || command === 'all') {
if (command === 'all') {
console.log('\n' + '='.repeat(60));
console.log('Press Enter to continue with fixes, or Ctrl+C to cancel...');
// Note: In a real implementation, you might want to add a prompt here
}
await runFix();
}
if (command === 'verify' || command === 'all') {
await verifyFixes();
}
console.log('\n✨ Done!\n');
} catch (error) {
console.error('\n❌ Error:', error.message);
process.exit(1);
}
}
// Run if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}
export { analyzeDatabase, runFix, verifyFixes };