/**
* Migrate JSON Data to PostgreSQL
*
* This script migrates historical quotes from JSON files to PostgreSQL.
* Run: npm run db:migrate
*/
import * as fs from 'fs';
import * as path from 'path';
import { PostgresStorageService } from '../src/database/postgres';
import { HistoricalQuote } from '../src/types';
import { embeddingService } from '../src/services/embedding';
async function migrateData() {
console.log('🔄 Starting data migration from JSON to PostgreSQL...\n');
const storage = PostgresStorageService.getInstance();
try {
// Check database connection
console.log('1️⃣ Checking database connection...');
const isHealthy = await storage.healthCheck();
if (!isHealthy) {
throw new Error('Database connection failed');
}
console.log('✓ Database connected\n');
// Read JSON files
console.log('2️⃣ Reading JSON files...');
const quotesPath = path.join(__dirname, '../data/quotes.json');
if (!fs.existsSync(quotesPath)) {
console.log('⚠ No quotes.json found. Creating empty database.');
return;
}
const quotesData = JSON.parse(fs.readFileSync(quotesPath, 'utf-8'));
const quotes: HistoricalQuote[] = quotesData.historicalQuotes || [];
console.log(`✓ Found ${quotes.length} historical quotes\n');
// Migrate quotes
console.log('3️⃣ Migrating quotes to PostgreSQL...');
let migratedCount = 0;
let skippedCount = 0;
let errorCount = 0;
for (const quote of quotes) {
try {
// Check if quote already exists
const existing = await storage.getQuoteById(quote.id);
if (existing) {
console.log(` ⊘ Skipping ${quote.id} (already exists)`);
skippedCount++;
continue;
}
// Add quote
await storage.addQuote(quote);
console.log(` ✓ Migrated ${quote.id} - ${quote.customerName}`);
migratedCount++;
// Generate and store embedding if OpenAI is enabled
if (embeddingService.isEnabled()) {
try {
const rfpText = embeddingService.createRfpText({
rawText: quote.rawRfp,
material: quote.normalized.material,
processes: quote.normalized.processes,
qty: quote.normalized.qtyRange?.[0],
tolerances: quote.normalized.tolerances,
finish: quote.normalized.finish,
notes: quote.notes
});
const embedding = await embeddingService.generateEmbedding(rfpText);
await storage.updateQuoteEmbedding(quote.id, embedding);
console.log(` ↳ Generated embedding for ${quote.id}`);
} catch (embError: any) {
console.log(` ⚠ Failed to generate embedding: ${embError.message}`);
}
}
} catch (error: any) {
console.error(` ✗ Error migrating ${quote.id}: ${error.message}`);
errorCount++;
}
}
console.log('\n4️⃣ Migration Summary:');
console.log(` ✓ Migrated: ${migratedCount} quotes`);
console.log(` ⊘ Skipped: ${skippedCount} quotes (already existed)`);
console.log(` ✗ Errors: ${errorCount} quotes`);
if (migratedCount > 0) {
console.log('\n✅ Migration completed successfully!');
console.log('💡 Tip: Original JSON files are preserved as backup');
} else if (skippedCount > 0) {
console.log('\n✅ All quotes already exist in database');
}
} catch (error: any) {
console.error('\n❌ Migration failed:', error.message);
process.exit(1);
} finally {
await storage.disconnect();
}
}
// Run migration
migrateData().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});