Add history window controls and expand taxonomy pack support

- add 3Y/5Y/10Y financial history filtering and reorganize normalization details UI
- add new fiscal taxonomy surface/income bridge/KPI packs and update Rust taxonomy loading
- auto-detect Homebrew SQLite for native `sqlite-vec` in local dev/e2e with docs and env guidance
This commit is contained in:
2026-03-18 23:40:28 -04:00
parent f8426c4dde
commit 17de3dd72d
102 changed files with 14978 additions and 1316 deletions

View File

@@ -14,7 +14,6 @@ import {
import { format } from "date-fns";
import { useSearchParams } from "next/navigation";
import {
Bar,
CartesianGrid,
Line,
LineChart,
@@ -25,6 +24,7 @@ import {
} from "recharts";
import {
AlertTriangle,
ChevronRight,
ChevronDown,
Download,
RefreshCcw,
@@ -57,6 +57,11 @@ import {
resolveStatementSelection,
type StatementInspectorSelection,
} from "@/lib/financials/statement-view-model";
import {
filterPeriodsByHistoryWindow,
financialHistoryLimit,
type FinancialHistoryWindow,
} from "@/lib/financials/history-window";
import { queryKeys } from "@/lib/query/keys";
import { companyFinancialStatementsQueryOptions } from "@/lib/query/options";
import type {
@@ -65,7 +70,6 @@ import type {
DerivedFinancialRow,
FinancialCadence,
FinancialDisplayMode,
FinancialStatementPeriod,
FinancialSurfaceKind,
FinancialUnit,
RatioRow,
@@ -73,7 +77,6 @@ import type {
StructuredKpiRow,
SurfaceFinancialRow,
TaxonomyStatementRow,
TrendSeries,
} from "@/lib/types";
type LoadOptions = {
@@ -111,6 +114,15 @@ const CADENCE_OPTIONS = [
{ value: "ltm", label: "LTM" },
];
const HISTORY_WINDOW_OPTIONS: Array<{
value: `${FinancialHistoryWindow}`;
label: string;
}> = [
{ value: "3", label: "3Y" },
{ value: "5", label: "5Y" },
{ value: "10", label: "10Y" },
];
const DISPLAY_MODE_OPTIONS = [
{ value: "standardized", label: "Standard" },
{ value: "faithful", label: "Faithful" },
@@ -410,6 +422,59 @@ function ChartFrame({ children }: { children: React.ReactNode }) {
);
}
function CollapsibleSection(props: {
title: string;
subtitle?: string;
children: React.ReactNode;
defaultOpen?: boolean;
}) {
const [open, setOpen] = useState(props.defaultOpen ?? false);
return (
<section className="border-t border-[color:var(--line-weak)] pt-4">
<button
type="button"
className="flex w-full items-start justify-between gap-3 rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel)] px-3 py-2 text-left transition hover:border-[color:var(--line-strong)]"
aria-expanded={open}
onClick={() => setOpen((current) => !current)}
>
<span className="min-w-0">
<span className="block text-sm font-semibold text-[color:var(--terminal-bright)]">
{props.title}
</span>
{props.subtitle ? (
<span className="mt-0.5 block text-xs text-[color:var(--terminal-muted)]">
{props.subtitle}
</span>
) : null}
</span>
<span className="mt-0.5 shrink-0 text-[color:var(--terminal-muted)]">
{open ? (
<ChevronDown className="size-4" />
) : (
<ChevronRight className="size-4" />
)}
</span>
</button>
{open ? <div className="pt-4">{props.children}</div> : null}
</section>
);
}
function SummarySubsection(props: {
title: string;
children: React.ReactNode;
}) {
return (
<div className="space-y-3">
<h3 className="text-sm font-semibold text-[color:var(--terminal-bright)]">
{props.title}
</h3>
{props.children}
</div>
);
}
function FinancialsPageContent() {
const { isPending, isAuthenticated } = useAuthGuard();
const searchParams = useSearchParams();
@@ -423,6 +488,7 @@ function FinancialsPageContent() {
const [cadence, setCadence] = useState<FinancialCadence>("annual");
const [displayMode, setDisplayMode] =
useState<FinancialDisplayMode>("standardized");
const [historyWindow, setHistoryWindow] = useState<FinancialHistoryWindow>(3);
const [valueScale, setValueScale] = useState<NumberScaleUnit>("millions");
const [rowSearch, setRowSearch] = useState("");
const [showPercentChange, setShowPercentChange] = useState(false);
@@ -438,10 +504,14 @@ function FinancialsPageContent() {
() => new Set(),
);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [, setLoadingMore] = useState(false);
const [syncingFinancials, setSyncingFinancials] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showTrendChart, setShowTrendChart] = useState(false);
const historyFetchLimit = useMemo(
() => financialHistoryLimit(cadence, historyWindow),
[cadence, historyWindow],
);
useEffect(() => {
const fromQuery = searchParams.get("ticker");
@@ -509,7 +579,7 @@ function FinancialsPageContent() {
includeDimensions,
includeFacts: false,
cursor: nextCursor,
limit: 12,
limit: historyFetchLimit,
}),
);
@@ -532,7 +602,14 @@ function FinancialsPageContent() {
setLoadingMore(false);
}
},
[cadence, queryClient, selectedFlatRowKey, selectedRowRef, surfaceKind],
[
cadence,
historyFetchLimit,
queryClient,
selectedFlatRowKey,
selectedRowRef,
surfaceKind,
],
);
const syncFinancials = useCallback(async () => {
@@ -578,6 +655,10 @@ function FinancialsPageContent() {
Date.parse(right.periodEnd ?? right.filingDate),
);
}, [financials?.periods]);
const visiblePeriods = useMemo(
() => filterPeriodsByHistoryWindow(periods, cadence, historyWindow),
[cadence, historyWindow, periods],
);
const isTreeStatementMode =
displayMode === "standardized" && isStatementSurfaceKind(surfaceKind);
@@ -708,11 +789,23 @@ function FinancialsPageContent() {
surfaceKind === "income_statement" ||
surfaceKind === "cash_flow_statement"
) {
return standardizedRows.find((row) => row.key === "revenue") ?? null;
return (
standardizedRows.find((row) => row.key === "revenue") ??
standardizedRows.find((row) => row.key === "net_cash_from_operating") ??
standardizedRows.find((row) => row.key === "operating_income") ??
null
);
}
if (surfaceKind === "balance_sheet") {
return standardizedRows.find((row) => row.key === "total_assets") ?? null;
return (
standardizedRows.find((row) => row.key === "total_assets") ??
standardizedRows.find(
(row) => row.key === "total_liabilities_and_equity",
) ??
standardizedRows.find((row) => row.key === "stockholders_equity") ??
null
);
}
return null;
@@ -724,7 +817,7 @@ function FinancialsPageContent() {
const trendSeries = financials?.trendSeries ?? [];
const chartData = useMemo(() => {
return periods.map((period) => ({
return visiblePeriods.map((period) => ({
label: formatLongDate(period.periodEnd ?? period.filingDate),
...Object.fromEntries(
trendSeries.map((series) => [
@@ -733,7 +826,7 @@ function FinancialsPageContent() {
]),
),
}));
}, [periods, trendSeries]);
}, [trendSeries, visiblePeriods]);
const controlSections = useMemo<FinancialControlSection[]>(() => {
const sections: FinancialControlSection[] = [
@@ -761,6 +854,16 @@ function FinancialsPageContent() {
setExpandedRowKeys(new Set());
},
},
{
key: "history",
label: "History",
options: HISTORY_WINDOW_OPTIONS,
value: String(historyWindow),
onChange: (value) =>
setHistoryWindow(
Number.parseInt(value, 10) as FinancialHistoryWindow,
),
},
];
if (
@@ -791,7 +894,7 @@ function FinancialsPageContent() {
});
return sections;
}, [cadence, displayMode, surfaceKind, valueScale]);
}, [cadence, displayMode, historyWindow, surfaceKind, valueScale]);
const toggleExpandedRow = useCallback((key: string) => {
setExpandedRowKeys((current) => {
@@ -1069,7 +1172,7 @@ function FinancialsPageContent() {
edits or derived rows are available yet.
</p>
</div>
) : periods.length === 0 ||
) : visiblePeriods.length === 0 ||
(isTreeStatementMode
? (treeModel?.visibleNodeCount ?? 0) === 0
: filteredRows.length === 0) ? (
@@ -1081,7 +1184,7 @@ function FinancialsPageContent() {
{isStatementSurfaceKind(surfaceKind) ? (
<div className="space-y-1.5">
<p className="text-xs uppercase tracking-[0.24em] text-[color:var(--terminal-muted)]">
USD · {valueScaleLabel}
USD · {valueScaleLabel} · Latest {historyWindow} years
</p>
{isTreeStatementMode && hasUnmappedResidualRows ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
@@ -1096,7 +1199,7 @@ function FinancialsPageContent() {
) : null}
{isTreeStatementMode && treeModel ? (
<StatementMatrix
periods={periods}
periods={visiblePeriods}
sections={treeModel.sections}
selectedRowRef={selectedRowRef}
onToggleRow={toggleExpandedRow}
@@ -1104,17 +1207,14 @@ function FinancialsPageContent() {
renderCellValue={renderStatementTreeCellValue}
periodLabelFormatter={formatLongDate}
dense={true}
virtualized={treeModel.visibleNodeCount > 50}
/>
) : (
<div className="data-table-wrap">
<table className="data-table min-w-[980px]">
<thead>
<table className="data-table financials-table min-w-[980px]">
<thead className="sticky top-0 z-20 bg-[color:var(--panel)]">
<tr>
<th className="sticky left-0 z-10 bg-[color:var(--panel)]">
Metric
</th>
{periods.map((period) => (
<th className="bg-[color:var(--panel)]">Metric</th>
{visiblePeriods.map((period) => (
<th key={period.id}>
<div className="flex flex-col gap-1">
<span>
@@ -1136,7 +1236,7 @@ function FinancialsPageContent() {
{group.label ? (
<tr className="bg-[color:var(--panel-soft)]">
<td
colSpan={periods.length + 1}
colSpan={visiblePeriods.length + 1}
className="font-semibold text-[color:var(--terminal-bright)]"
>
{group.label}
@@ -1146,14 +1246,9 @@ function FinancialsPageContent() {
{group.rows.map((row) => (
<tr
key={row.key}
className={
selectedFlatRowKey === row.key
? "bg-[color:var(--panel-soft)]"
: undefined
}
onClick={() => setSelectedFlatRowKey(row.key)}
>
<td className="sticky left-0 z-10 bg-[color:var(--panel)]">
<td className="bg-[color:var(--panel)]">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<span
@@ -1180,14 +1275,14 @@ function FinancialsPageContent() {
) : null}
</div>
</td>
{periods.map((period, index) => (
{visiblePeriods.map((period, index) => (
<td key={`${row.key}-${period.id}`}>
{buildDisplayValue({
row,
periodId: period.id,
previousPeriodId:
index > 0
? (periods[index - 1]?.id ?? null)
? (visiblePeriods[index - 1]?.id ?? null)
: null,
commonSizeRow,
displayMode,
@@ -1210,207 +1305,235 @@ function FinancialsPageContent() {
)}
</Panel>
{isTreeStatementMode && isStatementSurfaceKind(surfaceKind) ? (
<StatementRowInspector
selection={selectedStatementRow}
dimensionRows={dimensionRows}
periods={periods}
surfaceKind={surfaceKind}
renderValue={renderStatementTreeCellValue}
renderDimensionValue={renderDimensionValue}
/>
) : (
<Panel title="Details" variant="flat" density="compact">
{!selectedRow ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
Select a row to inspect details.
</p>
) : (
<div className="space-y-4 text-sm">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">Label</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.label}
</p>
</div>
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">Key</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.key}
</p>
</div>
</div>
{isTaxonomyRow(selectedRow) ? (
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Taxonomy Concept
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.qname}
</p>
</div>
{financials ? (
<CollapsibleSection
title="Normalization Summary"
subtitle="Row details, validation, and normalization diagnostics."
>
<div className="space-y-6">
<SummarySubsection title="Row Details">
{isTreeStatementMode && isStatementSurfaceKind(surfaceKind) ? (
<StatementRowInspector
selection={selectedStatementRow}
dimensionRows={dimensionRows}
periods={visiblePeriods}
surfaceKind={surfaceKind}
renderValue={renderStatementTreeCellValue}
renderDimensionValue={renderDimensionValue}
showHeader={false}
/>
) : (
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Category
<Panel variant="flat" density="compact">
{!selectedRow ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
Select a row to inspect details.
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.category}
</p>
</div>
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">Unit</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.unit}
</p>
</div>
</div>
) : (
<div className="space-y-4 text-sm">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Label
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.label}
</p>
</div>
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Key
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.key}
</p>
</div>
</div>
{isTaxonomyRow(selectedRow) ? (
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Taxonomy Concept
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.qname}
</p>
</div>
) : (
<div className="grid grid-cols-1 gap-2 md:grid-cols-2">
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Category
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.category}
</p>
</div>
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Unit
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{selectedRow.unit}
</p>
</div>
</div>
)}
{isDerivedRow(selectedRow) ? (
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Source Row Keys
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{(selectedRow.sourceRowKeys ?? []).join(", ") ||
"n/a"}
</p>
<p className="mt-2 text-[color:var(--terminal-muted)]">
Source Concepts
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{(selectedRow.sourceConcepts ?? []).join(", ") ||
"n/a"}
</p>
<p className="mt-2 text-[color:var(--terminal-muted)]">
Source Fact IDs
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{(selectedRow.sourceFactIds ?? []).join(", ") ||
"n/a"}
</p>
</div>
) : null}
{!selectedRow ||
!("hasDimensions" in selectedRow) ||
!selectedRow.hasDimensions ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
No dimensional drill-down is available for this row.
</p>
) : dimensionRows.length === 0 ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
No dimensional facts were returned for the selected
row.
</p>
) : (
<div className="data-table-wrap">
<table className="data-table min-w-[760px]">
<thead>
<tr>
<th>Period</th>
<th>Axis</th>
<th>Member</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{dimensionRows.map((row, index) => (
<tr
key={`${row.rowKey}-${row.periodId}-${row.axis}-${row.member}-${index}`}
>
<td>
{visiblePeriods.find(
(period) => period.id === row.periodId,
)?.periodLabel ?? row.periodId}
</td>
<td>{row.axis}</td>
<td>{row.member}</td>
<td>
{formatMetricValue({
value: row.value,
unit: isTaxonomyRow(selectedRow)
? "currency"
: selectedRow.unit,
scale: valueScale,
rowKey: selectedRow.key,
surfaceKind,
})}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
)}
</Panel>
)}
</SummarySubsection>
{isDerivedRow(selectedRow) ? (
<div className="rounded-lg border border-[color:var(--line-weak)] bg-[color:var(--panel-soft)] px-2.5 py-1.5">
<p className="text-[color:var(--terminal-muted)]">
Source Row Keys
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{(selectedRow.sourceRowKeys ?? []).join(", ") || "n/a"}
</p>
<p className="mt-2 text-[color:var(--terminal-muted)]">
Source Concepts
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{(selectedRow.sourceConcepts ?? []).join(", ") || "n/a"}
</p>
<p className="mt-2 text-[color:var(--terminal-muted)]">
Source Fact IDs
</p>
<p className="font-semibold text-[color:var(--terminal-bright)]">
{(selectedRow.sourceFactIds ?? []).join(", ") || "n/a"}
</p>
{(surfaceKind === "income_statement" ||
surfaceKind === "balance_sheet" ||
surfaceKind === "cash_flow_statement") && (
<SummarySubsection title="Validation">
<div className="mb-3 flex items-center gap-2 text-sm text-[color:var(--terminal-muted)]">
<AlertTriangle className="size-4" />
<span>
Overall status:{" "}
{financials.metrics.validation?.status ?? "not_run"}
</span>
</div>
) : null}
{!selectedRow ||
!("hasDimensions" in selectedRow) ||
!selectedRow.hasDimensions ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
No dimensional drill-down is available for this row.
</p>
) : dimensionRows.length === 0 ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
No dimensional facts were returned for the selected row.
</p>
) : (
<div className="data-table-wrap">
<table className="data-table min-w-[760px]">
<thead>
<tr>
<th>Period</th>
<th>Axis</th>
<th>Member</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{dimensionRows.map((row, index) => (
<tr
key={`${row.rowKey}-${row.periodId}-${row.axis}-${row.member}-${index}`}
>
<td>
{periods.find(
(period) => period.id === row.periodId,
)?.periodLabel ?? row.periodId}
</td>
<td>{row.axis}</td>
<td>{row.member}</td>
<td>
{formatMetricValue({
value: row.value,
unit: isTaxonomyRow(selectedRow)
? "currency"
: selectedRow.unit,
scale: valueScale,
rowKey: selectedRow.key,
surfaceKind,
})}
</td>
{(financials.metrics.validation?.checks.length ?? 0) === 0 ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
No validation checks available yet.
</p>
) : (
<div className="data-table-wrap">
<table className="data-table min-w-[760px]">
<thead>
<tr>
<th>Metric</th>
<th>Taxonomy</th>
<th>LLM (PDF)</th>
<th>Status</th>
<th>Pages</th>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
)}
</Panel>
)}
</thead>
<tbody>
{financials.metrics.validation?.checks.map((check) => (
<tr key={check.metricKey}>
<td>{check.metricKey}</td>
<td>
{formatMetricValue({
value: check.taxonomyValue,
unit: "currency",
scale: valueScale,
rowKey: check.metricKey,
surfaceKind,
})}
</td>
<td>
{formatMetricValue({
value: check.llmValue,
unit: "currency",
scale: valueScale,
rowKey: check.metricKey,
surfaceKind,
})}
</td>
<td>{check.status}</td>
<td>
{(check.evidencePages ?? []).join(", ") || "n/a"}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</SummarySubsection>
)}
{(surfaceKind === "income_statement" ||
surfaceKind === "balance_sheet" ||
surfaceKind === "cash_flow_statement") &&
financials ? (
<Panel title="Validation" variant="flat" density="compact">
<div className="mb-3 flex items-center gap-2 text-sm text-[color:var(--terminal-muted)]">
<AlertTriangle className="size-4" />
<span>
Overall status:{" "}
{financials.metrics.validation?.status ?? "not_run"}
</span>
{isTreeStatementMode ? (
<SummarySubsection title="Normalization">
<NormalizationSummary
normalization={financials.normalization}
showHeader={false}
/>
</SummarySubsection>
) : null}
</div>
{(financials.metrics.validation?.checks.length ?? 0) === 0 ? (
<p className="text-sm text-[color:var(--terminal-muted)]">
No validation checks available yet.
</p>
) : (
<div className="data-table-wrap">
<table className="data-table min-w-[760px]">
<thead>
<tr>
<th>Metric</th>
<th>Taxonomy</th>
<th>LLM (PDF)</th>
<th>Status</th>
<th>Pages</th>
</tr>
</thead>
<tbody>
{financials.metrics.validation?.checks.map((check) => (
<tr key={check.metricKey}>
<td>{check.metricKey}</td>
<td>
{formatMetricValue({
value: check.taxonomyValue,
unit: "currency",
scale: valueScale,
rowKey: check.metricKey,
surfaceKind,
})}
</td>
<td>
{formatMetricValue({
value: check.llmValue,
unit: "currency",
scale: valueScale,
rowKey: check.metricKey,
surfaceKind,
})}
</td>
<td>{check.status}</td>
<td>{(check.evidencePages ?? []).join(", ") || "n/a"}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</Panel>
) : null}
{financials && isTreeStatementMode ? (
<NormalizationSummary normalization={financials.normalization} />
</CollapsibleSection>
) : null}
</AppShell>
);