import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { ShieldAlert, RefreshCcw, FileText, Banknote } from "lucide-react"; function ModelGauge({ label, score, maxScore, color }) { const [animPct, setAnimPct] = useState(0); const safeScore = score || 0; const targetPct = Math.min(1, Math.max(0, safeScore / maxScore)); useEffect(() => { let raf; const start = performance.now(); const duration = 1200; function step(now) { const t = Math.min((now - start) / duration, 1); const ease = 1 - Math.pow(1 - t, 3); setAnimPct(ease * targetPct); if (t < 1) raf = requestAnimationFrame(step); } raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [targetPct]); const angleDeg = animPct * 180; const angleRad = ((180 - angleDeg) * Math.PI) / 180; const cx = 100, cy = 100, r = 70; const px = cx + (r - 16) * Math.cos(angleRad); const py = cy - (r - 16) * Math.sin(angleRad); const arcFlag = 0; const arcPx = cx + r * Math.cos(angleRad); const arcPy = cy - r * Math.sin(angleRad); const arcPath = `M 30 100 A 70 70 0 ${arcFlag} 1 ${arcPx.toFixed(1)} ${arcPy.toFixed(1)}`; const ticks = []; for (let i = 0; i <= 20; i++) { const tAngle = (180 - (i / 20) * 180) * (Math.PI / 180); const innerR = i % 5 === 0 ? r - 16 : r - 10; ticks.push({ x1: cx + innerR * Math.cos(tAngle), y1: cy - innerR * Math.sin(tAngle), x2: cx + (r + 2) * Math.cos(tAngle), y2: cy - (r + 2) * Math.sin(tAngle), major: i % 5 === 0 }); } const gradId = `grad-${label.replace(/\\s+/g, '')}`; return (
{ticks.map((t, i) => ( ))} 0 {maxScore} {safeScore.toFixed(1)}

{label}

); } function DecisionBanner({ scoreBreakdown, structurallyFragile }) { const { decision, loan_limit_crore, interest_rate_str } = scoreBreakdown; const configs = { APPROVE: { bg: "var(--success-subtle)", border: "var(--success)", color: "var(--success)", icon: "✓", label: "APPROVE" }, CONDITIONAL: { bg: "var(--warning-subtle)", border: "var(--warning)", color: "var(--warning)", icon: "⚠", label: "CONDITIONAL APPROVE" }, REJECT: { bg: "var(--danger-subtle)", border: "var(--danger)", color: "var(--danger)", icon: "✗", label: "REJECT" }, }; const cfg = configs[decision] || configs.REJECT; return (
{cfg.icon} {cfg.label}
{loan_limit_crore != null && (

Loan Limit

₹{loan_limit_crore} Cr

)} {interest_rate_str && (

Interest Rate

{interest_rate_str}

)} {structurallyFragile && ( ⚠ STRUCTURALLY FRAGILE )}
); } const FLAG_FIELDS = [ { key: "gst_bank_flag", label: "GST vs Bank Variance", icon: , varianceStr: "0% variance", desc: "GST declared turnover vs actual bank credits. Gap above 30% suggests revenue inflation.", impact: "No penalty", metric1: { lbl: "GST DECLARED", pct: 100 }, metric2: { lbl: "BANK CREDITS", pct: 100 }, }, { key: "gstr_flag", label: "GSTR-2A vs GSTR-3B Mismatch", icon: , varianceStr: "0% mismatch", desc: "ITC claimed vs ITC declared by suppliers. Above 15% gap suggests fake invoices.", impact: "No penalty", metric1: { lbl: "CLAIMED ITC", pct: 100 }, metric2: { lbl: "SUPPLIER ITC", pct: 100 }, }, { key: "round_trip_flag", label: "Round-Trip Transactions", icon: , varianceStr: "0 patterns detected", desc: "Money-in followed by near-identical money-out within 48 hours.", impact: "No penalty", }, { key: "cash_flag", label: "Cash Deposit Ratio", icon: , varianceStr: "0% of total credits", desc: "Cash deposits as % of total bank credits. Above 40% for B2B suggests cash economy.", impact: "No penalty", }, ]; export default function OverviewTab({ result }) { const navigate = useNavigate(); const { score_breakdown, fraud_features, structurally_fragile, processing_time_seconds, industry, job_id } = result; // We show all flags as cards, updating their state if they fired or not const renderCards = FLAG_FIELDS.map(f => { const isClean = !(fraud_features && fraud_features[f.key] && fraud_features[f.key] !== "CLEAN"); const flagVal = fraud_features?.[f.key] || "CLEAN"; const highlight = !isClean ? "var(--danger)" : "var(--accent)"; // Simulate what actual flagged data might look like based on flag state let vStr = f.varianceStr; let impact = f.impact; let m1Pct = f.metric1?.pct; let m2Pct = f.metric2?.pct; if (!isClean) { if (f.key === "gst_bank_flag") { vStr = "34% variance"; impact = "-15 pts"; m1Pct = 100; m2Pct = 66; } else if (f.key === "gstr_flag") { vStr = "21% mismatch"; impact = "-8 pts"; m1Pct = 100; m2Pct = 79; } else if (f.key === "round_trip_flag") { vStr = "3 patterns detected"; impact = "-12 pts"; } else if (f.key === "cash_flag") { vStr = "45% of total credits"; impact = "-5 pts"; } } return (
{f.icon} {f.label}
HIGH % {flagVal}

{vStr}

{f.desc}

Score impact: {impact}
{f.metric1 && (
{f.metric1.lbl}
{m1Pct}%
{f.metric2.lbl}
{m2Pct}%
)}
); }); return (

Final Score

{score_breakdown.final_score} /100

Decision

{score_breakdown.decision}
4-Model Score Breakdown
Fraud / Risk Signals
{renderCards}
); }