Mihir Mungara commited on
Commit
d980cf6
Β·
1 Parent(s): 601f21d

changes made in readme.md file

Browse files
Files changed (1) hide show
  1. README.md +541 -329
README.md CHANGED
@@ -1,493 +1,705 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Data Cleaning OpenEnv
3
- sdk: docker
4
- tags:
5
- - openenv
6
- - data-cleaning
7
- - reinforcement-learning
8
- - agent
9
- - real-world
10
- - tabular
11
- - pandas
12
- ---
13
 
14
- # 🧹 Data Cleaning OpenEnv
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- > *An OpenEnv-compliant environment where AI agents learn to clean messy real-world datasets β€” step by step, with dense reward signals and sequence-aware penalties.*
17
 
18
- [![Live Demo](https://img.shields.io/badge/πŸš€%20Live%20Demo-HF%20Space-blue)](https://thorodin103-data-cleaning.hf.space/ui)
19
- [![HF Space](https://img.shields.io/badge/πŸ€—%20HuggingFace-Space-yellow)](https://huggingface.co/spaces/thorodin103/Data-cleaning/tree/main)
20
- [![OpenEnv Valid](https://img.shields.io/badge/openenv%20validate-βœ…%20passing-brightgreen)](#-openenv-spec-compliance)
21
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
22
- [![Docker](https://img.shields.io/badge/Docker-Ready-blue?logo=docker)](Dockerfile)
23
 
24
- | | |
25
  |---|---|
26
- | **Live UI** | https://thorodin103-data-cleaning.hf.space/ui |
27
- | **HF Repository** | https://huggingface.co/spaces/thorodin103/Data-cleaning/tree/main |
28
- | **API Base** | https://thorodin103-data-cleaning.hf.space |
29
- | **Validate** | https://thorodin103-data-cleaning.hf.space/validate |
30
- | **API Docs** | https://thorodin103-data-cleaning.hf.space/docs |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  ---
33
 
34
- ## 🌍 Why This Environment Exists
35
 
36
- Data engineers and analysts spend **up to 80% of their time cleaning data** before it can be used. Despite being one of the most universal tasks in the real world, no existing OpenEnv benchmark captures it.
37
 
38
- This environment fills that gap. An AI agent receives a dirty dataset and must apply a sequence of cleaning operations to match a gold-standard output. What makes it non-trivial:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- - **Order matters.** Filling missing values before fixing data types produces wrong results β€” the reward function knows this and penalizes it.
41
- - **Partial progress is rewarded.** Every action shifts the score, giving RL agents a dense learning signal rather than sparse end-of-episode feedback.
42
- - **Four difficulty tiers.** From a 3-step deduplication task to a full expert-level sales pipeline requiring case normalization, outlier removal, and schema validation β€” in the right sequence.
43
- - **Fully live.** The environment runs on Hugging Face Spaces, passes `openenv validate`, and exposes a REST API any agent can call against right now.
44
 
45
  ---
46
 
47
  ## 🎯 Tasks
48
 
49
- | Task ID | Difficulty | Max Steps | Dataset | What the Agent Must Do |
 
 
50
  |---|---|---|---|---|
51
- | `easy_dedup_rename` | 🟒 Easy | 10 | Employee records | Remove duplicate rows + rename columns to snake_case |
52
- | `medium_missing_dtype` | 🟑 Medium | 15 | Customer records | Fill missing values (mean/mode) + fix wrong data types |
53
- | `hard_full_pipeline` | πŸ”΄ Hard | 20 | Orders data | Full pipeline: dedup β†’ fill β†’ fix types β†’ remove outliers β†’ validate schema |
54
- | `expert_sales_pipeline` | ⚫ Expert | 25 | Sales transactions | Expert pipeline with case standardization + all of the above, in strict order |
55
 
56
  ---
57
 
58
- ### Task 1 β€” Easy: Deduplicate & Rename (`easy_dedup_rename`)
 
 
59
 
60
- The agent receives an employee dataset with duplicate rows and column names like `EMP ID`, `DEP T`, `SAL ARY`. It must remove the duplicates and rename all columns to `snake_case`.
 
 
61
 
62
- **Dirty dataset sample:**
63
  ```
64
- EMP ID | EMP NAME | DEP T | SAL ARY | AGE
65
- 101 | Alice | HR | 50000 | 28
66
- 102 | Bob | IT | 60000 | 35
67
- 102 | Bob | IT | 60000 | 35 ← duplicate
68
- 103 | Charlie | IT | 55000 | 29
69
- 103 | Charlie | IT | 55000 | 29 ← duplicate
70
  ```
71
 
72
- **Scoring:**
73
- - `duplicate_score` (0.5) β€” how close row count is to gold
74
- - `schema_score` (0.5) β€” proportion of column names matching gold
75
-
76
- **Optimal actions:** `remove_duplicates` β†’ `rename_columns` β†’ `finish` (3 steps, score = 0.99)
77
 
78
  ---
79
 
80
- ### Task 2 β€” Medium: Missing Values & Types (`medium_missing_dtype`)
81
 
82
- The agent receives a customer dataset where numeric columns like `age`, `purchases`, and `salary` contain missing values and are stored as the wrong type (strings instead of numbers).
83
 
84
- **Issues present:**
85
- - `age` column: 2 missing values, stored as `object` dtype
86
- - `purchases` column: 3 missing values
87
- - `salary` column: stored as string, needs numeric conversion
88
 
89
- **Scoring:**
90
- - `missing_score` (0.5) β€” proportion of missing values correctly filled
91
- - `dtype_score` (0.5) β€” proportion of columns with correct data types
 
 
 
92
 
93
- **Key challenge:** The agent must fix types *before* filling missing values with mean/median β€” doing it backwards triggers a sequence penalty.
94
 
95
  ---
96
 
97
- ### Task 3 β€” Hard: Full Pipeline (`hard_full_pipeline`)
98
 
99
- The agent receives an orders dataset with all classes of data quality issues simultaneously. Must execute the full cleaning pipeline in the correct sequence.
100
 
101
- **Issues present:**
102
- - Duplicate order IDs
103
- - Missing values in `quantity`, `product`, and `rating` columns
104
- - `quantity` and `price` stored as wrong types
105
- - Extreme outliers (e.g., `quantity=999`, `price=99999`)
106
- - Column names not matching gold schema
107
 
108
- **Scoring:** All 5 components weighted equally at 0.2 each:
109
- `duplicate_score + missing_score + dtype_score + outlier_score + schema_score`
 
 
110
 
111
- **Why it's hard:** The agent must not only apply all 5 operations but apply them in the correct order. Doing `fill_missing` before `remove_duplicates` incurs a -0.08 penalty. Doing `remove_outliers` before `fix_dtype` incurs another. Penalties compound.
112
 
113
  ---
114
 
115
- ### Task 4 β€” Expert: Sales Pipeline (`expert_sales_pipeline`)
 
 
 
 
 
 
 
 
 
116
 
117
- The hardest task. A sales transaction dataset with messy region names (`north`, `EAST`, `South` β€” all meaning the same thing), missing sales reps and commission values, mixed column naming conventions, and outlier transactions.
118
 
119
- **Issues present:**
120
- - Duplicate transactions
121
- - Mixed-case region names requiring standardization
122
- - Missing `SALES REP` and `COMMISSION %` values
123
- - Inconsistent column naming (`Transaction_ID`, `SALES REP`, `Sale Amount` β€” all need snake_case)
124
- - Outlier transactions in `Sale Amount` and `Units Sold`
125
 
126
- **Scoring:**
127
- - `duplicate_score` (0.15), `missing_score` (0.20), `dtype_score` (0.20), `outlier_score` (0.20), `schema_score` (0.25)
128
 
129
- **Total max steps: 25** β€” the most complex episode in the environment.
 
 
 
 
 
130
 
131
  ---
132
 
133
- ## πŸ‘οΈ Observation Space
 
 
 
 
 
134
 
135
- At every step, the agent receives a structured observation describing the current state of the dataset:
136
 
 
137
  ```json
138
- {
139
- "task_id": "easy_dedup_rename",
140
- "step": 1,
141
- "dataset_info": {
142
- "total_rows": 8,
143
- "total_columns": 5,
144
- "has_duplicates": true,
145
- "has_missing": false
146
- },
147
- "columns": ["EMP ID", "EMP NAME", "DEP T", "SAL ARY", "AGE"],
148
- "shape": [8, 5],
149
- "missing_values": {"EMP ID": 0, "EMP NAME": 0, "DEP T": 0, "SAL ARY": 0, "AGE": 0},
150
- "dtypes": {"EMP ID": "int64", "EMP NAME": "object", "DEP T": "object", "SAL ARY": "int64", "AGE": "int64"},
151
- "duplicate_count": 2,
152
- "sample_rows": [
153
- {"EMP ID": 101, "EMP NAME": "Alice", "DEP T": "HR", "SAL ARY": 50000, "AGE": 28},
154
- {"EMP ID": 102, "EMP NAME": "Bob", "DEP T": "IT", "SAL ARY": 60000, "AGE": 35},
155
- {"EMP ID": 102, "EMP NAME": "Bob", "DEP T": "IT", "SAL ARY": 60000, "AGE": 35}
156
- ],
157
- "available_operations": ["remove_duplicates", "rename_columns", "finish"],
158
- "task_description": "Remove duplicate rows and rename columns to snake_case in an employee dataset.",
159
- "message": "Environment reset. Start cleaning!"
160
- }
161
  ```
 
 
162
 
163
- **What's hidden from the agent:** the gold-standard dataset. The agent only sees the dirty data and its own progress metrics.
 
 
 
 
 
 
 
164
 
165
  ---
166
 
167
- ## ⚑ Action Space
 
 
 
 
 
 
 
 
 
168
 
169
- Actions are JSON objects with an `operation` and optional `parameters`:
170
 
 
171
  ```json
172
- {
173
- "operation": "fill_missing",
174
- "parameters": {
175
- "column": "age",
176
- "strategy": "mean"
177
- }
178
- }
179
  ```
 
 
 
 
180
 
181
- | Operation | Parameters | Description |
182
- |---|---|---|
183
- | `remove_duplicates` | `subset` (optional list of columns) | Drop duplicate rows |
184
- | `fill_missing` | `column` (optional), `strategy`: `mean`/`median`/`mode`/`ffill` | Fill NaN values |
185
- | `fix_dtype` | `column` (optional), `dtype`: `int`/`float`/`str`/`auto` | Cast column types |
186
- | `remove_outliers` | `column` (optional), `method`: `iqr`/`zscore` | Remove statistical outliers |
187
- | `rename_columns` | `mapping` (optional dict, auto snake_case if omitted) | Rename column headers |
188
- | `validate_schema` | β€” | Check columns against gold standard, returns feedback |
189
- | `finish` | β€” | End the episode and lock in the final score |
190
 
191
  ---
192
 
193
- ## πŸ† Reward Function
 
 
 
 
 
 
 
194
 
195
- Rewards are computed **after every action** β€” dense signal at every step, not just at episode end.
196
 
197
- ### Components
 
 
 
 
198
 
199
- | Component | Formula | Weight (hard task) |
200
- |---|---|---|
201
- | `duplicate_score` | `min(1, gold_rows / curr_rows)` | 0.20 |
202
- | `missing_score` | `filled_so_far / total_needed` | 0.20 |
203
- | `dtype_score` | `matching_dtypes / total_columns` | 0.20 |
204
- | `outlier_score` | `1 βˆ’ outlier_rows / total_rows` | 0.20 |
205
- | `schema_score` | `matching_cols / gold_cols` | 0.20 |
206
- | `penalty` | sequence + step violations (subtracted) | β€” |
207
 
208
- **Total reward = weighted component sum βˆ’ penalties, clamped to [0.0, 1.0]**
209
 
210
- ### The Sequence-Penalty Mechanic
211
 
212
- This is the environment's core design innovation. The optimal cleaning sequence is:
213
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  ```
215
- 1. remove_duplicates β†’ clean redundant data first
216
- 2. fix_dtype β†’ establish correct column types
217
- 3. fill_missing β†’ impute based on correct types
218
- 4. remove_outliers β†’ after type-correct distributions
219
- 5. validate_schema β†’ final check
220
  ```
221
 
222
- Violations are penalized:
223
 
224
  | Violation | Penalty |
225
  |---|---|
226
- | Out-of-order operation (e.g., `fill_missing` before `fix_dtype`) | βˆ’0.08 |
227
- | Repeated identical operation back-to-back | βˆ’0.02 |
228
- | Exceeding 80% of the step budget | βˆ’0.05 |
229
- | Total penalty cap | 0.25 |
 
 
 
230
 
231
- **Why this matters for agent training:** An agent that randomly applies operations will peak early then watch its score decay as penalties accumulate. This forces the agent to learn the *why* behind the sequence, not just the *what*.
232
 
233
- From the baseline run on the hard task β€” the agent peaks at **0.885** on step 5 then degrades to **0.664** by step 20 by looping between `remove_outliers` and `fix_dtype` in a penalty-accumulating cycle. This is a real learning signal.
234
 
235
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
- ## πŸ“Š Baseline Scores
 
238
 
239
- Baseline agent: **GPT-4o-mini** via OpenAI API, temperature 0.1.
240
 
241
- | Task | Difficulty | Final Score | Steps Used | Notes |
242
- |---|---|---|---|---|
243
- | `easy_dedup_rename` | 🟒 Easy | **0.99** | 3 / 10 | Near-perfect β€” follows optimal sequence |
244
- | `medium_missing_dtype` | 🟑 Medium | **0.70** | 15 / 15 | Hits step limit, oscillates on dtype/fill |
245
- | `hard_full_pipeline` | πŸ”΄ Hard | **0.6636** | 20 / 20 | Peaks at 0.885 then degrades from looping |
246
- | `expert_sales_pipeline` | ⚫ Expert | **~0.55** | ~20 / 25 | Case normalization is the main challenge |
247
- | **Average** | β€” | **~0.75** | β€” | Significant room for better agents |
248
 
249
- The gap between easy (0.99) and hard (0.66) demonstrates genuine difficulty scaling. The hard task's reward degradation curve is directly caused by sequence violations β€” a smarter agent that plans its sequence upfront would score 0.88+.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
  ---
252
 
253
- ## πŸ”Œ API Reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
- The environment runs as a live REST API. All endpoints are accessible now:
256
 
 
 
 
 
 
257
  ```
258
- Base URL: https://thorodin103-data-cleaning.hf.space
 
 
 
 
 
259
  ```
260
 
261
- | Method | Endpoint | Description |
262
- |---|---|---|
263
- | `GET` | `/health` | Health check β€” returns `{"status": "ok"}` |
264
- | `GET` | `/tasks` | List all 4 tasks with metadata |
265
- | `POST` | `/reset/{task_id}` | Reset environment, returns initial observation |
266
- | `POST` | `/step/{task_id}` | Submit action, returns observation + reward + done |
267
- | `GET` | `/state/{task_id}` | Current internal state (for debugging) |
268
- | `GET` | `/validate` | OpenEnv compliance check β€” all 4 tasks pass |
269
- | `GET` | `/leaderboard` | View agent rankings |
270
- | `GET` | `/docs` | Interactive Swagger UI |
271
- | `GET` | `/ui` | Live browser demo |
272
 
273
- **Live validate response (confirmed passing):**
 
 
 
274
  ```json
275
  {
276
  "openenv_valid": true,
277
  "tasks": {
278
- "easy_dedup_rename": {"status": "passed", "reset": "ok", "step": "ok", "state": "ok"},
279
- "medium_missing_dtype": {"status": "passed", "reset": "ok", "step": "ok", "state": "ok"},
280
- "hard_full_pipeline": {"status": "passed", "reset": "ok", "step": "ok", "state": "ok"},
281
- "expert_sales_pipeline": {"status": "passed", "reset": "ok", "step": "ok", "state": "ok"}
282
  }
283
  }
284
  ```
285
 
286
  ---
287
 
288
- ## πŸš€ Setup & Usage
289
-
290
- ### Option 1 β€” Use the live API (no setup needed)
291
 
292
- ```python
293
- import requests
294
 
295
- BASE = "https://thorodin103-data-cleaning.hf.space"
296
 
297
- # Reset the hard task
298
- obs = requests.post(f"{BASE}/reset/hard_full_pipeline").json()
299
- print(obs["observation"]["duplicate_count"]) # β†’ 3
300
 
301
- # Take an action
302
- result = requests.post(f"{BASE}/step/hard_full_pipeline", json={
303
- "operation": "remove_duplicates",
304
- "parameters": {}
305
- }).json()
306
 
307
- print(result["reward"]["total"]) # β†’ 0.6955
308
- print(result["reward"]["duplicate_score"]) # β†’ 1.0
309
- print(result["done"]) # β†’ False
 
 
 
 
 
310
 
311
- # Get state
312
- state = requests.get(f"{BASE}/state/hard_full_pipeline").json()
 
 
 
 
 
313
  ```
314
 
315
- ### Option 2 β€” Run locally with Docker
316
 
317
- ```bash
318
- git clone https://huggingface.co/spaces/thorodin103/Data-cleaning
319
- cd Data-cleaning
320
 
321
- docker build -t data-cleaning-openenv .
322
- docker run -p 7860:7860 data-cleaning-openenv
323
 
324
- # Environment now live at http://localhost:7860
325
- # UI at http://localhost:7860/ui
326
- ```
 
 
 
 
 
 
327
 
328
- ### Option 3 β€” Run locally without Docker
329
 
330
- ```bash
331
- pip install fastapi uvicorn pydantic pandas numpy openai python-dotenv
332
 
333
- uvicorn main:app --host 0.0.0.0 --port 7860 --reload
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  ```
 
 
335
 
336
- ### Option 4 β€” Run the baseline inference script
337
 
338
- ```bash
339
- export OPENAI_API_KEY=your_key_here
340
- export MODEL_NAME=gpt-4o-mini
341
- export API_BASE_URL=https://api.openai.com/v1
342
- export HF_TOKEN=your_hf_token
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
- python inference.py
345
- # Runs all 3 core tasks, prints [START] / [STEP] / [END] trace
346
- # Saves results to baseline_results.json
 
 
 
 
 
 
 
 
 
 
347
  ```
 
348
 
349
  ---
350
 
351
- ## πŸ’» Programmatic Usage (Python)
 
 
 
 
 
 
 
 
 
 
 
 
352
 
353
- ```python
354
- from environment import DataCleaningEnv
355
- from models import Action
356
 
357
- # Initialize any task
358
- env = DataCleaningEnv(task_id="hard_full_pipeline")
359
 
360
- # Reset β€” returns StepResult with initial observation
361
- result = env.reset()
362
- obs = result.observation
363
- print(f"Dirty dataset: {obs.shape} with {obs.duplicate_count} duplicates")
364
- # β†’ Dirty dataset: [20, 8] with 3 duplicates
365
 
366
- # Optimal sequence for the hard task
367
- actions = [
368
- Action(operation="remove_duplicates", parameters={}),
369
- Action(operation="fix_dtype", parameters={"dtype": "auto"}),
370
- Action(operation="fill_missing", parameters={"strategy": "mean"}),
371
- Action(operation="remove_outliers", parameters={"method": "iqr"}),
372
- Action(operation="validate_schema", parameters={}),
373
- Action(operation="finish", parameters={}),
374
- ]
375
 
376
- for action in actions:
377
- result = env.step(action)
378
- print(f"{action.operation:20s} β†’ reward: {result.reward.total:.4f}")
 
 
 
379
 
380
- # β†’ remove_duplicates β†’ reward: 0.6955
381
- # β†’ fix_dtype β†’ reward: 0.8235
382
- # β†’ fill_missing β†’ reward: 0.8852 (no penalty β€” correct order)
383
- # β†’ remove_outliers β†’ reward: 0.9800
384
- # β†’ validate_schema β†’ reward: 0.9800
385
- # β†’ finish β†’ reward: 0.9800
386
 
387
- # Full state inspection
388
- state = env.state()
389
- print(state["reward_history"])
390
- # β†’ [0.6955, 0.8235, 0.8852, 0.9800, 0.9800, 0.9800]
391
- ```
392
 
393
  ---
394
 
395
- ## πŸ§ͺ OpenEnv Spec Compliance
396
 
397
- All four required interface methods are implemented:
398
 
399
- | Method | Signature | Returns |
400
- |---|---|---|
401
- | `reset()` | `env.reset()` | `StepResult` (obs + reward + done + info) |
402
- | `step(action)` | `env.step(Action)` | `StepResult` |
403
- | `state()` | `env.state()` | `Dict` with full internal state |
404
- | *(validation)* | `GET /validate` | `{"openenv_valid": true, ...}` |
405
 
406
- All models are strict Pydantic v2:
 
 
 
407
 
408
- ```python
409
- class Action(BaseModel):
410
- operation: str
411
- parameters: Dict[str, Any] = {}
412
-
413
- class Observation(BaseModel):
414
- task_id, step, dataset_info, columns, shape,
415
- missing_values, dtypes, duplicate_count,
416
- sample_rows, available_operations,
417
- task_description, message
418
 
419
- class Reward(BaseModel):
420
- total, duplicate_score, missing_score,
421
- dtype_score, outlier_score, schema_score, penalty
422
 
423
- class StepResult(BaseModel):
424
- observation: Observation
425
- reward: Reward
426
- done: bool
427
- info: Dict
428
- ```
429
 
430
  ---
431
 
432
- ## πŸ“ Project Structure
433
 
434
- ```
435
- Data-cleaning/
436
- β”œβ”€β”€ main.py # FastAPI server β€” all REST endpoints
437
- β”œβ”€β”€ environment.py # DataCleaningEnv β€” reset/step/state + all ops
438
- β”œβ”€β”€ models.py # Pydantic models β€” Action/Observation/Reward/StepResult
439
- β”œβ”€β”€ inference.py # Baseline LLM agent β€” runs all tasks, outputs traces
440
- β”œβ”€β”€ openenv.yaml # OpenEnv metadata β€” tasks, spaces, API config
441
- β”œβ”€β”€ Dockerfile # Single-stage Python 3.10 container, port 7860
442
- β”œβ”€β”€ README.md # This file
443
- β”œβ”€β”€ baseline_results.json # Pre-run baseline scores (gpt-4o-mini)
444
- └── datasets/
445
- β”œβ”€β”€ task_metadata.json # Task configs, allowed ops, scoring weights
446
- β”œβ”€β”€ easy/
447
- β”‚ β”œβ”€β”€ dirty.csv # Employee dataset with duplicates + bad column names
448
- β”‚ └── gold.csv # Ground truth
449
- β”œβ”€β”€ medium/
450
- β”‚ β”œβ”€β”€ dirty.csv # Customer dataset with missing values + wrong types
451
- β”‚ └── gold.csv
452
- β”œβ”€β”€ hard/
453
- β”‚ β”œβ”€β”€ dirty.csv # Orders dataset with all issue types
454
- β”‚ └── gold.csv
455
- └── expert/
456
- β”œβ”€β”€ dirty.csv # Sales data with case normalization + full pipeline
457
- └── gold.csv
458
- ```
459
 
460
  ---
461
 
462
- ## πŸ”¬ Research Applications
463
 
464
- This environment can be used to study:
 
 
 
465
 
466
- - **Sequence learning in RL** β€” Can agents learn optimal operation ordering from reward signals alone?
467
- - **Dense vs. sparse rewards** β€” Compare agent performance with/without the sequence penalties disabled
468
- - **Tool use planning** β€” Does the agent build a plan before acting, or does it react greedily?
469
- - **Generalization** β€” Train on easy/medium tasks, evaluate zero-shot on expert
470
- - **LLM agent benchmarking** β€” Evaluate frontier models on a deterministic, math-graded task with no LLM-judge subjectivity
471
 
472
  ---
473
 
474
- ## πŸ”— Links
475
 
476
- | Resource | URL |
 
 
477
  |---|---|
478
- | πŸš€ Live Demo (UI) | https://thorodin103-data-cleaning.hf.space/ui |
479
- | πŸ€— HuggingFace Space | https://huggingface.co/spaces/thorodin103/Data-cleaning/tree/main |
480
- | βœ… OpenEnv Validate | https://thorodin103-data-cleaning.hf.space/validate |
481
- | πŸ“– API Docs (Swagger) | https://thorodin103-data-cleaning.hf.space/docs |
482
- | πŸ“‹ Task List | https://thorodin103-data-cleaning.hf.space/tasks |
483
- | πŸ… Leaderboard | https://thorodin103-data-cleaning.hf.space/leaderboard |
484
 
485
  ---
486
 
487
- ## πŸ“œ License
488
 
489
- MIT β€” free for research and commercial use.
490
 
491
  ---
492
 
493
- *Built for the OpenEnv Hackathon. Powered by FastAPI + Pandas + Pydantic + Hugging Face Spaces.*
 
 
 
 
 
 
 
 
 
1
+ # 🧹 CleanifyAI β€” Data Cleaning OpenEnv
2
+
3
+ <div align="center">
4
+
5
+ [![HuggingFace Space](https://img.shields.io/badge/πŸ€—%20HuggingFace-Space-blue)](https://huggingface.co/spaces/cleanify-ai/Data-cleaning)
6
+ [![GitHub](https://img.shields.io/badge/GitHub-ReverseCoder1%2FCleanifyAI-black?logo=github)](https://github.com/ReverseCoder1/CleanifyAI)
7
+ [![OpenEnv](https://img.shields.io/badge/OpenEnv-Compliant-green)](https://huggingface.co/spaces/cleanify-ai/Data-cleaning)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+ [![Python 3.10](https://img.shields.io/badge/Python-3.10-blue?logo=python)](https://python.org)
10
+ [![FastAPI](https://img.shields.io/badge/FastAPI-0.104.1-009688?logo=fastapi)](https://fastapi.tiangolo.com)
11
+
12
+ **A reinforcement-learning environment where AI agents learn to clean real-world messy datasets β€” step by step.**
13
+
14
+ *Scaler Γ— OpenEnv Hackathon Submission*
15
+
16
+ [πŸš€ Live API](https://thorodin103-data-cleaning-openenv.hf.space) Β· [πŸ“– Swagger Docs](https://thorodin103-data-cleaning-openenv.hf.space/docs) Β· [πŸ€— HuggingFace](https://huggingface.co/spaces/cleanify-ai/Data-cleaning)
17
+
18
+ </div>
19
+
20
  ---
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ ## πŸ“‹ Table of Contents
23
+
24
+ - [Overview](#-overview)
25
+ - [Project Structure](#-project-structure)
26
+ - [Setup & Installation](#-setup--installation)
27
+ - [Tasks](#-tasks)
28
+ - [Operations Reference](#-operations-reference)
29
+ - [Reward & Scoring System](#-reward--scoring-system)
30
+ - [API Reference](#-api-reference)
31
+ - [Inference Script](#-inference-script)
32
+ - [Data Models](#-data-models)
33
+ - [Datasets](#-datasets)
34
+ - [Troubleshooting](#-troubleshooting)
35
+ - [Baseline Scores](#-baseline-scores)
36
+ - [License](#-license)
37
+
38
+ ---
39
 
40
+ ## 🌟 Overview
41
 
42
+ **CleanifyAI** is a fully OpenEnv-compliant environment that challenges AI agents to autonomously clean messy, real-world datasets through a sequence of structured operations. It mimics professional data engineering pipelines and rewards agents that apply operations in the correct, logical order.
 
 
 
 
43
 
44
+ | Property | Value |
45
  |---|---|
46
+ | **Environment Name** | `data-cleaning-openenv` |
47
+ | **Tasks** | 4 (Easy, Medium, Hard, Expert) |
48
+ | **Operations** | 9 (dedup, fill, dtype fix, outlier removal, rename, validate, finish) |
49
+ | **Scoring** | Weighted multi-component, strictly in `(0, 1)` |
50
+ | **API** | OpenEnv-compliant REST via FastAPI |
51
+ | **Framework** | Python 3.10, FastAPI, Pandas, NumPy |
52
+ | **Inference** | OpenAI-compatible LLM client |
53
+ | **Deployed at** | `https://thorodin103-data-cleaning-openenv.hf.space` |
54
+
55
+ > ⚠️ **Score Constraint**: The Scaler platform rejects scores of exactly `0.0` or `1.0`. All scoring paths in this codebase clamp strictly to `(0.0001, 0.9999)`.
56
+
57
+ ---
58
+
59
+ ## πŸ“ Project Structure
60
+
61
+ ```
62
+ CleanifyAI/
63
+ β”‚
64
+ β”œβ”€β”€ inference.py # πŸ€– LLM agent β€” emits [START]/[STEP]/[END] stdout lines
65
+ β”œβ”€β”€ environment.py # πŸ‹οΈ Core OpenEnv environment & reward computation
66
+ β”œβ”€β”€ models.py # πŸ“¦ Pydantic models: Action, Observation, Reward, StepResult
67
+ β”œβ”€β”€ main.py # 🌐 FastAPI server with all REST endpoints
68
+ β”œβ”€β”€ Dockerfile # 🐳 Python 3.10-slim container, port 7860
69
+ β”œβ”€β”€ openenv.yaml # πŸ“„ OpenEnv spec manifest
70
+ β”œβ”€β”€ pyproject.toml # πŸ“¦ Python dependency config
71
+ β”œβ”€β”€ uv.lock # πŸ”’ Locked dependency versions
72
+ β”‚
73
+ β”œβ”€β”€ datasets/
74
+ β”‚ β”œβ”€β”€ task_metadata.json # βš™οΈ Per-task config (steps, operations, scoring weights)
75
+ β”‚ β”œβ”€β”€ easy/
76
+ β”‚ β”‚ β”œβ”€β”€ dirty.csv # πŸ—‘οΈ Employee dataset with duplicates + bad column names
77
+ β”‚ β”‚ └── gold.csv # βœ… Gold standard cleaned version
78
+ β”‚ β”œβ”€β”€ medium/
79
+ β”‚ β”‚ β”œβ”€β”€ dirty.csv # πŸ—‘οΈ Customer dataset with missing values + wrong dtypes
80
+ β”‚ β”‚ └── gold.csv # βœ… Gold standard
81
+ β”‚ β”œβ”€β”€ hard/
82
+ β”‚ β”‚ β”œβ”€β”€ dirty.csv # πŸ—‘οΈ Orders dataset requiring full pipeline
83
+ β”‚ β”‚ └── gold.csv # βœ… Gold standard
84
+ β”‚ └── expert/
85
+ β”‚ β”œβ”€β”€ dirty.csv # πŸ—‘οΈ Sales dataset β€” strict operation order required
86
+ β”‚ └── gold.csv # βœ… Gold standard
87
+ β”‚
88
+ β”œβ”€β”€ static/
89
+ β”‚ └── index.html # πŸ–₯️ Web UI for interactive exploration
90
+ β”‚
91
+ └── server/
92
+ └── app.py # πŸ”§ Server initialization module
93
+ ```
94
 
95
  ---
96
 
97
+ ## πŸš€ Setup & Installation
98
 
99
+ ### Prerequisites
100
 
101
+ - Python 3.10+
102
+ - Docker (for containerized deployment)
103
+ - A Hugging Face account (`HF_TOKEN`)
104
+ - An OpenAI-compatible API endpoint and model
105
+
106
+ ---
107
+
108
+ ### Local Development
109
+
110
+ **1. Clone the repository**
111
+ ```bash
112
+ git clone https://github.com/ReverseCoder1/CleanifyAI.git
113
+ cd CleanifyAI
114
+ ```
115
+
116
+ **2. Install dependencies**
117
+ ```bash
118
+ pip install fastapi==0.104.1 uvicorn==0.24.0 pydantic==2.5.0 \
119
+ pandas==2.1.3 numpy==1.26.2 openai>=2.7.2 \
120
+ pyyaml==6.0.1 python-dotenv==1.0.0
121
+ ```
122
+
123
+ **3. Create a `.env` file**
124
+ ```env
125
+ API_BASE_URL=https://api.openai.com/v1
126
+ MODEL_NAME=gpt-4o-mini
127
+ HF_TOKEN=your_hugging_face_token_here
128
+ ```
129
+
130
+ **4. Start the FastAPI server**
131
+ ```bash
132
+ uvicorn main:app --host 0.0.0.0 --port 7860 --reload
133
+ ```
134
+
135
+ - **API:** http://localhost:7860
136
+ - **Swagger UI:** http://localhost:7860/docs
137
+
138
+ ---
139
+
140
+ ### Docker Deployment
141
+
142
+ ```bash
143
+ # Build
144
+ docker build -t cleanify-ai .
145
+
146
+ # Run
147
+ docker run -p 7860:7860 \
148
+ -e HF_TOKEN=your_token \
149
+ -e MODEL_NAME=gpt-4o-mini \
150
+ -e API_BASE_URL=https://api.openai.com/v1 \
151
+ cleanify-ai
152
+ ```
153
+
154
+ ---
155
+
156
+ ### Run the Inference Agent
157
+
158
+ ```bash
159
+ python inference.py
160
+ ```
161
 
162
+ Runs the LLM agent across all 3 hackathon tasks and streams hackathon-spec log lines to stdout.
 
 
 
163
 
164
  ---
165
 
166
  ## 🎯 Tasks
167
 
168
+ Four progressively complex tasks. The hackathon evaluates **easy**, **medium**, and **hard**. Expert is available for extended benchmarking.
169
+
170
+ | Task ID | Difficulty | Max Steps | Key Operations | Scoring |
171
  |---|---|---|---|---|
172
+ | `easy_dedup_rename` | ⭐ Easy | 10 | `remove_duplicates`, `rename_columns` | dup 50% + schema 50% |
173
+ | `medium_missing_dtype` | ⭐⭐ Medium | 15 | `fill_missing_*`, `fix_dtype` | missing 50% + dtype 50% |
174
+ | `hard_full_pipeline` | ⭐⭐⭐ Hard | 20 | Full pipeline | 20% Γ— 5 components |
175
+ | `expert_sales_pipeline` | ⭐⭐⭐⭐ Expert | 25 | All 9 ops in strict order | Weighted (schema 25%) |
176
 
177
  ---
178
 
179
+ ### ⭐ Easy β€” `easy_dedup_rename`
180
+
181
+ **Dataset:** Employee records (`emp_id`, `emp_name`, `dept`, `salary`, `age`)
182
 
183
+ **Dirty conditions:**
184
+ - Duplicate rows
185
+ - Column names with spaces and inconsistent casing (`EMP ID`, `DEP T`, `SAL ARY`)
186
 
187
+ **Optimal sequence:**
188
  ```
189
+ remove_duplicates β†’ rename_columns β†’ finish
 
 
 
 
 
190
  ```
191
 
192
+ **Scoring:** `duplicate_score Γ— 0.5 + schema_score Γ— 0.5`
 
 
 
 
193
 
194
  ---
195
 
196
+ ### ⭐⭐ Medium β€” `medium_missing_dtype`
197
 
198
+ **Dataset:** Customer records (`customer_id`, `age`, `salary`, `gender`, `purchases`, `region`, `joined_date`)
199
 
200
+ **Dirty conditions:**
201
+ - NaN values in `age`, `salary`, `gender`, `region`
202
+ - `salary` stored as `object` instead of `float`
 
203
 
204
+ **Optimal sequence:**
205
+ ```
206
+ fill_missing_mean (numeric columns)
207
+ fill_missing_mode (categorical columns)
208
+ fix_dtype β†’ finish
209
+ ```
210
 
211
+ **Scoring:** `missing_score Γ— 0.5 + dtype_score Γ— 0.5`
212
 
213
  ---
214
 
215
+ ### ⭐⭐⭐ Hard β€” `hard_full_pipeline`
216
 
217
+ **Dataset:** Orders (`order_id`, `product`, `quantity`, `price`, `customer_id`, `status`, `order_date`, `rating`)
218
 
219
+ **Dirty conditions:**
220
+ - Duplicate order entries
221
+ - Missing `quantity` and `price` values
222
+ - `quantity` stored as object type
223
+ - Extreme price outliers
 
224
 
225
+ **Optimal sequence:**
226
+ ```
227
+ remove_duplicates β†’ fill_missing_* β†’ fix_dtype β†’ remove_outliers β†’ validate_schema β†’ finish
228
+ ```
229
 
230
+ **Scoring:** `duplicate Γ— 0.2 + missing Γ— 0.2 + dtype Γ— 0.2 + outlier Γ— 0.2 + schema Γ— 0.2`
231
 
232
  ---
233
 
234
+ ### ⭐⭐⭐⭐ Expert β€” `expert_sales_pipeline`
235
+
236
+ **Dataset:** Sales transactions β€” highest complexity, penalises out-of-order operations heavily.
237
+
238
+ **Optimal sequence (strictly enforced):**
239
+ ```
240
+ remove_duplicates β†’ rename_columns β†’ fill_missing_mode β†’ fix_dtype β†’ remove_outliers β†’ validate_schema β†’ finish
241
+ ```
242
+
243
+ **Scoring:** `duplicate Γ— 0.15 + missing Γ— 0.20 + dtype Γ— 0.20 + outlier Γ— 0.20 + schema Γ— 0.25`
244
 
245
+ ---
246
 
247
+ ## πŸ”§ Operations Reference
 
 
 
 
 
248
 
249
+ All operations are invoked via JSON actions sent to `POST /step/{task_id}`.
 
250
 
251
+ ### `remove_duplicates`
252
+ ```json
253
+ {"operation": "remove_duplicates", "parameters": {}}
254
+ ```
255
+ Drops exact duplicate rows using `pandas.drop_duplicates()`. Resets the index after removal.
256
+ - Optional parameter: `"subset": ["col1", "col2"]` β€” deduplicate on specific columns only
257
 
258
  ---
259
 
260
+ ### `fill_missing_mean`
261
+ ```json
262
+ {"operation": "fill_missing_mean", "parameters": {}}
263
+ ```
264
+ Fills NaN values in numeric columns with the column mean. Skips non-numeric columns to avoid type errors.
265
+ - Optional parameter: `"column": "col_name"` β€” target a single column
266
 
267
+ ---
268
 
269
+ ### `fill_missing_mode`
270
  ```json
271
+ {"operation": "fill_missing_mode", "parameters": {}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  ```
273
+ Fills NaN values with the most frequent value (mode). Works for both numeric and categorical columns.
274
+ - Optional parameter: `"column": "col_name"`
275
 
276
+ ---
277
+
278
+ ### `fill_missing_median`
279
+ ```json
280
+ {"operation": "fill_missing_median", "parameters": {}}
281
+ ```
282
+ Fills NaN values in numeric columns with the column median. More robust to outliers than mean.
283
+ - Optional parameter: `"column": "col_name"`
284
 
285
  ---
286
 
287
+ ### `fix_dtype`
288
+ ```json
289
+ {"operation": "fix_dtype", "parameters": {"dtype": "auto"}}
290
+ ```
291
+ Attempts to convert columns to the most appropriate type.
292
+ - `"dtype": "auto"` β€” tries `int` then `float`, skips if conversion fails
293
+ - `"dtype": "int"` β€” convert to integer
294
+ - `"dtype": "float"` β€” convert to float
295
+ - `"dtype": "str"` β€” convert to string
296
+ - Optional parameter: `"column": "col_name"`
297
 
298
+ ---
299
 
300
+ ### `remove_outliers`
301
  ```json
302
+ {"operation": "remove_outliers", "parameters": {"method": "iqr"}}
 
 
 
 
 
 
303
  ```
304
+ Removes rows where numeric values fall outside the outlier fence.
305
+ - `"method": "iqr"` β€” IQR method: removes values outside `[Q1 βˆ’ 1.5Γ—IQR, Q3 + 1.5Γ—IQR]`
306
+ - `"method": "zscore"` β€” Z-score method: removes values beyond Β±3Οƒ
307
+ - Optional parameter: `"column": "col_name"` β€” target a single numeric column
308
 
309
+ ---
310
+
311
+ ### `rename_columns`
312
+ ```json
313
+ {"operation": "rename_columns", "parameters": {}}
314
+ ```
315
+ Auto-renames all columns to `snake_case` (lowercase, spaces β†’ underscores).
316
+ - Optional parameter: `"mapping": {"Old Name": "new_name"}` β€” explicit rename map
 
317
 
318
  ---
319
 
320
+ ### `validate_schema`
321
+ ```json
322
+ {"operation": "validate_schema", "parameters": {}}
323
+ ```
324
+ Compares current column names against the gold dataset schema.
325
+ - Returns missing columns (in gold but not current)
326
+ - Returns extra columns (in current but not in gold)
327
+ - Returns a success message if schemas match perfectly
328
 
329
+ ---
330
 
331
+ ### `finish`
332
+ ```json
333
+ {"operation": "finish", "parameters": {}}
334
+ ```
335
+ Signals the agent is done. Triggers final reward computation and ends the episode immediately. **Always call this when cleaning is complete.**
336
 
337
+ ---
 
 
 
 
 
 
 
338
 
339
+ ## πŸ“Š Reward & Scoring System
340
 
341
+ Reward is computed after every step and returned as a `Reward` object. The total is a weighted sum of components minus penalties, **clamped strictly to `(0.0001, 0.9999)`**.
342
 
343
+ ### Score Components
344
 
345
+ | Component | What It Measures | How It's Calculated |
346
+ |---|---|---|
347
+ | `duplicate_score` | Row count vs gold dataset | Proportional to excess/deficit rows |
348
+ | `missing_score` | Missing values filled vs gold | Fraction of needed fills completed |
349
+ | `dtype_score` | Column types match gold | Matched columns Γ· total columns |
350
+ | `outlier_score` | Numeric values within 3Οƒ of gold mean | Per-column average, then mean across columns |
351
+ | `schema_score` | Column names match gold schema | Matched column names Γ· gold column count |
352
+ | `penalty` | Step efficiency + operation order | See sequence penalty below |
353
+
354
+ ---
355
+
356
+ ### Sequence Penalty
357
+
358
+ The optimal operation order is:
359
  ```
360
+ remove_duplicates β†’ fix_dtype β†’ fill_missing_* β†’ remove_outliers β†’ validate_schema
 
 
 
 
361
  ```
362
 
363
+ Penalties for deviations:
364
 
365
  | Violation | Penalty |
366
  |---|---|
367
+ | Out-of-order operation | βˆ’0.08 |
368
+ | Repeated operation (non-fill/outlier) | βˆ’0.02 |
369
+ | Unknown operation | βˆ’0.01 |
370
+ | Using >80% of allowed steps | βˆ’0.05 |
371
+ | **Maximum total penalty** | **βˆ’0.25** |
372
+
373
+ ---
374
 
375
+ ### Score Clamping (Critical)
376
 
377
+ The Scaler grader rejects scores of exactly `0.0` or `1.0`. The following clamping is enforced at every level:
378
 
379
+ ```python
380
+ # environment.py β€” _compute_reward()
381
+ def _sc(v):
382
+ return round(max(0.0001, min(0.9999, float(v))), 4)
383
+
384
+ # Applied to ALL Reward fields: total, duplicate_score, missing_score, etc.
385
+ return Reward(
386
+ total=_sc(total),
387
+ duplicate_score=_sc(dup_score),
388
+ ...
389
+ )
390
+ ```
391
+
392
+ ```python
393
+ # inference.py β€” every printed reward
394
+ def _clamp(v: float) -> float:
395
+ return max(0.01, min(0.99, float(v)))
396
 
397
+ # [STEP] and [END] lines both use _clamp() before formatting
398
+ ```
399
 
400
+ ---
401
 
402
+ ## 🌐 API Reference
 
 
 
 
 
 
403
 
404
+ **Base URL:** `https://thorodin103-data-cleaning-openenv.hf.space`
405
+
406
+ | Method | Endpoint | Description |
407
+ |---|---|---|
408
+ | `POST` | `/reset` | Reset environment (body: `{"task_id": "..."}`) |
409
+ | `POST` | `/reset/{task_id}` | Reset specific task environment |
410
+ | `POST` | `/step` | Take action (body: `{"task_id": "...", "operation": "...", "parameters": {}}`) |
411
+ | `POST` | `/step/{task_id}` | Take action in specific task |
412
+ | `GET` | `/state` | Get current environment state |
413
+ | `GET` | `/state/{task_id}` | Get state for specific task |
414
+ | `GET` | `/tasks` | List all tasks with full metadata |
415
+ | `GET` | `/validate` | Run OpenEnv spec validation across all tasks |
416
+ | `GET` | `/health` | Health check |
417
+ | `GET` | `/docs` | Interactive Swagger UI |
418
+ | `POST` | `/leaderboard/submit` | Submit a score entry |
419
+ | `GET` | `/leaderboard` | Get current leaderboard rankings |
420
 
421
  ---
422
 
423
+ ### Example: Reset a task
424
+ ```bash
425
+ curl -X POST https://thorodin103-data-cleaning-openenv.hf.space/reset/easy_dedup_rename
426
+ ```
427
+ ```json
428
+ {
429
+ "observation": {
430
+ "task_id": "easy_dedup_rename",
431
+ "step": 0,
432
+ "columns": ["EMP ID", "EMP NAME", "DEP T", "SAL ARY", "AGE"],
433
+ "duplicate_count": 5,
434
+ "missing_values": {"EMP ID": 0, "EMP NAME": 0, ...},
435
+ "message": "Environment reset. Start cleaning!"
436
+ },
437
+ "reward": {"total": 0.0001},
438
+ "done": false
439
+ }
440
+ ```
441
 
442
+ ---
443
 
444
+ ### Example: Take a step
445
+ ```bash
446
+ curl -X POST https://thorodin103-data-cleaning-openenv.hf.space/step/easy_dedup_rename \
447
+ -H "Content-Type: application/json" \
448
+ -d '{"operation": "remove_duplicates", "parameters": {}}'
449
  ```
450
+ ```json
451
+ {
452
+ "observation": {"step": 1, "duplicate_count": 0, "message": "Removed 5 duplicate rows. Rows: 20 -> 15"},
453
+ "reward": {"total": 0.4821, "duplicate_score": 0.9999, "schema_score": 0.0001},
454
+ "done": false
455
+ }
456
  ```
457
 
458
+ ---
 
 
 
 
 
 
 
 
 
 
459
 
460
+ ### Example: Validate the environment
461
+ ```bash
462
+ curl https://thorodin103-data-cleaning-openenv.hf.space/validate
463
+ ```
464
  ```json
465
  {
466
  "openenv_valid": true,
467
  "tasks": {
468
+ "easy_dedup_rename": {"status": "passed"},
469
+ "medium_missing_dtype": {"status": "passed"},
470
+ "hard_full_pipeline": {"status": "passed"},
471
+ "expert_sales_pipeline":{"status": "passed"}
472
  }
473
  }
474
  ```
475
 
476
  ---
477
 
478
+ ## πŸ€– Inference Script
 
 
479
 
480
+ `inference.py` is the hackathon submission entry point. It runs an LLM agent across all tasks and emits structured stdout lines that the platform parser reads.
 
481
 
482
+ ### Required Stdout Format
483
 
484
+ > The format below is **mandatory**. The platform parser reads these exact line types.
 
 
485
 
486
+ ```
487
+ [START] task=<task_name> env=<benchmark> model=<model_name>
488
+ [STEP] step=<n> action=<action_str> reward=<0.00> done=<true|false> error=<msg|null>
489
+ [END] success=<true|false> steps=<n> score=<0.00> rewards=<r1,r2,...,rn>
490
+ ```
491
 
492
+ **Rules:**
493
+ - One `[START]` line at episode begin
494
+ - One `[STEP]` line per step, immediately after `env.step()` returns
495
+ - One `[END]` line after episode end β€” **always emitted, even on exception** (via `finally` block)
496
+ - `reward` and `rewards` formatted to **2 decimal places**
497
+ - `done` and `success` are lowercase: `true` or `false`
498
+ - `score=` field in `[END]` is **mandatory** β€” its absence causes Task Validation failure
499
+ - `error` is the raw error string, or `null` if none
500
 
501
+ **Example output:**
502
+ ```
503
+ [START] task=easy_dedup_rename env=data-cleaning-openenv model=gpt-4o-mini
504
+ [STEP] step=1 action=remove_duplicates reward=0.48 done=false error=null
505
+ [STEP] step=2 action=rename_columns reward=0.96 done=false error=null
506
+ [STEP] step=3 action=finish reward=0.96 done=true error=null
507
+ [END] success=true steps=3 score=0.96 rewards=0.48,0.96,0.96
508
  ```
509
 
510
+ ---
511
 
512
+ ### Agent Loop
 
 
513
 
514
+ For each task the agent follows this loop:
 
515
 
516
+ 1. Call `env.reset()` to initialise the episode
517
+ 2. Build a prompt from the observation (shape, columns, missing values, dtypes, sample rows)
518
+ 3. Send prompt to LLM via OpenAI-compatible client
519
+ 4. Parse the JSON response into an `Action`
520
+ 5. Call `env.step(action)` and record the reward
521
+ 6. Emit a `[STEP]` line
522
+ 7. Repeat until `done=true` or `MAX_STEPS` (20) reached
523
+ 8. Compute `score = average(rewards)`, clamped to `(0, 1)`
524
+ 9. Emit `[END]` line via `finally` block
525
 
526
+ ---
527
 
528
+ ### Environment Variables
 
529
 
530
+ | Variable | Default | Description |
531
+ |---|---|---|
532
+ | `API_BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible API endpoint |
533
+ | `MODEL_NAME` | `gpt-4o-mini` | Model identifier |
534
+ | `HF_TOKEN` | *(required)* | Hugging Face / API key |
535
+
536
+ ---
537
+
538
+ ## πŸ“¦ Data Models
539
+
540
+ ### `Action`
541
+ ```json
542
+ {
543
+ "operation": "remove_duplicates",
544
+ "parameters": {}
545
+ }
546
  ```
547
+ - `operation` β€” one of the 9 valid operations
548
+ - `parameters` β€” operation-specific options (`column`, `strategy`, `method`, `dtype`, `mapping`, `subset`)
549
 
550
+ ---
551
 
552
+ ### `Observation`
553
+ ```json
554
+ {
555
+ "task_id": "easy_dedup_rename",
556
+ "step": 1,
557
+ "dataset_info": {"total_rows": 15, "has_duplicates": false, "has_missing": false},
558
+ "columns": ["emp_id", "emp_name", "dept", "salary", "age"],
559
+ "shape": [15, 5],
560
+ "missing_values": {"emp_id": 0, "emp_name": 0},
561
+ "dtypes": {"emp_id": "int64", "emp_name": "object"},
562
+ "duplicate_count": 0,
563
+ "sample_rows": [{"emp_id": 101, "emp_name": "Alice", ...}],
564
+ "available_operations": ["remove_duplicates", "rename_columns", "finish"],
565
+ "task_description": "Clean an employee dataset by...",
566
+ "message": "Removed 5 duplicate rows."
567
+ }
568
+ ```
569
 
570
+ ---
571
+
572
+ ### `Reward`
573
+ ```json
574
+ {
575
+ "total": 0.4821,
576
+ "duplicate_score": 0.9999,
577
+ "missing_score": 0.0001,
578
+ "dtype_score": 0.0001,
579
+ "outlier_score": 0.0001,
580
+ "schema_score": 0.0001,
581
+ "penalty": 0.0
582
+ }
583
  ```
584
+ All values are clamped to `(0.0001, 0.9999)`.
585
 
586
  ---
587
 
588
+ ### `StepResult`
589
+ ```json
590
+ {
591
+ "observation": { ... },
592
+ "reward": { ... },
593
+ "done": false,
594
+ "info": {
595
+ "step": 1,
596
+ "operation": "remove_duplicates",
597
+ "reward_history": [0.4821]
598
+ }
599
+ }
600
+ ```
601
 
602
+ ---
 
 
603
 
604
+ ## πŸ—ƒοΈ Datasets
 
605
 
606
+ Each task has a paired `dirty.csv` and `gold.csv`. The dirty file is loaded at reset; the gold file is used as the scoring reference throughout the episode.
 
 
 
 
607
 
608
+ ### Easy β€” Employee Dataset
609
+ | Property | Value |
610
+ |---|---|
611
+ | Dirty columns | `EMP ID`, `EMP NAME`, `DEP T`, `SAL ARY`, `AGE` |
612
+ | Gold columns | `emp_id`, `emp_name`, `dept`, `salary`, `age` |
613
+ | Issues | Duplicate rows, space-separated column names |
614
+ | Rows | ~20 dirty β†’ ~15 gold after dedup |
 
 
615
 
616
+ ### Medium β€” Customer Dataset
617
+ | Property | Value |
618
+ |---|---|
619
+ | Columns | `customer_id`, `age`, `salary`, `gender`, `purchases`, `region`, `joined_date` |
620
+ | Issues | NaN in `age`, `salary`, `gender`, `region`; `salary` as `object` instead of `float` |
621
+ | Rows | ~30, no duplicates |
622
 
623
+ ### Hard β€” Orders Dataset
624
+ | Property | Value |
625
+ |---|---|
626
+ | Columns | `order_id`, `product`, `quantity`, `price`, `customer_id`, `status`, `order_date`, `rating` |
627
+ | Issues | Duplicate orders, missing `quantity`/`price`, wrong dtypes, price outliers |
628
+ | Rows | ~50 dirty, full pipeline required |
629
 
630
+ ### Expert β€” Sales Dataset
631
+ | Property | Value |
632
+ |---|---|
633
+ | Issues | All of the above plus column naming problems |
634
+ | Unique challenge | Operations must be applied in strict optimal order β€” out-of-order is penalised βˆ’0.08 per violation |
635
 
636
  ---
637
 
638
+ ## πŸ› οΈ Troubleshooting
639
 
640
+ ### ❌ Phase 2 Task Validation: "score out of range"
641
 
642
+ The Scaler platform rejects any score that is exactly `0.0` or `1.0`.
 
 
 
 
 
643
 
644
+ - **`environment.py`** β€” all `Reward` fields must go through `_sc()` clamping at return
645
+ - **`inference.py`** β€” `[END]` line must include `score=` field; all rewards via `_clamp()`
646
+ - **`inference.py`** β€” fallback/exception reward must be `0.01`, not `0.0`
647
+ - **Format** β€” use `:.2f` (per spec), not `:.4f`
648
 
649
+ ---
 
 
 
 
 
 
 
 
 
650
 
651
+ ### ❌ Output Parsing failure
 
 
652
 
653
+ - Ensure `[START]`, `[STEP]`, `[END]` lines use `flush=True`
654
+ - No newlines within a single log line
655
+ - `done` and `success` must be lowercase `true`/`false`
656
+ - `[END]` must include the `score=` field β€” this is the most common cause of Task Validation failure
 
 
657
 
658
  ---
659
 
660
+ ### ❌ Environment not initialized error
661
 
662
+ - Always call `POST /reset/{task_id}` before `POST /step/{task_id}`
663
+ - Each `task_id` has its own independent environment instance
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
664
 
665
  ---
666
 
667
+ ### ❌ LLM returns invalid JSON
668
 
669
+ `parse_action()` handles these cases automatically:
670
+ - Strips markdown code fences (` ```json ` and ` ``` `)
671
+ - Falls back to regex `{...}` extraction
672
+ - Default fallback: `{"operation": "finish", "parameters": {}}`
673
 
674
+ If the model consistently fails, try increasing `MAX_TOKENS` in `inference.py`.
 
 
 
 
675
 
676
  ---
677
 
678
+ ## πŸ“ˆ Baseline Scores
679
 
680
+ Baseline agent: **gpt-4o-mini** (from `openenv.yaml`)
681
+
682
+ | Task | Score |
683
  |---|---|
684
+ | `easy_dedup_rename` | **0.9900** |
685
+ | `medium_missing_dtype` | **0.7000** |
686
+ | `hard_full_pipeline` | **0.6636** |
687
+ | **Average** | **0.7845** |
 
 
688
 
689
  ---
690
 
691
+ ## πŸ“„ License
692
 
693
+ MIT License β€” free to use, modify, and distribute.
694
 
695
  ---
696
 
697
+ <div align="center">
698
+
699
+ Built for the **Scaler Γ— OpenEnv Hackathon**
700
+
701
+ πŸ”— [GitHub](https://github.com/ReverseCoder1/CleanifyAI) Β· [HuggingFace Space](https://huggingface.co/spaces/cleanify-ai/Data-cleaning) Β· [Live API Docs](https://thorodin103-data-cleaning-openenv.hf.space/docs)
702
+
703
+ *CleanifyAI β€” making data clean, one step at a time.*
704
+
705
+ </div>