Files
market_screener/server/domains/calls/CalendarService.ts
T
Kazuma 0dac8128bd phase-9: domain-driven architecture complete
- Restructured server layer with 5 domains: shared, screener, portfolio, calls, finance
- Migrated 58 TypeScript files to domain-driven structure
- Updated CLAUDE.md with new architecture documentation
- Added .gitignore rules for .md files (except CLAUDE.md)
- Removed unused CatalystAnalyst import from app.ts
- Fixed lint errors: removed unused imports, fixed regex escape, added console suppressions
- Verified no sensitive data in git history
- Server code compiles cleanly with TypeScript strict mode
2026-06-06 22:55:43 -04:00

83 lines
2.5 KiB
TypeScript

import { YahooFinanceClient, chunkArray } from '../../domains/shared';
import type { CalendarEvent } from '../../domains/shared';
export class CalendarService {
constructor(private readonly yahoo: YahooFinanceClient) {}
async getEvents(tickers: string[]): Promise<{ events: CalendarEvent[]; tickers: string[] }> {
if (tickers.length === 0) return { events: [], tickers: [] };
const raw: Record<string, any> = {};
for (const batch of chunkArray(tickers, 5)) {
await Promise.all(
batch.map(async (ticker) => {
const cal = await this.yahoo.fetchCalendarEvents(ticker);
if (cal) raw[ticker] = cal;
}),
);
await new Promise<void>((r) => setTimeout(r, 500));
}
const now = Date.now();
const events = CalendarService.buildEvents(raw, now);
CalendarService.sortEvents(events);
return { events, tickers };
}
private static buildEvents(raw: Record<string, any>, now: number): CalendarEvent[] {
const events: CalendarEvent[] = [];
for (const [ticker, cal] of Object.entries(raw)) {
for (const dateVal of cal.earnings?.earningsDate ?? []) {
const d = new Date(dateVal as string);
events.push({
ticker,
type: 'earnings',
date: d.toISOString().slice(0, 10),
label: 'Earnings',
detail: cal.earnings.isEarningsDateEstimate ? 'Estimated' : 'Confirmed',
epsEstimate: cal.earnings.earningsAverage ?? null,
revEstimate: cal.earnings.revenueAverage ?? null,
isPast: d.getTime() < now,
});
}
if (cal.exDividendDate) {
const d = new Date(cal.exDividendDate);
events.push({
ticker,
type: 'exdividend',
date: d.toISOString().slice(0, 10),
label: 'Ex-Dividend',
detail: null,
isPast: d.getTime() < now,
});
}
if (cal.dividendDate) {
const d = new Date(cal.dividendDate);
events.push({
ticker,
type: 'dividend',
date: d.toISOString().slice(0, 10),
label: 'Dividend',
detail: null,
isPast: d.getTime() < now,
});
}
}
return events;
}
private static sortEvents(events: CalendarEvent[]): void {
events.sort((a, b) => {
if (a.isPast !== b.isPast) return a.isPast ? 1 : -1;
return a.isPast
? new Date(b.date).getTime() - new Date(a.date).getTime()
: new Date(a.date).getTime() - new Date(b.date).getTime();
});
}
}