import { desc, eq } from 'drizzle-orm'; import type { Filing } from '@/lib/types'; import { db } from '@/lib/server/db'; import { filing, filingLink } from '@/lib/server/db/schema'; type FilingRow = typeof filing.$inferSelect; type FilingLinkInput = { link_type: string; url: string; }; type UpsertFilingInput = { ticker: string; filing_type: Filing['filing_type']; filing_date: string; accession_number: string; cik: string; company_name: string; filing_url: string | null; submission_url: string | null; primary_document: string | null; metrics: Filing['metrics']; links: FilingLinkInput[]; }; function toFiling(row: FilingRow): Filing { return { id: row.id, ticker: row.ticker, filing_type: row.filing_type, filing_date: row.filing_date, accession_number: row.accession_number, cik: row.cik, company_name: row.company_name, filing_url: row.filing_url, submission_url: row.submission_url, primary_document: row.primary_document, metrics: row.metrics ?? null, analysis: row.analysis ?? null, created_at: row.created_at, updated_at: row.updated_at }; } function dedupeLinks(links: FilingLinkInput[]) { const unique = new Map(); for (const link of links) { const url = link.url.trim(); if (!url) { continue; } unique.set(`${link.link_type}::${url}`, { ...link, url }); } return [...unique.values()]; } export async function listFilingsRecords(query?: { ticker?: string; limit?: number }) { const safeLimit = Math.min(Math.max(Math.trunc(query?.limit ?? 50), 1), 250); const rows = query?.ticker ? await db .select() .from(filing) .where(eq(filing.ticker, query.ticker)) .orderBy(desc(filing.filing_date), desc(filing.updated_at)) .limit(safeLimit) : await db .select() .from(filing) .orderBy(desc(filing.filing_date), desc(filing.updated_at)) .limit(safeLimit); return rows.map(toFiling); } export async function getFilingByAccession(accessionNumber: string) { const [row] = await db .select() .from(filing) .where(eq(filing.accession_number, accessionNumber)) .limit(1); return row ? toFiling(row) : null; } export async function upsertFilingsRecords(items: UpsertFilingInput[]) { let inserted = 0; let updated = 0; for (const item of items) { const now = new Date().toISOString(); const existing = await getFilingByAccession(item.accession_number); const [saved] = await db .insert(filing) .values({ ticker: item.ticker, filing_type: item.filing_type, filing_date: item.filing_date, accession_number: item.accession_number, cik: item.cik, company_name: item.company_name, filing_url: item.filing_url, submission_url: item.submission_url, primary_document: item.primary_document, metrics: item.metrics, analysis: existing?.analysis ?? null, created_at: existing?.created_at ?? now, updated_at: now }) .onConflictDoUpdate({ target: filing.accession_number, set: { ticker: item.ticker, filing_type: item.filing_type, filing_date: item.filing_date, cik: item.cik, company_name: item.company_name, filing_url: item.filing_url, submission_url: item.submission_url, primary_document: item.primary_document, metrics: item.metrics, updated_at: now } }) .returning({ id: filing.id }); const links = dedupeLinks(item.links); for (const link of links) { await db .insert(filingLink) .values({ filing_id: saved.id, link_type: link.link_type, url: link.url, source: 'sec', created_at: now }) .onConflictDoNothing(); } if (existing) { updated += 1; } else { inserted += 1; } } return { inserted, updated }; } export async function saveFilingAnalysis( accessionNumber: string, analysis: Filing['analysis'] ) { const [updated] = await db .update(filing) .set({ analysis, updated_at: new Date().toISOString() }) .where(eq(filing.accession_number, accessionNumber)) .returning(); return updated ? toFiling(updated) : null; } export async function updateFilingMetricsById( filingId: number, metrics: Filing['metrics'] ) { const [updated] = await db .update(filing) .set({ metrics, updated_at: new Date().toISOString() }) .where(eq(filing.id, filingId)) .returning(); return updated ? toFiling(updated) : null; }