cyhuang-tw commited on
Commit
c0930b6
·
verified ·
1 Parent(s): 6ad9359

Create checker.html

Browse files
Files changed (1) hide show
  1. checker.html +612 -0
checker.html ADDED
@@ -0,0 +1,612 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Checker-V1.1: Dynamic-SUPERB Validator</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ margin: 0;
11
+ padding: 20px;
12
+ background: linear-gradient(120deg, #f8fafc 0%, #e9ecef 100%);
13
+ color: #212529;
14
+ }
15
+
16
+ /* Menu Styles */
17
+ .menu-container {
18
+ position: fixed;
19
+ top: 15px;
20
+ left: 15px;
21
+ z-index: 1000;
22
+ }
23
+
24
+ .menu-icon {
25
+ font-size: 28px;
26
+ cursor: pointer;
27
+ user-select: none;
28
+ color: #667eea;
29
+ background: white;
30
+ padding: 8px 12px;
31
+ border-radius: 8px;
32
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
33
+ transition: all 0.2s ease;
34
+ }
35
+
36
+ .menu-icon:hover {
37
+ background: #667eea;
38
+ color: white;
39
+ transform: scale(1.05);
40
+ }
41
+
42
+ .menu-list {
43
+ display: none;
44
+ position: absolute;
45
+ top: 50px;
46
+ left: 0;
47
+ background: white;
48
+ border: 1px solid #dee2e6;
49
+ border-radius: 8px;
50
+ box-shadow: 0 4px 16px rgba(0,0,0,0.15);
51
+ min-width: 220px;
52
+ font-size: 14px;
53
+ overflow: hidden;
54
+ }
55
+
56
+ .menu-list.show {
57
+ display: block;
58
+ animation: slideDown 0.2s ease;
59
+ }
60
+
61
+ @keyframes slideDown {
62
+ from { opacity: 0; transform: translateY(-10px); }
63
+ to { opacity: 1; transform: translateY(0); }
64
+ }
65
+
66
+ .menu-list a {
67
+ display: block;
68
+ padding: 12px 16px;
69
+ color: #495057;
70
+ text-decoration: none;
71
+ border-bottom: 1px solid #f8f9fa;
72
+ transition: background-color 0.2s ease;
73
+ }
74
+
75
+ .menu-list a:last-child {
76
+ border-bottom: none;
77
+ }
78
+
79
+ .menu-list a:hover {
80
+ background-color: #667eea;
81
+ color: white;
82
+ }
83
+
84
+ .menu-list a::before {
85
+ content: "→ ";
86
+ margin-right: 8px;
87
+ opacity: 0;
88
+ transition: opacity 0.2s ease;
89
+ }
90
+
91
+ .menu-list a:hover::before {
92
+ opacity: 1;
93
+ }
94
+
95
+ .container {
96
+ max-width: 800px;
97
+ margin: 0 auto;
98
+ background: white;
99
+ border-radius: 8px;
100
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
101
+ overflow: hidden;
102
+ }
103
+
104
+ .header {
105
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
106
+ color: white;
107
+ padding: 30px;
108
+ text-align: center;
109
+ }
110
+
111
+ .header h1 {
112
+ margin: 0;
113
+ font-size: 2.5em;
114
+ font-weight: 300;
115
+ }
116
+
117
+ .controls {
118
+ padding: 20px 30px;
119
+ border-bottom: 1px solid #dee2e6;
120
+ background: #f8f9fa;
121
+ }
122
+
123
+ .upload-section {
124
+ background: #f5f7fa;
125
+ border-radius: 10px;
126
+ padding: 24px;
127
+ margin-bottom: 24px;
128
+ border: 1.5px solid #e0e3e8;
129
+ }
130
+
131
+ label {
132
+ display: block;
133
+ margin-bottom: 5px;
134
+ font-weight: 600;
135
+ color: #495057;
136
+ }
137
+
138
+ input[type="file"] {
139
+ width: 100%;
140
+ margin: 10px 0;
141
+ padding: 12px;
142
+ border: 2px solid #dee2e6;
143
+ border-radius: 6px;
144
+ background: white;
145
+ }
146
+
147
+ .btn {
148
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
149
+ color: white;
150
+ padding: 12px 28px;
151
+ border: none;
152
+ border-radius: 6px;
153
+ cursor: pointer;
154
+ font-size: 1.08em;
155
+ font-weight: 600;
156
+ margin: 8px 12px 0 0;
157
+ transition: transform 0.2s;
158
+ }
159
+
160
+ .btn:hover {
161
+ transform: translateY(-1px);
162
+ }
163
+
164
+ .btn:disabled {
165
+ opacity: 0.7;
166
+ cursor: not-allowed;
167
+ }
168
+
169
+ .btn-download {
170
+ background: #e9ecef;
171
+ color: #495057;
172
+ }
173
+
174
+ .validation-panel {
175
+ margin: 24px 0;
176
+ border-radius: 12px;
177
+ overflow: hidden;
178
+ box-shadow: 0 2px 8px rgba(80,80,160,0.04);
179
+ }
180
+
181
+ .validation-header {
182
+ padding: 16px 24px;
183
+ font-weight: 600;
184
+ font-size: 1.1em;
185
+ }
186
+
187
+ .validation-content {
188
+ padding: 20px 24px;
189
+ background: white;
190
+ }
191
+
192
+ .error-list {
193
+ margin: 0;
194
+ padding-left: 24px;
195
+ }
196
+
197
+ .error-list li {
198
+ margin-bottom: 8px;
199
+ padding: 6px 0;
200
+ border-bottom: 1px solid #eee;
201
+ }
202
+
203
+ .success { background: #d4edda; color: #155724; }
204
+ .error { background: #f8d7da; color: #721c24; }
205
+ .warning { background: #fff3cd; color: #856404; }
206
+
207
+ .stats {
208
+ padding: 15px 30px;
209
+ background: #e9ecef;
210
+ font-size: 14px;
211
+ color: #495057;
212
+ }
213
+ </style>
214
+ </head>
215
+ <body>
216
+ <!-- Menu Container -->
217
+ <div class="menu-container">
218
+ <div class="menu-icon" id="menuIcon">☰</div>
219
+ <div class="menu-list" id="menuList">
220
+ <a href="index.html">Leaderboard V3</a>
221
+ <a href="https://github.com/dynamic-superb/dynamic-superb" target="_blank">GitHub Repository</a>
222
+ <a href="https://arxiv.org/abs/2411.05361" target="_blank">Paper on arXiv</a>
223
+ </div>
224
+ </div>
225
+
226
+ <div class="container">
227
+ <div class="header">
228
+ <h1>Dynamic-SUPERB Submission Validator</h1>
229
+ </div>
230
+
231
+ <div class="controls">
232
+ <div class="upload-section">
233
+ <label for="submissionFile">Upload Model CSV:</label>
234
+ <input type="file" id="submissionFile" accept=".csv">
235
+ <button id="validateBtn" class="btn" onclick="validateSubmission()">Validate</button>
236
+ <button id="downloadBtn" class="btn btn-download" style="display:none;" onclick="downloadErrors()">Download Error Report</button>
237
+ </div>
238
+
239
+ <div id="validationResult"></div>
240
+ </div>
241
+
242
+ <div class="stats">
243
+ Dynamic-SUPERB Submission Validator
244
+ </div>
245
+ </div>
246
+
247
+ <script>
248
+ // Menu functionality
249
+ const menuIcon = document.getElementById('menuIcon');
250
+ const menuList = document.getElementById('menuList');
251
+
252
+ menuIcon.addEventListener('click', (e) => {
253
+ e.stopPropagation();
254
+ menuList.classList.toggle('show');
255
+ });
256
+
257
+ // Close menu if clicked outside
258
+ document.addEventListener('click', (e) => {
259
+ if (!menuIcon.contains(e.target) && !menuList.contains(e.target)) {
260
+ menuList.classList.remove('show');
261
+ }
262
+ });
263
+
264
+ let errorReport = [];
265
+
266
+ // Enhanced CSV parsing
267
+ function parseCSVLine(line) {
268
+ const result = [];
269
+ let current = '';
270
+ let inQuotes = false;
271
+
272
+ for (let i = 0; i < line.length; i++) {
273
+ const char = line[i];
274
+
275
+ if (char === '"') {
276
+ inQuotes = !inQuotes;
277
+ } else if (char === ',' && !inQuotes) {
278
+ result.push(current.trim());
279
+ current = '';
280
+ } else {
281
+ current += char;
282
+ }
283
+ }
284
+
285
+ result.push(current.trim());
286
+ return result;
287
+ }
288
+
289
+ function normalizeTaskName(name) {
290
+ return name.trim().toLowerCase().replace(/[_\s]+/g, ' ');
291
+ }
292
+
293
+ class SimplifiedDynamicSUPERBValidator {
294
+ constructor() {
295
+ this.reference = new Map();
296
+ this.existingModelNames = new Set();
297
+ this.loadReference();
298
+ }
299
+
300
+ async loadReference() {
301
+ try {
302
+ const response = await fetch('data.csv');
303
+ const csvText = await response.text();
304
+ const lines = csvText.trim().split('\n');
305
+ const headers = parseCSVLine(lines[0]);
306
+
307
+ // Find HigherBetter and Taxonomy column indices in REFERENCE data only
308
+ const higherBetterIndex = headers.findIndex(h => h.trim().toLowerCase() === 'higherbetter');
309
+ const taxonomyIndex = headers.findIndex(h => h.trim().toLowerCase() === 'taxonomy');
310
+
311
+ // Extract existing model names from reference data
312
+ const modelStartIndex = higherBetterIndex + 1;
313
+ const modelEndIndex = taxonomyIndex;
314
+ for (let i = modelStartIndex; i < modelEndIndex; i++) {
315
+ this.existingModelNames.add(headers[i].trim());
316
+ }
317
+
318
+ for (let i = 1; i < lines.length; i++) {
319
+ const row = parseCSVLine(lines[i]);
320
+ if (row.length < 4) continue;
321
+
322
+ const taskName = normalizeTaskName(row[0]);
323
+ const metric = row[1];
324
+
325
+ if (metric === 'X') continue;
326
+
327
+ if (!this.reference.has(taskName)) {
328
+ this.reference.set(taskName, {
329
+ originalName: row[0],
330
+ metrics: [],
331
+ narMetrics: []
332
+ });
333
+ }
334
+
335
+ const task = this.reference.get(taskName);
336
+ if (!task.metrics.includes(metric)) task.metrics.push(metric);
337
+
338
+ // Track NAR metrics specifically
339
+ if (metric.includes('NAR')) {
340
+ task.narMetrics.push(metric);
341
+ }
342
+ }
343
+
344
+ // Sort metrics with NAR first
345
+ this.reference.forEach(task => {
346
+ task.metrics.sort((a, b) => {
347
+ if (a.includes('NAR')) return -1;
348
+ if (b.includes('NAR')) return 1;
349
+ return a.localeCompare(b);
350
+ });
351
+ });
352
+
353
+ } catch (error) {
354
+ console.error('Error loading reference:', error);
355
+ }
356
+ }
357
+
358
+ async validate(file) {
359
+ const csvText = await this.readFile(file);
360
+ const lines = csvText.trim().split('\n');
361
+ const header = parseCSVLine(lines[0]);
362
+
363
+ console.log('Submission CSV Headers:', header);
364
+
365
+ // SIMPLIFIED: Model columns are everything after "Metric" (index 1)
366
+ const modelStartIndex = 2; // After "Task Name" and "Metric"
367
+ const submissionModelNames = header.slice(modelStartIndex);
368
+
369
+ console.log('Found models in submission:', submissionModelNames);
370
+ console.log('Model count:', submissionModelNames.length);
371
+
372
+ const result = {
373
+ isValid: true,
374
+ errors: [],
375
+ warnings: [],
376
+ stats: { total: 0, valid: 0, invalid: 0 },
377
+ tasks: new Map(),
378
+ modelNames: submissionModelNames
379
+ };
380
+
381
+ // Check for model name overlaps
382
+ const overlappingModels = submissionModelNames.filter(modelName =>
383
+ this.existingModelNames.has(modelName.trim())
384
+ );
385
+
386
+ if (overlappingModels.length > 0) {
387
+ result.isValid = false;
388
+ result.errors.push({
389
+ line: 'Header',
390
+ task: 'Model Names',
391
+ error: `Model name overlap detected: ${overlappingModels.join(', ')} already exist in data.csv`
392
+ });
393
+ }
394
+
395
+ // Validate each row
396
+ for (let i = 1; i < lines.length; i++) {
397
+ result.stats.total++;
398
+ const row = parseCSVLine(lines[i]);
399
+ const errors = [];
400
+
401
+ // Basic structure check - must have at least Task Name, Metric, and one model column
402
+ if (row.length < 3) {
403
+ errors.push('Invalid row format (minimum: Task Name, Metric, and at least one model score)');
404
+ result.errors.push({
405
+ line: i+1,
406
+ task: row[0] || 'Unknown',
407
+ error: 'Invalid row format (minimum: Task Name, Metric, and at least one model score)'
408
+ });
409
+ result.isValid = false;
410
+ result.stats.invalid++;
411
+ continue;
412
+ }
413
+
414
+ const taskName = normalizeTaskName(row[0]);
415
+ const metric = row[1];
416
+
417
+ // Skip X metrics
418
+ if (metric === 'X') {
419
+ result.warnings.push(`Line ${i+1}: Skipped metric "X"`);
420
+ continue;
421
+ }
422
+
423
+ // Task existence check
424
+ if (!this.reference.has(taskName)) {
425
+ errors.push(`Unknown task: "${row[0]}"`);
426
+ } else {
427
+ const refTask = this.reference.get(taskName);
428
+
429
+ // Metric check
430
+ if (!refTask.metrics.includes(metric)) {
431
+ errors.push(`Invalid metric: "${metric}" for task "${row[0]}"`);
432
+ }
433
+
434
+ // Track metrics per task for NAR check
435
+ if (!result.tasks.has(taskName)) {
436
+ result.tasks.set(taskName, { metrics: new Map() });
437
+ }
438
+
439
+ const taskData = result.tasks.get(taskName);
440
+ if (!taskData.metrics.has(metric)) {
441
+ taskData.metrics.set(metric, []);
442
+ }
443
+
444
+ // Track which models have this metric
445
+ for (let j = 0; j < submissionModelNames.length; j++) {
446
+ const modelValue = row[modelStartIndex + j];
447
+ if (modelValue && modelValue !== '-' && modelValue !== '') {
448
+ taskData.metrics.get(metric).push(submissionModelNames[j]);
449
+ }
450
+ }
451
+ }
452
+
453
+ // Record errors
454
+ if (errors.length > 0) {
455
+ result.isValid = false;
456
+ result.stats.invalid++;
457
+ errors.forEach(error => {
458
+ result.errors.push({
459
+ line: i+1,
460
+ task: row[0],
461
+ error: error
462
+ });
463
+ });
464
+ } else {
465
+ result.stats.valid++;
466
+ }
467
+ }
468
+
469
+ // Check for missing NAR metrics for each model
470
+ result.tasks.forEach((taskData, taskName) => {
471
+ if (!this.reference.has(taskName)) return;
472
+
473
+ const refTask = this.reference.get(taskName);
474
+ if (refTask.narMetrics.length === 0) return; // No NAR metrics for this task
475
+
476
+ // For each NAR metric in reference
477
+ refTask.narMetrics.forEach(narMetric => {
478
+ // Check if this NAR metric is reported
479
+ const reportedModels = taskData.metrics.has(narMetric)
480
+ ? taskData.metrics.get(narMetric)
481
+ : [];
482
+
483
+ // Find models missing this NAR metric
484
+ const missingModels = submissionModelNames.filter(model =>
485
+ !reportedModels.includes(model)
486
+ );
487
+
488
+ if (missingModels.length > 0) {
489
+ result.isValid = false;
490
+ result.errors.push({
491
+ line: 'Multiple',
492
+ task: refTask.originalName,
493
+ error: `Missing NAR metric "${narMetric}" for models: ${missingModels.join(', ')}`
494
+ });
495
+ }
496
+ });
497
+ });
498
+
499
+ return result;
500
+ }
501
+
502
+ readFile(file) {
503
+ return new Promise((resolve, reject) => {
504
+ const reader = new FileReader();
505
+ reader.onload = () => resolve(reader.result);
506
+ reader.onerror = reject;
507
+ reader.readAsText(file);
508
+ });
509
+ }
510
+ }
511
+
512
+ // UI Handling
513
+ const validator = new SimplifiedDynamicSUPERBValidator();
514
+ let currentErrors = [];
515
+
516
+ async function validateSubmission() {
517
+ const fileInput = document.getElementById('submissionFile');
518
+ const resultDiv = document.getElementById('validationResult');
519
+ const downloadBtn = document.getElementById('downloadBtn');
520
+ const validateBtn = document.getElementById('validateBtn');
521
+
522
+ if (!fileInput.files.length) {
523
+ resultDiv.innerHTML = `<div class="validation-panel error">
524
+ <div class="validation-header">Error</div>
525
+ <div class="validation-content">Please select a CSV file</div>
526
+ </div>`;
527
+ return;
528
+ }
529
+
530
+ validateBtn.disabled = true;
531
+ validateBtn.textContent = 'Validating...';
532
+ downloadBtn.style.display = 'none';
533
+ currentErrors = [];
534
+
535
+ try {
536
+ const result = await validator.validate(fileInput.files[0]);
537
+ let html = '';
538
+
539
+ if (result.isValid) {
540
+ html = `<div class="validation-panel success">
541
+ <div class="validation-header">Validation Successful!</div>
542
+ <div class="validation-content">
543
+ <p><strong>Total Rows:</strong> ${result.stats.total}</p>
544
+ <p><strong>Valid Rows:</strong> ${result.stats.valid}</p>
545
+ <p><strong>Models:</strong> ${result.modelNames.length}</p>
546
+ <p>✅ All NAR metrics are properly reported for all models!</p>
547
+ <p>✅ No model name conflicts detected!</p>
548
+ <p>✅ All tasks and metrics are valid!</p>
549
+ </div>
550
+ </div>`;
551
+ } else {
552
+ currentErrors = result.errors;
553
+ downloadBtn.style.display = 'inline-block';
554
+
555
+ html = `<div class="validation-panel error">
556
+ <div class="validation-header">Validation Failed</div>
557
+ <div class="validation-content">
558
+ <p><strong>Total Rows:</strong> ${result.stats.total}</p>
559
+ <p><strong>Valid Rows:</strong> ${result.stats.valid}</p>
560
+ <p><strong>Errors Found:</strong> ${result.errors.length}</p>
561
+ <ul class="error-list">${result.errors.map(e => `
562
+ <li><strong>Line ${e.line}:</strong> ${e.error} (${e.task})</li>
563
+ `).join('')}</ul>
564
+ </div>
565
+ </div>`;
566
+ }
567
+
568
+ if (result.warnings.length > 0) {
569
+ html += `<div class="validation-panel warning">
570
+ <div class="validation-header">Warnings</div>
571
+ <div class="validation-content">
572
+ <ul class="error-list">${result.warnings.map(w => `
573
+ <li>${w}</li>
574
+ `).join('')}</ul>
575
+ </div>
576
+ </div>`;
577
+ }
578
+
579
+ resultDiv.innerHTML = html;
580
+
581
+ } catch (error) {
582
+ resultDiv.innerHTML = `<div class="validation-panel error">
583
+ <div class="validation-header">Error</div>
584
+ <div class="validation-content">${error.message}</div>
585
+ </div>`;
586
+ }
587
+
588
+ validateBtn.disabled = false;
589
+ validateBtn.textContent = 'Validate';
590
+ }
591
+
592
+ function downloadErrors() {
593
+ if (currentErrors.length === 0) return;
594
+
595
+ const csvContent = [
596
+ ['Line', 'Task', 'Error'],
597
+ ...currentErrors.map(e => [e.line, `"${e.task}"`, `"${e.error}"`])
598
+ ].map(row => row.join(',')).join('\n');
599
+
600
+ const blob = new Blob([csvContent], { type: 'text/csv' });
601
+ const url = URL.createObjectURL(blob);
602
+ const a = document.createElement('a');
603
+ a.href = url;
604
+ a.download = 'validation_errors.csv';
605
+ document.body.appendChild(a);
606
+ a.click();
607
+ document.body.removeChild(a);
608
+ URL.revokeObjectURL(url);
609
+ }
610
+ </script>
611
+ </body>
612
+ </html>