sairaj2 commited on
Commit
be90070
·
verified ·
1 Parent(s): c8406b1

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. app.py +11 -4
  2. static/index.html +483 -667
  3. tasks/employee_demo.py +61 -0
app.py CHANGED
@@ -155,8 +155,11 @@ async def reset_environment(request: ResetRequest = None):
155
  )
156
 
157
  return {
 
158
  "status": "reset_complete",
159
- "observation": observation.metadata,
 
 
160
  "session_id": openenv_env.get_session_id(),
161
  "task_id": openenv_env.get_task_id()
162
  }
@@ -192,10 +195,14 @@ async def execute_step(request: StepRequest):
192
  observation = openenv_env.step(action)
193
 
194
  return {
 
195
  "status": "success",
196
- "observation": observation.metadata,
197
- "reward": observation.reward,
198
- "done": observation.done,
 
 
 
199
  }
200
 
201
  except TypeError as e:
 
155
  )
156
 
157
  return {
158
+ "success": True,
159
  "status": "reset_complete",
160
+ "data": {
161
+ "observation": observation.metadata
162
+ },
163
  "session_id": openenv_env.get_session_id(),
164
  "task_id": openenv_env.get_task_id()
165
  }
 
195
  observation = openenv_env.step(action)
196
 
197
  return {
198
+ "success": True,
199
  "status": "success",
200
+ "data": {
201
+ "observation": observation.metadata,
202
+ "reward": observation.reward,
203
+ "done": observation.done,
204
+ "info": {}
205
+ }
206
  }
207
 
208
  except TypeError as e:
static/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Data Cleaner - Learn Data Cleaning</title>
7
  <style>
8
  * {
9
  margin: 0;
@@ -17,15 +17,12 @@
17
  --success: #22c55e;
18
  --warning: #f59e0b;
19
  --danger: #ef4444;
20
- --bg: #f8fafc;
21
- --bg-card: #ffffff;
22
- --bg-input: #f1f5f9;
23
- --text: #1e293b;
24
- --text-muted: #64748b;
25
- --border: #e2e8f0;
26
- --changed: #fef3c7;
27
- --fixed: #dcfce7;
28
- --removed: #fee2e2;
29
  }
30
 
31
  body {
@@ -36,23 +33,24 @@
36
  }
37
 
38
  .container {
39
- max-width: 1600px;
40
  margin: 0 auto;
41
  padding: 20px;
42
  }
43
 
44
  header {
45
  text-align: center;
46
- padding: 24px 0;
47
- margin-bottom: 20px;
 
48
  }
49
 
50
  header h1 {
51
- font-size: 2.2rem;
52
  background: linear-gradient(135deg, var(--primary), #a855f7);
53
  -webkit-background-clip: text;
54
  -webkit-text-fill-color: transparent;
55
- margin-bottom: 8px;
56
  }
57
 
58
  header p {
@@ -60,74 +58,63 @@
60
  font-size: 1.1rem;
61
  }
62
 
63
- .progress-header {
64
- background: var(--bg-card);
65
- border-radius: 16px;
66
- padding: 20px;
67
- border: 1px solid var(--border);
68
- margin-bottom: 20px;
69
- }
70
-
71
- .progress-bars {
72
- display: grid;
73
- grid-template-columns: repeat(4, 1fr);
74
- gap: 16px;
75
- margin-bottom: 16px;
76
  }
77
 
78
- .progress-item {
 
 
 
 
 
79
  text-align: center;
80
  }
81
 
82
- .progress-item .label {
83
  font-size: 0.8rem;
84
  color: var(--text-muted);
85
  text-transform: uppercase;
86
- letter-spacing: 0.5px;
87
- margin-bottom: 6px;
88
  }
89
 
90
- .progress-bar {
91
- height: 8px;
92
- background: var(--bg-input);
93
- border-radius: 4px;
94
- overflow: hidden;
95
- margin-bottom: 4px;
96
- }
97
-
98
- .progress-fill {
99
- height: 100%;
100
- border-radius: 4px;
101
- transition: width 0.5s ease;
102
- }
103
-
104
- .progress-fill.success { background: var(--success); }
105
- .progress-fill.warning { background: var(--warning); }
106
- .progress-fill.primary { background: var(--primary); }
107
-
108
- .progress-value {
109
  font-weight: 600;
110
- font-size: 1.1rem;
111
  }
112
 
113
- .main-grid {
 
 
 
 
114
  display: grid;
115
- grid-template-columns: 320px 1fr 340px;
116
- gap: 20px;
117
  }
118
 
119
- @media (max-width: 1300px) {
120
- .main-grid {
121
  grid-template-columns: 1fr;
122
  }
123
  }
124
 
 
 
 
 
 
 
125
  .card {
126
  background: var(--bg-card);
127
  border-radius: 16px;
128
- padding: 20px;
129
  border: 1px solid var(--border);
130
- margin-bottom: 20px;
131
  }
132
 
133
  .card h3 {
@@ -136,7 +123,11 @@
136
  color: var(--text);
137
  display: flex;
138
  align-items: center;
139
- gap: 8px;
 
 
 
 
140
  }
141
 
142
  select, button {
@@ -166,12 +157,12 @@
166
  align-items: center;
167
  justify-content: center;
168
  gap: 8px;
169
- border: none;
170
  }
171
 
172
  .btn-primary {
173
  background: var(--primary);
174
  color: white;
 
175
  }
176
 
177
  .btn-primary:hover {
@@ -181,6 +172,7 @@
181
  .btn-success {
182
  background: var(--success);
183
  color: white;
 
184
  }
185
 
186
  .btn-success:hover {
@@ -190,6 +182,7 @@
190
  .btn-warning {
191
  background: var(--warning);
192
  color: white;
 
193
  }
194
 
195
  .btn-warning:hover {
@@ -199,6 +192,7 @@
199
  .btn-danger {
200
  background: var(--danger);
201
  color: white;
 
202
  }
203
 
204
  .btn-danger:hover {
@@ -224,22 +218,17 @@
224
  background: var(--bg-input);
225
  border: 1px solid var(--border);
226
  color: var(--text);
227
- padding: 12px 14px;
228
  border-radius: 8px;
229
  cursor: pointer;
230
  transition: all 0.2s;
231
  text-align: left;
232
  font-size: 0.9rem;
233
- display: flex;
234
- align-items: center;
235
- gap: 10px;
236
  }
237
 
238
  .action-btn:hover {
239
  background: var(--primary);
240
  border-color: var(--primary);
241
- color: white;
242
- transform: translateX(4px);
243
  }
244
 
245
  .action-btn:disabled {
@@ -247,152 +236,103 @@
247
  cursor: not-allowed;
248
  }
249
 
250
- .action-btn .icon {
251
- font-size: 1.2rem;
252
- }
253
-
254
- .action-btn .desc {
255
- font-size: 0.8rem;
256
- color: var(--text-muted);
257
- display: block;
258
- margin-top: 2px;
259
- }
260
-
261
- .action-btn:hover .desc {
262
- color: rgba(255,255,255,0.8);
263
- }
264
-
265
- .data-table-container {
266
- max-height: 500px;
267
- overflow: auto;
268
- border-radius: 10px;
269
- border: 1px solid var(--border);
270
  }
271
 
272
- .data-table {
273
  width: 100%;
274
  border-collapse: collapse;
275
- font-size: 0.85rem;
276
  }
277
 
278
- .data-table th,
279
- .data-table td {
280
- padding: 10px 12px;
281
  text-align: left;
282
  border-bottom: 1px solid var(--border);
283
  }
284
 
285
- .data-table th {
286
  background: var(--bg-input);
287
  font-weight: 600;
288
- position: sticky;
289
- top: 0;
290
- z-index: 10;
291
- }
292
-
293
- .data-table tr:hover {
294
- background: rgba(99, 102, 241, 0.05);
295
- }
296
-
297
- .data-table td.changed {
298
- background: var(--changed);
299
- animation: pulse 1s ease;
300
- }
301
-
302
- .data-table td.fixed {
303
- background: var(--fixed);
304
- animation: pulse 1s ease;
305
  }
306
 
307
- .data-table td.removed {
308
- background: var(--removed);
309
- text-decoration: line-through;
310
- opacity: 0.7;
311
  }
312
 
313
- .null-value {
314
  color: var(--warning);
315
  font-weight: 600;
316
  }
317
 
318
- @keyframes pulse {
319
- 0% { transform: scale(1.05); }
320
- 100% { transform: scale(1); }
321
- }
322
-
323
- .history-item {
324
- padding: 12px;
325
- border-radius: 8px;
326
- border: 1px solid var(--border);
327
- margin-bottom: 8px;
328
  background: var(--bg-input);
 
 
 
 
 
 
329
  }
330
 
331
- .history-item .action-name {
332
- font-weight: 600;
333
- margin-bottom: 4px;
334
  }
335
 
336
- .history-item .action-desc {
337
- font-size: 0.85rem;
338
- color: var(--text-muted);
339
  }
340
 
341
- .history-item .changes {
342
- margin-top: 8px;
343
- font-size: 0.8rem;
344
- padding: 6px;
345
- background: white;
346
- border-radius: 6px;
347
  }
348
 
349
- .help-box {
350
- background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(168, 85, 247, 0.1));
351
- border-radius: 12px;
352
- padding: 16px;
353
- border: 1px solid rgba(99, 102, 241, 0.2);
354
- margin-top: 16px;
355
  }
356
 
357
- .help-box h4 {
358
- font-size: 0.9rem;
359
- margin-bottom: 8px;
360
- color: var(--primary);
361
  }
362
 
363
- .help-box p {
364
- font-size: 0.85rem;
365
- color: var(--text-muted);
366
- line-height: 1.5;
367
  }
368
 
369
- .view-toggle {
370
  display: flex;
371
- gap: 8px;
372
- margin-bottom: 12px;
 
373
  }
374
 
375
- .view-toggle button {
376
- flex: 1;
377
- padding: 8px 12px;
378
  font-size: 0.85rem;
 
379
  }
380
 
381
- .view-toggle button.active {
382
- background: var(--primary);
383
- color: white;
384
- }
385
-
386
- .comparison-grid {
387
- display: grid;
388
- grid-template-columns: 1fr 1fr;
389
- gap: 12px;
390
  }
391
 
392
- .comparison-col h4 {
393
- font-size: 0.9rem;
394
- margin-bottom: 8px;
395
- color: var(--text-muted);
396
  }
397
 
398
  .modal-overlay {
@@ -411,8 +351,8 @@
411
  .modal {
412
  background: var(--bg-card);
413
  border-radius: 16px;
414
- padding: 24px;
415
- max-width: 450px;
416
  width: 90%;
417
  border: 1px solid var(--border);
418
  }
@@ -431,31 +371,20 @@
431
  flex: 1;
432
  }
433
 
434
- .param-form {
435
- display: flex;
436
- flex-direction: column;
437
- gap: 12px;
438
- }
439
-
440
- .param-form label {
441
- font-size: 0.9rem;
442
- font-weight: 500;
443
  }
444
 
445
- .param-form input,
446
- .param-form select {
447
- background: var(--bg-input);
448
- border: 1px solid var(--border);
449
- color: var(--text);
450
- padding: 10px;
451
- border-radius: 8px;
452
- width: 100%;
453
  }
454
 
455
- .param-form .hint {
456
- font-size: 0.8rem;
457
  color: var(--text-muted);
458
- margin-top: 4px;
459
  }
460
 
461
  .hidden {
@@ -476,9 +405,15 @@
476
  to { transform: rotate(360deg); }
477
  }
478
 
479
- .difficulty {
 
 
 
 
 
 
480
  display: inline-block;
481
- padding: 4px 8px;
482
  border-radius: 4px;
483
  font-size: 0.75rem;
484
  font-weight: 600;
@@ -488,220 +423,104 @@
488
  .difficulty.easy { background: var(--success); color: white; }
489
  .difficulty.medium { background: var(--warning); color: white; }
490
  .difficulty.hard { background: var(--danger); color: white; }
491
-
492
- .tab-header {
493
- display: flex;
494
- gap: 4px;
495
- margin-bottom: 12px;
496
- border-bottom: 1px solid var(--border);
497
- }
498
-
499
- .tab-btn {
500
- padding: 8px 16px;
501
- background: transparent;
502
- color: var(--text-muted);
503
- border-bottom: 2px solid transparent;
504
- border-radius: 0;
505
- }
506
-
507
- .tab-btn.active {
508
- color: var(--primary);
509
- border-bottom-color: var(--primary);
510
- }
511
  </style>
512
  </head>
513
  <body>
514
  <div class="container">
515
  <header>
516
- <h1>🧹 Data Cleaner</h1>
517
- <p>Learn how to clean messy data step by step - no coding required!</p>
518
  </header>
519
 
520
- <div class="progress-header">
521
- <div class="progress-bars">
522
- <div class="progress-item">
523
- <div class="label">✅ Issues Fixed</div>
524
- <div class="progress-bar"><div class="progress-fill success" id="fixed-bar" style="width: 0%"></div></div>
525
- <div class="progress-value" id="fixed-value">0 / 0</div>
526
- </div>
527
- <div class="progress-item">
528
- <div class="label">📝 Steps Used</div>
529
- <div class="progress-bar"><div class="progress-fill primary" id="steps-bar" style="width: 0%"></div></div>
530
- <div class="progress-value" id="steps-value">0 / 0</div>
531
- </div>
532
- <div class="progress-item">
533
- <div class="label">⭐ Quality Score</div>
534
- <div class="progress-bar"><div class="progress-fill success" id="quality-bar" style="width: 0%"></div></div>
535
- <div class="progress-value" id="quality-value">0%</div>
536
- </div>
537
- <div class="progress-item">
538
- <div class="label">🏆 Points Earned</div>
539
- <div class="progress-bar"><div class="progress-fill warning" id="points-bar" style="width: 0%"></div></div>
540
- <div class="progress-value" id="points-value">0</div>
541
- </div>
542
  </div>
543
- <div id="task-message" style="text-align: center; color: var(--text-muted); font-size: 0.95rem;">
544
- 👋 Welcome! Start by selecting a task below and clicking "Start Task"
 
 
 
 
 
 
 
 
 
 
 
 
 
545
  </div>
546
  </div>
547
 
548
- <div class="main-grid">
549
- <!-- Left Sidebar - Actions -->
550
- <div>
551
  <div class="card">
552
- <h3>📋 Choose a Task</h3>
553
  <select id="task-select">
554
- <option value="">Loading tasks...</option>
 
 
 
555
  </select>
556
- <div id="task-info"></div>
557
- <button class="btn-primary" onclick="startTask()">
558
- ▶️ Start Task
559
- </button>
560
- </div>
561
-
562
- <div class="card">
563
- <h3>🔧 Cleaning Actions</h3>
564
- <div class="action-list" id="action-list">
565
- <button class="action-btn" onclick="openAction('fill_missing')">
566
- <span class="icon">➕</span>
567
- <div>
568
- Fill Missing Values
569
- <span class="desc">Fix empty cells automatically</span>
570
- </div>
571
- </button>
572
- <button class="action-btn" onclick="openAction('drop_duplicates')">
573
- <span class="icon">🗑️</span>
574
- <div>
575
- Remove Duplicates
576
- <span class="desc">Delete repeated rows</span>
577
- </div>
578
- </button>
579
- <button class="action-btn" onclick="openAction('normalize_text')">
580
- <span class="icon">📝</span>
581
- <div>
582
- Fix Text Formatting
583
- <span class="desc">Clean up messy text</span>
584
- </div>
585
- </button>
586
- <button class="action-btn" onclick="openAction('standardize_format')">
587
- <span class="icon">📞</span>
588
- <div>
589
- Standardize Data
590
- <span class="desc">Fix emails, phones, dates</span>
591
- </div>
592
- </button>
593
- <button class="action-btn" onclick="openAction('detect_outliers')">
594
- <span class="icon">📉</span>
595
- <div>
596
- Find Odd Values
597
- <span class="desc">Spot numbers that don't belong</span>
598
- </div>
599
- </button>
600
- <button class="action-btn" onclick="openAction('flag_invalid')">
601
- <span class="icon">⚠️</span>
602
- <div>
603
- Check for Errors
604
- <span class="desc">Mark bad data</span>
605
- </div>
606
  </button>
 
607
  </div>
608
 
609
- <div style="margin-top: 16px; display: flex; flex-direction: column; gap: 8px;">
610
- <button class="btn-warning" onclick="undoAction()">↩️ Undo Last Action</button>
611
- <button class="btn-success" onclick="submitTask()">✅ I'm Done Cleaning!</button>
612
- </div>
613
- </div>
614
-
615
- <div class="help-box">
616
- <h4>💡 What is data cleaning?</h4>
617
- <p>
618
- Real world data is always messy! It has empty cells, typos, duplicates, and wrong values.
619
- Your job is to fix these problems one step at a time to make the data clean and ready to use.
620
- </p>
621
- </div>
622
- </div>
623
-
624
- <!-- Center - Data Table -->
625
- <div>
626
- <div class="card">
627
- <h3>📊 Your Data Table</h3>
628
-
629
- <div class="tab-header">
630
- <button class="tab-btn active" onclick="showTab('current')">Current Data</button>
631
- <button class="tab-btn" onclick="showTab('before')">Before Last Change</button>
632
- <button class="tab-btn" onclick="showTab('comparison')">Side by Side</button>
633
- </div>
634
-
635
- <div id="tab-current">
636
- <div class="data-table-container">
637
- <table class="data-table" id="data-table">
638
- <thead></thead>
639
- <tbody></tbody>
640
- </table>
641
  </div>
642
  </div>
 
643
 
644
- <div id="tab-before" class="hidden">
645
- <div class="data-table-container">
646
- <table class="data-table" id="before-table">
647
- <thead></thead>
648
- <tbody></tbody>
649
- </table>
650
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  </div>
652
 
653
- <div id="tab-comparison" class="hidden">
654
- <div class="comparison-grid">
655
- <div class="comparison-col">
656
- <h4>⬅️ Before</h4>
657
- <div class="data-table-container" style="max-height: 400px;">
658
- <table class="data-table" id="compare-before">
659
- <thead></thead>
660
- <tbody></tbody>
661
- </table>
662
- </div>
663
- </div>
664
- <div class="comparison-col">
665
- <h4>➡️ After</h4>
666
- <div class="data-table-container" style="max-height: 400px;">
667
- <table class="data-table" id="compare-after">
668
- <thead></thead>
669
- <tbody></tbody>
670
- </table>
671
- </div>
672
  </div>
673
  </div>
674
  </div>
675
-
676
- <div style="margin-top: 12px; display: flex; gap: 8px;">
677
- <button class="btn-secondary" onclick="loadMoreRows()">⬇️ Show More Rows</button>
678
- <div style="flex: 1;"></div>
679
- <span style="color: var(--text-muted); font-size: 0.85rem;" id="row-count">Showing 0 rows</span>
680
- </div>
681
- </div>
682
-
683
- <div class="card">
684
- <h3>⚠️ Problems Found</h3>
685
- <div id="issues-list">
686
- <p style="color: var(--text-muted);">Start a task to see what needs fixing</p>
687
- </div>
688
- </div>
689
- </div>
690
-
691
- <!-- Right Sidebar - History -->
692
- <div>
693
- <div class="card">
694
- <h3>📜 Your Cleaning History</h3>
695
- <div id="history-list">
696
- <p style="color: var(--text-muted); font-size: 0.9rem;">No actions yet. Start cleaning to see your progress!</p>
697
- </div>
698
- </div>
699
-
700
- <div class="card">
701
- <h3>📈 Column Health</h3>
702
- <div id="column-health">
703
- <p style="color: var(--text-muted);">Start a task to see column status</p>
704
- </div>
705
  </div>
706
  </div>
707
  </div>
@@ -709,39 +528,34 @@
709
  <!-- Action Modal -->
710
  <div class="modal-overlay hidden" id="action-modal">
711
  <div class="modal">
712
- <h3 id="action-title">Action</h3>
713
- <div class="param-form" id="action-form"></div>
714
- <div class="help-box">
715
- <h4>ℹ️ About this action</h4>
716
- <p id="action-help">Select what you want to clean</p>
717
- </div>
718
  <div class="modal-actions">
719
  <button class="btn-secondary" onclick="closeModal()">Cancel</button>
720
- <button class="btn-primary" onclick="executeAction()">Run Cleaning</button>
721
  </div>
722
  </div>
723
  </div>
724
 
725
- <!-- Result Modal -->
726
- <div class="modal-overlay hidden" id="result-modal">
727
  <div class="modal">
728
- <h3>🎉 Great Job!</h3>
729
- <div id="result-content"></div>
 
 
 
730
  <div class="modal-actions">
731
- <button class="btn-primary" onclick="closeResultModal()">Continue</button>
732
  </div>
733
  </div>
734
  </div>
735
 
736
  <script>
737
- let sessionId = 'student_' + Math.random().toString(36).substring(2, 8);
738
- let currentData = [];
739
- let previousData = [];
740
- let history = [];
741
  let currentAction = null;
742
- let totalPoints = 0;
743
- let rowsShown = 20;
744
 
 
745
  document.addEventListener('DOMContentLoaded', () => {
746
  loadTasks();
747
  });
@@ -750,343 +564,345 @@
750
  try {
751
  const res = await fetch('/tasks');
752
  const data = await res.json();
753
- const select = document.getElementById('task-select');
754
- select.innerHTML = '';
755
-
756
- data.tasks.forEach(task => {
757
- const option = document.createElement('option');
758
- option.value = task.task_id;
759
- option.textContent = `${task.task_id} - ${task.description}`;
760
- select.appendChild(option);
761
- });
762
-
763
- select.addEventListener('change', (e) => {
764
- const task = data.tasks.find(t => t.task_id === e.target.value);
765
- updateTaskInfo(task);
766
- });
767
-
768
- if (data.tasks.length > 0) {
769
  updateTaskInfo(data.tasks[0]);
770
  }
771
  } catch (e) {
772
- console.log('Tasks not available');
773
  }
774
  }
775
 
 
 
 
 
 
 
 
776
  function updateTaskInfo(task) {
 
777
  const info = document.getElementById('task-info');
778
  info.innerHTML = `
779
- <span class="difficulty ${task.task_level}">${task.task_level}</span>
780
- <p style="margin-top: 8px; font-size: 0.9rem;">${task.description}</p>
781
- <p style="margin-top: 6px; font-size: 0.85rem; color: var(--text-muted);">
782
- 🎯 ${task.issues.length} issues to fix • ⏱️ ${task.max_steps} steps allowed
783
- </p>
784
  `;
785
  }
786
 
787
- async function startTask() {
788
  const taskId = document.getElementById('task-select').value;
789
- document.getElementById('task-message').innerHTML = '🔄 Loading task...';
790
-
791
  try {
 
792
  const res = await fetch('/reset', {
793
  method: 'POST',
794
  headers: { 'Content-Type': 'application/json' },
795
- body: JSON.stringify({ task_id: taskId, session_id: sessionId })
796
  });
797
-
798
  const data = await res.json();
799
 
800
- if (data.success) {
801
- document.getElementById('task-message').innerHTML = '✅ Task ready! Start cleaning below.';
802
- updateStats(data.data.observation);
803
- renderTable(data.data.observation.table_preview);
804
- renderIssues(data.data.observation.detected_issues);
805
- renderColumnHealth(data.data.observation.quality_metrics);
806
- history = [];
807
- renderHistory();
808
  }
809
  } catch (e) {
810
- document.getElementById('task-message').innerHTML = '❌ Error loading task: ' + e.message;
811
  }
812
  }
813
 
814
- function showTab(tab) {
815
- document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
816
- document.querySelector(`[onclick="showTab('${tab}')"]`).classList.add('active');
817
-
818
- document.getElementById('tab-current').classList.add('hidden');
819
- document.getElementById('tab-before').classList.add('hidden');
820
- document.getElementById('tab-comparison').classList.add('hidden');
821
- document.getElementById(`tab-${tab}`).classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
822
  }
823
 
824
- function updateStats(obs) {
825
- const totalIssues = obs.detected_issues.length + obs.issues_remaining;
826
- const fixed = totalIssues - obs.issues_remaining;
 
 
827
 
828
- document.getElementById('fixed-value').textContent = `${fixed} / ${totalIssues}`;
829
- document.getElementById('fixed-bar').style.width = `${(fixed/totalIssues)*100}%`;
830
 
831
- document.getElementById('steps-value').textContent = `${obs.step_count} / ${obs.max_steps}`;
832
- document.getElementById('steps-bar').style.width = `${(obs.step_count/obs.max_steps)*100}%`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
833
 
834
- document.getElementById('quality-value').textContent = `${Math.round(obs.quality_metrics.overall * 100)}%`;
835
- document.getElementById('quality-bar').style.width = `${obs.quality_metrics.overall * 100}%`;
836
  }
837
 
838
- function renderTable(rows, highlightChanges = false) {
839
- if (!rows || rows.length === 0) return;
840
-
841
- previousData = [...currentData];
842
- currentData = rows;
843
-
844
- const columns = Object.keys(rows[0]);
845
- const table = document.getElementById('data-table');
846
-
847
- let html = '<thead><tr>';
848
- columns.forEach(col => html += `<th>${col}</th>`);
849
- html += '</tr></thead><tbody>';
850
-
851
- rows.slice(0, rowsShown).forEach((row, idx) => {
852
- html += '<tr>';
853
- columns.forEach(col => {
854
- let val = row[col];
855
- let cls = '';
856
-
857
- if (val === null || val === undefined || val === '') {
858
- val = '<span class="null-value">EMPTY</span>';
859
- }
860
-
861
- if (highlightChanges && previousData[idx] && previousData[idx][col] !== row[col]) {
862
- cls = 'class="changed"';
863
- }
864
-
865
- html += `<td ${cls}>${val}</td>`;
866
- });
867
- html += '</tr>';
868
- });
869
-
870
- html += '</tbody>';
871
- table.innerHTML = html;
872
- document.getElementById('row-count').textContent = `Showing ${Math.min(rowsShown, rows.length)} of ${rows.length} rows`;
873
  }
874
 
875
- function renderIssues(issues) {
876
- const container = document.getElementById('issues-list');
877
-
878
- if (!issues || issues.length === 0) {
879
- container.innerHTML = '<p style="color: var(--success); font-weight: 600;">✅ Great! No issues found in this view</p>';
880
- return;
881
- }
882
 
883
- let html = '';
884
- issues.slice(0, 8).forEach(issue => {
885
- html += `
886
- <div style="padding: 10px; background: var(--bg-input); border-radius: 8px; margin-bottom: 8px;">
887
- <div style="font-weight: 600;">${issue.type.replace('_', ' ')}</div>
888
- <div style="font-size: 0.85rem; color: var(--text-muted); margin-top: 4px;">
889
- Column: ${issue.column} • Rows affected: ${issue.count}
890
- </div>
891
- </div>
892
- `;
893
- });
894
 
895
- container.innerHTML = html;
896
- }
897
-
898
- function renderColumnHealth(metrics) {
899
- const container = document.getElementById('column-health');
900
- let html = '';
901
 
902
- Object.entries(metrics.columns || {}).forEach(([col, health]) => {
903
- const percent = Math.round(health.score * 100);
904
- const color = percent > 80 ? 'success' : percent > 50 ? 'warning' : 'danger';
905
- html += `
906
- <div style="margin-bottom: 10px;">
907
- <div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
908
- <span>${col}</span>
909
- <span style="color: var(--${color}); font-weight: 600;">${percent}%</span>
910
- </div>
911
- <div class="progress-bar" style="height: 6px;">
912
- <div class="progress-fill ${color}" style="width: ${percent}%"></div>
913
- </div>
914
- </div>
915
- `;
916
- });
917
 
918
- container.innerHTML = html || '<p style="color: var(--text-muted);">No column data yet</p>';
919
- }
920
-
921
- function renderHistory() {
922
- const container = document.getElementById('history-list');
923
 
924
- if (history.length === 0) {
925
- container.innerHTML = '<p style="color: var(--text-muted); font-size: 0.9rem;">No actions yet. Start cleaning!</p>';
926
- return;
927
- }
928
 
929
- let html = '';
930
- [...history].reverse().forEach((item, idx) => {
931
- html += `
932
- <div class="history-item">
933
- <div class="action-name">${idx + 1}. ${item.action.replace('_', ' ')}</div>
934
- <div class="action-desc">${item.description}</div>
935
- <div class="changes">
936
- ✅ ${item.fixed} fixed • ⭐ +${item.points} points
937
- </div>
938
- </div>
939
- `;
940
- });
941
 
942
- container.innerHTML = html;
943
- }
944
-
945
- const actionHelp = {
946
- fill_missing: 'This will automatically fill empty cells using smart methods like median for numbers.',
947
- drop_duplicates: 'Removes rows that are exactly the same so you only have unique entries.',
948
- normalize_text: 'Fixes capitalization, extra spaces, and common typos in text columns.',
949
- standardize_format: 'Makes sure things like emails, phone numbers and dates all follow the same format.',
950
- detect_outliers: 'Finds values that are way too big or too small compared to the rest of the data.',
951
- flag_invalid: 'Checks for values that don\'t belong and marks them so you can fix them.'
952
- };
953
-
954
- function openAction(actionType) {
955
- currentAction = actionType;
956
- document.getElementById('action-title').textContent = actionType.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase());
957
- document.getElementById('action-help').textContent = actionHelp[actionType];
958
 
959
- let formHtml = `
960
- <label>Which column would you like to clean?</label>
961
- <select id="action-column">
962
- ${currentData.length > 0 ? Object.keys(currentData[0]).map(c => `<option value="${c}">${c}</option>`).join('') : ''}
963
- </select>
964
- `;
965
 
966
- if (actionType === 'fill_missing') {
967
- formHtml += `
968
- <label>Fill Strategy</label>
969
- <select id="action-strategy">
970
- <option value="median">Smart Average (recommended)</option>
971
- <option value="mean">Average</option>
972
- <option value="mode">Most Common Value</option>
973
- </select>
974
- `;
975
- }
976
-
977
- document.getElementById('action-form').innerHTML = formHtml;
978
- document.getElementById('action-modal').classList.remove('hidden');
979
- }
980
-
981
- function closeModal() {
982
- document.getElementById('action-modal').classList.add('hidden');
983
- currentAction = null;
984
- }
985
-
986
- async function executeAction() {
987
- const column = document.getElementById('action-column')?.value;
988
- const strategy = document.getElementById('action-strategy')?.value;
989
 
990
  try {
 
991
  const res = await fetch('/step', {
992
  method: 'POST',
993
  headers: { 'Content-Type': 'application/json' },
994
- body: JSON.stringify({
995
- session_id: sessionId,
996
- action: {
997
- action_type: currentAction,
998
- params: { column, strategy }
999
- }
1000
- })
1001
  });
1002
-
1003
  const data = await res.json();
1004
 
1005
- if (data.success) {
1006
- renderTable(data.data.observation.table_preview, true);
1007
- updateStats(data.data.observation);
1008
- renderIssues(data.data.observation.detected_issues);
1009
- renderColumnHealth(data.data.observation.quality_metrics);
1010
-
1011
- const points = Math.round(data.data.reward.total * 100);
1012
- totalPoints += points;
1013
- document.getElementById('points-value').textContent = totalPoints;
1014
- document.getElementById('points-bar').style.width = `${Math.min(totalPoints / 10, 100)}%`;
1015
-
1016
- history.push({
1017
- action: currentAction,
1018
- description: `Cleaned column "${column}"`,
1019
- fixed: data.data.reward.issues_fixed || 0,
1020
- points: points
1021
- });
1022
- renderHistory();
1023
-
1024
- document.getElementById('result-content').innerHTML = `
1025
- <p>You successfully cleaned the <b>${column}</b> column!</p>
1026
- <p style="margin-top: 12px;">✅ ${data.data.reward.issues_fixed || 0} issues fixed</p>
1027
- <p>⭐ You earned <b>${points} points</b>!</p>
1028
- `;
1029
- document.getElementById('result-modal').classList.remove('hidden');
1030
  }
1031
  } catch (e) {
1032
- alert('Error: ' + e.message);
1033
  }
1034
-
1035
  closeModal();
1036
  }
1037
 
1038
- function closeResultModal() {
1039
- document.getElementById('result-modal').classList.add('hidden');
 
 
 
 
 
 
 
1040
  }
1041
 
1042
- async function undoAction() {
1043
  try {
1044
- await fetch('/step', {
1045
- method: 'POST',
1046
- headers: { 'Content-Type': 'application/json' },
1047
- body: JSON.stringify({
1048
- session_id: sessionId,
1049
- action: { action_type: 'revert_last_action', params: {} }
1050
- })
1051
- });
1052
- alert('Last action undone!');
1053
- startTask();
 
1054
  } catch (e) {
1055
- alert('Error undoing: ' + e.message);
1056
  }
1057
  }
1058
 
1059
- async function submitTask() {
1060
- try {
1061
- const res = await fetch('/step', {
1062
- method: 'POST',
1063
- headers: { 'Content-Type': 'application/json' },
1064
- body: JSON.stringify({
1065
- session_id: sessionId,
1066
- action: { action_type: 'submit', params: {} }
1067
- })
1068
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1069
 
 
 
 
1070
  const data = await res.json();
1071
 
1072
- if (data.success && data.data.done) {
1073
- document.getElementById('result-content').innerHTML = `
1074
- <p>🎉 Amazing! You completed the cleaning task!</p>
1075
- <p style="margin-top: 12px;">Final Score: <b>${Math.round(data.data.info.grade.final_score * 100)}%</b></p>
1076
- <p>Total Points: <b>${totalPoints}</b></p>
1077
- <p style="margin-top: 12px; color: var(--text-muted);">${data.data.info.grade.feedback}</p>
1078
- `;
1079
- document.getElementById('result-modal').classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1080
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1081
  } catch (e) {
1082
- alert('Error submitting: ' + e.message);
1083
  }
1084
  }
1085
-
1086
- function loadMoreRows() {
1087
- rowsShown += 20;
1088
- renderTable(currentData);
1089
- }
1090
  </script>
1091
  </body>
1092
- </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenEnv Data Cleaner</title>
7
  <style>
8
  * {
9
  margin: 0;
 
17
  --success: #22c55e;
18
  --warning: #f59e0b;
19
  --danger: #ef4444;
20
+ --bg: #0f172a;
21
+ --bg-card: #1e293b;
22
+ --bg-input: #334155;
23
+ --text: #f1f5f9;
24
+ --text-muted: #94a3b8;
25
+ --border: #475569;
 
 
 
26
  }
27
 
28
  body {
 
33
  }
34
 
35
  .container {
36
+ max-width: 1400px;
37
  margin: 0 auto;
38
  padding: 20px;
39
  }
40
 
41
  header {
42
  text-align: center;
43
+ padding: 30px 0;
44
+ border-bottom: 1px solid var(--border);
45
+ margin-bottom: 30px;
46
  }
47
 
48
  header h1 {
49
+ font-size: 2.5rem;
50
  background: linear-gradient(135deg, var(--primary), #a855f7);
51
  -webkit-background-clip: text;
52
  -webkit-text-fill-color: transparent;
53
+ margin-bottom: 10px;
54
  }
55
 
56
  header p {
 
58
  font-size: 1.1rem;
59
  }
60
 
61
+ .status-bar {
62
+ display: flex;
63
+ gap: 20px;
64
+ justify-content: center;
65
+ flex-wrap: wrap;
66
+ margin-bottom: 30px;
 
 
 
 
 
 
 
67
  }
68
 
69
+ .status-item {
70
+ background: var(--bg-card);
71
+ padding: 15px 25px;
72
+ border-radius: 12px;
73
+ border: 1px solid var(--border);
74
+ min-width: 150px;
75
  text-align: center;
76
  }
77
 
78
+ .status-item .label {
79
  font-size: 0.8rem;
80
  color: var(--text-muted);
81
  text-transform: uppercase;
82
+ letter-spacing: 1px;
 
83
  }
84
 
85
+ .status-item .value {
86
+ font-size: 1.3rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  font-weight: 600;
88
+ margin-top: 5px;
89
  }
90
 
91
+ .status-item .value.healthy { color: var(--success); }
92
+ .status-item .value.warning { color: var(--warning); }
93
+ .status-item .value.error { color: var(--danger); }
94
+
95
+ .grid {
96
  display: grid;
97
+ grid-template-columns: 300px 1fr;
98
+ gap: 30px;
99
  }
100
 
101
+ @media (max-width: 900px) {
102
+ .grid {
103
  grid-template-columns: 1fr;
104
  }
105
  }
106
 
107
+ .sidebar {
108
+ display: flex;
109
+ flex-direction: column;
110
+ gap: 20px;
111
+ }
112
+
113
  .card {
114
  background: var(--bg-card);
115
  border-radius: 16px;
116
+ padding: 24px;
117
  border: 1px solid var(--border);
 
118
  }
119
 
120
  .card h3 {
 
123
  color: var(--text);
124
  display: flex;
125
  align-items: center;
126
+ gap: 10px;
127
+ }
128
+
129
+ .card h3 .icon {
130
+ font-size: 1.3rem;
131
  }
132
 
133
  select, button {
 
157
  align-items: center;
158
  justify-content: center;
159
  gap: 8px;
 
160
  }
161
 
162
  .btn-primary {
163
  background: var(--primary);
164
  color: white;
165
+ border: none;
166
  }
167
 
168
  .btn-primary:hover {
 
172
  .btn-success {
173
  background: var(--success);
174
  color: white;
175
+ border: none;
176
  }
177
 
178
  .btn-success:hover {
 
182
  .btn-warning {
183
  background: var(--warning);
184
  color: white;
185
+ border: none;
186
  }
187
 
188
  .btn-warning:hover {
 
192
  .btn-danger {
193
  background: var(--danger);
194
  color: white;
195
+ border: none;
196
  }
197
 
198
  .btn-danger:hover {
 
218
  background: var(--bg-input);
219
  border: 1px solid var(--border);
220
  color: var(--text);
221
+ padding: 10px 14px;
222
  border-radius: 8px;
223
  cursor: pointer;
224
  transition: all 0.2s;
225
  text-align: left;
226
  font-size: 0.9rem;
 
 
 
227
  }
228
 
229
  .action-btn:hover {
230
  background: var(--primary);
231
  border-color: var(--primary);
 
 
232
  }
233
 
234
  .action-btn:disabled {
 
236
  cursor: not-allowed;
237
  }
238
 
239
+ .action-btn:disabled:hover {
240
+ background: var(--bg-input);
241
+ border-color: var(--border);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  }
243
 
244
+ .dataset-table {
245
  width: 100%;
246
  border-collapse: collapse;
247
+ margin-top: 16px;
248
  }
249
 
250
+ .dataset-table th,
251
+ .dataset-table td {
252
+ padding: 10px 14px;
253
  text-align: left;
254
  border-bottom: 1px solid var(--border);
255
  }
256
 
257
+ .dataset-table th {
258
  background: var(--bg-input);
259
  font-weight: 600;
260
+ font-size: 0.85rem;
261
+ text-transform: uppercase;
262
+ letter-spacing: 0.5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  }
264
 
265
+ .dataset-table tr:hover {
266
+ background: rgba(99, 102, 241, 0.1);
 
 
267
  }
268
 
269
+ .null-count {
270
  color: var(--warning);
271
  font-weight: 600;
272
  }
273
 
274
+ .log-container {
 
 
 
 
 
 
 
 
 
275
  background: var(--bg-input);
276
+ border-radius: 10px;
277
+ padding: 16px;
278
+ max-height: 300px;
279
+ overflow-y: auto;
280
+ font-family: 'Courier New', monospace;
281
+ font-size: 0.85rem;
282
  }
283
 
284
+ .log-entry {
285
+ padding: 4px 0;
286
+ border-bottom: 1px solid rgba(71, 85, 105, 0.3);
287
  }
288
 
289
+ .log-entry:last-child {
290
+ border-bottom: none;
 
291
  }
292
 
293
+ .log-entry .timestamp {
294
+ color: var(--text-muted);
295
+ margin-right: 10px;
 
 
 
296
  }
297
 
298
+ .log-entry .action {
299
+ color: var(--primary);
300
+ font-weight: 600;
 
 
 
301
  }
302
 
303
+ .log-entry .reward {
304
+ color: var(--success);
 
 
305
  }
306
 
307
+ .log-entry .error {
308
+ color: var(--danger);
 
 
309
  }
310
 
311
+ .param-form {
312
  display: flex;
313
+ flex-direction: column;
314
+ gap: 10px;
315
+ margin-top: 12px;
316
  }
317
 
318
+ .param-form label {
 
 
319
  font-size: 0.85rem;
320
+ color: var(--text-muted);
321
  }
322
 
323
+ .param-form input,
324
+ .param-form select {
325
+ background: var(--bg-input);
326
+ border: 1px solid var(--border);
327
+ color: var(--text);
328
+ padding: 8px 12px;
329
+ border-radius: 6px;
 
 
330
  }
331
 
332
+ .param-form input:focus,
333
+ .param-form select:focus {
334
+ outline: none;
335
+ border-color: var(--primary);
336
  }
337
 
338
  .modal-overlay {
 
351
  .modal {
352
  background: var(--bg-card);
353
  border-radius: 16px;
354
+ padding: 30px;
355
+ max-width: 500px;
356
  width: 90%;
357
  border: 1px solid var(--border);
358
  }
 
371
  flex: 1;
372
  }
373
 
374
+ .score-display {
375
+ text-align: center;
376
+ padding: 20px;
 
 
 
 
 
 
377
  }
378
 
379
+ .score-display .score {
380
+ font-size: 3rem;
381
+ font-weight: 700;
382
+ color: var(--primary);
 
 
 
 
383
  }
384
 
385
+ .score-display .label {
 
386
  color: var(--text-muted);
387
+ margin-top: 10px;
388
  }
389
 
390
  .hidden {
 
405
  to { transform: rotate(360deg); }
406
  }
407
 
408
+ .task-info {
409
+ font-size: 0.9rem;
410
+ color: var(--text-muted);
411
+ margin-bottom: 16px;
412
+ }
413
+
414
+ .task-info .difficulty {
415
  display: inline-block;
416
+ padding: 2px 8px;
417
  border-radius: 4px;
418
  font-size: 0.75rem;
419
  font-weight: 600;
 
423
  .difficulty.easy { background: var(--success); color: white; }
424
  .difficulty.medium { background: var(--warning); color: white; }
425
  .difficulty.hard { background: var(--danger); color: white; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  </style>
427
  </head>
428
  <body>
429
  <div class="container">
430
  <header>
431
+ <h1>OpenEnv Data Cleaner</h1>
432
+ <p>AI-powered data cleaning environment with reinforcement learning</p>
433
  </header>
434
 
435
+ <div class="status-bar">
436
+ <div class="status-item">
437
+ <div class="label">Status</div>
438
+ <div class="value" id="status-value">-</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
  </div>
440
+ <div class="status-item">
441
+ <div class="label">Task</div>
442
+ <div class="value" id="task-value">-</div>
443
+ </div>
444
+ <div class="status-item">
445
+ <div class="label">Steps</div>
446
+ <div class="value" id="step-value">0</div>
447
+ </div>
448
+ <div class="status-item">
449
+ <div class="label">Reward</div>
450
+ <div class="value" id="reward-value">0.00</div>
451
+ </div>
452
+ <div class="status-item">
453
+ <div class="label">Score</div>
454
+ <div class="value" id="score-value">-</div>
455
  </div>
456
  </div>
457
 
458
+ <div class="grid">
459
+ <div class="sidebar">
 
460
  <div class="card">
461
+ <h3><span class="icon">📋</span> Task Selection</h3>
462
  <select id="task-select">
463
+ <option value="employee_demo">📊 Employee Dataset - Demo</option>
464
+ <option value="easy_001">Easy - Basic Cleaning</option>
465
+ <option value="medium_001">Medium - Intermediate</option>
466
+ <option value="hard_001">Hard - Advanced Pipeline</option>
467
  </select>
468
+ <button class="btn-primary" onclick="resetEnvironment()">
469
+ <span>🔄</span> Start New Task
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  </button>
471
+ <div class="task-info" id="task-info"></div>
472
  </div>
473
 
474
+ <div class="card">
475
+ <h3><span class="icon">🔧</span> Actions</h3>
476
+ <div class="action-list" id="action-list">
477
+ <button class="action-btn" onclick="showActionModal('drop_nulls')">Drop Nulls</button>
478
+ <button class="action-btn" onclick="showActionModal('fill_nulls')">Fill Nulls</button>
479
+ <button class="action-btn" onclick="showActionModal('remove_duplicates')">Remove Duplicates</button>
480
+ <button class="action-btn" onclick="showActionModal('filter_rows')">Filter Rows</button>
481
+ <button class="action-btn" onclick="showActionModal('drop_columns')">Drop Columns</button>
482
+ <button class="action-btn" onclick="showActionModal('convert_types')">Convert Types</button>
483
+ <button class="action-btn" onclick="showActionModal('validate_email')">Validate Email</button>
484
+ <button class="action-btn" onclick="showActionModal('outlier_removal')">Outlier Removal</button>
485
+ <button class="action-btn" onclick="showActionModal('normalize')">Normalize</button>
486
+ </div>
487
+ <div style="margin-top: 16px; display: flex; flex-direction: column; gap: 8px;">
488
+ <button class="btn-warning" onclick="revertAction()">↩️ Revert Last</button>
489
+ <button class="btn-success" onclick="submitSolution()">✅ Submit Solution</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
490
  </div>
491
  </div>
492
+ </div>
493
 
494
+ <div class="main-content">
495
+ <div class="card">
496
+ <h3><span class="icon">📊</span> Dataset Information</h3>
497
+ <div id="dataset-info">
498
+ <p style="color: var(--text-muted);">Start a task to view dataset information.</p>
 
499
  </div>
500
+ <table class="dataset-table hidden" id="dataset-table">
501
+ <thead>
502
+ <tr>
503
+ <th>Column</th>
504
+ <th>Type</th>
505
+ <th>Null Count</th>
506
+ </tr>
507
+ </thead>
508
+ <tbody id="dataset-tbody"></tbody>
509
+ </table>
510
+ <button class="btn-secondary" onclick="showSampleData()" style="margin-top: 12px;">
511
+ <span>👁️</span> Preview Sample Data
512
+ </button>
513
  </div>
514
 
515
+ <div class="card" style="margin-top: 20px;">
516
+ <h3><span class="icon">📝</span> Action Log</h3>
517
+ <div class="log-container" id="log-container">
518
+ <div class="log-entry">
519
+ <span class="timestamp">--:--:--</span>
520
+ <span>Waiting for task to start...</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
521
  </div>
522
  </div>
523
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
524
  </div>
525
  </div>
526
  </div>
 
528
  <!-- Action Modal -->
529
  <div class="modal-overlay hidden" id="action-modal">
530
  <div class="modal">
531
+ <h3 id="modal-title">Action Parameters</h3>
532
+ <div class="param-form" id="param-form"></div>
 
 
 
 
533
  <div class="modal-actions">
534
  <button class="btn-secondary" onclick="closeModal()">Cancel</button>
535
+ <button class="btn-primary" onclick="executeAction()">Execute</button>
536
  </div>
537
  </div>
538
  </div>
539
 
540
+ <!-- Score Modal -->
541
+ <div class="modal-overlay hidden" id="score-modal">
542
  <div class="modal">
543
+ <div class="score-display">
544
+ <div class="label">Final Score</div>
545
+ <div class="score" id="final-score">0.00</div>
546
+ <div id="score-feedback" style="margin-top: 16px; color: var(--text-muted);"></div>
547
+ </div>
548
  <div class="modal-actions">
549
+ <button class="btn-primary" onclick="closeScoreModal()">Close</button>
550
  </div>
551
  </div>
552
  </div>
553
 
554
  <script>
 
 
 
 
555
  let currentAction = null;
556
+ let totalReward = 0;
 
557
 
558
+ // Initialize
559
  document.addEventListener('DOMContentLoaded', () => {
560
  loadTasks();
561
  });
 
564
  try {
565
  const res = await fetch('/tasks');
566
  const data = await res.json();
567
+ if (data.tasks?.length > 0) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
  updateTaskInfo(data.tasks[0]);
569
  }
570
  } catch (e) {
571
+ console.error('Failed to load tasks:', e);
572
  }
573
  }
574
 
575
+ document.getElementById('task-select').addEventListener('change', (e) => {
576
+ fetch(`/tasks`).then(r => r.json()).then(data => {
577
+ const task = data.tasks.find(t => t.task_id === e.target.value);
578
+ updateTaskInfo(task);
579
+ });
580
+ });
581
+
582
  function updateTaskInfo(task) {
583
+ if (!task) return;
584
  const info = document.getElementById('task-info');
585
  info.innerHTML = `
586
+ <span class="difficulty ${task.difficulty}">${task.difficulty}</span>
587
+ <p style="margin-top: 8px;">${task.description}</p>
588
+ <p style="margin-top: 8px;">Expected: ${(task.expected_actions || []).join(', ')}</p>
 
 
589
  `;
590
  }
591
 
592
+ async function resetEnvironment() {
593
  const taskId = document.getElementById('task-select').value;
 
 
594
  try {
595
+ addLog('Starting new task...', 'info');
596
  const res = await fetch('/reset', {
597
  method: 'POST',
598
  headers: { 'Content-Type': 'application/json' },
599
+ body: JSON.stringify({ task_id: taskId })
600
  });
 
601
  const data = await res.json();
602
 
603
+ if (data.success || data.status === 'reset_complete') {
604
+ totalReward = 0;
605
+ updateStatus(data);
606
+ await loadDatasetInfo();
607
+ addLog(`Task '${taskId}' initialized`, 'success');
 
 
 
608
  }
609
  } catch (e) {
610
+ addLog(`Reset failed: ${e.message}`, 'error');
611
  }
612
  }
613
 
614
+ async function loadDatasetInfo() {
615
+ try {
616
+ const res = await fetch('/dataset');
617
+ const data = await res.json();
618
+
619
+ document.getElementById('task-value').textContent = data.task_id || '-';
620
+ document.getElementById('step-value').textContent = data.step_count || 0;
621
+
622
+ const tbody = document.getElementById('dataset-tbody');
623
+ tbody.innerHTML = '';
624
+
625
+ (data.columns || []).forEach(col => {
626
+ const tr = document.createElement('tr');
627
+ tr.innerHTML = `
628
+ <td>${col}</td>
629
+ <td>${data.dtypes?.[col] || '-'}</td>
630
+ <td class="null-count">${data.null_counts?.[col] || 0}</td>
631
+ `;
632
+ tbody.appendChild(tr);
633
+ });
634
+
635
+ document.getElementById('dataset-table').classList.remove('hidden');
636
+ document.getElementById('dataset-info').innerHTML = `
637
+ <p>Shape: ${data.shape?.[0] || 0} rows × ${data.shape?.[1] || 0} columns</p>
638
+ `;
639
+ } catch (e) {
640
+ console.error('Failed to load dataset info:', e);
641
+ }
642
  }
643
 
644
+ function showActionModal(actionType) {
645
+ currentAction = actionType;
646
+ const modal = document.getElementById('action-modal');
647
+ const title = document.getElementById('modal-title');
648
+ const form = document.getElementById('param-form');
649
 
650
+ title.textContent = actionType.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase());
 
651
 
652
+ let paramsHtml = '';
653
+ switch (actionType) {
654
+ case 'drop_nulls':
655
+ paramsHtml = '<label>Column (optional, leave empty for all)</label><input type="text" id="param-column" placeholder="Column name">';
656
+ break;
657
+ case 'fill_nulls':
658
+ paramsHtml = `
659
+ <label>Column (optional)</label><input type="text" id="param-column" placeholder="Column name">
660
+ <label>Strategy</label>
661
+ <select id="param-strategy">
662
+ <option value="mean">Mean</option>
663
+ <option value="median">Median</option>
664
+ <option value="mode">Mode</option>
665
+ <option value="forward_fill">Forward Fill</option>
666
+ <option value="backward_fill">Backward Fill</option>
667
+ </select>
668
+ `;
669
+ break;
670
+ case 'remove_duplicates':
671
+ paramsHtml = '<label>Columns (optional, comma-separated)</label><input type="text" id="param-columns" placeholder="col1, col2">';
672
+ break;
673
+ case 'filter_rows':
674
+ paramsHtml = `
675
+ <label>Column</label><input type="text" id="param-column" placeholder="Column name">
676
+ <label>Operator</label>
677
+ <select id="param-operator">
678
+ <option value="==">==</option>
679
+ <option value="!=">!=</option>
680
+ <option value=">">></option>
681
+ <option value="<"><</option>
682
+ <option value=">=">>=</option>
683
+ <option value="<="><=</option>
684
+ <option value="contains">contains</option>
685
+ </select>
686
+ <label>Value</label><input type="text" id="param-value" placeholder="Value">
687
+ `;
688
+ break;
689
+ case 'drop_columns':
690
+ paramsHtml = '<label>Columns (comma-separated)</label><input type="text" id="param-columns" placeholder="col1, col2">';
691
+ break;
692
+ case 'convert_types':
693
+ paramsHtml = `
694
+ <label>Column</label><input type="text" id="param-column" placeholder="Column name">
695
+ <label>Data Type</label>
696
+ <select id="param-dtype">
697
+ <option value="str">String</option>
698
+ <option value="int">Integer</option>
699
+ <option value="float">Float</option>
700
+ <option value="datetime">Datetime</option>
701
+ </select>
702
+ `;
703
+ break;
704
+ case 'validate_email':
705
+ paramsHtml = `
706
+ <label>Column</label><input type="text" id="param-column" value="email" placeholder="Column name">
707
+ <label><input type="checkbox" id="param-drop_invalid"> Drop invalid emails</label>
708
+ `;
709
+ break;
710
+ case 'outlier_removal':
711
+ paramsHtml = `
712
+ <label>Column</label><input type="text" id="param-column" placeholder="Column name">
713
+ <label>IQR Multiplier</label><input type="number" id="param-multiplier" value="1.5" step="0.1">
714
+ `;
715
+ break;
716
+ case 'normalize':
717
+ paramsHtml = `
718
+ <label>Column</label><input type="text" id="param-column" placeholder="Column name">
719
+ <label>Method</label>
720
+ <select id="param-method">
721
+ <option value="minmax">Min-Max</option>
722
+ <option value="zscore">Z-Score</option>
723
+ </select>
724
+ `;
725
+ break;
726
+ }
727
 
728
+ form.innerHTML = paramsHtml;
729
+ modal.classList.remove('hidden');
730
  }
731
 
732
+ function closeModal() {
733
+ document.getElementById('action-modal').classList.add('hidden');
734
+ currentAction = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
735
  }
736
 
737
+ async function executeAction() {
738
+ if (!currentAction) return;
 
 
 
 
 
739
 
740
+ const params = {};
741
+ const column = document.getElementById('param-column')?.value;
742
+ if (column) params.column = column;
 
 
 
 
 
 
 
 
743
 
744
+ const strategy = document.getElementById('param-strategy')?.value;
745
+ if (strategy) params.strategy = strategy;
 
 
 
 
746
 
747
+ const columns = document.getElementById('param-columns')?.value;
748
+ if (columns) params.columns = columns.split(',').map(c => c.trim());
 
 
 
 
 
 
 
 
 
 
 
 
 
749
 
750
+ const operator = document.getElementById('param-operator')?.value;
751
+ if (operator) params.operator = operator;
 
 
 
752
 
753
+ const value = document.getElementById('param-value')?.value;
754
+ if (value) params.value = value;
 
 
755
 
756
+ const dtype = document.getElementById('param-dtype')?.value;
757
+ if (dtype) params.dtype = dtype;
 
 
 
 
 
 
 
 
 
 
758
 
759
+ const multiplier = document.getElementById('param-multiplier')?.value;
760
+ if (multiplier) params.multiplier = parseFloat(multiplier);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
761
 
762
+ const method = document.getElementById('param-method')?.value;
763
+ if (method) params.method = method;
 
 
 
 
764
 
765
+ const dropInvalid = document.getElementById('param-drop_invalid')?.checked;
766
+ if (dropInvalid) params.drop_invalid = true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
767
 
768
  try {
769
+ addLog(`Executing: ${currentAction}...`, 'info');
770
  const res = await fetch('/step', {
771
  method: 'POST',
772
  headers: { 'Content-Type': 'application/json' },
773
+ body: JSON.stringify({ action_type: currentAction, params })
 
 
 
 
 
 
774
  });
 
775
  const data = await res.json();
776
 
777
+ if (data.success || data.status === 'success') {
778
+ totalReward += (data.reward || data.data?.reward || 0);
779
+ document.getElementById('reward-value').textContent = totalReward.toFixed(4);
780
+ document.getElementById('step-value').textContent = (data.observation?.step_count || data.data?.observation?.step_count || 0);
781
+ addLog(`${currentAction}: reward=${(data.reward || data.data?.reward || 0).toFixed(4)}`, 'success');
782
+ await loadDatasetInfo();
783
+ } else {
784
+ addLog(`${currentAction} failed`, 'error');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
785
  }
786
  } catch (e) {
787
+ addLog(`Error: ${e.message}`, 'error');
788
  }
789
+
790
  closeModal();
791
  }
792
 
793
+ async function revertAction() {
794
+ try {
795
+ const res = await fetch('/revert', { method: 'POST' });
796
+ const data = await res.json();
797
+ addLog('Last action reverted', 'warning');
798
+ await loadDatasetInfo();
799
+ } catch (e) {
800
+ addLog(`Revert failed: ${e.message}`, 'error');
801
+ }
802
  }
803
 
804
+ async function submitSolution() {
805
  try {
806
+ addLog('Submitting solution...', 'info');
807
+ const res = await fetch('/submit', { method: 'POST' });
808
+ const data = await res.json();
809
+
810
+ const score = data.final_score || data.grade?.final_score || 0;
811
+ const feedback = data.grade?.feedback || '';
812
+ document.getElementById('final-score').textContent = score.toFixed(2);
813
+ document.getElementById('score-feedback').textContent = feedback;
814
+ document.getElementById('score-value').textContent = score.toFixed(2);
815
+ document.getElementById('score-modal').classList.remove('hidden');
816
+ addLog(`Submitted! Score: ${score.toFixed(2)}`, 'success');
817
  } catch (e) {
818
+ addLog(`Submit failed: ${e.message}`, 'error');
819
  }
820
  }
821
 
822
+ function closeScoreModal() {
823
+ document.getElementById('score-modal').classList.add('hidden');
824
+ }
825
+
826
+ function updateStatus(data) {
827
+ document.getElementById('status-value').textContent = 'Ready';
828
+ document.getElementById('status-value').className = 'value healthy';
829
+ document.getElementById('task-value').textContent = data.task_id || '-';
830
+ }
831
+
832
+ function addLog(message, type = 'info') {
833
+ const container = document.getElementById('log-container');
834
+ const time = new Date().toLocaleTimeString();
835
+ const entry = document.createElement('div');
836
+ entry.className = 'log-entry';
837
+
838
+ let typeClass = '';
839
+ if (type === 'success') typeClass = 'reward';
840
+ else if (type === 'error') typeClass = 'error';
841
+
842
+ entry.innerHTML = `<span class="timestamp">${time}</span><span class="${typeClass}">${message}</span>`;
843
+ container.insertBefore(entry, container.firstChild);
844
+
845
+ // Keep only last 50 entries
846
+ while (container.children.length > 50) {
847
+ container.removeChild(container.lastChild);
848
+ }
849
+ }
850
 
851
+ async function showSampleData() {
852
+ try {
853
+ const res = await fetch('/dataset');
854
  const data = await res.json();
855
 
856
+ if (!data.columns || data.columns.length === 0) {
857
+ addLog('No dataset loaded. Start a task first.', 'warning');
858
+ return;
859
+ }
860
+
861
+ // Create sample data modal
862
+ const modal = document.createElement('div');
863
+ modal.className = 'modal-overlay';
864
+ modal.id = 'sample-data-modal';
865
+
866
+ let tableHtml = '<table class="dataset-table"><thead><tr>';
867
+ data.columns.forEach(col => {
868
+ tableHtml += `<th>${col}</th>`;
869
+ });
870
+ tableHtml += '</tr></thead><tbody>';
871
+
872
+ // Show first 5 rows as sample
873
+ const sampleRows = Math.min(5, data.shape?.[0] || 0);
874
+ for (let i = 0; i < sampleRows; i++) {
875
+ tableHtml += '<tr>';
876
+ data.columns.forEach(col => {
877
+ const nullCount = data.null_counts?.[col] || 0;
878
+ const isNull = nullCount > 0 && Math.random() < (nullCount / (data.shape?.[0] || 1));
879
+ tableHtml += `<td>${isNull ? '<span style="color: var(--warning);">NULL</span>' : `Sample ${col} ${i+1}`}</td>`;
880
+ });
881
+ tableHtml += '</tr>';
882
  }
883
+ tableHtml += '</tbody></table>';
884
+
885
+ modal.innerHTML = `
886
+ <div class="modal" style="max-width: 800px;">
887
+ <h3>📊 Sample Data Preview</h3>
888
+ <p style="color: var(--text-muted); margin-bottom: 16px;">
889
+ Showing ${sampleRows} sample rows from ${data.shape?.[0] || 0} total rows
890
+ </p>
891
+ <div style="overflow-x: auto;">
892
+ ${tableHtml}
893
+ </div>
894
+ <div class="modal-actions">
895
+ <button class="btn-primary" onclick="document.getElementById('sample-data-modal').remove()">Close</button>
896
+ </div>
897
+ </div>
898
+ `;
899
+
900
+ document.body.appendChild(modal);
901
+ addLog('Sample data preview opened', 'info');
902
  } catch (e) {
903
+ addLog(`Failed to load sample data: ${e.message}`, 'error');
904
  }
905
  }
 
 
 
 
 
906
  </script>
907
  </body>
908
+ </html>
tasks/employee_demo.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Employee Dataset Demo Task
3
+ Real HR Employee Attrition Dataset
4
+ """
5
+
6
+ import pandas as pd
7
+ import numpy as np
8
+ from typing import Dict, Any
9
+
10
+ TASK_ID = "employee_demo"
11
+ DIFFICULTY = "demo"
12
+ DESCRIPTION = "Real employee dataset. Practice cleaning real HR data with missing values, formatting issues and duplicates."
13
+
14
+
15
+ def generate_dataset() -> pd.DataFrame:
16
+ """Load and return the employee dataset with intentional errors added for cleaning practice"""
17
+ # Load original dataset
18
+ df = pd.read_csv("/Users/sahil/Downloads/Employee.csv")
19
+
20
+ # Add realistic cleaning challenges
21
+ df = df.copy()
22
+
23
+ # 1. Add 5% missing values randomly
24
+ mask = np.random.random(df.shape) < 0.05
25
+ df[mask] = np.nan
26
+
27
+ # 2. Add duplicate rows
28
+ duplicates = df.sample(n=25).copy()
29
+ df = pd.concat([df, duplicates], ignore_index=True)
30
+
31
+ # 3. Mess up case in Education column
32
+ df['Education'] = df['Education'].apply(
33
+ lambda x: x.lower() if isinstance(x, str) and np.random.random() < 0.3 else x
34
+ )
35
+
36
+ # 4. Add some invalid ages
37
+ invalid_idx = np.random.choice(df.index, 12)
38
+ df.loc[invalid_idx, 'Age'] = np.random.randint(100, 150, size=12)
39
+
40
+ # 5. Mess up gender values
41
+ df['Gender'] = df['Gender'].apply(
42
+ lambda x: x.upper() if isinstance(x, str) and np.random.random() < 0.25 else x
43
+ )
44
+
45
+ return df
46
+
47
+
48
+ def get_task_config() -> Dict[str, Any]:
49
+ return {
50
+ "task_id": TASK_ID,
51
+ "difficulty": DIFFICULTY,
52
+ "description": DESCRIPTION,
53
+ "expected_actions": [
54
+ "fill_missing",
55
+ "drop_duplicates",
56
+ "standardize_format",
57
+ "detect_outliers"
58
+ ],
59
+ "max_steps": 12,
60
+ "target_score": 0.85
61
+ }