mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-25 08:18:05 +00:00
- Add missing numpy, scipy, dbnomics to requirements.txt (fixes ImportError on fresh install) - Sync claude.md with actual codebase: §3 file structure (37 services, 21 routers), §5 API endpoints (92 routes), §6 frontend pages (12), §13 TODO status - Update README.md with current architecture (92 API routes, 21 routers, 37 services), multi-asset overview, research grid, macro dashboard, screener+backtest, multi-jurisdiction filings, and 2026-03-26 changelog entry - Add new routers: dart, edinet, fmp, macro, research - Add new services: cache, dart_fetcher, dart_filing_service, economic_calendar, ecos_fetcher, edinet_filing_service, fmp_client, global_macro_quadrant, kpi_history_service, macro_cycle, macro_fetcher, oecd_cycle, peer_comparison_service, research_dashboard, smart_money_service, yield_fx_service - Add new frontend: macro page, screener+backtest, research grid components, overview (Equity/ETF/Commodity), filings (SEC/DART/EDINET), error boundaries - Remove 6 unused services: copilot_context, crypto_fetcher, fx_fetcher, gemini_analysis, market_data, technical_analysis - Remove obsolete docs: .agent/, AGENT.md, ATLAS_EVALUATION.md, docs/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
99 lines
4.8 KiB
TypeScript
99 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { KpiSection, type KpiHistoryData } from "./KpiSection";
|
|
import { PeerComparison, type PeerComparisonData } from "./PeerComparison";
|
|
|
|
interface EquityOverviewProps {
|
|
ticker: string;
|
|
sector: Record<string, unknown> | null;
|
|
health: Record<string, unknown> | null;
|
|
}
|
|
|
|
export function EquityOverview({ ticker, sector, health }: EquityOverviewProps) {
|
|
const [peerData, setPeerData] = useState<PeerComparisonData | null>(null);
|
|
const [kpiData, setKpiData] = useState<KpiHistoryData | null>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
Promise.all([
|
|
fetch(`/api/market/peers/${encodeURIComponent(ticker)}`).then((r) => (r.ok ? r.json() : null)),
|
|
fetch(`/api/financials/${encodeURIComponent(ticker)}/kpi-history`).then((r) => (r.ok ? r.json() : null)),
|
|
]).then(([p, k]) => {
|
|
if (!cancelled) {
|
|
setPeerData(p && Array.isArray(p.peers) ? p : null);
|
|
setKpiData(k && Array.isArray(k.quarters) ? k : null);
|
|
}
|
|
});
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [ticker]);
|
|
const metrics: { label: string; value: string }[] = [
|
|
{ label: "Sector", value: String(sector?.sector ?? "—") },
|
|
{ label: "Industry", value: String(sector?.industry ?? "—") },
|
|
{ label: "Market Cap", value: sector?.market_cap ? `$${(Number(sector.market_cap) / 1e9).toFixed(1)}B` : "—" },
|
|
{ label: "P/E Ratio", value: sector?.pe_ratio != null ? Number(sector.pe_ratio).toFixed(1) : "—" },
|
|
{ label: "Beta", value: sector?.beta != null ? Number(sector.beta).toFixed(2) : "—" },
|
|
{ label: "Div Yield", value: sector?.dividend_yield != null ? `${Number(sector.dividend_yield).toFixed(2)}%` : "—" },
|
|
{ label: "52W High", value: sector?.fifty_two_week_high != null ? `$${Number(sector.fifty_two_week_high).toFixed(2)}` : "—" },
|
|
{ label: "52W Low", value: sector?.fifty_two_week_low != null ? `$${Number(sector.fifty_two_week_low).toFixed(2)}` : "—" },
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<h1 className="text-2xl font-bold mb-1">
|
|
<span className="text-accent-green">{ticker}</span> Overview
|
|
</h1>
|
|
{sector?.current_price != null && (
|
|
<p className="text-3xl font-mono font-bold text-text-primary mb-6">${Number(sector.current_price).toFixed(2)}</p>
|
|
)}
|
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-6">
|
|
{metrics.map((m) => (
|
|
<div key={m.label} className="bg-bg-card border border-border rounded-lg p-4">
|
|
<div className="text-text-muted text-xs mb-1">{m.label}</div>
|
|
<div className="text-text-primary font-semibold">{m.value}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<KpiSection data={kpiData} />
|
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4 mb-4">
|
|
<Card title="Altman Z-Score" value={health?.altman_z != null ? Number(health.altman_z).toFixed(2) : "—"} />
|
|
<Card title="Current Ratio" value={health?.current_ratio != null ? Number(health.current_ratio).toFixed(2) : "—"} />
|
|
<Card title="Interest Cov." value={health?.interest_coverage != null ? `${Number(health.interest_coverage).toFixed(1)}x` : "—"} />
|
|
<Card title="D/E Ratio" value={health?.debt_to_equity != null ? Number(health.debt_to_equity).toFixed(2) : "—"} />
|
|
</div>
|
|
<div className="bg-bg-card border border-border rounded-lg p-5">
|
|
<h3 className="text-text-secondary text-sm font-semibold mb-3">DuPont Analysis</h3>
|
|
{!!health?.dupont && typeof health.dupont === "object" && health.dupont !== null ? (
|
|
<div className="space-y-2">
|
|
{[
|
|
{ label: "ROE", value: (health.dupont as { roe?: unknown }).roe },
|
|
{ label: "Net Profit Margin", value: (health.dupont as { npm?: unknown }).npm },
|
|
{ label: "Asset Turnover", value: (health.dupont as { asset_turnover?: unknown }).asset_turnover },
|
|
{ label: "Equity Multiplier", value: (health.dupont as { equity_multiplier?: unknown }).equity_multiplier },
|
|
].map((d) => (
|
|
<div key={d.label} className="flex justify-between">
|
|
<span className="text-text-muted text-sm">{d.label}</span>
|
|
<span className="text-text-primary font-mono">{d.value != null ? Number(d.value).toFixed(2) : "—"}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="text-text-muted">No data</div>
|
|
)}
|
|
</div>
|
|
<PeerComparison currentTicker={ticker} data={peerData} />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Card({ title, value }: { title: string; value: string }) {
|
|
return (
|
|
<div className="bg-bg-card border border-border rounded-lg p-5">
|
|
<h3 className="text-text-secondary text-sm font-semibold mb-3">{title}</h3>
|
|
<div className="text-3xl font-mono font-bold text-text-primary">{value}</div>
|
|
</div>
|
|
);
|
|
}
|