From 75408f855925254593dd38454135c801ed42032a Mon Sep 17 00:00:00 2001 From: francy51 Date: Sun, 1 Mar 2026 18:43:48 -0500 Subject: [PATCH] Focus financial views on quarter/FY ends and add period filters --- app/analysis/page.tsx | 95 ++++++++++++++++++++++++++++++++------- app/financials/page.tsx | 99 ++++++++++++++++++++++++++++++++--------- 2 files changed, 158 insertions(+), 36 deletions(-) diff --git a/app/analysis/page.tsx b/app/analysis/page.tsx index cad8a9f..e7bf328 100644 --- a/app/analysis/page.tsx +++ b/app/analysis/page.tsx @@ -26,14 +26,45 @@ import { getCompanyAnalysis } from '@/lib/api'; import { asNumber, formatCompactCurrency, formatCurrency, formatPercent } from '@/lib/format'; import type { CompanyAnalysis } from '@/lib/types'; +type FinancialPeriodFilter = 'quarterlyAndFiscalYearEnd' | 'quarterlyOnly' | 'fiscalYearEndOnly'; + +type FinancialSeriesPoint = { + label: string; + filingType: '10-K' | '10-Q'; + periodLabel: 'Quarter End' | 'Fiscal Year End'; + revenue: number | null; + netIncome: number | null; + assets: number | null; +}; + +const FINANCIAL_PERIOD_FILTER_OPTIONS: Array<{ value: FinancialPeriodFilter; label: string }> = [ + { value: 'quarterlyAndFiscalYearEnd', label: 'Quarterly + Fiscal Year End' }, + { value: 'quarterlyOnly', label: 'Quarterly only' }, + { value: 'fiscalYearEndOnly', label: 'Fiscal Year End only' } +]; + function formatShortDate(value: string) { return format(new Date(value), 'MMM yyyy'); } -function hasFinancialSnapshot(filingType: CompanyAnalysis['filings'][number]['filing_type']) { +function isFinancialSnapshotForm( + filingType: CompanyAnalysis['filings'][number]['filing_type'] +): filingType is '10-K' | '10-Q' { return filingType === '10-K' || filingType === '10-Q'; } +function includesFinancialPeriod(filingType: '10-K' | '10-Q', filter: FinancialPeriodFilter) { + if (filter === 'quarterlyOnly') { + return filingType === '10-Q'; + } + + if (filter === 'fiscalYearEndOnly') { + return filingType === '10-K'; + } + + return true; +} + export default function AnalysisPage() { return ( Loading analysis desk...}> @@ -51,6 +82,7 @@ function AnalysisPageContent() { const [analysis, setAnalysis] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [financialPeriodFilter, setFinancialPeriodFilter] = useState('quarterlyAndFiscalYearEnd'); useEffect(() => { const fromQuery = searchParams.get('ticker'); @@ -95,18 +127,31 @@ function AnalysisPageContent() { })); }, [analysis?.priceHistory]); - const financialSeries = useMemo(() => { + const financialSeries = useMemo(() => { return (analysis?.financials ?? []) + .filter((item): item is CompanyAnalysis['financials'][number] & { filingType: '10-K' | '10-Q' } => { + return isFinancialSnapshotForm(item.filingType); + }) .slice() - .reverse() + .sort((a, b) => Date.parse(a.filingDate) - Date.parse(b.filingDate)) .map((item) => ({ label: formatShortDate(item.filingDate), + filingType: item.filingType, + periodLabel: item.filingType === '10-Q' ? 'Quarter End' : 'Fiscal Year End', revenue: item.revenue, netIncome: item.netIncome, assets: item.totalAssets })); }, [analysis?.financials]); + const filteredFinancialSeries = useMemo(() => { + return financialSeries.filter((point) => includesFinancialPeriod(point.filingType, financialPeriodFilter)); + }, [financialSeries, financialPeriodFilter]); + + const periodEndFilings = useMemo(() => { + return (analysis?.filings ?? []).filter((filing) => isFinancialSnapshotForm(filing.filing_type)); + }, [analysis?.filings]); + if (isPending || !isAuthenticated) { return
Loading analysis desk...
; } @@ -204,15 +249,33 @@ function AnalysisPageContent() { )} - + + {FINANCIAL_PERIOD_FILTER_OPTIONS.map((option) => ( + + ))} + + )} + > {loading ? (

Loading financials...

- ) : financialSeries.length === 0 ? ( -

No parsed filing metrics yet.

+ ) : filteredFinancialSeries.length === 0 ? ( +

No filing metrics match the selected period filter.

) : (
- + `$${Math.round(value / 1_000_000_000)}B`} /> @@ -228,17 +291,18 @@ function AnalysisPageContent() {
- + {loading ? (

Loading filings...

- ) : !analysis || analysis.filings.length === 0 ? ( -

No filings available for this ticker.

+ ) : periodEndFilings.length === 0 ? ( +

No 10-K or 10-Q filings available for this ticker.

) : (
+ @@ -247,13 +311,14 @@ function AnalysisPageContent() { - {analysis.filings.map((filing) => ( + {periodEndFilings.map((filing) => ( - - - - + + + + + + @@ -427,9 +483,10 @@ function FinancialsPageContent() { - {financialSeries.map((point) => ( - + {financialSeries.map((point, index) => ( + +
FiledPeriod Type Revenue Net Income
{format(new Date(filing.filing_date), 'MMM dd, yyyy')}{filing.filing_type}{hasFinancialSnapshot(filing.filing_type) ? '' : ' (Qualitative)'}{hasFinancialSnapshot(filing.filing_type) ? (filing.metrics?.revenue ? formatCompactCurrency(filing.metrics.revenue) : 'n/a') : 'qualitative only'}{hasFinancialSnapshot(filing.filing_type) ? (filing.metrics?.netIncome ? formatCompactCurrency(filing.metrics.netIncome) : 'n/a') : 'qualitative only'}{hasFinancialSnapshot(filing.filing_type) ? (filing.metrics?.totalAssets ? formatCompactCurrency(filing.metrics.totalAssets) : 'n/a') : 'qualitative only'}{filing.filing_type === '10-Q' ? 'Quarter End' : 'Fiscal Year End'}{filing.filing_type}{filing.metrics?.revenue !== null && filing.metrics?.revenue !== undefined ? formatCompactCurrency(filing.metrics.revenue) : 'n/a'}{filing.metrics?.netIncome !== null && filing.metrics?.netIncome !== undefined ? formatCompactCurrency(filing.metrics.netIncome) : 'n/a'}{filing.metrics?.totalAssets !== null && filing.metrics?.totalAssets !== undefined ? formatCompactCurrency(filing.metrics.totalAssets) : 'n/a'} {filing.filing_url ? ( diff --git a/app/financials/page.tsx b/app/financials/page.tsx index cdaf77e..e935bfc 100644 --- a/app/financials/page.tsx +++ b/app/financials/page.tsx @@ -31,7 +31,9 @@ import type { CompanyAnalysis } from '@/lib/types'; type FinancialSeriesPoint = { filingDate: string; - filingType: '10-K' | '10-Q' | '8-K'; + filingType: '10-K' | '10-Q'; + periodKind: 'quarterly' | 'fiscalYearEnd'; + periodLabel: 'Quarter End' | 'Fiscal Year End'; label: string; revenue: number | null; netIncome: number | null; @@ -42,6 +44,8 @@ type FinancialSeriesPoint = { debtToAssets: number | null; }; +type ChartPeriodFilter = 'quarterlyAndFiscalYearEnd' | 'quarterlyOnly' | 'fiscalYearEndOnly'; + const AXIS_CURRENCY = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', @@ -49,6 +53,12 @@ const AXIS_CURRENCY = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 }); +const CHART_PERIOD_FILTER_OPTIONS: Array<{ value: ChartPeriodFilter; label: string }> = [ + { value: 'quarterlyAndFiscalYearEnd', label: 'Quarterly + Fiscal Year End' }, + { value: 'quarterlyOnly', label: 'Quarterly only' }, + { value: 'fiscalYearEndOnly', label: 'Fiscal Year End only' } +]; + function formatShortDate(value: string) { const parsed = new Date(value); if (Number.isNaN(parsed.getTime())) { @@ -110,6 +120,22 @@ function asTooltipCurrency(value: unknown) { return formatCompactCurrency(numeric); } +function includesChartPeriod(point: FinancialSeriesPoint, filter: ChartPeriodFilter) { + if (filter === 'quarterlyOnly') { + return point.periodKind === 'quarterly'; + } + + if (filter === 'fiscalYearEndOnly') { + return point.periodKind === 'fiscalYearEnd'; + } + + return true; +} + +function isFinancialPeriodForm(filingType: CompanyAnalysis['financials'][number]['filingType']): filingType is '10-K' | '10-Q' { + return filingType === '10-K' || filingType === '10-Q'; +} + export default function FinancialsPage() { return ( Loading financial terminal...}> @@ -127,6 +153,7 @@ function FinancialsPageContent() { const [analysis, setAnalysis] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [chartPeriodFilter, setChartPeriodFilter] = useState('quarterlyAndFiscalYearEnd'); useEffect(() => { const fromQuery = searchParams.get('ticker'); @@ -166,11 +193,16 @@ function FinancialsPageContent() { const financialSeries = useMemo(() => { return (analysis?.financials ?? []) + .filter((entry): entry is CompanyAnalysis['financials'][number] & { filingType: '10-K' | '10-Q' } => { + return isFinancialPeriodForm(entry.filingType); + }) .slice() .sort((a, b) => Date.parse(a.filingDate) - Date.parse(b.filingDate)) .map((entry) => ({ filingDate: entry.filingDate, filingType: entry.filingType, + periodKind: entry.filingType === '10-Q' ? 'quarterly' : 'fiscalYearEnd', + periodLabel: entry.filingType === '10-Q' ? 'Quarter End' : 'Fiscal Year End', label: formatShortDate(entry.filingDate), revenue: entry.revenue ?? null, netIncome: entry.netIncome ?? null, @@ -182,6 +214,14 @@ function FinancialsPageContent() { })); }, [analysis?.financials]); + const chartSeries = useMemo(() => { + return financialSeries.filter((point) => includesChartPeriod(point, chartPeriodFilter)); + }, [financialSeries, chartPeriodFilter]); + + const selectedChartFilterLabel = useMemo(() => { + return CHART_PERIOD_FILTER_OPTIONS.find((option) => option.value === chartPeriodFilter)?.label ?? 'Quarterly + Fiscal Year End'; + }, [chartPeriodFilter]); + const latestSnapshot = financialSeries[financialSeries.length - 1] ?? null; const liquidityRatio = useMemo(() => { @@ -193,7 +233,7 @@ function FinancialsPageContent() { }, [latestSnapshot]); const coverage = useMemo(() => { - const total = financialSeries.length; + const total = chartSeries.length; const asCoverage = (entries: number) => { if (total === 0) { @@ -205,13 +245,13 @@ function FinancialsPageContent() { return { total, - revenue: asCoverage(financialSeries.filter((point) => point.revenue !== null).length), - netIncome: asCoverage(financialSeries.filter((point) => point.netIncome !== null).length), - assets: asCoverage(financialSeries.filter((point) => point.totalAssets !== null).length), - cash: asCoverage(financialSeries.filter((point) => point.cash !== null).length), - debt: asCoverage(financialSeries.filter((point) => point.debt !== null).length) + revenue: asCoverage(chartSeries.filter((point) => point.revenue !== null).length), + netIncome: asCoverage(chartSeries.filter((point) => point.netIncome !== null).length), + assets: asCoverage(chartSeries.filter((point) => point.totalAssets !== null).length), + cash: asCoverage(chartSeries.filter((point) => point.cash !== null).length), + debt: asCoverage(chartSeries.filter((point) => point.debt !== null).length) }; - }, [financialSeries]); + }, [chartSeries]); if (isPending || !isAuthenticated) { return
Loading financial terminal...
; @@ -294,16 +334,31 @@ function FinancialsPageContent() { /> + +
+ {CHART_PERIOD_FILTER_OPTIONS.map((option) => ( + + ))} +
+
+
{loading ? (

Loading statement data...

- ) : financialSeries.length === 0 ? ( -

No parsed filing metrics available yet for this ticker.

+ ) : chartSeries.length === 0 ? ( +

No filing metrics match the current chart period filter.

) : (
- + AXIS_CURRENCY.format(value)} /> @@ -320,12 +375,12 @@ function FinancialsPageContent() { {loading ? (

Loading balance sheet data...

- ) : financialSeries.length === 0 ? ( -

No balance sheet metrics available yet.

+ ) : chartSeries.length === 0 ? ( +

No balance sheet metrics match the current chart period filter.

) : (
- + @@ -351,12 +406,12 @@ function FinancialsPageContent() { {loading ? (

Loading ratio trends...

- ) : financialSeries.length === 0 ? ( -

No ratio data available yet.

+ ) : chartSeries.length === 0 ? ( +

No ratio points match the current chart period filter.

) : (
- + `${value.toFixed(0)}%`} /> @@ -380,7 +435,7 @@ function FinancialsPageContent() { )} - +
Revenue coverage
@@ -406,7 +461,7 @@ function FinancialsPageContent() {
- + {loading ? (

Loading table...

) : financialSeries.length === 0 ? ( @@ -417,6 +472,7 @@ function FinancialsPageContent() {
FiledPeriod Form Revenue Net Income
{formatLongDate(point.filingDate)}{point.periodLabel} {point.filingType} {asDisplayCurrency(point.revenue)} = 0 ? 'text-[#96f5bf]' : 'text-[#ff9f9f]'}>{asDisplayCurrency(point.netIncome)}