diff --git a/frontend/src/components/ticker/FundamentalsPanel.tsx b/frontend/src/components/ticker/FundamentalsPanel.tsx index da275d9..cf9aa91 100644 --- a/frontend/src/components/ticker/FundamentalsPanel.tsx +++ b/frontend/src/components/ticker/FundamentalsPanel.tsx @@ -1,5 +1,10 @@ -import { useState } from 'react'; +import { useMemo, useState } from 'react'; import { formatPercent, formatLargeNumber } from '../../lib/format'; +import { + fundamentalScore, + metricStatus, + overallFundamentalStatus, +} from '../../lib/fundamentals'; import type { FundamentalResponse } from '../../lib/types'; interface FundamentalsPanelProps { @@ -13,11 +18,29 @@ const FIELD_LABELS: Record = { market_cap: 'Market Cap', }; +type MetricKey = 'pe_ratio' | 'revenue_growth' | 'earnings_surprise' | 'market_cap'; + export function FundamentalsPanel({ data }: FundamentalsPanelProps) { const [expanded, setExpanded] = useState(false); - const items = [ - { key: 'pe_ratio', label: 'P/E Ratio', value: data.pe_ratio, format: (v: number) => v.toFixed(2) }, + const score = useMemo( + () => + fundamentalScore({ + pe_ratio: data.pe_ratio, + revenue_growth: data.revenue_growth, + earnings_surprise: data.earnings_surprise, + }), + [data.pe_ratio, data.revenue_growth, data.earnings_surprise], + ); + const overall = overallFundamentalStatus(score); + + const items: { + key: MetricKey; + label: string; + value: number | null; + format: (v: number) => string; + }[] = [ + { key: 'pe_ratio', label: 'P/E Ratio', value: data.pe_ratio, format: (v) => v.toFixed(2) }, { key: 'revenue_growth', label: 'Revenue Growth', value: data.revenue_growth, format: formatPercent }, { key: 'earnings_surprise', label: 'Earnings Surprise', value: data.earnings_surprise, format: formatPercent }, { key: 'market_cap', label: 'Market Cap', value: data.market_cap, format: formatLargeNumber }, @@ -27,10 +50,21 @@ export function FundamentalsPanel({ data }: FundamentalsPanelProps) { return (
-

Fundamentals

-
+
+

Fundamentals

+ {score != null && ( + + score {score.toFixed(0)} + + )} +
+ +

{overall.text}

+ +
{items.map((item) => { const reason = data.unavailable_fields?.[item.key]; + const status = item.value !== null ? metricStatus(item.key, item.value) : null; let display: React.ReactNode; let valueClass = 'text-gray-200'; @@ -44,18 +78,30 @@ export function FundamentalsPanel({ data }: FundamentalsPanelProps) { } return ( -
+
{item.label} - {display} +
+
{display}
+ {status && ( +
{status.text}
+ )} +
); })}
+

+ Score = average of available P/E, revenue growth, and earnings surprise (need 2+). + {' '}P/E: lower scores higher (≈15 best, ≈45 worst). + {' '}Growth / surprise: 0% is neutral; stronger positives lift the score. + {' '}Market cap is size context only — not scored. +

+