2026-02-14 15:26:42 +01:00
|
|
|
'use client'
|
|
|
|
|
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
import { ResponsiveScatterPlot } from '@nivo/scatterplot'
|
|
|
|
|
import type {
|
|
|
|
|
ScatterPlotDatum,
|
|
|
|
|
ScatterPlotNodeProps,
|
|
|
|
|
} from '@nivo/scatterplot'
|
|
|
|
|
import { animated } from '@react-spring/web'
|
2026-02-14 15:26:42 +01:00
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
|
|
|
import { Badge } from '@/components/ui/badge'
|
|
|
|
|
import {
|
|
|
|
|
Table,
|
|
|
|
|
TableBody,
|
|
|
|
|
TableCell,
|
|
|
|
|
TableHead,
|
|
|
|
|
TableHeader,
|
|
|
|
|
TableRow,
|
|
|
|
|
} from '@/components/ui/table'
|
|
|
|
|
import { AlertTriangle } from 'lucide-react'
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
import { nivoTheme, BRAND_DARK_BLUE, BRAND_RED } from './chart-theme'
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
interface JurorMetric {
|
|
|
|
|
userId: string
|
|
|
|
|
name: string
|
|
|
|
|
evaluationCount: number
|
|
|
|
|
averageScore: number
|
|
|
|
|
stddev: number
|
|
|
|
|
deviationFromOverall: number
|
|
|
|
|
isOutlier: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface JurorConsistencyProps {
|
|
|
|
|
data: {
|
|
|
|
|
overallAverage: number
|
|
|
|
|
jurors: JurorMetric[]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
interface JurorDatum extends ScatterPlotDatum {
|
|
|
|
|
x: number
|
|
|
|
|
y: number
|
|
|
|
|
name: string
|
|
|
|
|
evaluations: number
|
|
|
|
|
isOutlier: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function CustomNode({
|
|
|
|
|
node,
|
|
|
|
|
style,
|
|
|
|
|
blendMode,
|
|
|
|
|
isInteractive,
|
|
|
|
|
onMouseEnter,
|
|
|
|
|
onMouseMove,
|
|
|
|
|
onMouseLeave,
|
|
|
|
|
onClick,
|
|
|
|
|
}: ScatterPlotNodeProps<JurorDatum>) {
|
|
|
|
|
const fillColor = node.data.isOutlier ? BRAND_RED : BRAND_DARK_BLUE
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<animated.circle
|
|
|
|
|
cx={style.x}
|
|
|
|
|
cy={style.y}
|
|
|
|
|
r={style.size.to((s: number) => s / 2)}
|
|
|
|
|
fill={fillColor}
|
|
|
|
|
fillOpacity={0.7}
|
|
|
|
|
stroke={fillColor}
|
|
|
|
|
strokeWidth={1}
|
|
|
|
|
style={{ mixBlendMode: blendMode }}
|
|
|
|
|
onMouseEnter={
|
|
|
|
|
isInteractive && onMouseEnter
|
|
|
|
|
? (event) => onMouseEnter(node, event)
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
onMouseMove={
|
|
|
|
|
isInteractive && onMouseMove
|
|
|
|
|
? (event) => onMouseMove(node, event)
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
onMouseLeave={
|
|
|
|
|
isInteractive && onMouseLeave
|
|
|
|
|
? (event) => onMouseLeave(node, event)
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
onClick={
|
|
|
|
|
isInteractive && onClick
|
|
|
|
|
? (event) => onClick(node, event)
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 15:26:42 +01:00
|
|
|
export function JurorConsistencyChart({ data }: JurorConsistencyProps) {
|
2026-02-20 13:42:31 +01:00
|
|
|
if (!data?.jurors?.length) {
|
|
|
|
|
return (
|
|
|
|
|
<Card>
|
|
|
|
|
<CardContent className="flex items-center justify-center py-12">
|
|
|
|
|
<p className="text-muted-foreground">No juror consistency data available</p>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
const scatterData = [
|
|
|
|
|
{
|
|
|
|
|
id: 'Jurors',
|
|
|
|
|
data: data.jurors.map((j) => ({
|
|
|
|
|
x: parseFloat(j.averageScore.toFixed(2)),
|
|
|
|
|
y: parseFloat(j.stddev.toFixed(2)),
|
|
|
|
|
name: j.name,
|
|
|
|
|
evaluations: j.evaluationCount,
|
|
|
|
|
isOutlier: j.isOutlier,
|
|
|
|
|
})),
|
|
|
|
|
},
|
|
|
|
|
]
|
2026-02-14 15:26:42 +01:00
|
|
|
|
|
|
|
|
const outlierCount = data.jurors.filter((j) => j.isOutlier).length
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* Scatter: Average Score vs Standard Deviation */}
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle className="flex items-center justify-between">
|
|
|
|
|
<span>Juror Scoring Patterns</span>
|
|
|
|
|
<span className="text-sm font-normal text-muted-foreground">
|
|
|
|
|
Overall Avg: {data.overallAverage.toFixed(2)}
|
|
|
|
|
{outlierCount > 0 && (
|
|
|
|
|
<Badge variant="destructive" className="ml-2">
|
|
|
|
|
{outlierCount} outlier{outlierCount > 1 ? 's' : ''}
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
</CardTitle>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
<div style={{ height: '400px' }}>
|
|
|
|
|
<ResponsiveScatterPlot<JurorDatum>
|
|
|
|
|
data={scatterData}
|
|
|
|
|
theme={nivoTheme}
|
|
|
|
|
colors={[BRAND_DARK_BLUE]}
|
|
|
|
|
xScale={{ type: 'linear', min: 0, max: 10 }}
|
|
|
|
|
yScale={{ type: 'linear', min: 0, max: 'auto' }}
|
|
|
|
|
axisBottom={{
|
|
|
|
|
legend: 'Average Score',
|
|
|
|
|
legendPosition: 'middle',
|
|
|
|
|
legendOffset: 40,
|
|
|
|
|
}}
|
|
|
|
|
axisLeft={{
|
|
|
|
|
legend: 'Std Deviation',
|
|
|
|
|
legendPosition: 'middle',
|
|
|
|
|
legendOffset: -50,
|
|
|
|
|
}}
|
|
|
|
|
useMesh={true}
|
|
|
|
|
nodeSize={(node) =>
|
|
|
|
|
Math.max(8, Math.min(20, node.data.evaluations * 2))
|
|
|
|
|
}
|
|
|
|
|
nodeComponent={CustomNode}
|
|
|
|
|
margin={{ top: 20, right: 20, bottom: 60, left: 60 }}
|
|
|
|
|
tooltip={({ node }) => (
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
background: '#fff',
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
border: '1px solid #e5e7eb',
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
2026-02-14 15:26:42 +01:00
|
|
|
}}
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
>
|
|
|
|
|
<strong>{node.data.name}</strong>
|
|
|
|
|
<div>Avg Score: {node.data.x}</div>
|
|
|
|
|
<div>Std Dev: {node.data.y}</div>
|
|
|
|
|
<div>Evaluations: {node.data.evaluations}</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
markers={[
|
|
|
|
|
{
|
|
|
|
|
axis: 'x',
|
|
|
|
|
value: data.overallAverage,
|
|
|
|
|
lineStyle: {
|
|
|
|
|
stroke: BRAND_RED,
|
|
|
|
|
strokeWidth: 2,
|
|
|
|
|
strokeDasharray: '6 4',
|
|
|
|
|
},
|
|
|
|
|
legend: `Avg: ${data.overallAverage.toFixed(1)}`,
|
|
|
|
|
legendPosition: 'top',
|
|
|
|
|
},
|
|
|
|
|
]}
|
|
|
|
|
/>
|
2026-02-14 15:26:42 +01:00
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-muted-foreground mt-2 text-center">
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
Dot size represents number of evaluations. Red dots indicate outlier
|
|
|
|
|
jurors (2+ points from mean).
|
2026-02-14 15:26:42 +01:00
|
|
|
</p>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
{/* Juror details table */}
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Juror Consistency Details</CardTitle>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<Table>
|
|
|
|
|
<TableHeader>
|
|
|
|
|
<TableRow>
|
|
|
|
|
<TableHead>Juror</TableHead>
|
|
|
|
|
<TableHead className="text-right">Evaluations</TableHead>
|
|
|
|
|
<TableHead className="text-right">Avg Score</TableHead>
|
|
|
|
|
<TableHead className="text-right">Std Dev</TableHead>
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
<TableHead className="text-right">
|
|
|
|
|
Deviation from Mean
|
|
|
|
|
</TableHead>
|
2026-02-14 15:26:42 +01:00
|
|
|
<TableHead className="text-center">Status</TableHead>
|
|
|
|
|
</TableRow>
|
|
|
|
|
</TableHeader>
|
|
|
|
|
<TableBody>
|
|
|
|
|
{data.jurors.map((juror) => (
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
<TableRow
|
|
|
|
|
key={juror.userId}
|
|
|
|
|
className={juror.isOutlier ? 'bg-destructive/5' : ''}
|
|
|
|
|
>
|
2026-02-14 15:26:42 +01:00
|
|
|
<TableCell>
|
Observer platform overhaul: Nivo charts, round-type stats, UX improvements
Phase 1: Fix 6 backend data bugs in analytics.ts (roundName filtering,
unscored projects, criteria scores, activeRoundCount scoping, email
privacy leaks in juror consistency + workload)
Phase 2-3: Migrate all 9 chart components from Recharts to Nivo
(@nivo/bar, @nivo/line, @nivo/pie, @nivo/scatterplot) with shared brand
theme, scoreGradient colors, and STATUS_COLORS map. Fixes scatter plot
outlier coloring and pie chart label visibility bugs.
Phase 4: Add round-type-aware stats (getRoundTypeStats backend +
RoundTypeStatsCards component) showing appropriate metrics per round
type (intake/filtering/evaluation/submission/mentoring/live/deliberation).
Phase 5: UX improvements — Stage→Round terminology, clickable dashboard
round links, URL-based round selection (?round=), round type indicators
in selectors, accessible Toggle-based cross-round comparison, sortable
project table columns (title/score/evaluations), brand score colors on
dashboard bar chart with aria labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 21:44:38 +01:00
|
|
|
<p className="font-medium">{juror.name}</p>
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="text-right tabular-nums">
|
|
|
|
|
{juror.evaluationCount}
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="text-right tabular-nums">
|
|
|
|
|
{juror.averageScore.toFixed(2)}
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="text-right tabular-nums">
|
|
|
|
|
{juror.stddev.toFixed(2)}
|
2026-02-14 15:26:42 +01:00
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="text-right tabular-nums">
|
|
|
|
|
{juror.deviationFromOverall.toFixed(2)}
|
|
|
|
|
</TableCell>
|
|
|
|
|
<TableCell className="text-center">
|
|
|
|
|
{juror.isOutlier ? (
|
|
|
|
|
<Badge variant="destructive" className="gap-1">
|
|
|
|
|
<AlertTriangle className="h-3 w-3" />
|
|
|
|
|
Outlier
|
|
|
|
|
</Badge>
|
|
|
|
|
) : (
|
|
|
|
|
<Badge variant="secondary">Normal</Badge>
|
|
|
|
|
)}
|
|
|
|
|
</TableCell>
|
|
|
|
|
</TableRow>
|
|
|
|
|
))}
|
|
|
|
|
</TableBody>
|
|
|
|
|
</Table>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|