/**
* SummaryBar — top bar showing stats, overall confidence meter, and Finalize button.
* Finalize is disabled until all flagged rules are actioned.
*/
import React, { useMemo } from 'react'
function Stat({ label, value, color = 'text-slate-200' }) {
return (
)
}
/** Animated SVG arc ring showing 0-100% confidence */
function ConfidenceRing({ score }) {
const pct = Math.round((score ?? 0) * 100)
const radius = 28
const circumference = 2 * Math.PI * radius
const filled = (pct / 100) * circumference
const color =
pct >= 90 ? '#34d399' // emerald
: pct >= 70 ? '#fbbf24' // amber
: '#f87171' // red
const label =
pct >= 90 ? 'High Confidence'
: pct >= 70 ? 'Medium — Review Flags'
: 'Low — Do Not Finalize'
return (
Policy Confidence
{label}
)
}
function GraphIcon({ className = 'w-4 h-4' }) {
return (
)
}
export default function SummaryBar({ rules, onFinalize, finalizing, onFlaggedClick, onGraphClick, graphPanelOpen, fallbackActive }) {
const total = rules.length
const flagged = rules.filter(
(r) => r.confidence_score < 0.9 || r.conflict_with?.length > 0
).length
const resolved = rules.filter((r) =>
['accepted', 'modified', 'kept_original'].includes(r.review_status)
).length
const lowConfidence = rules.filter((r) => r.confidence_score < 0.7).length
const unresolvedFlags = rules.filter(
(r) => (r.confidence_score < 0.9 || r.conflict_with?.length > 0) &&
!['accepted', 'modified', 'kept_original'].includes(r.review_status)
).length
const avgConfidence = useMemo(() => {
if (!rules.length) return 0
const sum = rules.reduce((acc, r) => {
let score = r.confidence_score ?? 0
const isFlagged = r.confidence_score < 0.9 || r.conflict_with?.length > 0
const isResolved = ['accepted', 'modified', 'kept_original'].includes(r.review_status)
if (isFlagged && !isResolved) score = score * 0.5
return acc + score
}, 0)
return sum / rules.length
}, [rules])
const allFlaggedResolved = unresolvedFlags === 0
const canFinalize = allFlaggedResolved && total > 0
return (
{fallbackActive && (
Fallback LLM Active
)}
PolicyPilot
/
Rule Review
0 ? 'text-amber-400' : 'text-slate-400'} />
0 ? 'text-emerald-400' : 'text-slate-400'} />
0 ? 'text-red-400' : 'text-slate-400'} />
{/* Rule Graph — left of Next Flag, flows purple, unlocks when all flags resolved */}
0
? graphPanelOpen
? 'bg-gradient-to-r from-violet-500 to-purple-500 text-white border border-violet-400/60 shadow-[0_0_20px_rgba(139,92,246,0.7)] scale-[1.02]'
: 'bg-gradient-to-r from-violet-600 to-purple-600 hover:from-violet-500 hover:to-purple-500 text-white border border-violet-500/50 shadow-[0_0_15px_rgba(139,92,246,0.4)] animate-pulse-slow'
: 'bg-slate-800 text-slate-600 border border-slate-700 cursor-not-allowed opacity-50'
}`}
>
Rule Graph
{/* Next Flag */}
0
? 'bg-amber-500/20 text-amber-400 border border-amber-500/60 shadow-[0_0_15px_rgba(245,158,11,0.5)] hover:bg-amber-500/30'
: 'bg-slate-800 text-slate-500 border border-slate-700 cursor-not-allowed opacity-50'
}`}
>
Next Flag ({unresolvedFlags})
{/* Finalize */}
{finalizing ? (
Finalizing…
) : (
`Finalize Rules ${unresolvedFlags > 0 ? `(${unresolvedFlags} pending)` : ''}`
)}
)
}