232 lines
No EOL
6.5 KiB
TypeScript
232 lines
No EOL
6.5 KiB
TypeScript
/**
|
|
* QM Symbol Info Actions - Fetch and update symbol metadata
|
|
*/
|
|
|
|
import type { ExecutionContext } from '@stock-bot/handlers';
|
|
import type { QMHandler } from '../qm.handler';
|
|
import { QM_CONFIG, QM_SESSION_IDS } from '../shared/config';
|
|
import { QMOperationTracker } from '../shared/operation-tracker';
|
|
import { QMSessionManager } from '../shared/session-manager';
|
|
|
|
// Cache tracker instance
|
|
let operationTracker: QMOperationTracker | null = null;
|
|
|
|
/**
|
|
* Get or initialize the operation tracker
|
|
*/
|
|
async function getOperationTracker(handler: QMHandler): Promise<QMOperationTracker> {
|
|
if (!operationTracker) {
|
|
const { initializeQMOperations } = await import('../shared/operation-registry');
|
|
operationTracker = await initializeQMOperations(handler.mongodb, handler.logger);
|
|
}
|
|
return operationTracker;
|
|
}
|
|
|
|
/**
|
|
* Update symbol info for a single symbol
|
|
* This is a simple API fetch operation - no tracking logic here
|
|
*/
|
|
export async function updateSymbolInfo(
|
|
this: QMHandler,
|
|
input: {
|
|
qmSearchCode: string;
|
|
},
|
|
_context?: ExecutionContext
|
|
): Promise<{
|
|
success: boolean;
|
|
qmSearchCode: string;
|
|
message: string;
|
|
data?: any;
|
|
}> {
|
|
const { qmSearchCode } = input;
|
|
|
|
this.logger.info(`Fetching symbol info ${qmSearchCode}`, { qmSearchCode });
|
|
|
|
const sessionManager = QMSessionManager.getInstance();
|
|
await sessionManager.initialize(this.cache, this.logger);
|
|
|
|
// Get a session
|
|
const sessionId = QM_SESSION_IDS.SYMBOL;
|
|
const session = await sessionManager.getSession(sessionId);
|
|
|
|
if (!session || !session.uuid) {
|
|
throw new Error(`No active session found for QM LOOKUP`);
|
|
}
|
|
|
|
try {
|
|
// Build API request for symbol info
|
|
const searchParams = new URLSearchParams({
|
|
fullDescription: 'true',
|
|
qmodTool: 'CompanyProfile',
|
|
symbols: qmSearchCode,
|
|
lang: 'en',
|
|
pathName: '/demo/portal/company-summary.php',
|
|
webmasterId: '500',
|
|
} as Record<string, string>);
|
|
|
|
const apiUrl = `${QM_CONFIG.SYMBOL_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 symbolData = await response.json();
|
|
|
|
// Update session success stats
|
|
await sessionManager.incrementSuccessfulCalls(sessionId, session.uuid);
|
|
|
|
// Process and store symbol info
|
|
if (symbolData && Array.isArray(symbolData?.results?.company) && symbolData?.results?.company.length > 0) {
|
|
|
|
// Update symbol in database with new metadata
|
|
const updateData = {
|
|
qmSearchCode: qmSearchCode,
|
|
profile: symbolData?.results?.company[0].profile,
|
|
symbolInfo: symbolData?.results?.company[0].symbolinfo,
|
|
}
|
|
|
|
await this.mongodb.updateOne(
|
|
'qmSymbols',
|
|
{ qmSearchCode, },
|
|
{ $set: updateData },
|
|
{ upsert: true }
|
|
);
|
|
|
|
// Update operation tracking
|
|
const tracker = await getOperationTracker(this);
|
|
await tracker.updateSymbolOperation(qmSearchCode, 'symbol_info', {
|
|
status: 'success',
|
|
lastRecordDate: new Date()
|
|
});
|
|
|
|
this.logger.info(`Symbol info updated successfully ${qmSearchCode}`, {
|
|
qmSearchCode,
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
qmSearchCode,
|
|
message: `Symbol info updated for ${qmSearchCode}`,
|
|
};
|
|
} else {
|
|
this.logger.warn('No symbol data returned from API', { qmSearchCode });
|
|
return {
|
|
success: false,
|
|
qmSearchCode,
|
|
message: `No data found for symbol ${qmSearchCode}`
|
|
};
|
|
}
|
|
|
|
} catch (error) {
|
|
// Update session failure stats
|
|
if (session.uuid) {
|
|
await sessionManager.incrementFailedCalls(sessionId, session.uuid);
|
|
}
|
|
|
|
this.logger.error('Error fetching symbol info', {
|
|
qmSearchCode,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
});
|
|
|
|
// Update operation tracking for failure
|
|
const tracker = await getOperationTracker(this);
|
|
await tracker.updateSymbolOperation(qmSearchCode, 'symbol_info', {
|
|
status: 'failure'
|
|
});
|
|
|
|
return {
|
|
success: false,
|
|
qmSearchCode,
|
|
message: `Failed to fetch symbol info: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Schedule symbol info updates for symbols that need refreshing
|
|
* This is the scheduled job that finds stale symbols and queues individual updates
|
|
*/
|
|
export async function scheduleSymbolInfoUpdates(
|
|
this: QMHandler,
|
|
input: {
|
|
limit?: number;
|
|
forceUpdate?: boolean;
|
|
} = {},
|
|
_context?: ExecutionContext
|
|
): Promise<{
|
|
message: string;
|
|
symbolsQueued: number;
|
|
errors: number;
|
|
}> {
|
|
const { limit = 100000, forceUpdate = false } = input;
|
|
const tracker = await getOperationTracker(this);
|
|
|
|
this.logger.info('Scheduling symbol info updates', { limit, forceUpdate });
|
|
|
|
try {
|
|
// Get symbols that need updating
|
|
const staleSymbols = await tracker.getStaleSymbols('symbol_info', {
|
|
minHoursSinceRun: forceUpdate ? 0 : 24 * 7, // Weekly by default
|
|
limit
|
|
});
|
|
|
|
if (staleSymbols.length === 0) {
|
|
this.logger.info('No symbols need info updates');
|
|
return {
|
|
message: 'No symbols need info updates',
|
|
symbolsQueued: 0,
|
|
errors: 0
|
|
};
|
|
}
|
|
|
|
this.logger.info(`Found ${staleSymbols.length} symbols needing info updates`);
|
|
|
|
// Get full symbol data to include qmSearchCode
|
|
const symbolDocs = await this.mongodb.find('qmSymbols', {
|
|
qmSearchCode: { $in: staleSymbols }
|
|
}, {
|
|
projection: { qmSearchCode: 1 }
|
|
});
|
|
|
|
let queued = 0;
|
|
let errors = 0;
|
|
|
|
// Schedule individual update jobs for each symbol
|
|
for (const doc of symbolDocs) {
|
|
try {
|
|
await this.scheduleOperation('update-symbol-info', {
|
|
qmSearchCode: doc.qmSearchCode
|
|
}, {
|
|
// priority: 3,
|
|
// Add some delay to avoid overwhelming the API
|
|
// delay: queued * 1000 // 1 second between jobs
|
|
});
|
|
|
|
queued++;
|
|
} catch (error) {
|
|
this.logger.error(`Failed to schedule update for ${doc.qmSearchCode}`, { error });
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
this.logger.info('Symbol info update scheduling completed', {
|
|
symbolsQueued: queued,
|
|
errors,
|
|
total: staleSymbols.length
|
|
});
|
|
|
|
return {
|
|
message: `Scheduled info updates for ${queued} symbols`,
|
|
symbolsQueued: queued,
|
|
errors
|
|
};
|
|
} catch (error) {
|
|
this.logger.error('Symbol info scheduling failed', { error });
|
|
throw error;
|
|
}
|
|
} |