feat: side panel shows raw + balanced averages, list drops delta
Removes the per-row '⇢ X.X' annotation from the ranking list — the list view stays clean. The side panel's stats area gains a combined Avg Score card that shows Raw and Balanced side-by-side, with the active one (per the round's toggle) bolded and tagged 'used for ranking'. Pass Rate and Evaluators move below into a 2-col grid. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -83,7 +83,6 @@ type SortableProjectRowProps = {
|
|||||||
entry: (RankedProjectEntry & { originalIndex?: number }) | undefined
|
entry: (RankedProjectEntry & { originalIndex?: number }) | undefined
|
||||||
projectInfo: ProjectInfo | undefined
|
projectInfo: ProjectInfo | undefined
|
||||||
jurorScores: JurorScore[] | undefined
|
jurorScores: JurorScore[] | undefined
|
||||||
balancedScore: number | null
|
|
||||||
onSelect: () => void
|
onSelect: () => void
|
||||||
isSelected: boolean
|
isSelected: boolean
|
||||||
originalRank: number | undefined // from snapshotOrder — always in sync with localOrder
|
originalRank: number | undefined // from snapshotOrder — always in sync with localOrder
|
||||||
@@ -97,7 +96,6 @@ function SortableProjectRow({
|
|||||||
entry,
|
entry,
|
||||||
projectInfo,
|
projectInfo,
|
||||||
jurorScores,
|
jurorScores,
|
||||||
balancedScore,
|
|
||||||
onSelect,
|
onSelect,
|
||||||
isSelected,
|
isSelected,
|
||||||
originalRank,
|
originalRank,
|
||||||
@@ -202,27 +200,6 @@ function SortableProjectRow({
|
|||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Raw + balanced averages shown side by side */}
|
|
||||||
{entry?.avgGlobalScore !== null && entry?.avgGlobalScore !== undefined && jurorScores && jurorScores.length > 1 && (
|
|
||||||
<div className="flex items-center gap-1.5 text-xs" title="Raw juror average vs. juror-balanced average (z-score normalized per juror, rescaled to 1-10)">
|
|
||||||
<span className="font-medium text-muted-foreground">
|
|
||||||
{entry.avgGlobalScore.toFixed(1)}
|
|
||||||
</span>
|
|
||||||
{balancedScore != null && Math.abs(balancedScore - entry.avgGlobalScore) >= 0.05 && (
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
'font-semibold tabular-nums rounded px-1.5 py-0.5 border',
|
|
||||||
balancedScore > entry.avgGlobalScore
|
|
||||||
? 'bg-emerald-50 text-emerald-700 border-emerald-200'
|
|
||||||
: 'bg-amber-50 text-amber-700 border-amber-200',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
⇢ {balancedScore.toFixed(1)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Advance decision indicator */}
|
{/* Advance decision indicator */}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',
|
'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',
|
||||||
@@ -1000,7 +977,6 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
|
|||||||
entry={rankingMap.get(projectId)}
|
entry={rankingMap.get(projectId)}
|
||||||
projectInfo={projectInfoMap.get(projectId)}
|
projectInfo={projectInfoMap.get(projectId)}
|
||||||
jurorScores={evalScores?.byProject[projectId]}
|
jurorScores={evalScores?.byProject[projectId]}
|
||||||
balancedScore={evalScores?.balanced[projectId]?.balancedAverage ?? null}
|
|
||||||
onSelect={() => setSelectedProjectId(projectId)}
|
onSelect={() => setSelectedProjectId(projectId)}
|
||||||
isSelected={selectedProjectId === projectId}
|
isSelected={selectedProjectId === projectId}
|
||||||
originalRank={hasReorders ? snapshotOrder[projectId] : undefined}
|
originalRank={hasReorders ? snapshotOrder[projectId] : undefined}
|
||||||
@@ -1075,31 +1051,50 @@ export function RankingDashboard({ competitionId: _competitionId, roundId }: Ran
|
|||||||
</div>
|
</div>
|
||||||
<Switch checked={useBalanced} onCheckedChange={persistUseBalanced} />
|
<Switch checked={useBalanced} onCheckedChange={persistUseBalanced} />
|
||||||
</div>
|
</div>
|
||||||
{/* Stats summary */}
|
{/* Stats summary: combined Avg card with Raw + Balanced side-by-side */}
|
||||||
{projectDetail.stats && (
|
{projectDetail.stats && (() => {
|
||||||
<div className="grid grid-cols-3 gap-3">
|
const raw = selectedProjectId
|
||||||
<div className="rounded-lg border p-3 text-center">
|
? evalScores?.balanced[selectedProjectId]?.rawAverage ?? null
|
||||||
<p className="text-xs text-muted-foreground">Avg Score</p>
|
: null
|
||||||
<p className="mt-1 text-lg font-semibold">
|
const balanced = selectedProjectId
|
||||||
{projectDetail.stats.averageGlobalScore?.toFixed(1) ?? '—'}
|
? evalScores?.balanced[selectedProjectId]?.balancedAverage ?? null
|
||||||
</p>
|
: null
|
||||||
|
return (
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="rounded-lg border p-3">
|
||||||
|
<p className="text-xs text-muted-foreground mb-2">Avg Score</p>
|
||||||
|
<div className="flex items-baseline gap-4 flex-wrap">
|
||||||
|
<div className={`flex items-baseline gap-1 ${useBalanced ? 'text-muted-foreground' : 'font-semibold'}`}>
|
||||||
|
<span className="text-xs">Raw</span>
|
||||||
|
<span className="text-lg tabular-nums">{raw != null ? raw.toFixed(1) : '—'}</span>
|
||||||
|
{!useBalanced && <span className="ml-1 text-[10px] text-muted-foreground">← used for ranking</span>}
|
||||||
|
</div>
|
||||||
|
<div className={`flex items-baseline gap-1 ${useBalanced ? 'font-semibold' : 'text-muted-foreground'}`}>
|
||||||
|
<span className="text-xs">Balanced</span>
|
||||||
|
<span className="text-lg tabular-nums">{balanced != null ? balanced.toFixed(1) : '—'}</span>
|
||||||
|
{useBalanced && <span className="ml-1 text-[10px] text-muted-foreground">← used for ranking</span>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div className="rounded-lg border p-3 text-center">
|
||||||
|
<p className="text-xs text-muted-foreground">Pass Rate</p>
|
||||||
|
<p className="mt-1 text-lg font-semibold">
|
||||||
|
{projectDetail.stats.totalEvaluations > 0
|
||||||
|
? `${Math.round((projectDetail.stats.yesVotes / projectDetail.stats.totalEvaluations) * 100)}%`
|
||||||
|
: '—'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="rounded-lg border p-3 text-center">
|
||||||
|
<p className="text-xs text-muted-foreground">Evaluators</p>
|
||||||
|
<p className="mt-1 text-lg font-semibold">
|
||||||
|
{projectDetail.stats.totalEvaluations}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg border p-3 text-center">
|
)
|
||||||
<p className="text-xs text-muted-foreground">Pass Rate</p>
|
})()}
|
||||||
<p className="mt-1 text-lg font-semibold">
|
|
||||||
{projectDetail.stats.totalEvaluations > 0
|
|
||||||
? `${Math.round((projectDetail.stats.yesVotes / projectDetail.stats.totalEvaluations) * 100)}%`
|
|
||||||
: '—'}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-lg border p-3 text-center">
|
|
||||||
<p className="text-xs text-muted-foreground">Evaluators</p>
|
|
||||||
<p className="mt-1 text-lg font-semibold">
|
|
||||||
{projectDetail.stats.totalEvaluations}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Per-juror evaluations */}
|
{/* Per-juror evaluations */}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user