/** * QM Filings Actions - Fetch and update SEC filings */ import type { ExecutionContext } from '@stock-bot/handlers'; import type { QMHandler } from '../qm.handler'; import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config'; import { QMSessionManager } from '../shared/session-manager'; /** * Update filings for a single symbol */ export async function updateFilings( this: QMHandler, input: { symbol: string; exchange: string; lastRecordDate?: Date | null; qmSearchCode: string; page: number; totalPages?: number; }, _context?: ExecutionContext ): Promise<{ success: boolean; symbol: string; message: string; data?: any; }> { const { qmSearchCode, page, symbol, exchange, lastRecordDate, totalPages } = input; this.logger.info(`Fetching filings ${qmSearchCode} - ${page}/${totalPages}`, { qmSearchCode, page }); const sessionManager = QMSessionManager.getInstance(); await sessionManager.initialize(this.cache, this.logger); // Get a session - you'll need to add the appropriate session ID for filings const sessionId = QM_SESSION_IDS.LOOKUP; // TODO: Update with correct session ID const session = await sessionManager.getSession(sessionId); if (!session || !session.uuid) { throw new Error(`No active session found for QM filings`); } try { // Build API request for filings const searchParams = new URLSearchParams({ symbol: qmSearchCode, webmasterId: "500", page: "1", xbrlSubDoc: "true", inclIxbrl: "true", inclXbrl: "true", resultsPerPage: "25", }); // TODO: Update with correct filings endpoint const apiUrl = `${QM_CONFIG.FILING_URL}?${searchParams.toString()}`; const response = await fetch(apiUrl, { method: 'GET', headers: session.headers, proxy: session.proxy, }); if (!response.ok) { throw new Error(`QM API request failed: ${response.status} ${response.statusText}`); } const filingsData = await response.json(); // Update session success stats await sessionManager.incrementSuccessfulCalls(sessionId, session.uuid); // Process and store filings data if (filingsData && filingsData.length > 0) { // Store filings in a separate collection await this.mongodb.batchUpsert( 'qmFilings', filingsData.map((filing: any) => ({ ...filing, symbol, exchange, })), ['qmSearchCode', 'filingId'] // Unique keys ); // Update symbol to track last filings update await this.operationRegistry.updateOperation('qm', qmSearchCode, 'filings_update', { status: 'success', lastRecordDate: new Date(), recordCount: filingsData.length }); this.logger.info('Filings updated successfully', { symbol, filingsCount: filingsData.length }); return { success: true, symbol, message: `Filings updated for ${symbol}`, data: { count: filingsData.length } }; } else { // Some symbols may not have filings (non-US companies, etc) await this.operationRegistry.updateOperation('qm', qmSearchCode, 'filings_update', { status: 'success', lastRecordDate: new Date(), recordCount: 0 }); this.logger.info('No filings found for symbol', { symbol }); return { success: true, symbol, message: `No filings found for ${symbol}`, data: { count: 0 } }; } } catch (error) { // Update session failure stats if (session.uuid) { await sessionManager.incrementFailedCalls(sessionId, session.uuid); } this.logger.error('Error fetching filings', { symbol, error: error instanceof Error ? error.message : 'Unknown error' }); // Track failure await this.operationRegistry.updateOperation('qm', qmSearchCode, 'filings_update', { status: 'failure' }); return { success: false, symbol, message: `Failed to fetch filings: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } /** * Schedule filings updates for symbols that need refreshing */ export async function scheduleFilingsUpdates( this: QMHandler, input: { limit?: number; forceUpdate?: boolean; } = {}, _context?: ExecutionContext ): Promise<{ message: string; symbolsQueued: number; errors: number; }> { const { limit = 1, forceUpdate = false } = input; this.logger.info('Scheduling filings updates', { limit, forceUpdate }); try { // Get symbols that need updating // const staleSymbols = await this.operationRegistry.getStaleSymbols('qm', 'filings_update', { // minHoursSinceRun: forceUpdate ? 0 : 24, // Daily for filings // limit // }); const staleSymbols = ['X:CA'] if (staleSymbols.length === 0) { this.logger.info('No symbols need filings updates'); return { message: 'No symbols need filings updates', symbolsQueued: 0, errors: 0 }; } this.logger.info(`Found ${staleSymbols.length} symbols needing filings updates`); // Get full symbol data to include symbolId const symbolDocs = await this.mongodb.find('qmSymbols', { qmSearchCode: { $in: staleSymbols } }, { projection: { qmSearchCode: 1, operations: 1, symbol: 1, exchange: 1 } }); let queued = 0; let errors = 0; // Schedule individual update jobs for each symbol for (const doc of symbolDocs) { try { if (!doc.symbolId) { this.logger.warn(`Symbol ${doc.symbol} missing symbolId, skipping`); continue; } await this.scheduleOperation('update-filings', { symbol: doc.symbol, exchange: doc.exchange, qmSearchCode: doc.qmSearchCode, lastRecordDate: doc.operations?.filings_update?.lastRecordDate || null, }, { priority: 5, // Lower priority than financial data }); queued++; } catch (error) { this.logger.error(`Failed to schedule filings update for ${doc.symbol}`, { error }); errors++; } } this.logger.info('Filings update scheduling completed', { symbolsQueued: queued, errors, total: staleSymbols.length }); return { message: `Scheduled filings updates for ${queued} symbols`, symbolsQueued: queued, errors }; } catch (error) { this.logger.error('Filings scheduling failed', { error }); throw error; } }