TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import type { Database } from '@rareindex/database';2import { logger } from '@rareindex/shared';3import { runAlerts, runTargets } from './alerts.js';4import { runPortfolioSnapshots } from './snapshots.js';5import { runBadges } from './badges.js';6import { runDigests } from './digest.js';7import { runMaintenance } from './maintenance.js';8import { runSavedSearches } from './saved-searches.js';910export { runAlerts, runTargets, runPortfolioSnapshots, runBadges, runDigests, runMaintenance, runSavedSearches };1112const log = logger.child({ job: 'account' });1314export interface AccountJobsResult {15 alerts: { evaluated: number; triggered: number };16 targets: number;17 snapshots: number | null;18 badges: number | null;19 digests: number | null;20 savedSearches: number | null;21 purged: number | null;22}2324let lastAlertRun: Date | null = null;25let lastDailyKey: string | null = null;2627/**28 * Entry point for the pipeline scheduler (agent D): call every 5–15 minutes.29 *30 * import { runAccountJobs } from '../account/index.ts';31 * await runAccountJobs(db); // alerts + targets each call; daily jobs once per UTC day32 * await runAccountJobs(db, { daily: true }); // force the daily set (snapshots, badges, digests, saved searches, purge)33 *34 * Alerts use the time of the previous call as `since` (first call: last hour). All functions are35 * idempotent per day and safe to re-run.36 */37export async function runAccountJobs(db: Database, opts: { daily?: boolean; now?: Date } = {}): Promise<AccountJobsResult> {38 const now = opts.now ?? new Date();39 const result: AccountJobsResult = { alerts: { evaluated: 0, triggered: 0 }, targets: 0, snapshots: null, badges: null, digests: null, savedSearches: null, purged: null };40 try {41 result.alerts = await runAlerts(db, { since: lastAlertRun ?? new Date(now.getTime() - 3600_000), now });42 result.targets = await runTargets(db, now);43 lastAlertRun = now;44 } catch (err) {45 log.error({ err: err instanceof Error ? err.message : String(err) }, 'alert jobs failed');46 }47 const dayKey = now.toISOString().slice(0, 10);48 const runDaily = opts.daily || lastDailyKey !== dayKey;49 if (runDaily) {50 const steps: Array<[keyof AccountJobsResult, () => Promise<number>]> = [51 ['snapshots', () => runPortfolioSnapshots(db, dayKey)],52 ['badges', () => runBadges(db)],53 ['savedSearches', () => runSavedSearches(db, now)],54 ['digests', () => runDigests(db, now)],55 ['purged', async () => (await runMaintenance(db, now)).purged],56 ];57 for (const [key, fn] of steps) {58 try {59 (result as unknown as Record<string, unknown>)[key] = await fn();60 } catch (err) {61 log.error({ err: err instanceof Error ? err.message : String(err), step: key }, 'daily account job failed');62 }63 }64 lastDailyKey = dayKey;65 }66 return result;67}68