"use client"; import { useState } from "react"; import { ErrorBanner } from "../ui/ErrorBanner"; import { LoadingPulse } from "../ui/LoadingPulse"; import { flags } from "../../lib/flags"; import { useApi } from "../../lib/use-api"; type StatementKey = "income" | "balance" | "cashflow"; type PeriodKey = "annual" | "quarter"; interface StatementResponse { ticker: string; statement: string; period: string; source: string; periods: string[]; line_items: Record>; } const STATEMENTS: { key: StatementKey; label: string }[] = [ { key: "income", label: "Income" }, { key: "balance", label: "Balance Sheet" }, { key: "cashflow", label: "Cash Flow" }, ]; const LINE_LABELS: Record = { revenue: "Revenue", totalRevenue: "Revenue", TotalRevenue: "Revenue", costOfRevenue: "Cost of Revenue", grossProfit: "Gross Profit", GrossProfit: "Gross Profit", operatingIncome: "Operating Income", OperatingIncome: "Operating Income", netIncome: "Net Income", NetIncome: "Net Income", totalAssets: "Total Assets", TotalAssets: "Total Assets", totalLiabilities: "Total Liabilities", TotalLiabilitiesNetMinorityInterest: "Total Liabilities", totalStockholdersEquity: "Equity", StockholdersEquity: "Equity", operatingCashFlow: "Operating Cash Flow", OperatingCashFlow: "Operating Cash Flow", capitalExpenditure: "Capex", CapitalExpenditure: "Capex", freeCashFlow: "Free Cash Flow", }; function formatLineLabel(key: string): string { return LINE_LABELS[key] || key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " "); } function formatValue(value: number | null): string { if (value == null) return "—"; const abs = Math.abs(value); const sign = value < 0 ? "-" : ""; if (abs >= 1e12) return `${sign}$${(abs / 1e12).toFixed(2)}T`; if (abs >= 1e9) return `${sign}$${(abs / 1e9).toFixed(1)}B`; if (abs >= 1e6) return `${sign}$${(abs / 1e6).toFixed(1)}M`; return `${sign}$${abs.toFixed(0)}`; } function yoy(values: Array, idx: number): number | null { const current = values[idx]; const previous = values[idx + 1]; if (current == null || previous == null || previous === 0) return null; return ((current - previous) / Math.abs(previous)) * 100; } function Sparkline({ values }: { values: Array }) { const nums = values.filter((value): value is number => value != null); if (nums.length < 2) return ; const min = Math.min(...nums); const max = Math.max(...nums); const range = max - min || 1; const points = values .map((value, idx) => { const v = value ?? min; return `${idx * 16},${24 - ((v - min) / range) * 22}`; }) .join(" "); return ( ); } export function FinancialStatements({ ticker }: { ticker: string }) { const [statement, setStatement] = useState("income"); const [period, setPeriod] = useState("annual"); const url = flags.financials ? `/api/financials/${encodeURIComponent(ticker)}/table?statement=${statement}&period=${period}` : null; const { data, loading, error } = useApi(url, { cacheTtlMs: 300_000 }); const entries = data ? Object.entries(data.line_items).slice(0, 12) : []; if (!flags.financials) return null; return (

Financial Statements

Gateway-backed statement table with YoY deltas.

{STATEMENTS.map((item) => ( ))} {(["annual", "quarter"] as PeriodKey[]).map((item) => ( ))}
{loading ? : (
{(data?.periods || []).map((p) => )} {entries.length > 0 ? entries.map(([key, values]) => ( {values.slice(0, data?.periods.length || 0).map((value, idx) => { const growth = yoy(values, idx); return ( ); })} )) : ( )}
Line Item{p}Trend
{formatLineLabel(key)}
{formatValue(value)}
{growth != null &&
= 0 ? "text-fin-positive" : "text-fin-negative"}`}>{growth >= 0 ? "+" : ""}{growth.toFixed(1)}%
}
No statement data available.
)}
); }