import type { Database } from '@rareindex/database'; import { logger } from '@rareindex/shared'; import { runAlerts, runTargets } from './alerts.js'; import { runPortfolioSnapshots } from './snapshots.js'; import { runBadges } from './badges.js'; import { runDigests } from './digest.js'; import { runMaintenance } from './maintenance.js'; import { runSavedSearches } from './saved-searches.js'; export { runAlerts, runTargets, runPortfolioSnapshots, runBadges, runDigests, runMaintenance, runSavedSearches }; const log = logger.child({ job: 'account' }); export interface AccountJobsResult { alerts: { evaluated: number; triggered: number }; targets: number; snapshots: number | null; badges: number | null; digests: number | null; savedSearches: number | null; purged: number | null; } let lastAlertRun: Date | null = null; let lastDailyKey: string | null = null; /** * Entry point for the pipeline scheduler (agent D): call every 5–15 minutes. * * import { runAccountJobs } from '../account/index.ts'; * await runAccountJobs(db); // alerts + targets each call; daily jobs once per UTC day * await runAccountJobs(db, { daily: true }); // force the daily set (snapshots, badges, digests, saved searches, purge) * * Alerts use the time of the previous call as `since` (first call: last hour). All functions are * idempotent per day and safe to re-run. */ export async function runAccountJobs(db: Database, opts: { daily?: boolean; now?: Date } = {}): Promise { const now = opts.now ?? new Date(); const result: AccountJobsResult = { alerts: { evaluated: 0, triggered: 0 }, targets: 0, snapshots: null, badges: null, digests: null, savedSearches: null, purged: null }; try { result.alerts = await runAlerts(db, { since: lastAlertRun ?? new Date(now.getTime() - 3600_000), now }); result.targets = await runTargets(db, now); lastAlertRun = now; } catch (err) { log.error({ err: err instanceof Error ? err.message : String(err) }, 'alert jobs failed'); } const dayKey = now.toISOString().slice(0, 10); const runDaily = opts.daily || lastDailyKey !== dayKey; if (runDaily) { const steps: Array<[keyof AccountJobsResult, () => Promise]> = [ ['snapshots', () => runPortfolioSnapshots(db, dayKey)], ['badges', () => runBadges(db)], ['savedSearches', () => runSavedSearches(db, now)], ['digests', () => runDigests(db, now)], ['purged', async () => (await runMaintenance(db, now)).purged], ]; for (const [key, fn] of steps) { try { (result as unknown as Record)[key] = await fn(); } catch (err) { log.error({ err: err instanceof Error ? err.message : String(err), step: key }, 'daily account job failed'); } } lastDailyKey = dayKey; } return result; }