File size: 6,531 Bytes
508bc3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
---

title: Data Cleaning Environment
emoji: 🧹
colorFrom: blue
colorTo: green
sdk: docker
app_port: 8000
base_path: /web
---

# Data Cleaning Environment

A real-world OpenEnv environment where an AI agent learns to clean tabular data
through the standard `step()` / `reset()` / `state()` API.

The agent receives a dirty CSV-style dataset and must apply cleaning operations
step-by-step to maximise a data-quality score (0.0 β†’ 1.0).

---

## Tasks

| Task ID  | Difficulty | Description                                                     | Max Steps |
|----------|------------|-----------------------------------------------------------------|-----------|
| `easy`   | Easy       | Fix 3 missing values (name / age) in a 5-row table             | 10        |
| `medium` | Medium     | Remove 2 duplicate rows **and** fix 2 type errors in `age`     | 15        |
| `hard`   | Hard       | Full pipeline: missing values + duplicates + outliers + normalise text | 25  |

---

## Action Space

```json

{

  "operation": "<string>",

  "column":    "<string | null>"

}

```

| Operation           | Task(s)          | Effect                                              |
|---------------------|------------------|-----------------------------------------------------|
| `impute_mean`       | easy             | Fill numeric `None` with column mean                |
| `impute_mode`       | easy             | Fill string `None` with column mode                 |
| `drop_missing_rows` | easy / medium    | Drop all rows containing any `None`                 |
| `remove_duplicates` | medium           | Drop exact-duplicate rows                           |
| `fix_type_errors`   | medium           | Coerce non-numeric values to `float` (or `None`)    |
| `remove_outliers`   | hard             | Drop rows where `price ≀ 0` or `price β‰₯ 500`        |
| `normalize_text`    | hard             | Strip whitespace + title-case all string columns    |
| `fill_quantity_mean`| hard             | Fill missing `quantity` with column mean            |

---

## Observation Space

| Field             | Type    | Description                                               |
|-------------------|---------|-----------------------------------------------------------|
| `current_text`    | `str`   | Human-readable table of current rows                      |
| `is_normalized`   | `bool`  | True when no missing values, duplicates, or outliers left |
| `remaining_typos` | `int`   | Composite issue count (missing + dupes + outliers)        |
| `html_found`      | `bool`  | Always `False` (compatibility field)                      |
| `done`            | `bool`  | Episode ended (score=1.0 or step limit reached)           |
| `reward`          | `float` | Ξ” quality score from this step (partial progress signal)  |
| `metadata`        | `dict`  | Full row data, per-field diagnostics, valid ops list       |

---

## Reward Function

```

reward = (quality_score_after - quality_score_before) + 0.01   # if improvement

reward = (quality_score_after - quality_score_before) - 0.01   # if no improvement

```

Each task has its own grader:

- **Easy**: fraction of the 3 original missing values that are resolved
- **Medium**: average of (dedup score, type-fix score)
- **Hard**: average of 4 sub-scores (no missing, no dupes, no outliers, normalised text)

Invalid operations incur a βˆ’0.05 penalty but do **not** end the episode.

---

## Baseline Scores

Deterministic rule-based agent (reproducible, seed=42):

| Task   | Score |
|--------|-------|
| easy   | 1.0   |
| medium | 1.0   |
| hard   | 1.0   |

---

## API Endpoints

| Method | Path        | Description                                      |
|--------|-------------|--------------------------------------------------|
| POST   | `/reset`    | Start new episode. Body: `{"task":"easy"}`       |
| POST   | `/step`     | Apply action. Body: `{"action":{...}}`           |
| GET    | `/state`    | Current episode state                            |
| GET    | `/schema`   | Action + Observation JSON schemas                |
| GET    | `/tasks`    | Task list + action schema per difficulty         |
| POST   | `/grader`   | Score the current episode (0.0–1.0)              |
| POST   | `/baseline` | Run baseline agent on all 3 tasks                |
| GET    | `/health`   | Health check                                     |
| WS     | `/ws`       | WebSocket for low-latency persistent sessions    |

---

## Quick Start

### 1. Reset to a task

```bash

curl -X POST http://localhost:8000/reset \

  -H "Content-Type: application/json" \

  -d '{"task": "easy"}'

```

### 2. Apply a cleaning operation

```bash

curl -X POST http://localhost:8000/step \

  -H "Content-Type: application/json" \

  -d '{"action": {"operation": "impute_mean"}}'

```

### 3. Get current state

```bash

curl http://localhost:8000/state

```

### 4. Score the episode

```bash

curl -X POST http://localhost:8000/grader

```

### 5. Run baseline on all tasks

```bash

curl -X POST http://localhost:8000/baseline

```

---

## Setup

### Run locally

```bash

uv run server

# or

uvicorn server.app:app --reload --host 0.0.0.0 --port 8000

```

### Run with Docker

```bash

docker build -t data-cleaning-env:latest -f server/Dockerfile .

docker run -p 8000:8000 data-cleaning-env:latest

```

### Deploy to Hugging Face Spaces

```bash

openenv push --repo-id your-username/data-cleaning-env

```

---

## Project Structure

```

data_cleaning_env/

β”œβ”€β”€ README.md                          # This file

β”œβ”€β”€ openenv.yaml                       # OpenEnv manifest

β”œβ”€β”€ pyproject.toml                     # Project metadata & dependencies

β”œβ”€β”€ uv.lock                            # Locked dependencies

β”œβ”€β”€ models.py                          # Action + Observation Pydantic models

β”œβ”€β”€ client.py                          # DataCleaningEnv HTTP/WS client

└── server/

    β”œβ”€β”€ app.py                         # FastAPI app + /tasks /grader /baseline

    β”œβ”€β”€ data_cleaning_env_environment.py  # Core environment logic + graders

    └── Dockerfile                     # Container image

```

---

## Dependencies

- `openenv-core[core] >= 0.2.2` β€” OpenEnv runtime
- Python 3.10+
- No external data dependencies β€” all datasets are generated synthetically at runtime