237 lines
No EOL
6.5 KiB
TypeScript
237 lines
No EOL
6.5 KiB
TypeScript
/**
|
|
* 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;
|
|
}
|
|
} |