feat(frontend): A4 — FundamentalsPanel v1 (quarter tape + earnings + peers)

Reshapes FundamentalsPanel to consume the additive API v1, within the app's
existing dark-glass language.

- types.ts updated to the exact v1 shape (metrics/earnings/valuation/reads +
  legacy fields preserved).
- The quarter tape is the single distinctive device: per-metric 4-cell tape
  (revenue/EPS growth, operating + FCF margin, share count) with the latest cell
  toned by the deterministic read; color is always paired with the read text.
- Restrained peer strips for Net debt/EBITDA, P/E, FCF yield: value + a
  polarity-aware percentile bar with a median marker + the read; hidden ("peers
  n/a") when industry is null (< 5 peers).
- Earnings: next date/session/countdown + last-N beat/miss arrows (▲/▼/·) with
  text aria-labels; explicit "no date" state.
- Explicit n/a, insufficient-peer, and no-earnings states; header shows the
  deterministic sentence. Removed the hard-coded "FMP" source label.

Frontend tsc -b passes; backend suite 778 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 22:05:17 +02:00
co-authored by Claude Opus 4.8
parent fb6d39c68b
commit 9172c1a699
2 changed files with 289 additions and 126 deletions
+71
View File
@@ -703,8 +703,74 @@ export interface SentimentResponse {
}
// Fundamentals
export interface MetricIndustry {
label: string;
median: number;
favorable_percentile: number; // 0-100, polarity-aware (higher = more favorable)
peer_count: number;
}
export interface MetricHistoryPoint {
period_end: string | null; // YYYY-MM-DD
value: number | null;
}
export type MetricKey =
| 'revenue_growth_yoy'
| 'eps_growth_yoy'
| 'operating_margin'
| 'fcf_margin'
| 'net_debt'
| 'net_debt_to_ebitda'
| 'share_count_change_yoy';
export interface MetricItem {
key: MetricKey;
value: number | null;
history: MetricHistoryPoint[];
industry: MetricIndustry | null;
period_end: string | null;
filed_date: string | null;
source: string; // 'sec' | 'legacy_api'
}
export interface EarningsNext {
date: string;
session: string; // bmo | amc | unknown
days_until: number;
}
export interface EarningsRecent {
announce_date: string;
period_end: string | null;
eps_estimate: number | null;
eps_actual: number | null;
surprise_pct: number | null;
}
export interface EarningsObject {
next: EarningsNext | null;
recent: EarningsRecent[];
}
export interface Valuation {
pe: number | null;
fcf_yield: number | null;
market_cap_est: number | null;
pe_industry: MetricIndustry | null;
fcf_yield_industry: MetricIndustry | null;
price_date: string | null;
}
export interface FundamentalsReads {
header: string | null;
// fixed map over every metric key plus 'pe' and 'fcf_yield'; null when unavailable
by_key: Record<string, string | null>;
}
export interface FundamentalResponse {
symbol: string;
// legacy fields (unchanged)
pe_ratio: number | null;
revenue_growth: number | null;
earnings_surprise: number | null;
@@ -712,6 +778,11 @@ export interface FundamentalResponse {
next_earnings_date: string | null;
fetched_at: string | null;
unavailable_fields: Record<string, string>;
// additive v1 (SEC/Dolt-derived) — present, with null/empty when unavailable
earnings: EarningsObject | null;
metrics: MetricItem[] | null;
valuation: Valuation | null;
reads: FundamentalsReads | null;
}
// Indicators