/* Brand Lens — scoreboard: alignment, calibration, divergences */
function Scoreboard({ recs, goReview, goGuide }) {
const reviewed = useMemo(() => recs.filter((r) => r.user && r.ai), [recs]);
const m = useMemo(() => {
let kk = 0, kr = 0, rk = 0, rr = 0; // you-keep/AI-keep ... you-reject/AI-reject
let likedScores = [], dislikedScores = [];
for (const r of reviewed) {
const aiKeep = r.ai.verdict === 'keep';
const youKeep = !!r.user.like;
if (youKeep && aiKeep) kk++;
else if (youKeep && !aiKeep) kr++;
else if (!youKeep && aiKeep) rk++;
else rr++;
(youKeep ? likedScores : dislikedScores).push(r.ai.score);
}
const aligned = kk + rr;
const total = reviewed.length;
const avg = (a) => (a.length ? Math.round(a.reduce((x, y) => x + y, 0) / a.length) : null);
return { kk, kr, rk, rr, aligned, total,
pct: total ? Math.round((aligned / total) * 100) : 0,
avgLiked: avg(likedScores), avgDisliked: avg(dislikedScores) };
}, [reviewed]);
const divergences = useMemo(
() => reviewed.filter((r) => (r.ai.verdict === 'keep') !== !!r.user.like)
.sort((a, b) => Math.abs(b.ai.score - 50) - Math.abs(a.ai.score - 50)),
[reviewed]
);
if (reviewed.length === 0) {
return
How often you agreed with the AI's call across {m.total} reviewed {m.total === 1 ? 'image' : 'images'} — and where you didn't. Refresh the style guide between rounds to re-teach the judge; this number should climb.
{brief && (Average brand-fit score the AI gave the images you actually kept vs. the ones you passed. A big gap means the score is a reliable proxy for your taste.
The images where you and the AI disagreed. These carry the most signal about what makes your taste yours.
{body}