61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
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<Logger, 'log' | 'warn'>;
|
|
private client: AnthropicClient;
|
|
|
|
constructor({ logger }: { logger?: Pick<Logger, 'log' | 'warn'> } = {}) {
|
|
// 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<string, number> = {},
|
|
): Promise<LLMAnalysis | null> {
|
|
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;
|
|
}
|
|
}
|