ThejasRao's picture
Initial OpenENV hackathon submission
c492c3f
const METRICS = [
{
key: 'final_action_score',
label: 'Final Action Accuracy',
weight: '50%',
desc: 'Did the agent take the correct terminal action (allow/flag/remove/escalate)?',
},
{
key: 'classification_score',
label: 'Classification Accuracy',
weight: '20%',
desc: 'Did the agent correctly identify the violation type?',
},
{
key: 'investigation_score',
label: 'Investigation Quality',
weight: '15%',
desc: 'Did the agent gather all recommended context before deciding?',
},
{
key: 'efficiency_score',
label: 'Efficiency',
weight: '15%',
desc: 'Did the agent reach a decision without unnecessary extra steps?',
},
]
function barColor(v) {
if (v >= 0.8) return '#4ade80'
if (v >= 0.5) return '#f0a500'
return '#f87171'
}
function Tooltip({ text }) {
return (
<span className="group relative cursor-default">
<span className="text-[9px] text-[#3a3f5a] border border-[#2a2a45] rounded-full px-1 leading-none select-none">?</span>
<span className="absolute left-0 bottom-full mb-1.5 w-48 bg-[#0f0f1a] border border-[#2a2a45] rounded p-2 text-[10px] text-[#5a5f7a] leading-relaxed z-10 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity duration-150 shadow-xl">
{text}
</span>
</span>
)
}
function ScoreBar({ value, animKey }) {
const color = barColor(value)
return (
<div className="relative h-1 bg-[#1f1f35] rounded-full overflow-hidden">
<div
key={animKey}
className="absolute inset-y-0 left-0 rounded-full score-bar"
style={{
width: `${Math.round(value * 100)}%`,
background: color,
boxShadow: `0 0 6px ${color}80`,
}}
/>
</div>
)
}
export default function ScoreCard({ score, animKey }) {
if (!score) return null
const total = score.total
const totalColor = barColor(total)
const isCorrect = score.final_action_score === 1.0
return (
<div className="space-y-4">
{/* Total */}
<div className="flex items-end justify-between pb-3 border-b border-[#1f1f35]">
<div>
<div className="text-[9px] text-[#3a3f5a] tracking-[0.18em] uppercase mb-1">
Total Score
</div>
<div
className="font-display font-bold tabular-nums leading-none"
style={{ fontSize: '42px', color: totalColor, textShadow: `0 0 18px ${totalColor}55` }}
>
{Math.round(total * 100)}
<span className="text-lg font-medium ml-0.5" style={{ color: `${totalColor}88` }}>
%
</span>
</div>
</div>
<div className="text-right space-y-1">
{/* Correct / wrong indicator */}
<div
className="text-xs font-semibold px-2 py-0.5 rounded"
style={
isCorrect
? { color: '#4ade80', background: 'rgba(74,222,128,0.1)', border: '1px solid rgba(74,222,128,0.3)' }
: { color: '#f87171', background: 'rgba(248,113,113,0.1)', border: '1px solid rgba(248,113,113,0.3)' }
}
>
{isCorrect ? '✓ Correct Decision' : '✗ Wrong Decision'}
</div>
{score.breakdown?.false_negative_penalty > 0 && (
<div className="text-[10px] text-[#f87171]">
⚠ false negative −{score.breakdown.false_negative_penalty.toFixed(2)}
</div>
)}
</div>
</div>
{/* Per-metric bars */}
<div className="space-y-3.5">
{METRICS.map(({ key, label, weight, desc }) => {
const v = score[key] ?? 0
const color = barColor(v)
return (
<div key={key}>
<div className="flex justify-between items-center mb-1.5">
<div className="flex items-center gap-1.5">
<span className="text-[11px] text-[#5a5f7a]">{label}</span>
<Tooltip text={desc} />
<span className="text-[9px] text-[#2a2a45] tracking-widest ml-0.5">{weight}</span>
</div>
<span
className="text-xs tabular-nums font-semibold"
style={{ color }}
>
{Math.round(v * 100)}%
</span>
</div>
<ScoreBar value={v} animKey={`${animKey}-${key}`} />
</div>
)
})}
</div>
</div>
)
}