Avijit Ghosh commited on
Commit
a0f8d42
·
1 Parent(s): a71573c

added analytics

Browse files
app/analytics/page.tsx ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useState, useEffect } from "react"
4
+ import AnalyticsDashboard from "@/components/analytics-dashboard"
5
+ import { Button } from "@/components/ui/button"
6
+ import { Home, Info, Moon, Sun } from "lucide-react"
7
+ import Link from "next/link"
8
+ import { useTheme } from "next-themes"
9
+
10
+ interface Evaluation {
11
+ id: string
12
+ name: string
13
+ organization: string
14
+ overallScore: number
15
+ modality: string[]
16
+ submittedDate: string
17
+ categoryEvaluations?: {
18
+ [categoryId: string]: {
19
+ benchmarkAnswers?: { [questionId: string]: string }
20
+ processAnswers?: { [questionId: string]: string }
21
+ }
22
+ }
23
+ }
24
+
25
+ const loadEvaluationData = async (): Promise<Evaluation[]> => {
26
+ const evaluationFiles = [
27
+ "/evaluations/gpt-4-turbo.json",
28
+ "/evaluations/claude-3-sonnet.json",
29
+ "/evaluations/gemini-pro.json",
30
+ "/evaluations/fraud-detector.json",
31
+ ]
32
+
33
+ const additionalFiles = []
34
+ for (let i = 1; i <= 10; i++) {
35
+ additionalFiles.push(`/evaluations/eval-${Date.now() - i * 86400000}.json`)
36
+ }
37
+
38
+ const allFiles = [...evaluationFiles, ...additionalFiles]
39
+ const evaluations: Evaluation[] = []
40
+
41
+ for (const file of allFiles) {
42
+ try {
43
+ const response = await fetch(file)
44
+ if (!response.ok) continue
45
+
46
+ const data = await response.json()
47
+
48
+ // Calculate overall score based on completion percentage
49
+ let overallScore = 0
50
+ if (data.categoryEvaluations) {
51
+ const allCategories = Object.keys(data.categoryEvaluations)
52
+ const categoryScores = allCategories.map(categoryId => {
53
+ const categoryData = data.categoryEvaluations[categoryId]
54
+ if (!categoryData) return 0
55
+
56
+ // Count total questions and answered questions
57
+ const benchmarkAnswers = categoryData.benchmarkAnswers || {}
58
+ const processAnswers = categoryData.processAnswers || {}
59
+
60
+ const allAnswers = { ...benchmarkAnswers, ...processAnswers }
61
+ const totalQuestions = Object.keys(allAnswers).length
62
+ if (totalQuestions === 0) return 0
63
+
64
+ // Count answered questions (not N/A, null, undefined, or empty)
65
+ const answeredQuestions = Object.values(allAnswers).filter(
66
+ (answer: any) => answer && answer !== "N/A" && String(answer).trim() !== ""
67
+ ).length
68
+
69
+ return (answeredQuestions / totalQuestions) * 100
70
+ })
71
+
72
+ overallScore = categoryScores.length > 0
73
+ ? categoryScores.reduce((sum, score) => sum + score, 0) / categoryScores.length
74
+ : 0
75
+ }
76
+
77
+ const evaluation: Evaluation = {
78
+ id: data.id || `eval-${Date.now()}`,
79
+ name: data.systemName || "Unknown System",
80
+ organization: data.provider || "Unknown Provider",
81
+ overallScore,
82
+ modality: [...(data.inputModalities || ["Text"]), ...(data.outputModalities || ["Text"])],
83
+ submittedDate: data.evaluationDate || new Date().toISOString().split("T")[0],
84
+ categoryEvaluations: data.categoryEvaluations
85
+ }
86
+
87
+ evaluations.push(evaluation)
88
+ } catch (error) {
89
+ console.error(`Failed to load evaluation from ${file}:`, error)
90
+ }
91
+ }
92
+
93
+ return evaluations
94
+ }
95
+
96
+ export default function AnalyticsPage() {
97
+ const { theme, setTheme } = useTheme()
98
+ const [evaluations, setEvaluations] = useState<Evaluation[]>([])
99
+ const [loading, setLoading] = useState(true)
100
+
101
+ useEffect(() => {
102
+ const loadData = async () => {
103
+ try {
104
+ const data = await loadEvaluationData()
105
+ setEvaluations(data)
106
+ } catch (error) {
107
+ console.error("Failed to load evaluation data:", error)
108
+ } finally {
109
+ setLoading(false)
110
+ }
111
+ }
112
+ loadData()
113
+ }, [])
114
+
115
+ if (loading) {
116
+ return (
117
+ <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-950 dark:to-slate-900 flex items-center justify-center">
118
+ <div className="text-center">
119
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
120
+ <p className="text-muted-foreground">Loading analytics...</p>
121
+ </div>
122
+ </div>
123
+ )
124
+ }
125
+
126
+ return (
127
+ <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-950 dark:to-slate-900">
128
+ <header className="border-b bg-card/80 backdrop-blur-sm">
129
+ <div className="container mx-auto px-4 sm:px-6 py-4">
130
+ <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
131
+ <div>
132
+ <h1 className="text-xl sm:text-2xl font-bold font-heading text-foreground">Analytics & Leaderboards</h1>
133
+ <p className="text-sm text-muted-foreground">Explore evaluation completeness across models and categories</p>
134
+ </div>
135
+ <div className="flex items-center gap-2 sm:gap-3">
136
+ <Link href="/">
137
+ <Button variant="ghost" size="sm" className="gap-2">
138
+ <Home className="h-4 w-4" />
139
+ <span className="hidden sm:inline">Dashboard</span>
140
+ </Button>
141
+ </Link>
142
+ <Link href="/about">
143
+ <Button variant="ghost" size="sm" className="gap-2">
144
+ <Info className="h-4 w-4" />
145
+ <span className="hidden sm:inline">About</span>
146
+ </Button>
147
+ </Link>
148
+ <Button
149
+ variant="ghost"
150
+ size="sm"
151
+ onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
152
+ className="h-9 w-9 p-0"
153
+ >
154
+ <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
155
+ <Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
156
+ <span className="sr-only">Toggle theme</span>
157
+ </Button>
158
+ </div>
159
+ </div>
160
+ </div>
161
+ </header>
162
+
163
+ <div className="container mx-auto px-4 py-8">
164
+ <AnalyticsDashboard evaluations={evaluations} />
165
+ </div>
166
+ </div>
167
+ )
168
+ }
app/page.tsx CHANGED
@@ -495,6 +495,12 @@ export default function HomePage() {
495
  <p className="text-sm text-muted-foreground">Manage and track your AI system evaluations</p>
496
  </div>
497
  <div className="flex items-center gap-2 sm:gap-3">
 
 
 
 
 
 
498
  <Link href="/about">
499
  <Button variant="ghost" size="sm" className="gap-2">
500
  <Info className="h-4 w-4" />
 
495
  <p className="text-sm text-muted-foreground">Manage and track your AI system evaluations</p>
496
  </div>
497
  <div className="flex items-center gap-2 sm:gap-3">
498
+ <Link href="/analytics">
499
+ <Button variant="ghost" size="sm" className="gap-2">
500
+ <ArrowUpDown className="h-4 w-4" />
501
+ <span className="hidden sm:inline">Analytics</span>
502
+ </Button>
503
+ </Link>
504
  <Link href="/about">
505
  <Button variant="ghost" size="sm" className="gap-2">
506
  <Info className="h-4 w-4" />
components/analytics-dashboard.tsx ADDED
@@ -0,0 +1,869 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useState, useMemo } from "react"
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
5
+ import { Badge } from "@/components/ui/badge"
6
+ import { Progress } from "@/components/ui/progress"
7
+ import { Button } from "@/components/ui/button"
8
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
10
+ import { Checkbox } from "@/components/ui/checkbox"
11
+ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
12
+ import { Trophy, Target, TrendingUp, BarChart3, Grid3X3, List, Filter, Brain, Shield, CheckCircle, ChevronDown } from "lucide-react"
13
+
14
+ // Category mappings from schema
15
+ const CATEGORY_NAMES: { [key: string]: string } = {
16
+ 'language-communication': 'Language & Communication',
17
+ 'social-intelligence': 'Social Intelligence & Interaction',
18
+ 'problem-solving': 'Problem Solving',
19
+ 'creativity-innovation': 'Creativity & Innovation',
20
+ 'learning-memory': 'Learning & Memory',
21
+ 'perception-vision': 'Perception & Vision',
22
+ 'physical-manipulation': 'Physical Manipulation & Motor Skills',
23
+ 'metacognition': 'Metacognition & Self-Awareness',
24
+ 'robotic-intelligence': 'Robotic Intelligence & Autonomy',
25
+ 'harmful-content': 'Harmful Content Generation',
26
+ 'information-integrity': 'Information Integrity & Misinformation',
27
+ 'privacy-data': 'Privacy & Data Protection',
28
+ 'bias-fairness': 'Bias & Fairness',
29
+ 'security-robustness': 'Security & Robustness',
30
+ 'dangerous-capabilities': 'Dangerous Capabilities & Misuse',
31
+ 'human-ai-interaction': 'Human-AI Interaction Risks',
32
+ 'environmental-impact': 'Environmental & Resource Impact',
33
+ 'economic-displacement': 'Economic & Labor Displacement',
34
+ 'governance-accountability': 'Governance & Accountability',
35
+ 'value-chain': 'Value Chain & Supply Chain Risks'
36
+ }
37
+
38
+ const CAPABILITY_CATEGORIES = [
39
+ 'language-communication',
40
+ 'social-intelligence',
41
+ 'problem-solving',
42
+ 'creativity-innovation',
43
+ 'learning-memory',
44
+ 'perception-vision',
45
+ 'physical-manipulation',
46
+ 'metacognition',
47
+ 'robotic-intelligence'
48
+ ]
49
+
50
+ const RISK_CATEGORIES = [
51
+ 'harmful-content',
52
+ 'information-integrity',
53
+ 'privacy-data',
54
+ 'bias-fairness',
55
+ 'security-robustness',
56
+ 'dangerous-capabilities',
57
+ 'human-ai-interaction',
58
+ 'environmental-impact',
59
+ 'economic-displacement',
60
+ 'governance-accountability',
61
+ 'value-chain'
62
+ ]
63
+
64
+ const MODALITIES = [
65
+ "Text", "Vision", "Audio", "Video", "Code", "Robotics/Action", "Other"
66
+ ]
67
+
68
+ interface Evaluation {
69
+ id: string
70
+ name: string
71
+ organization: string
72
+ overallScore: number
73
+ modality: string[]
74
+ submittedDate: string
75
+ categoryEvaluations?: {
76
+ [categoryId: string]: {
77
+ benchmarkAnswers?: { [questionId: string]: string }
78
+ processAnswers?: { [questionId: string]: string }
79
+ }
80
+ }
81
+ }
82
+
83
+ interface AnalyticsDashboardProps {
84
+ evaluations?: Evaluation[]
85
+ }
86
+
87
+ export default function AnalyticsDashboard({ evaluations = [] }: AnalyticsDashboardProps) {
88
+ const [loading, setLoading] = useState(false)
89
+
90
+ // Filter states
91
+ const [modalityFilter, setModalityFilter] = useState("all")
92
+ const [organizationFilter, setOrganizationFilter] = useState("all")
93
+ const [sortBy, setSortBy] = useState<"score" | "date" | "name">("score")
94
+ const [viewMode, setViewMode] = useState<"grid" | "list">("list")
95
+
96
+ // Category selection state
97
+ const [selectedCategories, setSelectedCategories] = useState<string[]>(Object.keys(CATEGORY_NAMES))
98
+
99
+ // Get unique organizations
100
+ const ORGANIZATIONS = useMemo(() =>
101
+ Array.from(new Set(evaluations.map(e => e.organization)))
102
+ , [evaluations])
103
+
104
+ // Calculate scores dynamically based on selected categories
105
+ const recalculatedEvaluations = useMemo(() => {
106
+ return evaluations.map(evaluation => {
107
+ if (!evaluation.categoryEvaluations || selectedCategories.length === 0) {
108
+ return { ...evaluation, overallScore: 0 }
109
+ }
110
+
111
+ const scores = selectedCategories.map(categoryId => {
112
+ const categoryData = evaluation.categoryEvaluations?.[categoryId]
113
+ if (!categoryData) return 0
114
+
115
+ // Count total questions and answered questions
116
+ const benchmarkAnswers = categoryData.benchmarkAnswers || {}
117
+ const processAnswers = categoryData.processAnswers || {}
118
+
119
+ const allAnswers = { ...benchmarkAnswers, ...processAnswers }
120
+ const totalQuestions = Object.keys(allAnswers).length
121
+ if (totalQuestions === 0) return 0
122
+
123
+ // Count answered questions (not N/A, null, undefined, or empty)
124
+ const answeredQuestions = Object.values(allAnswers).filter(
125
+ (answer: any) => answer && answer !== "N/A" && String(answer).trim() !== ""
126
+ ).length
127
+
128
+ return (answeredQuestions / totalQuestions) * 100
129
+ })
130
+
131
+ const overallScore = scores.length > 0
132
+ ? scores.reduce((sum, score) => sum + score, 0) / scores.length
133
+ : 0
134
+
135
+ return { ...evaluation, overallScore }
136
+ })
137
+ }, [evaluations, selectedCategories])
138
+
139
+ // Apply filters
140
+ const filteredEvaluations = useMemo(() => {
141
+ return recalculatedEvaluations
142
+ .filter(evaluation => {
143
+ const matchesModality = modalityFilter === "all" || evaluation.modality.includes(modalityFilter)
144
+ const matchesOrganization = organizationFilter === "all" || evaluation.organization === organizationFilter
145
+ return matchesModality && matchesOrganization
146
+ })
147
+ .sort((a, b) => {
148
+ switch (sortBy) {
149
+ case "score":
150
+ return b.overallScore - a.overallScore
151
+ case "date":
152
+ return new Date(b.submittedDate).getTime() - new Date(a.submittedDate).getTime()
153
+ case "name":
154
+ return a.name.localeCompare(b.name)
155
+ default:
156
+ return 0
157
+ }
158
+ })
159
+ }, [recalculatedEvaluations, modalityFilter, organizationFilter, sortBy])
160
+
161
+ // Calculate category data based on selected categories
162
+ const categoryData = useMemo(() => {
163
+ return selectedCategories.map(categoryId => {
164
+ const scores = evaluations.map(evaluation => {
165
+ const categoryData = evaluation.categoryEvaluations?.[categoryId]
166
+ if (!categoryData) return 0
167
+
168
+ // Count total questions and answered questions
169
+ const benchmarkAnswers = categoryData.benchmarkAnswers || {}
170
+ const processAnswers = categoryData.processAnswers || {}
171
+
172
+ const allAnswers = { ...benchmarkAnswers, ...processAnswers }
173
+ const totalQuestions = Object.keys(allAnswers).length
174
+ if (totalQuestions === 0) return 0
175
+
176
+ // Count answered questions (not N/A, null, undefined, or empty)
177
+ const answeredQuestions = Object.values(allAnswers).filter(
178
+ (answer: any) => answer && answer !== "N/A" && String(answer).trim() !== ""
179
+ ).length
180
+
181
+ return (answeredQuestions / totalQuestions) * 100
182
+ })
183
+
184
+ const averageScore = scores.length > 0
185
+ ? scores.reduce((sum, score) => sum + score, 0) / scores.length
186
+ : 0
187
+
188
+ return {
189
+ id: categoryId,
190
+ name: CATEGORY_NAMES[categoryId],
191
+ type: CAPABILITY_CATEGORIES.includes(categoryId) ? "capability" : "risk",
192
+ averageScore,
193
+ evaluationCount: evaluations.length
194
+ }
195
+ })
196
+ }, [evaluations, selectedCategories])
197
+
198
+ const getScoreBadgeVariant = (score: number) => {
199
+ if (score >= 80) return "default"
200
+ if (score >= 60) return "secondary"
201
+ if (score >= 40) return "outline"
202
+ return "destructive"
203
+ }
204
+
205
+ const getScoreColor = (score: number) => {
206
+ if (score >= 80) return "text-green-600 dark:text-green-400"
207
+ if (score >= 60) return "text-blue-600 dark:text-blue-400"
208
+ if (score >= 40) return "text-yellow-600 dark:text-yellow-400"
209
+ return "text-red-600 dark:text-red-400"
210
+ }
211
+
212
+ const handleCategoryToggle = (categoryId: string) => {
213
+ setSelectedCategories(prev =>
214
+ prev.includes(categoryId)
215
+ ? prev.filter(id => id !== categoryId)
216
+ : [...prev, categoryId]
217
+ )
218
+ }
219
+
220
+ const selectAllCategories = () => {
221
+ setSelectedCategories(Object.keys(CATEGORY_NAMES))
222
+ }
223
+
224
+ const selectOnlyCapabilities = () => {
225
+ setSelectedCategories(CAPABILITY_CATEGORIES)
226
+ }
227
+
228
+ const selectOnlyRisks = () => {
229
+ setSelectedCategories(RISK_CATEGORIES)
230
+ }
231
+
232
+ const clearAllCategories = () => {
233
+ setSelectedCategories([])
234
+ }
235
+
236
+ if (loading) {
237
+ return (
238
+ <div className="flex items-center justify-center h-64">
239
+ <div className="text-center">
240
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
241
+ <p className="text-muted-foreground">Loading analytics...</p>
242
+ </div>
243
+ </div>
244
+ )
245
+ }
246
+
247
+ return (
248
+ <div className="space-y-8">
249
+ {/* Overview Stats */}
250
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
251
+ <Card>
252
+ <CardContent className="p-6">
253
+ <div className="flex items-center space-x-2">
254
+ <Trophy className="h-5 w-5 text-primary" />
255
+ <div>
256
+ <p className="text-2xl font-bold">{evaluations.length}</p>
257
+ <p className="text-sm text-muted-foreground">Total Evaluations</p>
258
+ </div>
259
+ </div>
260
+ </CardContent>
261
+ </Card>
262
+
263
+ <Card>
264
+ <CardContent className="p-6">
265
+ <div className="flex items-center space-x-2">
266
+ <Target className="h-5 w-5 text-primary" />
267
+ <div>
268
+ <p className="text-2xl font-bold">
269
+ {filteredEvaluations.length > 0 ?
270
+ Math.round(filteredEvaluations.reduce((sum, evaluation) => sum + evaluation.overallScore, 0) / filteredEvaluations.length) : 0}%
271
+ </p>
272
+ <p className="text-sm text-muted-foreground">Average Score</p>
273
+ </div>
274
+ </div>
275
+ </CardContent>
276
+ </Card>
277
+
278
+ <Card>
279
+ <CardContent className="p-6">
280
+ <div className="flex items-center space-x-2">
281
+ <TrendingUp className="h-5 w-5 text-primary" />
282
+ <div>
283
+ <p className="text-2xl font-bold">
284
+ {filteredEvaluations.length > 0 ? Math.round(Math.max(...filteredEvaluations.map(e => e.overallScore))) : 0}%
285
+ </p>
286
+ <p className="text-sm text-muted-foreground">Highest Score</p>
287
+ </div>
288
+ </div>
289
+ </CardContent>
290
+ </Card>
291
+
292
+ <Card>
293
+ <CardContent className="p-6">
294
+ <div className="flex items-center space-x-2">
295
+ <BarChart3 className="h-5 w-5 text-primary" />
296
+ <div>
297
+ <p className="text-2xl font-bold">{selectedCategories.length}/{Object.keys(CATEGORY_NAMES).length}</p>
298
+ <p className="text-sm text-muted-foreground">Selected Categories</p>
299
+ </div>
300
+ </div>
301
+ </CardContent>
302
+ </Card>
303
+ </div>
304
+
305
+ <Tabs defaultValue="overall" className="space-y-6">
306
+ <TabsList className="grid w-full grid-cols-2">
307
+ <TabsTrigger value="overall">Overall Leaderboard</TabsTrigger>
308
+ <TabsTrigger value="insights">Insights</TabsTrigger>
309
+ </TabsList>
310
+
311
+ <TabsContent value="overall" className="space-y-6">
312
+ {/* Filters */}
313
+ <Card>
314
+ <CardHeader>
315
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
316
+ <div>
317
+ <CardTitle className="flex items-center gap-2">
318
+ <Filter className="h-5 w-5" />
319
+ Filters & View
320
+ </CardTitle>
321
+ <CardDescription>Filter and sort evaluation results</CardDescription>
322
+ </div>
323
+ <div className="flex items-center gap-2">
324
+ <Button
325
+ variant={viewMode === "grid" ? "default" : "outline"}
326
+ size="sm"
327
+ onClick={() => setViewMode("grid")}
328
+ >
329
+ <Grid3X3 className="h-4 w-4" />
330
+ </Button>
331
+ <Button
332
+ variant={viewMode === "list" ? "default" : "outline"}
333
+ size="sm"
334
+ onClick={() => setViewMode("list")}
335
+ >
336
+ <List className="h-4 w-4" />
337
+ </Button>
338
+ </div>
339
+ </div>
340
+ </CardHeader>
341
+ <CardContent>
342
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
343
+ {/* Category Selection Dropdown */}
344
+ <div>
345
+ <label className="text-sm font-medium mb-2 block">Categories ({selectedCategories.length} selected)</label>
346
+ <Popover>
347
+ <PopoverTrigger asChild>
348
+ <Button variant="outline" className="w-full justify-between">
349
+ Select Categories
350
+ <ChevronDown className="h-4 w-4 opacity-50" />
351
+ </Button>
352
+ </PopoverTrigger>
353
+ <PopoverContent className="w-80 p-0" align="start">
354
+ <div className="p-4 space-y-4">
355
+ {/* Quick Actions */}
356
+ <div className="flex flex-wrap gap-2">
357
+ <Button size="sm" variant="outline" onClick={selectAllCategories}>
358
+ All
359
+ </Button>
360
+ <Button size="sm" variant="outline" onClick={selectOnlyCapabilities}>
361
+ <Brain className="h-3 w-3 mr-1" />
362
+ Capabilities
363
+ </Button>
364
+ <Button size="sm" variant="outline" onClick={selectOnlyRisks}>
365
+ <Shield className="h-3 w-3 mr-1" />
366
+ Risks
367
+ </Button>
368
+ <Button size="sm" variant="outline" onClick={clearAllCategories}>
369
+ Clear
370
+ </Button>
371
+ </div>
372
+
373
+ {/* Capabilities */}
374
+ <div className="space-y-2">
375
+ <h4 className="font-semibold text-sm flex items-center gap-2">
376
+ <Brain className="h-4 w-4 text-blue-500" />
377
+ Capabilities
378
+ </h4>
379
+ <div className="space-y-1 max-h-32 overflow-y-auto">
380
+ {CAPABILITY_CATEGORIES.map(categoryId => (
381
+ <div key={categoryId} className="flex items-center space-x-2">
382
+ <Checkbox
383
+ id={`cap-${categoryId}`}
384
+ checked={selectedCategories.includes(categoryId)}
385
+ onCheckedChange={() => handleCategoryToggle(categoryId)}
386
+ />
387
+ <label htmlFor={`cap-${categoryId}`} className="text-xs cursor-pointer">
388
+ {CATEGORY_NAMES[categoryId]}
389
+ </label>
390
+ </div>
391
+ ))}
392
+ </div>
393
+ </div>
394
+
395
+ {/* Risks */}
396
+ <div className="space-y-2">
397
+ <h4 className="font-semibold text-sm flex items-center gap-2">
398
+ <Shield className="h-4 w-4 text-red-500" />
399
+ Risks
400
+ </h4>
401
+ <div className="space-y-1 max-h-40 overflow-y-auto">
402
+ {RISK_CATEGORIES.map(categoryId => (
403
+ <div key={categoryId} className="flex items-center space-x-2">
404
+ <Checkbox
405
+ id={`risk-${categoryId}`}
406
+ checked={selectedCategories.includes(categoryId)}
407
+ onCheckedChange={() => handleCategoryToggle(categoryId)}
408
+ />
409
+ <label htmlFor={`risk-${categoryId}`} className="text-xs cursor-pointer">
410
+ {CATEGORY_NAMES[categoryId]}
411
+ </label>
412
+ </div>
413
+ ))}
414
+ </div>
415
+ </div>
416
+ </div>
417
+ </PopoverContent>
418
+ </Popover>
419
+ </div>
420
+
421
+ <div>
422
+ <label className="text-sm font-medium mb-2 block">Modality</label>
423
+ <Select value={modalityFilter} onValueChange={setModalityFilter}>
424
+ <SelectTrigger>
425
+ <SelectValue />
426
+ </SelectTrigger>
427
+ <SelectContent>
428
+ <SelectItem value="all">All Modalities</SelectItem>
429
+ {MODALITIES.map(modality => (
430
+ <SelectItem key={modality} value={modality}>{modality}</SelectItem>
431
+ ))}
432
+ </SelectContent>
433
+ </Select>
434
+ </div>
435
+
436
+ <div>
437
+ <label className="text-sm font-medium mb-2 block">Organization</label>
438
+ <Select value={organizationFilter} onValueChange={setOrganizationFilter}>
439
+ <SelectTrigger>
440
+ <SelectValue />
441
+ </SelectTrigger>
442
+ <SelectContent>
443
+ <SelectItem value="all">All Organizations</SelectItem>
444
+ {ORGANIZATIONS.map(org => (
445
+ <SelectItem key={org} value={org}>{org}</SelectItem>
446
+ ))}
447
+ </SelectContent>
448
+ </Select>
449
+ </div>
450
+
451
+ <div>
452
+ <label className="text-sm font-medium mb-2 block">Sort By</label>
453
+ <Select value={sortBy} onValueChange={(value: "score" | "date" | "name") => setSortBy(value)}>
454
+ <SelectTrigger>
455
+ <SelectValue />
456
+ </SelectTrigger>
457
+ <SelectContent>
458
+ <SelectItem value="score">Completeness Score</SelectItem>
459
+ <SelectItem value="date">Submit Date</SelectItem>
460
+ <SelectItem value="name">Name</SelectItem>
461
+ </SelectContent>
462
+ </Select>
463
+ </div>
464
+
465
+ <div className="flex items-end">
466
+ <Button
467
+ variant="outline"
468
+ onClick={() => {
469
+ setModalityFilter("all")
470
+ setOrganizationFilter("all")
471
+ setSortBy("score")
472
+ }}
473
+ className="w-full"
474
+ >
475
+ Reset Filters
476
+ </Button>
477
+ </div>
478
+ </div>
479
+ </CardContent>
480
+ </Card>
481
+
482
+ {/* Overall Leaderboard */}
483
+ <Card>
484
+ <CardHeader>
485
+ <CardTitle>Overall Completeness Leaderboard</CardTitle>
486
+ <CardDescription>
487
+ Ranked by evaluation completeness score ({filteredEvaluations.length} {filteredEvaluations.length === 1 ? 'evaluation' : 'evaluations'})
488
+ </CardDescription>
489
+ </CardHeader>
490
+ <CardContent>
491
+ {viewMode === "grid" ? (
492
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
493
+ {filteredEvaluations.map((evaluation, index) => (
494
+ <Card key={evaluation.id} className="relative">
495
+ <CardContent className="p-6">
496
+ {index < 3 && (
497
+ <div className="absolute -top-2 -right-2">
498
+ <Badge variant={index === 0 ? "default" : "secondary"} className="rounded-full px-3 py-1">
499
+ #{index + 1}
500
+ </Badge>
501
+ </div>
502
+ )}
503
+
504
+ <div className="space-y-4">
505
+ <div>
506
+ <h3 className="font-semibold text-lg">{evaluation.name}</h3>
507
+ <p className="text-sm text-muted-foreground">{evaluation.organization}</p>
508
+ </div>
509
+
510
+ <div className="space-y-2">
511
+ <div className="flex items-center justify-between">
512
+ <span className="text-sm font-medium">Completeness Score</span>
513
+ <Badge variant={getScoreBadgeVariant(evaluation.overallScore)}>
514
+ {Math.round(evaluation.overallScore)}%
515
+ </Badge>
516
+ </div>
517
+ <Progress value={evaluation.overallScore} className="h-2" />
518
+ </div>
519
+
520
+ <div className="flex flex-wrap gap-1">
521
+ {evaluation.modality.map(mod => (
522
+ <Badge key={mod} variant="outline" className="text-xs">
523
+ {mod}
524
+ </Badge>
525
+ ))}
526
+ </div>
527
+
528
+ <div className="text-xs text-muted-foreground">
529
+ Submitted: {new Date(evaluation.submittedDate).toLocaleDateString()}
530
+ </div>
531
+ </div>
532
+ </CardContent>
533
+ </Card>
534
+ ))}
535
+ </div>
536
+ ) : (
537
+ <div className="space-y-3">
538
+ {filteredEvaluations.map((evaluation, index) => (
539
+ <div key={evaluation.id} className="group hover:bg-muted/30 transition-colors rounded-lg p-3">
540
+ <div className="flex items-center gap-4">
541
+ {/* Organization Logo/Icon */}
542
+ <div className="flex items-center gap-3 min-w-[200px]">
543
+ <div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-bold text-primary">
544
+ {evaluation.organization.charAt(0)}
545
+ </div>
546
+ <div className="text-left">
547
+ <div className="font-medium text-sm">{evaluation.organization}</div>
548
+ <div className="font-semibold">{evaluation.name}</div>
549
+ </div>
550
+ </div>
551
+
552
+ {/* Horizontal Bar Chart */}
553
+ <div className="flex-1 relative">
554
+ <div className="flex items-center gap-3">
555
+ {/* Progress Bar Container */}
556
+ <div className="flex-1 relative h-6 bg-muted rounded-full overflow-hidden">
557
+ {/* Background bar */}
558
+ <div className="absolute inset-0 bg-gray-200 dark:bg-gray-700"></div>
559
+ {/* Progress bar */}
560
+ <div
561
+ className={`absolute left-0 top-0 h-full transition-all duration-500 ease-out ${
562
+ evaluation.overallScore >= 80 ? 'bg-purple-600' :
563
+ evaluation.overallScore >= 60 ? 'bg-purple-500' :
564
+ evaluation.overallScore >= 40 ? 'bg-purple-400' :
565
+ 'bg-purple-300'
566
+ }`}
567
+ style={{
568
+ width: `${Math.max(evaluation.overallScore, 5)}%`,
569
+ animationDelay: `${index * 100}ms`
570
+ }}
571
+ ></div>
572
+ </div>
573
+
574
+ {/* Score */}
575
+ <div className="min-w-[50px] text-right">
576
+ <span className="text-lg font-bold">
577
+ {Math.round(evaluation.overallScore)}%
578
+ </span>
579
+ </div>
580
+ </div>
581
+
582
+ {/* Modality badges - shown on hover */}
583
+ <div className="flex flex-wrap gap-1 mt-2 opacity-0 group-hover:opacity-100 transition-opacity">
584
+ {evaluation.modality.slice(0, 3).map(mod => (
585
+ <Badge key={mod} variant="outline" className="text-xs">
586
+ {mod}
587
+ </Badge>
588
+ ))}
589
+ {evaluation.modality.length > 3 && (
590
+ <Badge variant="outline" className="text-xs">
591
+ +{evaluation.modality.length - 3}
592
+ </Badge>
593
+ )}
594
+ </div>
595
+ </div>
596
+ </div>
597
+ </div>
598
+ ))}
599
+ </div>
600
+ )}
601
+ </CardContent>
602
+ </Card>
603
+ </TabsContent>
604
+
605
+ <TabsContent value="insights" className="space-y-6">
606
+ {/* Key Insights */}
607
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
608
+ <Card>
609
+ <CardHeader>
610
+ <CardTitle>Top Performing Categories</CardTitle>
611
+ <CardDescription>Categories with highest average completeness from selected categories</CardDescription>
612
+ </CardHeader>
613
+ <CardContent>
614
+ <div className="space-y-3">
615
+ {categoryData
616
+ .sort((a, b) => b.averageScore - a.averageScore)
617
+ .slice(0, 5)
618
+ .map((category, index) => (
619
+ <div key={category.id} className="flex items-center justify-between">
620
+ <div className="flex items-center space-x-3">
621
+ <Badge variant="outline" className="w-8 h-8 rounded-full flex items-center justify-center p-0">
622
+ {index + 1}
623
+ </Badge>
624
+ <div>
625
+ <p className="font-medium text-sm">{category.name}</p>
626
+ <Badge variant={category.type === "capability" ? "secondary" : "destructive"} className="text-xs">
627
+ {category.type}
628
+ </Badge>
629
+ </div>
630
+ </div>
631
+ <div className="text-right">
632
+ <p className={`font-bold ${getScoreColor(category.averageScore)}`}>
633
+ {Math.round(category.averageScore)}%
634
+ </p>
635
+ </div>
636
+ </div>
637
+ ))}
638
+ {categoryData.length === 0 && (
639
+ <p className="text-muted-foreground text-center py-4">
640
+ No categories selected. Please select categories from the filters above.
641
+ </p>
642
+ )}
643
+ </div>
644
+ </CardContent>
645
+ </Card>
646
+
647
+ <Card>
648
+ <CardHeader>
649
+ <CardTitle>Areas for Improvement</CardTitle>
650
+ <CardDescription>Categories with lowest average completeness from selected categories</CardDescription>
651
+ </CardHeader>
652
+ <CardContent>
653
+ <div className="space-y-3">
654
+ {categoryData
655
+ .sort((a, b) => a.averageScore - b.averageScore)
656
+ .slice(0, 5)
657
+ .map((category, index) => (
658
+ <div key={category.id} className="flex items-center justify-between">
659
+ <div className="flex items-center space-x-3">
660
+ <Badge variant="outline" className="w-8 h-8 rounded-full flex items-center justify-center p-0">
661
+ {index + 1}
662
+ </Badge>
663
+ <div>
664
+ <p className="font-medium text-sm">{category.name}</p>
665
+ <Badge variant={category.type === "capability" ? "secondary" : "destructive"} className="text-xs">
666
+ {category.type}
667
+ </Badge>
668
+ </div>
669
+ </div>
670
+ <div className="text-right">
671
+ <p className={`font-bold ${getScoreColor(category.averageScore)}`}>
672
+ {Math.round(category.averageScore)}%
673
+ </p>
674
+ </div>
675
+ </div>
676
+ ))}
677
+ {categoryData.length === 0 && (
678
+ <p className="text-muted-foreground text-center py-4">
679
+ No categories selected. Please select categories from the filters above.
680
+ </p>
681
+ )}
682
+ </div>
683
+ </CardContent>
684
+ </Card>
685
+ </div>
686
+
687
+ {/* Category Performance Breakdown */}
688
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
689
+ {/* Capabilities */}
690
+ <Card>
691
+ <CardHeader>
692
+ <CardTitle className="flex items-center gap-2">
693
+ <Brain className="h-5 w-5 text-blue-500" />
694
+ Capability Categories
695
+ </CardTitle>
696
+ <CardDescription>Average completeness by capability category</CardDescription>
697
+ </CardHeader>
698
+ <CardContent>
699
+ <div className="space-y-4">
700
+ {categoryData
701
+ .filter(cat => cat.type === "capability")
702
+ .sort((a, b) => b.averageScore - a.averageScore)
703
+ .map(category => (
704
+ <div key={category.id} className="space-y-2">
705
+ <div className="flex items-center justify-between text-sm">
706
+ <span className="font-medium truncate pr-2">{category.name}</span>
707
+ <Badge variant={getScoreBadgeVariant(category.averageScore)}>
708
+ {Math.round(category.averageScore)}%
709
+ </Badge>
710
+ </div>
711
+ <Progress value={category.averageScore} className="h-2" />
712
+ <div className="text-xs text-muted-foreground">
713
+ {category.evaluationCount} evaluation{category.evaluationCount !== 1 ? 's' : ''}
714
+ </div>
715
+ </div>
716
+ ))}
717
+ {categoryData.filter(cat => cat.type === "capability").length === 0 && (
718
+ <p className="text-muted-foreground text-center py-4">
719
+ No capability categories selected.
720
+ </p>
721
+ )}
722
+ </div>
723
+ </CardContent>
724
+ </Card>
725
+
726
+ {/* Risks */}
727
+ <Card>
728
+ <CardHeader>
729
+ <CardTitle className="flex items-center gap-2">
730
+ <Shield className="h-5 w-5 text-red-500" />
731
+ Risk Categories
732
+ </CardTitle>
733
+ <CardDescription>Average completeness by risk category</CardDescription>
734
+ </CardHeader>
735
+ <CardContent>
736
+ <div className="space-y-4">
737
+ {categoryData
738
+ .filter(cat => cat.type === "risk")
739
+ .sort((a, b) => b.averageScore - a.averageScore)
740
+ .map(category => (
741
+ <div key={category.id} className="space-y-2">
742
+ <div className="flex items-center justify-between text-sm">
743
+ <span className="font-medium truncate pr-2">{category.name}</span>
744
+ <Badge variant={getScoreBadgeVariant(category.averageScore)}>
745
+ {Math.round(category.averageScore)}%
746
+ </Badge>
747
+ </div>
748
+ <Progress value={category.averageScore} className="h-2" />
749
+ <div className="text-xs text-muted-foreground">
750
+ {category.evaluationCount} evaluation{category.evaluationCount !== 1 ? 's' : ''}
751
+ </div>
752
+ </div>
753
+ ))}
754
+ {categoryData.filter(cat => cat.type === "risk").length === 0 && (
755
+ <p className="text-muted-foreground text-center py-4">
756
+ No risk categories selected.
757
+ </p>
758
+ )}
759
+ </div>
760
+ </CardContent>
761
+ </Card>
762
+ </div>
763
+
764
+ {/* Distribution Analysis */}
765
+ <Card>
766
+ <CardHeader>
767
+ <CardTitle>Score Distribution</CardTitle>
768
+ <CardDescription>Breakdown of evaluation completeness scores based on current filters and selected categories</CardDescription>
769
+ </CardHeader>
770
+ <CardContent>
771
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
772
+ {[
773
+ { range: "80-100%", color: "bg-green-500", count: filteredEvaluations.filter(e => e.overallScore >= 80).length },
774
+ { range: "60-79%", color: "bg-blue-500", count: filteredEvaluations.filter(e => e.overallScore >= 60 && e.overallScore < 80).length },
775
+ { range: "40-59%", color: "bg-yellow-500", count: filteredEvaluations.filter(e => e.overallScore >= 40 && e.overallScore < 60).length },
776
+ { range: "0-39%", color: "bg-red-500", count: filteredEvaluations.filter(e => e.overallScore < 40).length }
777
+ ].map(item => (
778
+ <div key={item.range} className="text-center space-y-2">
779
+ <div className={`${item.color} rounded-full w-16 h-16 flex items-center justify-center text-white font-bold text-xl mx-auto`}>
780
+ {item.count}
781
+ </div>
782
+ <p className="text-sm font-medium">{item.range}</p>
783
+ <p className="text-xs text-muted-foreground">
784
+ {filteredEvaluations.length > 0 ? Math.round((item.count / filteredEvaluations.length) * 100) : 0}%
785
+ </p>
786
+ </div>
787
+ ))}
788
+ </div>
789
+ </CardContent>
790
+ </Card>
791
+
792
+ {/* Additional Insights */}
793
+ {evaluations.length > 0 && (
794
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
795
+ <Card>
796
+ <CardHeader>
797
+ <CardTitle>Most Common Modalities</CardTitle>
798
+ <CardDescription>Frequently evaluated modality combinations</CardDescription>
799
+ </CardHeader>
800
+ <CardContent>
801
+ <div className="space-y-2">
802
+ {Array.from(new Set(evaluations.flatMap(e => e.modality)))
803
+ .map(modality => ({
804
+ modality,
805
+ count: evaluations.filter(e => e.modality.includes(modality)).length
806
+ }))
807
+ .sort((a, b) => b.count - a.count)
808
+ .slice(0, 5)
809
+ .map(item => (
810
+ <div key={item.modality} className="flex justify-between items-center">
811
+ <Badge variant="outline">{item.modality}</Badge>
812
+ <span className="text-sm font-medium">{item.count} evaluation{item.count !== 1 ? 's' : ''}</span>
813
+ </div>
814
+ ))}
815
+ </div>
816
+ </CardContent>
817
+ </Card>
818
+
819
+ <Card>
820
+ <CardHeader>
821
+ <CardTitle>Organizations</CardTitle>
822
+ <CardDescription>Number of evaluations per organization</CardDescription>
823
+ </CardHeader>
824
+ <CardContent>
825
+ <div className="space-y-2">
826
+ {ORGANIZATIONS
827
+ .map(org => ({
828
+ organization: org,
829
+ count: evaluations.filter(e => e.organization === org).length
830
+ }))
831
+ .sort((a, b) => b.count - a.count)
832
+ .map(item => (
833
+ <div key={item.organization} className="flex justify-between items-center">
834
+ <span className="text-sm font-medium">{item.organization}</span>
835
+ <Badge variant="secondary">{item.count}</Badge>
836
+ </div>
837
+ ))}
838
+ </div>
839
+ </CardContent>
840
+ </Card>
841
+
842
+ <Card>
843
+ <CardHeader>
844
+ <CardTitle>Evaluation Timeline</CardTitle>
845
+ <CardDescription>Recent evaluation activity</CardDescription>
846
+ </CardHeader>
847
+ <CardContent>
848
+ <div className="space-y-2">
849
+ {evaluations
850
+ .sort((a, b) => new Date(b.submittedDate).getTime() - new Date(a.submittedDate).getTime())
851
+ .slice(0, 5)
852
+ .map(evaluation => (
853
+ <div key={evaluation.id} className="flex justify-between items-center text-sm">
854
+ <span className="font-medium">{evaluation.name}</span>
855
+ <span className="text-muted-foreground">
856
+ {new Date(evaluation.submittedDate).toLocaleDateString()}
857
+ </span>
858
+ </div>
859
+ ))}
860
+ </div>
861
+ </CardContent>
862
+ </Card>
863
+ </div>
864
+ )}
865
+ </TabsContent>
866
+ </Tabs>
867
+ </div>
868
+ )
869
+ }