From ca54e9313894600feac548b13da8fab335db2905 Mon Sep 17 00:00:00 2001 From: Tyler Hoang Date: Mon, 29 Jun 2026 01:41:26 -0700 Subject: feat: update frontend API types, client and formatters --- frontend/lib/api.ts | 7 ++++++- frontend/lib/format.ts | 31 ++++++++++++++++++++++++------- frontend/types/api.ts | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index 2635af5..a584241 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -1,4 +1,4 @@ -import type { FilingsResponse, FinancialsResponse, HistoryPoint, InsidersResponse, MarketIndex, NewsResponse, RatiosResponse, SearchResult, TickerOverview, ValuationResponse, WatchlistResponse } from "@/types/api"; +import type { FilingsResponse, FinancialsResponse, HistoryPoint, InsidersResponse, MarketIndex, NewsResponse, RatiosResponse, SearchResult, TickerOverview, ValuationResponse, WaccResponse, WatchlistResponse } from "@/types/api"; const API_BASE = (process.env.NEXT_PUBLIC_API_BASE_URL || "").trim().replace(/\/$/, ""); @@ -59,6 +59,11 @@ export const api = { `/api/tickers/${encodeURIComponent(symbol)}/valuation` ); }, + wacc(symbol: string) { + return request( + `/api/tickers/${encodeURIComponent(symbol)}/wacc` + ); + }, ratios(symbol: string) { return request( `/api/tickers/${encodeURIComponent(symbol)}/ratios` diff --git a/frontend/lib/format.ts b/frontend/lib/format.ts index 6a1fcc2..e4bae13 100644 --- a/frontend/lib/format.ts +++ b/frontend/lib/format.ts @@ -1,6 +1,22 @@ -export function fmtCurrency(value?: number | null, decimals = 2): string { +const CURRENCY_SYMBOLS: Record = { + USD: "$", + CAD: "C$", + GBP: "£", + EUR: "€", + JPY: "¥", + AUD: "A$", + CHF: "CHF ", + CNY: "CN¥", + HKD: "HK$", +}; + +function currencySymbol(currency = "USD"): string { + return CURRENCY_SYMBOLS[currency.toUpperCase()] || `${currency.toUpperCase()} `; +} + +export function fmtCurrency(value?: number | null, decimals = 2, currency = "USD"): string { if (value === null || value === undefined || Number.isNaN(value)) return "-"; - return `$${value.toLocaleString(undefined, { maximumFractionDigits: decimals, minimumFractionDigits: decimals })}`; + return `${currencySymbol(currency)}${value.toLocaleString(undefined, { maximumFractionDigits: decimals, minimumFractionDigits: decimals })}`; } export function fmtNumber(value?: number | null, decimals = 2): string { @@ -8,14 +24,15 @@ export function fmtNumber(value?: number | null, decimals = 2): string { return value.toLocaleString(undefined, { maximumFractionDigits: decimals, minimumFractionDigits: decimals }); } -export function fmtLarge(value?: number | null): string { +export function fmtLarge(value?: number | null, currency = "USD"): string { if (value === null || value === undefined || Number.isNaN(value)) return "-"; const abs = Math.abs(value); const sign = value < 0 ? "-" : ""; - if (abs >= 1e12) return `${sign}$${(abs / 1e12).toFixed(2)}T`; - if (abs >= 1e9) return `${sign}$${(abs / 1e9).toFixed(2)}B`; - if (abs >= 1e6) return `${sign}$${(abs / 1e6).toFixed(1)}M`; - return `${sign}$${abs.toLocaleString(undefined, { maximumFractionDigits: 0 })}`; + const symbol = currencySymbol(currency); + if (abs >= 1e12) return `${sign}${symbol}${(abs / 1e12).toFixed(2)}T`; + if (abs >= 1e9) return `${sign}${symbol}${(abs / 1e9).toFixed(2)}B`; + if (abs >= 1e6) return `${sign}${symbol}${(abs / 1e6).toFixed(1)}M`; + return `${sign}${symbol}${abs.toLocaleString(undefined, { maximumFractionDigits: 0 })}`; } export function fmtPct(value?: number | null, decimals = 2, signed = false): string { diff --git a/frontend/types/api.ts b/frontend/types/api.ts index 8143d56..3b2cd70 100644 --- a/frontend/types/api.ts +++ b/frontend/types/api.ts @@ -25,6 +25,8 @@ export type Signal = { }; export type TickerOverview = { + currency: string; + currency_warning: string | null; profile: { symbol: string; name: string; @@ -115,12 +117,33 @@ export type FinancialStatement = { }; export type FinancialsResponse = { + currency: string; + currency_warning: string | null; period: "annual" | "quarterly"; income: FinancialStatement; balance: FinancialStatement; cash_flow: FinancialStatement; }; +export type AdvancedDcfInputs = { + base_revenue: number; + revenue_growth: number[]; + ebitda_margin: number[]; + dna_pct_revenue: number[]; + capex_pct_revenue: number[]; + nwc_chg_pct_delta_rev: number[]; + tax_rate: number[]; + wacc: number; + terminal_growth: number; + projection_years: number; +}; + +export type SensitivityMatrix = { + wacc: number[]; + terminal_growth: number[]; + implied_prices: (number | null)[][]; +}; + export type DcfResult = { available: boolean; error?: string | null; @@ -137,6 +160,8 @@ export type DcfResult = { wacc: number; terminal_growth: number; projection_years: number; + sensitivity?: SensitivityMatrix | null; + advanced_inputs?: AdvancedDcfInputs | null; }; export type MultipleResult = { @@ -149,6 +174,8 @@ export type MultipleResult = { }; export type ValuationResponse = { + currency: string; + currency_warning: string | null; symbol: string; current_price?: number | null; shares_outstanding?: number | null; @@ -158,6 +185,27 @@ export type ValuationResponse = { price_to_book: MultipleResult; }; +export type WaccResponse = { + currency: string; + currency_warning: string | null; + symbol: string; + available: boolean; + wacc?: number | null; + cost_of_equity?: number | null; + after_tax_cost_of_debt?: number | null; + risk_free_rate: number; + beta: number; + equity_risk_premium: number; + cost_of_debt: number; + tax_rate: number; + market_cap?: number | null; + total_debt?: number | null; + cash?: number | null; + net_debt?: number | null; + equity_weight?: number | null; + debt_weight?: number | null; +}; + export type RatioPoint = { value: number | null; spark: (number | null)[]; @@ -230,6 +278,8 @@ export type FilingsResponse = { }; export type RatiosResponse = { + currency: string; + currency_warning: string | null; pe_ttm: RatioPoint; ev_ebitda: RatioPoint; gross_margin: RatioPoint; -- cgit v1.3-2-g0d8e