import { readFileSync } from 'fs'; import { join } from 'path'; import { AnthropicClient } from '../adapters/AnthropicClient'; import type { Logger, LLMAnalysis, Story } from '../types/index'; export class LLMAnalyst { private logger: Pick; private client: AnthropicClient; constructor({ logger }: { logger?: Pick } = {}) { // eslint-disable-next-line no-console this.logger = logger ?? { log: console.log, warn: console.warn }; this.client = new AnthropicClient(); } get isAvailable(): boolean { return this.client.isAvailable; } async analyze( stories: Story[], existingTickers: string[] = [], tickerFrequency: Record = {}, ): Promise { if (!this.client.isAvailable) { this.logger.warn('LLMAnalyst: ANTHROPIC_API_KEY not set — skipping analysis'); return null; } if (!stories?.length) return null; const headlines = stories .slice(0, 15) .map((s, i) => { const tickers = s.tickers.length ? ` [${s.tickers.join(', ')}]` : ''; return `${i + 1}. ${s.title} (${s.source})${tickers}`; }) .join('\n'); const freqLines = Object.entries(tickerFrequency) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([t, n]) => ` ${t}: ${n} ${n === 1 ? 'story' : 'stories'}`) .join('\n'); const freqSection = freqLines ? `\nTicker mention frequency (ranked):\n${freqLines}\n` : ''; const userMessage = `Today's market news headlines:\n\n${headlines}\n${freqSection}\nAlready identified catalyst tickers: ${existingTickers.join(', ') || 'none'}`; const PROMPT_PATH = join(process.cwd(), 'prompts', 'llm-analyst.md'); const SYSTEM_PROMPT = readFileSync(PROMPT_PATH, 'utf8'); const raw = await this.client.complete(SYSTEM_PROMPT, userMessage); if (!raw) return null; const cleaned = raw .replace(/^```(?:json)?\s*/i, '') .replace(/```\s*$/i, '') .trim(); return JSON.parse(cleaned) as LLMAnalysis; } }