QuantumShield / frontend /components /ConfusionMatrix.tsx
SantoshKumar1310's picture
Upload folder using huggingface_hub
49e53ae verified
'use client'
import { Grid3X3 } from 'lucide-react'
interface Metrics {
tp: number
fp: number
tn: number
fn: number
}
interface ConfusionMatrixProps {
metrics: Metrics | null
}
export default function ConfusionMatrix({ metrics }: ConfusionMatrixProps) {
if (!metrics) {
return (
<div className="glass rounded-xl p-6 h-full flex flex-col items-center justify-center">
<Grid3X3 className="w-12 h-12 text-dark-500 mb-4" />
<p className="text-dark-400 text-center">
Process transactions to see confusion matrix
</p>
</div>
)
}
const total = metrics.tp + metrics.fp + metrics.tn + metrics.fn
const Cell = ({
value,
label,
color,
description
}: {
value: number
label: string
color: string
description: string
}) => (
<div
className={`
${color} rounded-lg p-4 text-center transition-transform hover:scale-105
`}
title={description}
>
<p className="text-2xl font-bold">{value}</p>
<p className="text-xs opacity-80">{label}</p>
<p className="text-xs opacity-60 mt-1">
{total > 0 ? ((value / total) * 100).toFixed(1) : 0}%
</p>
</div>
)
return (
<div className="glass rounded-xl p-6">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<Grid3X3 className="w-5 h-5 text-primary-500" />
Detection Performance
</h3>
{/* Matrix labels */}
<div className="mb-2 text-center">
<span className="text-xs text-dark-400">Predicted</span>
</div>
<div className="flex">
{/* Y-axis label */}
<div className="flex items-center -rotate-90 mr-2">
<span className="text-xs text-dark-400 whitespace-nowrap">Actual</span>
</div>
{/* Matrix grid */}
<div className="flex-1">
{/* Column headers */}
<div className="grid grid-cols-2 gap-2 mb-2">
<div className="text-center text-xs text-dark-400">Safe</div>
<div className="text-center text-xs text-dark-400">Fraud</div>
</div>
{/* Matrix cells */}
<div className="space-y-2">
{/* Row: Actual Safe */}
<div className="grid grid-cols-2 gap-2">
<Cell
value={metrics.tn}
label="True Negative"
color="bg-gray-600/50"
description="Correctly identified as safe"
/>
<Cell
value={metrics.fp}
label="False Positive"
color="bg-yellow-500/30"
description="Safe but flagged as fraud"
/>
</div>
{/* Row: Actual Fraud */}
<div className="grid grid-cols-2 gap-2">
<Cell
value={metrics.fn}
label="False Negative"
color="bg-red-500/30"
description="Fraud but missed"
/>
<Cell
value={metrics.tp}
label="True Positive"
color="bg-green-500/30"
description="Correctly caught fraud"
/>
</div>
</div>
{/* Row labels */}
<div className="mt-2 grid grid-cols-2 gap-2">
<div className="text-xs text-dark-400 text-right pr-4">Safe</div>
<div className="text-xs text-dark-400 text-right pr-4">Fraud</div>
</div>
</div>
</div>
{/* Summary stats */}
<div className="mt-4 pt-4 border-t border-dark-700 grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-dark-400">F1 Score</span>
<p className="font-semibold text-primary-500">
{(() => {
const precision = metrics.tp / (metrics.tp + metrics.fp) || 0
const recall = metrics.tp / (metrics.tp + metrics.fn) || 0
const f1 = 2 * (precision * recall) / (precision + recall) || 0
return f1.toFixed(3)
})()}
</p>
</div>
<div>
<span className="text-dark-400">Total</span>
<p className="font-semibold">{total.toLocaleString()}</p>
</div>
</div>
</div>
)
}