import { Activity, ChevronDown, ChevronRight, Minus, Target, TrendingDown, TrendingUp, } from 'lucide-react'; import { useState } from 'react'; import { Area, AreaChart, Bar, BarChart, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts'; const BG = { surface: 'var(--gi-bg-surface)', elevated: 'var(--gi-bg-raised)' }; const BORDER = { subtle: 'rgba(255,255,255,0.04)', muted: 'rgba(255,255,255,0.07)' }; const TEXT = { primary: 'rgba(255,255,255,0.88)', secondary: 'rgba(255,255,255,0.55)', tertiary: 'rgba(255,255,255,0.28)', muted: 'rgba(255,255,255,0.14)', }; export interface SimulationScenario { id: string; label: string; color: string; outputMetric: string; outputUnit: string; p5: number; p25: number; p50: number; p75: number; p95: number; mean: number; stdDev: number; confidence: number; } export interface SensitivityDriver { input: string; lowValue: number; highValue: number; baseOutput: number; lowOutput: number; highOutput: number; unit: string; } export interface MonteCarloSimPanelProps { scenarios: SimulationScenario[]; sensitivityDrivers: SensitivityDriver[]; iterations: number; compact?: boolean; } function formatValue(v: number, unit: string): string { if (unit === '$' || unit === 'USD') { if (Math.abs(v) >= 1_000_000) return `$${(v / 1_000_000).toFixed(1)}M`; if (Math.abs(v) >= 1_000) return `$${(v / 1_000).toFixed(0)}K`; return `$${v.toFixed(0)}`; } if (unit === '%') return `${v.toFixed(1)}%`; return v.toFixed(1); } function DistributionChart({ scenario }: { scenario: SimulationScenario }) { const bins = 30; const range = scenario.p95 - scenario.p5; const binWidth = range / bins; const data = Array.from({ length: bins }, (_, i) => { const x = scenario.p5 + i * binWidth; const z = (x - scenario.mean) / scenario.stdDev; const density = Math.exp(-0.5 * z * z) / (scenario.stdDev * Math.sqrt(2 * Math.PI)); return { x: Math.round(x), density: density * 1000, label: formatValue(x, scenario.outputUnit), }; }); return (
Math.abs(d.x - scenario.p50) < Math.abs(data[closest].x - scenario.p50) ? i : closest, 0, )} stroke={scenario.color} strokeDasharray="3 3" strokeOpacity={0.6} /> [value.toFixed(2), 'Density']} />
); } function TornadoChart({ drivers, unit }: { drivers: SensitivityDriver[]; unit: string }) { const sorted = [...drivers].sort( (a, b) => Math.abs(b.highOutput - b.lowOutput) - Math.abs(a.highOutput - a.lowOutput), ); const data = sorted.map((d) => ({ name: d.input, low: d.lowOutput - d.baseOutput, high: d.highOutput - d.baseOutput, lowLabel: formatValue(d.lowOutput, unit), highLabel: formatValue(d.highOutput, unit), })); return (
[formatValue(value, unit), 'Impact']} />
); } export function MonteCarloSimPanel({ scenarios, sensitivityDrivers, iterations, compact, }: MonteCarloSimPanelProps) { const [activeScenario, setActiveScenario] = useState(scenarios[0]?.id); const [showTornado, setShowTornado] = useState(false); const selected = scenarios.find((s) => s.id === activeScenario) ?? scenarios[0]; if (compact && selected) { const trend = selected.p50 > selected.mean ? 'up' : selected.p50 < selected.mean ? 'down' : 'flat'; const TrendIcon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus; const trendColor = trend === 'up' ? '#6b8f71' : trend === 'down' ? '#c45a4a' : TEXT.tertiary; return (
Monte Carlo Simulation {iterations.toLocaleString()} runs
{formatValue(selected.p50, selected.outputUnit)}
Median ({selected.outputMetric})
{formatValue(selected.p5, selected.outputUnit)} —{' '} {formatValue(selected.p95, selected.outputUnit)}
= 75 ? '#6b8f71' : '#c8953c' }} > {selected.confidence}%
Confidence
); } return (
Monte Carlo Simulation {iterations.toLocaleString()} iterations
{scenarios.length > 1 && (
{scenarios.map((s) => ( ))}
)} {selected && (
{[ { label: 'P5 (Worst)', value: formatValue(selected.p5, selected.outputUnit), color: '#c45a4a', }, { label: 'P25', value: formatValue(selected.p25, selected.outputUnit), color: '#c8953c', }, { label: 'P50 (Median)', value: formatValue(selected.p50, selected.outputUnit), color: selected.color, }, { label: 'P75', value: formatValue(selected.p75, selected.outputUnit), color: '#6b8f71', }, { label: 'P95 (Best)', value: formatValue(selected.p95, selected.outputUnit), color: '#6b8f71', }, ].map((p) => (
{p.label}
{p.value}
))}
Decision Confidence
= 75 ? '#6b8f71' : '#c8953c' }} /> = 75 ? '#6b8f71' : '#c8953c' }} > {selected.confidence}%
Std Deviation
{formatValue(selected.stdDev, selected.outputUnit)}
{showTornado && sensitivityDrivers.length > 0 && (
Tornado Chart — Key Sensitivity Drivers
)}
)}
); }