SPB Git

spb/vquant Public MIT

VibeQuant — AI-powered institutional-grade financial intelligence platform.

TypeScript 84.3% Python 11.7% JavaScript 1.6% CSS 1.5% HTML 0.7%
13.0 KB · 325 lines tsx
Raw Blame History
1/*2 * =============================================================================3 *  VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 *  File:      client/src/components/explore/metrics-view.tsx6 *7 *  Author:    Simon-Pierre Boucher8 *  Contact:   contact@spboucher.ai9 *  Website:   https://www.spboucher.ai10 *  Demo:      https://www.vquant.ai11 *  License:   MIT (see LICENSE)12 *13 *  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";18import { Badge } from "@/components/ui/badge";19import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";20import {21  TrendingUp,22  TrendingDown,23  DollarSign,24  Percent,25  TrendingUpDown,26  Calculator,27  PieChart,28  Activity29} from "lucide-react";3031interface KeyMetrics {32  date: string;33  symbol: string;34  period: string;35  revenuePerShare: number;36  netIncomePerShare: number;37  operatingCashFlowPerShare: number;38  freeCashFlowPerShare: number;39  cashPerShare: number;40  bookValuePerShare: number;41  tangibleBookValuePerShare: number;42  shareholdersEquityPerShare: number;43  interestDebtPerShare: number;44  marketCap: number;45  enterpriseValue: number;46  peRatio: number;47  priceToSalesRatio: number;48  pocfratio: number;49  pfcfRatio: number;50  pbRatio: number;51  ptbRatio: number;52  evToSales: number;53  enterpriseValueOverEBITDA: number;54  evToOperatingCashFlow: number;55  evToFreeCashFlow: number;56  earningsYield: number;57  freeCashFlowYield: number;58  debtToEquity: number;59  debtToAssets: number;60  netDebtToEBITDA: number;61  currentRatio: number;62  interestCoverage: number;63  incomeQuality: number;64  dividendYield: number;65  payoutRatio: number;66  salesGeneralAndAdministrativeToRevenue: number;67  researchAndDdevelopementToRevenue: number;68  intangiblesToTotalAssets: number;69  capexToOperatingCashFlow: number;70  capexToRevenue: number;71  capexToDepreciation: number;72  stockBasedCompensationToRevenue: number;73  grahamNumber: number;74  roic: number;75  returnOnTangibleAssets: number;76  grahamNetNet: number;77  workingCapital: number;78  tangibleAssetValue: number;79  netCurrentAssetValue: number;80  investedCapital: number;81  averageReceivables: number;82  averagePayables: number;83  averageInventory: number;84  daysSalesOutstanding: number;85  daysPayablesOutstanding: number;86  daysOfInventoryOnHand: number;87  receivablesTurnover: number;88  payablesTurnover: number;89  inventoryTurnover: number;90  roe: number;91  capexPerShare: number;92  [key: string]: any;93}9495interface MetricsViewProps {96  keyMetrics?: KeyMetrics[];97}9899export function MetricsView({ keyMetrics }: MetricsViewProps) {100  const formatNumber = (value: number) => {101    if (!value) return "N/A";102    const absValue = Math.abs(value);103    if (absValue >= 1e12) return `$${(value / 1e12).toFixed(2)}T`;104    if (absValue >= 1e9) return `$${(value / 1e9).toFixed(2)}B`;105    if (absValue >= 1e6) return `$${(value / 1e6).toFixed(2)}M`;106    if (absValue >= 1e3) return `$${(value / 1e3).toFixed(2)}K`;107    return `$${value.toFixed(2)}`;108  };109110  const formatPercent = (value: number) => {111    if (!value && value !== 0) return "N/A";112    return `${(value * 100).toFixed(2)}%`;113  };114115  const formatRatio = (value: number) => {116    if (!value && value !== 0) return "N/A";117    return value.toFixed(2);118  };119120  const formatDate = (dateString: string) => {121    return new Date(dateString).toLocaleDateString('fr-FR', {122      year: 'numeric',123      month: 'short'124    });125  };126127  const getChangeIndicator = (current: number, previous: number) => {128    if (!current || !previous) return null;129    const change = ((current - previous) / Math.abs(previous)) * 100;130    const isPositive = change >= 0;131132    return (133      <span className={`text-xs flex items-center gap-1 ${isPositive ? 'text-green-500' : 'text-red-500'}`}>134        {isPositive ? <TrendingUp className="w-3 h-3" /> : <TrendingDown className="w-3 h-3" />}135        {isPositive ? '+' : ''}{change.toFixed(1)}%136      </span>137    );138  };139140  if (!keyMetrics || keyMetrics.length === 0) {141    return (142      <Card>143        <CardHeader>144          <CardTitle>Métriques Clés</CardTitle>145          <CardDescription>Aucune donnée disponible</CardDescription>146        </CardHeader>147      </Card>148    );149  }150151  const metricsSections = [152    {153      title: "Métriques de Valorisation",154      icon: <DollarSign className="w-5 h-5" />,155      description: "Ratios de valorisation et prix",156      metrics: [157        { label: "Capitalisation Boursière", key: "marketCap", format: "currency" },158        { label: "Valeur d'Entreprise", key: "enterpriseValue", format: "currency" },159        { label: "P/E Ratio", key: "peRatio", format: "ratio" },160        { label: "P/S Ratio", key: "priceToSalesRatio", format: "ratio" },161        { label: "P/B Ratio", key: "pbRatio", format: "ratio" },162        { label: "P/TBV Ratio", key: "ptbRatio", format: "ratio" },163        { label: "P/FCF Ratio", key: "pfcfRatio", format: "ratio" },164        { label: "EV/Sales", key: "evToSales", format: "ratio" },165        { label: "EV/EBITDA", key: "enterpriseValueOverEBITDA", format: "ratio" },166        { label: "EV/Operating CF", key: "evToOperatingCashFlow", format: "ratio" },167        { label: "EV/Free CF", key: "evToFreeCashFlow", format: "ratio" },168        { label: "Graham Number", key: "grahamNumber", format: "currency" },169      ]170    },171    {172      title: "Rentabilité & Rendements",173      icon: <TrendingUp className="w-5 h-5" />,174      description: "Indicateurs de rentabilité",175      metrics: [176        { label: "ROE (Return on Equity)", key: "roe", format: "percent" },177        { label: "ROIC (Return on Invested Capital)", key: "roic", format: "percent" },178        { label: "Return on Tangible Assets", key: "returnOnTangibleAssets", format: "percent" },179        { label: "Earnings Yield", key: "earningsYield", format: "percent" },180        { label: "Free Cash Flow Yield", key: "freeCashFlowYield", format: "percent" },181        { label: "Dividend Yield", key: "dividendYield", format: "percent" },182        { label: "Payout Ratio", key: "payoutRatio", format: "percent" },183        { label: "Income Quality", key: "incomeQuality", format: "ratio" },184      ]185    },186    {187      title: "Métriques par Action",188      icon: <PieChart className="w-5 h-5" />,189      description: "Données normalisées par action",190      metrics: [191        { label: "Revenu par Action", key: "revenuePerShare", format: "currency" },192        { label: "Bénéfice Net par Action", key: "netIncomePerShare", format: "currency" },193        { label: "Operating CF par Action", key: "operatingCashFlowPerShare", format: "currency" },194        { label: "Free CF par Action", key: "freeCashFlowPerShare", format: "currency" },195        { label: "Cash par Action", key: "cashPerShare", format: "currency" },196        { label: "Book Value par Action", key: "bookValuePerShare", format: "currency" },197        { label: "Tangible BV par Action", key: "tangibleBookValuePerShare", format: "currency" },198        { label: "Shareholders Equity par Action", key: "shareholdersEquityPerShare", format: "currency" },199        { label: "Capex par Action", key: "capexPerShare", format: "currency" },200      ]201    },202    {203      title: "Santé Financière & Dette",204      icon: <Calculator className="w-5 h-5" />,205      description: "Indicateurs de solvabilité et liquidité",206      metrics: [207        { label: "Dette/Capitaux Propres", key: "debtToEquity", format: "ratio" },208        { label: "Dette/Actifs", key: "debtToAssets", format: "ratio" },209        { label: "Dette Nette/EBITDA", key: "netDebtToEBITDA", format: "ratio" },210        { label: "Ratio de Liquidité", key: "currentRatio", format: "ratio" },211        { label: "Couverture des Intérêts", key: "interestCoverage", format: "ratio" },212        { label: "Fonds de Roulement", key: "workingCapital", format: "currency" },213        { label: "Capital Investi", key: "investedCapital", format: "currency" },214        { label: "Tangible Asset Value", key: "tangibleAssetValue", format: "currency" },215      ]216    },217    {218      title: "Efficacité Opérationnelle",219      icon: <Activity className="w-5 h-5" />,220      description: "Ratios de rotation et cycles d'exploitation",221      metrics: [222        { label: "Rotation des Créances", key: "receivablesTurnover", format: "ratio" },223        { label: "Rotation des Dettes", key: "payablesTurnover", format: "ratio" },224        { label: "Rotation des Stocks", key: "inventoryTurnover", format: "ratio" },225        { label: "Jours de Recouvrement (DSO)", key: "daysSalesOutstanding", format: "ratio" },226        { label: "Jours de Paiement (DPO)", key: "daysPayablesOutstanding", format: "ratio" },227        { label: "Jours de Stock (DIO)", key: "daysOfInventoryOnHand", format: "ratio" },228        { label: "Créances Moyennes", key: "averageReceivables", format: "currency" },229        { label: "Dettes Moyennes", key: "averagePayables", format: "currency" },230        { label: "Stock Moyen", key: "averageInventory", format: "currency" },231      ]232    },233    {234      title: "Dépenses & Allocation",235      icon: <Percent className="w-5 h-5" />,236      description: "Structure des dépenses en % du revenu",237      metrics: [238        { label: "SG&A / Revenu", key: "salesGeneralAndAdministrativeToRevenue", format: "percent" },239        { label: "R&D / Revenu", key: "researchAndDdevelopementToRevenue", format: "percent" },240        { label: "Stock Comp / Revenu", key: "stockBasedCompensationToRevenue", format: "percent" },241        { label: "Capex / Operating CF", key: "capexToOperatingCashFlow", format: "percent" },242        { label: "Capex / Revenu", key: "capexToRevenue", format: "percent" },243        { label: "Capex / Depreciation", key: "capexToDepreciation", format: "ratio" },244        { label: "Intangibles / Total Assets", key: "intangiblesToTotalAssets", format: "percent" },245      ]246    },247  ];248249  return (250    <div className="space-y-6">251      {metricsSections.map((section, sectionIdx) => (252        <Card key={sectionIdx}>253          <CardHeader>254            <CardTitle className="flex items-center gap-2">255              {section.icon}256              {section.title}257            </CardTitle>258            <CardDescription>{section.description}</CardDescription>259          </CardHeader>260          <CardContent>261            <div className="overflow-x-auto">262              <Table>263                <TableHeader>264                  <TableRow>265                    <TableHead className="w-[250px] sticky left-0 bg-card z-10">Métrique</TableHead>266                    {keyMetrics.slice(0, 5).map((item, idx) => (267                      <TableHead key={idx} className="text-right min-w-[150px]">268                        <div>269                          <div className="font-semibold">{formatDate(item.date)}</div>270                          <Badge variant="outline" className="text-xs mt-1">271                            {item.period}272                          </Badge>273                        </div>274                      </TableHead>275                    ))}276                  </TableRow>277                </TableHeader>278                <TableBody>279                  {section.metrics.map((metric, metricIdx) => (280                    <TableRow key={metricIdx} className="hover:bg-muted/50">281                      <TableCell className="font-medium sticky left-0 bg-card z-10">282                        {metric.label}283                      </TableCell>284                      {keyMetrics.slice(0, 5).map((item, idx) => {285                        const value = item[metric.key];286                        const prevValue = idx < keyMetrics.length - 1 ? keyMetrics[idx + 1][metric.key] : null;287                        let formattedValue = "N/A";288289                        if (value || value === 0) {290                          switch (metric.format) {291                            case "currency":292                              formattedValue = formatNumber(value);293                              break;294                            case "percent":295                              formattedValue = formatPercent(value);296                              break;297                            case "ratio":298                              formattedValue = formatRatio(value);299                              break;300                            default:301                              formattedValue = value.toString();302                          }303                        }304305                        return (306                          <TableCell key={idx} className="text-right">307                            <div>308                              <div className="font-semibold">{formattedValue}</div>309                              {value && prevValue && getChangeIndicator(value, prevValue)}310                            </div>311                          </TableCell>312                        );313                      })}314                    </TableRow>315                  ))}316                </TableBody>317              </Table>318            </div>319          </CardContent>320        </Card>321      ))}322    </div>323  );324}325