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 = {}; 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((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, 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(); }); } }