import { getLogger } from '@stock-bot/logger'; import { getMongoDBClient, getPostgreSQLClient } from '../../../clients'; import type { JobPayload, SyncResult } from '../../../types/job-payloads'; const logger = getLogger('enhanced-sync-qm-provider-mappings'); export async function syncQMProviderMappings(payload: JobPayload): Promise { logger.info('Starting QM provider exchange mappings sync...'); const result: SyncResult = { processed: 0, created: 0, updated: 0, skipped: 0, errors: 0, }; try { const mongoClient = getMongoDBClient(); const postgresClient = getPostgreSQLClient(); // Start transaction await postgresClient.query('BEGIN'); // Get unique exchange combinations from QM symbols const db = mongoClient.getDatabase(); const pipeline = [ { $group: { _id: { exchangeCode: '$exchangeCode', exchange: '$exchange', countryCode: '$countryCode', }, count: { $sum: 1 }, sampleExchange: { $first: '$exchange' }, }, }, { $project: { exchangeCode: '$_id.exchangeCode', exchange: '$_id.exchange', countryCode: '$_id.countryCode', count: 1, sampleExchange: 1, }, }, ]; const qmExchanges = await db.collection('qmSymbols').aggregate(pipeline).toArray(); logger.info(`Found ${qmExchanges.length} unique QM exchange combinations`); for (const exchange of qmExchanges) { try { // Create provider exchange mapping for QM await createProviderExchangeMapping( 'qm', // provider exchange.exchangeCode, exchange.sampleExchange || exchange.exchangeCode, exchange.countryCode, exchange.countryCode === 'CA' ? 'CAD' : 'USD', // Simple currency mapping 0.8 // good confidence for QM data ); result.processed++; result.created++; } catch (error) { logger.error('Failed to process QM exchange mapping', { error, exchange }); result.errors++; } } await postgresClient.query('COMMIT'); logger.info('QM provider exchange mappings sync completed', result); return result; } catch (error) { const postgresClient = getPostgreSQLClient(); await postgresClient.query('ROLLBACK'); logger.error('QM provider exchange mappings sync failed', { error }); throw error; } } async function createProviderExchangeMapping( provider: string, providerExchangeCode: string, providerExchangeName: string, countryCode: string | null, currency: string | null, confidence: number ): Promise { if (!providerExchangeCode) { return; } const postgresClient = getPostgreSQLClient(); // Check if mapping already exists const existingMapping = await findProviderExchangeMapping(provider, providerExchangeCode); if (existingMapping) { // Don't override existing mappings to preserve manual work return; } // Find or create master exchange const masterExchange = await findOrCreateMasterExchange( providerExchangeCode, providerExchangeName, countryCode, currency ); // Create the provider exchange mapping const query = ` INSERT INTO provider_exchange_mappings (provider, provider_exchange_code, provider_exchange_name, master_exchange_id, country_code, currency, confidence, active, auto_mapped) VALUES ($1, $2, $3, $4, $5, $6, $7, false, true) ON CONFLICT (provider, provider_exchange_code) DO NOTHING `; await postgresClient.query(query, [ provider, providerExchangeCode, providerExchangeName, masterExchange.id, countryCode, currency, confidence, ]); } async function findProviderExchangeMapping(provider: string, providerExchangeCode: string): Promise { const postgresClient = getPostgreSQLClient(); const query = 'SELECT * FROM provider_exchange_mappings WHERE provider = $1 AND provider_exchange_code = $2'; const result = await postgresClient.query(query, [provider, providerExchangeCode]); return result.rows[0] || null; } async function findOrCreateMasterExchange( providerCode: string, providerName: string, countryCode: string | null, currency: string | null ): Promise { const postgresClient = getPostgreSQLClient(); // First, try to find exact match let masterExchange = await findExchangeByCode(providerCode); if (masterExchange) { return masterExchange; } // Try to find by similar codes (basic mapping) const basicMapping = getBasicExchangeMapping(providerCode); if (basicMapping) { masterExchange = await findExchangeByCode(basicMapping); if (masterExchange) { return masterExchange; } } // Create new master exchange (inactive by default) const query = ` INSERT INTO exchanges (code, name, country, currency, active) VALUES ($1, $2, $3, $4, false) ON CONFLICT (code) DO UPDATE SET name = COALESCE(EXCLUDED.name, exchanges.name), country = COALESCE(EXCLUDED.country, exchanges.country), currency = COALESCE(EXCLUDED.currency, exchanges.currency) RETURNING id, code, name, country, currency `; const result = await postgresClient.query(query, [ providerCode, providerName || providerCode, countryCode || 'US', currency || 'USD', ]); return result.rows[0]; } function getBasicExchangeMapping(providerCode: string): string | null { const mappings: Record = { NYE: 'NYSE', NAS: 'NASDAQ', TO: 'TSX', LN: 'LSE', LON: 'LSE', }; return mappings[providerCode.toUpperCase()] || null; } async function findExchangeByCode(code: string): Promise { const postgresClient = getPostgreSQLClient(); const query = 'SELECT * FROM exchanges WHERE code = $1'; const result = await postgresClient.query(query, [code]); return result.rows[0] || null; }