phase-7: code restructure

This commit is contained in:
Kazuma
2026-06-05 22:05:55 -04:00
committed by Kazuma
parent 2b785aa861
commit 5b32bd7a04
108 changed files with 8931 additions and 3434 deletions
+66
View File
@@ -0,0 +1,66 @@
import { readFileSync } from 'fs';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { AnthropicClient } from '../clients/AnthropicClient';
import type { Logger, LLMAnalysis, Story } from '../types';
export class LLMAnalyst {
private logger: Pick<Logger, 'log' | 'warn'>;
private client: AnthropicClient;
constructor({ logger }: { logger?: Pick<Logger, 'log' | 'warn'> } = {}) {
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'}`;
try {
const PROMPT_FILE = '../../prompts/llm-analyst.md';
const PROMPT_PATH = join(fileURLToPath(import.meta.url), PROMPT_FILE);
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;
} catch (err) {
this.logger.warn('LLMAnalyst: analysis failed —', (err as Error).message);
return null;
}
}
}