Swethaditya commited on
Commit
3887caa
·
1 Parent(s): 420c217

final: add pyproject.toml, uv.lock, server/app.py for openenv validate

Browse files
Files changed (1) hide show
  1. README.md +202 -387
README.md CHANGED
@@ -14,11 +14,15 @@ pinned: false
14
 
15
  # SQLSherlock-Env
16
 
17
- An RL environment where an AI agent acts as a data scientist investigating a dirty dataset.
18
 
19
- The agent discovers real data quality issues through statistical investigation — exactly like a human data scientist — fixes them with documented reasoning, validates fixes against the raw baseline, and exports the cleaned output in the same format as the input.
20
 
21
- Real datasets already have data quality problems. The issue detector scans the dataset at `reset()` time and builds a ground-truth catalogue from what it finds. The agent never sees this catalogue — it must discover everything through investigation.
 
 
 
 
22
 
23
  ---
24
 
@@ -28,238 +32,146 @@ The agent discovers real data quality issues through statistical investigation
28
 
29
  ```
30
  reset(dataset, task_id)
31
- │
32
- ▼
33
- ┌───────────────────────────────────────────────────────────────────┐
34
- │ DatabaseEngine.__init__ │
35
- │ │
36
- │ 1. load(source) ← CSV / JSON / JSONL / Parquet / HF │
37
- │ 2. records_to_sqlite() ← In-memory SQLite, isolated per episode│
38
- │ 3. deep_copy(originals) ← Immutable snapshot before any edits │
39
- │ 4. profile_table() ← mean/std/z-scores per column │
40
- │ 5. detect_issues() ← null / type / constraint / outlier │
41
- │ duplicate / fk_violation │
42
- │ 6. Validator(baseline) ← 6-check baseline captured │
43
- │ 7. detect_trap() ← Task 3 only: plant 2x value in DB │
44
- └───────────────────────────────────────────────────────────────────┘
45
- │
46
- ▼
47
- SQLSherlockObservation returned to agent
48
- │
49
- ▼
50
- ┌─────────────────────────────────────────────────────┐
51
- │ Agent Step Loop │
52
- │ │
53
- │ ┌──────────────────────────────────────────────┐ │
54
- │ │ Agent decides action (LLM call) │ │
55
- │ │ │ │
56
- │ │ investigate: inspect / profile / run_sql │ │
57
- │ │ fix: fix_cell / delete_row │ │
58
- │ │ check: validate │ │
59
- │ │ end: submit / export │ │
60
- │ └───────────────────┬──────────────────────────┘ │
61
- │ │ │
62
- │ ▼ │
63
- │ ┌──────────────────────────────────────────────┐ │
64
- │ │ Environment.step(action) │ │
65
- │ │ │ │
66
- │ │ 1. dispatch action → DatabaseEngine │ │
67
- │ │ 2. reward.calc() → RB breakdown │ │
68
- │ │ 3. build observation (feedback + results) │ │
69
- │ │ 4. return (obs, reward, done, info) │ │
70
- │ └──────────────────────────────────────────────┘ │
71
- │ │
72
- │ Repeat until submit/export or budget exhausted │
73
- └────────────────────────────────────────────��────────┘
74
- │
75
- ▼
76
- Grader.score() → final score [0.0 – 1.0]
77
- ```
78
-
79
- ### Component Diagram
80
-
81
- ```
82
- inference.py / train.py / custom agent
83
- │ HTTP + WebSocket
84
- ▼
85
- ┌─────────────────────────────────────────────────────────────┐
86
- │ FastAPI App (server/app.py) │
87
- │ POST /reset POST /step GET /state GET /health │
88
- │ WS /ws │
89
- └──────────────────────┬──────────────────────────────────────┘
90
- │
91
- ▼
92
- ┌─────────────────────────────────────────────────────────────┐
93
- │ SQLSherlockEnvironment (server/environment.py) │
94
- │ │
95
- │ reset() ─────────────────────────────────────────────► │
96
- │ DatabaseEngine │
97
- │ step(action) ─────► dispatch ──────────────────────► │
98
- │ │ │
99
- │ │ │
100
- │ ┌────▼────┐ │
101
- │ │ reward │ │
102
- │ │ .calc()│ │
103
- │ └─────────┘ │
104
- │ │
105
- │ on submit/export ─────► Grader.score() │
106
- └─────────────────────────────────────────────────────────────┘
107
- │
108
- ┌──────────────┼──────────────────────┐
109
- ▼ ▼ ▼
110
- ┌─────────────┐ ┌─────────────────┐ ┌──────────────────┐
111
- │ Database │ │ IssueDetector │ │ Validator │
112
- │ Engine │ │ │ │ │
113
- │ │ │ detect_issues()│ │ 6-check before/ │
114
- │ SQLite │ │ detect_trap() │ │ after comparison │
115
- │ in-memory │ │ │ │ │
116
- │ per episode│ │ null │ │ null_check │
117
- │ │ │ type_error │ │ type_check │
118
- │ profile_ │ │ constraint │ │ range_check │
119
- │ table() │ │ outlier │ │ distribution_ │
120
- │ │ │ duplicate │ │ check │
121
- │ z_scores │ │ fk_violation │ │ duplicate_check │
122
- │ per row │ │ │ │ outlier_check │
123
- └─────────────┘ └─────────────────┘ └──────────────────┘
124
  ```
125
 
126
  ### Grading Pipeline (7 steps)
127
 
128
  ```
129
- submit / export triggered
130
- │
131
- ▼
132
- ┌─────────────────────────────────────────────────────────────┐
133
- │ universal.py — 7-step grader │
134
- │ │
135
- │ Step 1: Zero-change guard │
136
- │ └── if nothing changed → score = 0.0 │
137
- │ │
138
- │ Step 2: Resolution score (0.0 – 1.0) │
139
- │ └── per issue: confidence-weighted correct/total │
140
- │ null: confidence 0.20 – 1.0 (structural=0.20) │
141
- │ type_error: always 1.0 │
142
- │ constraint / outlier: 0.80 │
143
- │ duplicate: 0.70 │
144
- │ │
145
- │ Step 3: False-positive penalty │
146
- │ └── −0.15 per clean cell touched │
147
- │ │
148
- │ Step 4: Trap penalty (Task 3 only) │
149
- │ └── −0.40 if trap cell was modified │
150
- │ │
151
- │ Step 5: Validation score (0.0 – 0.30) │
152
- │ └── checks_passed / total_checks × 0.30 │
153
- │ │
154
- │ Step 6: Reasoning bonus (0.0 – 0.10) │
155
- │ └── +0.02 per fix_cell/delete_row with reason str │
156
- │ │
157
- │ Step 7: Final clamp │
158
- │ raw = res×0.60 + val×0.30 + bonus×0.10 − fp − trap│
159
- │ score = clamp(raw, 0.0, 1.0) │
160
- └─────────────────────────────────────────────────────────────┘
161
  ```
162
 
163
  ---
164
 
165
  ## Quick Start
166
 
167
- ### 1. Docker (recommended)
168
 
169
  ```bash
170
- # Build from repo root
171
  docker build -t sqlsherlock-env:latest .
172
-
173
- # Run
174
  docker run -p 7860:7860 sqlsherlock-env:latest
175
-
176
- # Verify
177
  curl http://localhost:7860/health
 
178
  ```
179
 
180
- ### 2. Local (without Docker)
181
 
182
  ```bash
 
 
183
  cd sqlsherlock_env
184
- pip install -r server/requirements.txt
185
- uvicorn server.app:app --host 0.0.0.0 --port 7860
186
  ```
187
 
188
- ### 3. Run baseline inference
189
 
190
  ```bash
191
  export API_BASE_URL="https://router.huggingface.co/v1"
192
  export MODEL_NAME="Qwen/Qwen2.5-72B-Instruct"
193
- export HF_TOKEN="hf_..."
194
  export SPACE_URL="http://localhost:7860"
195
-
196
  python inference.py
197
  ```
198
 
199
- Expected stdout (judges parse this exactly):
200
 
201
  ```
202
  [START] task=task1_null_and_types env=sqlsherlock_env model=Qwen/Qwen2.5-72B-Instruct
203
  [STEP] step=1 action=inspect reward=0.02 done=false error=null
204
- [STEP] step=2 action=profile_column(age) reward=0.03 done=false error=null
 
205
  ...
206
- [END] success=true steps=8 score=0.820 rewards=0.02,0.03,0.15,0.15,0.05,0.15,0.10
207
  ```
208
 
209
  ---
210
 
211
- ## Using Your Own Dataset
212
-
213
- `inference.py` uses `phihung/titanic` for hackathon validation. To use your own dataset, connect the client directly:
214
 
215
- ### HuggingFace dataset
216
 
217
  ```python
218
  from sqlsherlock_env.client import SQLSherlockEnv
219
 
220
  env = SQLSherlockEnv(base_url="http://localhost:7860")
221
- obs = env.reset(
222
- dataset="your_org/your_dataset", # any public HF dataset
223
- task_id="task1_null_and_types",
224
- max_rows=500,
225
- )
226
- ```
227
 
228
- ### Local file (CSV / JSON / JSONL / Parquet)
 
229
 
230
- ```python
231
- obs = env.reset(
232
- dataset="/absolute/path/to/data.csv",
233
- task_id="task2_constraints_and_fk",
234
- )
235
- ```
236
 
237
- ### Raw CSV string
238
-
239
- ```python
240
- csv_text = "id,name,age,fare\n1,Alice,,25.0\n2,Bob,FORTY,50.0\n..."
241
- obs = env.reset(
242
- dataset=csv_text,
243
- task_id="task1_null_and_types",
244
- )
245
  ```
246
 
247
- ### Upload via API
248
 
249
- ```bash
250
- curl -X POST http://localhost:7860/upload_dataset \
251
- -F "file=@data.csv" \
252
- -F "task_id=task1_null_and_types"
253
- ```
254
 
255
- **What the environment does with your dataset:**
256
- 1. Loads the data (any format above)
257
- 2. Auto-detects column types (int / float / str / bool)
258
- 3. Scans for real data quality issues — no injection
259
- 4. Builds a ground-truth issue catalogue the agent never sees
260
- 5. Plants a trap value in Task 3
261
 
262
- The agent then investigates, fixes, validates, and exports. The exported file matches the input format (CSV in → CSV out, Parquet in → Parquet out).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
 
264
  ---
265
 
@@ -267,82 +179,81 @@ The agent then investigates, fixes, validates, and exports. The exported file ma
267
 
268
  | `action_type` | Required fields | Description |
269
  |---|---|---|
270
- | `inspect` | `table` | View all rows |
271
- | `profile_column` | `table`, `column` | Stats: mean/std/min/max/nulls/z-scores |
272
- | `run_sql` | `sql` | SELECT query (read-only, max 50 rows) |
273
  | `fix_cell` | `table`, `row_id`, `column`, `value`, `reason` | Fix one cell with justification |
274
- | `fix_column` | `table`, `column`, `value`, `reason` | Fix ALL nulls in a column at once (bulk) |
275
- | `delete_row` | `table`, `row_id`, `reason` | Remove duplicate or FK row |
276
- | `validate` | — | Run all 6 before/after checks |
277
- | `submit` | — | Score and end episode |
278
- | `export` | — | Write cleaned file, score and end episode |
279
 
280
  ---
281
 
282
  ## Reward System
283
 
 
 
284
  | Action | Reward | Cap |
285
  |---|---|---|
286
  | `inspect` | +0.02 | 3 rewarded |
287
  | `profile_column` | +0.03 | 3 rewarded |
288
  | `run_sql` | +0.03 | 3 rewarded |
289
- | `validate` | +0.05 × (checks_passed / 6) | 2 rewarded |
290
- | `fix_cell` — correct | **+0.15** | — |
291
- | `fix_cell` — false positive | **−0.20** | — |
292
- | `fix_cell` — trap cell | **−0.40** | — |
293
- | `fix_cell` — wrong value | **−0.10** | — |
294
- | `delete_row` — valid | **+0.15** | — |
295
- | `delete_row` — false positive | **−0.20** | �� |
296
- | `submit` — all resolved | +0.10 | — |
297
- | `submit` — issues remain | −0.10 | — |
 
298
 
299
  ---
300
 
301
  ## Three Tasks
302
 
303
- ### Task 1 — `task1_null_and_types` (Easy, max 20 steps)
304
 
305
- Find and fix **null values** and **type errors**.
306
 
307
- - Null: `None` or empty string in any non-PK column
308
- - Type error: text in a numeric column (e.g. `"FORTY"` in age)
309
- - Score: `resolution × 0.70 + validation × 0.30`
310
 
311
- ### Task 2 — `task2_constraints_and_fk` (Medium, max 25 steps)
312
 
313
- Everything in Task 1 plus:
314
 
315
- - **Constraint violations**: negative values in must-be-positive columns (age, fare, price)
316
  - **FK violations**: orphan references in related tables
 
 
317
 
318
- ### Task 3 — `task3_full_audit_with_trap` (Hard, max 30 steps)
319
-
320
- Full audit including:
321
 
322
- - **Statistical outliers**: z-score > 5 in any numeric column
323
- - **Duplicates**: natural key appearing more than once
324
 
325
- **THE TRAP**: One numeric value is set to 2x original — looks suspicious but has `z < 3`. Touching it costs **−0.40**.
 
 
326
 
327
- > Rule: Always `profile_column` before fixing any numeric value.
328
- > `z > 5` → real outlier → fix it. `z < 3` → legitimate → leave it.
329
 
330
  ---
331
 
332
  ## Validation (6 Checks)
333
 
334
- Run with `validate` action. Compares current state against the baseline from `reset()`:
335
-
336
  | Check | Passes when |
337
  |---|---|
338
- | `null_check` | High-confidence nulls resolved |
339
- | `type_check` | All type errors castable to float |
340
  | `range_check` | No negatives in must-be-positive columns |
341
  | `distribution_check` | Column mean drift < 20% |
342
  | `duplicate_check` | Duplicate count reduced |
343
- | `outlier_check` | No previously-flagged rows still exceed z > 5 |
344
-
345
- Returns `PASS` / `PARTIAL` / `FAIL` with per-check detail and drift warnings.
346
 
347
  ---
348
 
@@ -354,148 +265,72 @@ Returns `PASS` / `PARTIAL` / `FAIL` with per-check detail and drift warnings.
354
  | `POST` | `/reset` | Reset environment, load dataset |
355
  | `POST` | `/step` | Execute one action |
356
  | `GET` | `/state` | Current episode state |
357
- | `GET` | `/health` | Health check (`{"status":"ok"}`) |
358
  | `GET` | `/tasks` | List all 3 tasks |
359
- | `POST` | `/upload_dataset` | Upload file, get session |
360
  | `GET` | `/download/{file_id}` | Download cleaned output |
361
- | `GET` | `/docs` | OpenAPI docs (Swagger UI) |
362
 
363
  ---
364
 
365
- ## Testing
366
-
367
- ### Run all tests
368
-
369
- ```bash
370
- cd SQLSherlock-env
371
- pip install pytest
372
- pytest tests/ -v
373
- ```
374
-
375
- ### Test checklist
376
-
377
- ```
378
- tests/test_issue_detector.py ← null / type_error / constraint / outlier / duplicate
379
- tests/test_graders.py ← task1 / task2 / task3 scoring, trap penalty, FP penalty
380
- tests/test_environment.py ← reset → step → submit full episode
381
- ```
382
-
383
- Expected: all tests pass. If any fail, check [tests/conftest.py](tests/conftest.py) — the `DIRTY_RECORDS` fixture must cover all issue types.
384
-
385
- ### Manual smoke test
386
-
387
- ```bash
388
- # 1. Start server
389
- docker run -p 7860:7860 sqlsherlock-env:latest
390
-
391
- # 2. Health check
392
- curl http://localhost:7860/health
393
- # → {"status":"ok"}
394
-
395
- # 3. List tasks
396
- curl http://localhost:7860/tasks
397
- # → [{id: task1_null_and_types, ...}, ...]
398
 
399
- # 4. Run inference (requires HF_TOKEN for model access)
400
- export HF_TOKEN="hf_..."
401
- python inference.py 2>results.txt
402
- # → check stdout for [START]/[STEP]/[END] lines
403
- # → check stderr (results.txt) for score summary
404
- ```
405
 
406
  ---
407
 
408
- ## Submission Checklist
409
-
410
- ```
411
- [ ] docker build -t sqlsherlock-env:latest . ← must succeed from repo root
412
- [ ] docker run -p 7860:7860 sqlsherlock-env:latest ← must start, port 7860
413
- [ ] curl http://localhost:7860/health ← must return {"status":"ok"}
414
- [ ] python inference.py ← must emit [START]/[STEP]/[END]
415
- [ ] openenv validate ← must pass (openenv.yaml at root)
416
- [ ] Dockerfile is at repo root (not inside subdir) ← validate-submission.sh checks this
417
- [ ] openenv.yaml is at repo root ← openenv validate checks this
418
- [ ] No hardcoded secrets in any file ← use env vars only
419
- [ ] All env vars documented (API_BASE_URL, MODEL_NAME, HF_TOKEN, SPACE_URL)
420
- [ ] pytest tests/ -v ← all tests pass
421
- ```
422
-
423
- ---
424
 
425
  ## Setup on a New Device
426
 
427
- ### Option A: Docker (recommended for deployment)
428
 
429
  ```bash
430
- # 1. Clone
431
- git clone <your-repo-url>
432
- cd SQLSherlock-env
433
-
434
- # 2. Build and run
435
  docker build -t sqlsherlock-env:latest .
436
  docker run -p 7860:7860 sqlsherlock-env:latest
437
-
438
- # 3. Verify (in another terminal)
439
- curl http://localhost:7860/health
440
- # → {"status":"healthy"}
441
-
442
- # 4. Run inference
443
- export HF_TOKEN="hf_your_token_here"
444
  export SPACE_URL="http://localhost:7860"
445
  python inference.py
446
  ```
447
 
448
- ### Option B: Local Python (for development)
449
 
450
  ```bash
451
- # 1. Clone
452
- git clone <your-repo-url>
453
- cd SQLSherlock-env
454
-
455
- # 2. Create virtual environment (Python 3.11+ required)
456
- python -m venv .venv
457
-
458
- # 3. Activate venv
459
- # Linux/Mac:
460
- source .venv/bin/activate
461
- # Windows PowerShell:
462
- .venv\Scripts\Activate.ps1
463
- # Windows CMD:
464
- .venv\Scripts\activate.bat
465
-
466
- # 4. Install dependencies
467
  pip install -r sqlsherlock_env/server/requirements.txt
468
- pip install pytest # for tests
 
 
 
 
 
469
 
470
- # 5. Start the server (Terminal 1)
471
- cd sqlsherlock_env
472
- # Linux/Mac:
473
- PYTHONPATH=. uvicorn server.app:app --host 0.0.0.0 --port 7860
474
- # Windows PowerShell:
475
- $env:PYTHONPATH = (Get-Location).Path
476
- python -m uvicorn server.app:app --host 0.0.0.0 --port 7860
477
 
478
- # 6. Run inference (Terminal 2)
479
- cd SQLSherlock-env
480
- # Linux/Mac:
481
- export HF_TOKEN="hf_your_token_here"
482
- export SPACE_URL="http://localhost:7860"
483
- python inference.py
484
- # Windows PowerShell:
485
- $env:HF_TOKEN = "hf_your_token_here"
486
- $env:SPACE_URL = "http://localhost:7860"
487
  python inference.py
488
-
489
- # 7. Run tests (server not needed for tests)
490
- cd SQLSherlock-env
491
- # Linux/Mac:
492
- PYTHONPATH=sqlsherlock_env pytest tests/ -v
493
- # Windows PowerShell:
494
- $env:PYTHONPATH = "sqlsherlock_env"
495
- python -m pytest tests/ -v
496
  ```
497
 
498
- **Python version**: 3.11+ required. Dependencies: `fastapi`, `uvicorn`, `openai`, `datasets`, `pandas`, `pyarrow`.
 
 
 
 
499
 
500
  ---
501
 
@@ -503,7 +338,6 @@ python -m pytest tests/ -v
503
 
504
  ```bash
505
  pip install trl transformers torch
506
-
507
  export SPACE_URL="http://localhost:7860"
508
  export MODEL_ID="Qwen/Qwen2.5-1.5B-Instruct"
509
  python train.py
@@ -511,59 +345,40 @@ python train.py
511
 
512
  ---
513
 
514
- ## Environment Variables
515
-
516
- | Variable | Default | Description |
517
- |---|---|---|
518
- | `API_BASE_URL` | `https://router.huggingface.co/v1` | LLM endpoint |
519
- | `MODEL_NAME` | `Qwen/Qwen2.5-72B-Instruct` | Model ID |
520
- | `HF_TOKEN` | — | HuggingFace token (dataset access + LLM) |
521
- | `SPACE_URL` | `http://localhost:7860` | Environment server URL |
522
-
523
- ---
524
-
525
- ## Baseline Scores (phihung/titanic, 150 rows)
526
-
527
- | Task | Difficulty | Expected Score |
528
- |---|---|---|
529
- | `task1_null_and_types` | Easy | 0.70 – 0.88 |
530
- | `task2_constraints_and_fk` | Medium | 0.55 – 0.76 |
531
- | `task3_full_audit_with_trap` | Hard | 0.40 – 0.65 |
532
-
533
- ---
534
-
535
  ## Project Structure
536
 
537
  ```
538
  SQLSherlock-env/
539
- ├── Dockerfile ← repo root (required for HF Spaces)
540
- ├── README.md ← this file
541
- ├── openenv.yaml ← OpenEnv + HF Spaces manifest (repo root)
542
- ├── inference.py ← baseline agent ([START]/[STEP]/[END] format)
543
- ├── train.py ← TRL GRPO training loop
544
- ├── sqlsherlock_env/
545
- │ ├── __init__.py
546
- │ ├── client.py ← SQLSherlockEnv WebSocket/HTTP client
547
- │ ├── models.py ← Action / Observation / State (Pydantic)
548
- │ └── server/
549
- │ ├── app.py ← FastAPI application + WebSocket handler
550
- │ ├── environment.py ← RL core: reset() / step() / get_state()
551
- │ ├── database.py ← In-memory SQLite engine, per-episode
552
- │ ├── dataset_loader.py ← CSV / JSON / JSONL / Parquet / HF loader
553
- │ ├── schema_profiler.py ← Column statistics + z-scores
554
- │ ├── issue_detector.py ← Real issue detection + trap planting
555
- │ ├── validator.py ← 6-check before/after validator
556
- │ ├── reward.py ← Dense per-step reward with InvestCounter
557
- │ ├── exporter.py ← Format-fidelity output (CSV→CSV, etc.)
558
- │ ├── requirements.txt
559
- │ └── graders/
560
- │ ├── universal.py ← 7-step scoring pipeline
561
- │ ├── task1.py ← Task 1 grader
562
- │ ├── task2.py ← Task 2 grader
563
- │ └── task3.py ← Task 3 grader (trap-aware)
564
- └── tests/
565
- ├── conftest.py ← DIRTY_RECORDS fixture (all issue types)
566
- ├── test_issue_detector.py
567
- ├── test_graders.py
568
- └── test_environment.py
 
 
569
  ```
 
14
 
15
  # SQLSherlock-Env
16
 
17
+ **An RL environment where an AI agent performs complete data quality audits on real-world datasets.**
18
 
19
+ Data cleaning consumes ~80% of a data scientist's time. This environment trains and evaluates AI agents to do it automatically: discover issues through statistical investigation, fix them with the right imputation strategy per column type, validate fixes, and export a clean dataset.
20
 
21
+ **Key design principles:**
22
+ - **Real world data scanning** — the environment scans real datasets at `reset()` and builds a ground-truth issue catalogue from what it actually finds
23
+ - **Any dataset** — pass any HuggingFace dataset URL, local CSV/JSON/Parquet file, or raw CSV text
24
+ - **Production data cleaning** — handles nulls, type errors, constraint violations, outliers, duplicates, whitespace issues, and inconsistent categories
25
+ - **Dense reward signal** — every action produces a training signal, not just end-of-episode binary feedback
26
 
27
  ---
28
 
 
32
 
33
  ```
34
  reset(dataset, task_id)
35
+ |
36
+ v
37
+ +---------------------------------------------------------------+
38
+ | DatabaseEngine |
39
+ | |
40
+ | 1. load(source) <-- CSV / JSON / JSONL / Parquet / HF |
41
+ | 2. records_to_sqlite() <-- In-memory SQLite per episode |
42
+ | 3. deep_copy(originals) <-- Immutable snapshot before edits |
43
+ | 4. profile_table() <-- median/mode/std/z-scores |
44
+ | 5. detect_issues() <-- null/type/constraint/outlier/ |
45
+ | duplicate/whitespace/inconsistent |
46
+ | 6. Validator(baseline) <-- 6-check baseline captured |
47
+ | 7. detect_trap() <-- Task 3 only: plant 2x value |
48
+ +---------------------------------------------------------------+
49
+ |
50
+ v
51
+ Observation returned to agent
52
+ |
53
+ v
54
+ +-----------------------------------------------------+
55
+ | Agent Step Loop |
56
+ | |
57
+ | investigate: inspect / profile_column / run_sql |
58
+ | bulk fix: fix_column (all nulls in one step) |
59
+ | single fix: fix_cell / delete_row |
60
+ | check: validate |
61
+ | end: submit / export |
62
+ | |
63
+ | Each step -> reward signal -> observation |
64
+ | Repeat until submit/export or max_steps reached |
65
+ +-----------------------------------------------------+
66
+ |
67
+ v
68
+ Grader.score() -> final score [0.0 - 1.0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  ```
70
 
71
  ### Grading Pipeline (7 steps)
72
 
73
  ```
74
+ 1. Zero-change guard -- if nothing changed -> 0.0
75
+ 2. Resolution score -- per issue: confidence-weighted
76
+ 3. False-positive penalty -- -0.05 per clean cell touched
77
+ 4. Trap penalty (task3) -- -0.40 if trap cell modified
78
+ 5. Validation score -- checks_passed / total * 0.30
79
+ 6. Reasoning bonus -- +0.05 for statistical reasoning
80
+ 7. Final: res*0.60 + val*0.30 + bonus*0.10 - fp - trap
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  ```
82
 
83
  ---
84
 
85
  ## Quick Start
86
 
87
+ ### Docker (recommended)
88
 
89
  ```bash
 
90
  docker build -t sqlsherlock-env:latest .
 
 
91
  docker run -p 7860:7860 sqlsherlock-env:latest
 
 
92
  curl http://localhost:7860/health
93
+ # -> {"status":"healthy"}
94
  ```
95
 
96
+ ### Local Python
97
 
98
  ```bash
99
+ python -m venv .venv && source .venv/bin/activate # or .venv\Scripts\activate on Windows
100
+ pip install -r sqlsherlock_env/server/requirements.txt
101
  cd sqlsherlock_env
102
+ PYTHONPATH=. uvicorn server.app:app --host 0.0.0.0 --port 7860
 
103
  ```
104
 
105
+ ### Run Baseline Inference
106
 
107
  ```bash
108
  export API_BASE_URL="https://router.huggingface.co/v1"
109
  export MODEL_NAME="Qwen/Qwen2.5-72B-Instruct"
110
+ export HF_TOKEN="hf_your_token"
111
  export SPACE_URL="http://localhost:7860"
 
112
  python inference.py
113
  ```
114
 
115
+ Output format (judges parse this exactly):
116
 
117
  ```
118
  [START] task=task1_null_and_types env=sqlsherlock_env model=Qwen/Qwen2.5-72B-Instruct
119
  [STEP] step=1 action=inspect reward=0.02 done=false error=null
120
+ [STEP] step=2 action=profile_column(Age) reward=0.03 done=false error=null
121
+ [STEP] step=7 action=fix_column reward=0.18 done=false error=null
122
  ...
123
+ [END] success=true steps=20 score=0.652 rewards=0.02,0.03,...
124
  ```
125
 
126
  ---
127
 
128
+ ## Using Any Dataset
 
 
129
 
130
+ The environment works with **any** dataset, not just Titanic:
131
 
132
  ```python
133
  from sqlsherlock_env.client import SQLSherlockEnv
134
 
135
  env = SQLSherlockEnv(base_url="http://localhost:7860")
 
 
 
 
 
 
136
 
137
+ # HuggingFace dataset
138
+ obs = env.reset(dataset="scikit-learn/iris", task_id="task1_null_and_types")
139
 
140
+ # Local file
141
+ obs = env.reset(dataset="/path/to/data.csv", task_id="task2_constraints_and_fk")
 
 
 
 
142
 
143
+ # Raw CSV text
144
+ obs = env.reset(dataset="id,name,age\n1,Alice,\n2,Bob,25\n", task_id="task1_null_and_types")
 
 
 
 
 
 
145
  ```
146
 
147
+ Upload via API: `POST /upload_dataset` with a CSV/JSON/Parquet file.
148
 
149
+ Download cleaned output: use `export` action, then `GET /download/{file_id}`. Output format matches input (CSV in -> CSV out, Parquet in -> Parquet out).
 
 
 
 
150
 
151
+ ---
 
 
 
 
 
152
 
153
+ ## Data Cleaning Capabilities
154
+
155
+ The agent detects and fixes these real-world data quality issues:
156
+
157
+ | Issue Type | Detection Method | Fix Strategy | Tasks |
158
+ |---|---|---|---|
159
+ | **Null values** | `IS NULL` or empty string | Numeric: column **median**. String: column **mode** | All |
160
+ | **Type errors** | Text in predominantly numeric column (>=80% castable) | Column median | All |
161
+ | **Constraint violations** | Negative values in must-be-positive columns | `ABS(value)` | Task 2+ |
162
+ | **FK violations** | Orphan references across tables | `delete_row` | Task 2+ |
163
+ | **Whitespace** | Leading/trailing/extra spaces | Trimmed string | Task 2+ |
164
+ | **Inconsistent categories** | Case variants ("male"/"Male"/"MALE") | Dominant form (mode) | Task 2+ |
165
+ | **Statistical outliers** | IQR-based (robust to outlier-inflated std) | Column median | Task 3 |
166
+ | **Duplicates** | Same natural key appearing twice | `delete_row` on later row | Task 3 |
167
+ | **Trap** (task3 only) | Planted 2x value with z < 3 | **Do NOT touch** (-0.40 penalty) | Task 3 |
168
+
169
+ **Smart imputation per column type:**
170
+ - `profile_column` returns **median**, **mode**, **mean**, **null_rate**, **dtype**, **z_scores**
171
+ - Numeric nulls -> median (robust to outliers)
172
+ - String nulls -> mode (most frequent value)
173
+ - Structural nulls (>70% null) -> "Unknown" (low confidence)
174
+ - `fix_column` bulk-fixes ALL nulls + type errors + negatives in one step
175
 
176
  ---
177
 
 
179
 
180
  | `action_type` | Required fields | Description |
181
  |---|---|---|
182
+ | `inspect` | `table` | View all rows in the table |
183
+ | `profile_column` | `table`, `column` | Statistics: median, mode, mean, std, null_count, null_rate, z_scores, dtype |
184
+ | `run_sql` | `sql` | Read-only SELECT query (max 50 rows) |
185
  | `fix_cell` | `table`, `row_id`, `column`, `value`, `reason` | Fix one cell with justification |
186
+ | `fix_column` | `table`, `column`, `value`, `reason` | Fix ALL nulls + type errors + negatives in a column (bulk) |
187
+ | `delete_row` | `table`, `row_id`, `reason` | Remove a duplicate or FK-violation row |
188
+ | `validate` | -- | Run all 6 before/after validation checks |
189
+ | `submit` | -- | Score and end episode |
190
+ | `export` | -- | Write cleaned file, score, and end episode |
191
 
192
  ---
193
 
194
  ## Reward System
195
 
196
+ Dense per-step rewards -- every action produces a training signal:
197
+
198
  | Action | Reward | Cap |
199
  |---|---|---|
200
  | `inspect` | +0.02 | 3 rewarded |
201
  | `profile_column` | +0.03 | 3 rewarded |
202
  | `run_sql` | +0.03 | 3 rewarded |
203
+ | `validate` | +0.05 * (checks_passed / 6) | 2 rewarded |
204
+ | `fix_cell` -- correct | **+0.15** | -- |
205
+ | `fix_cell` -- false positive | **-0.20** | -- |
206
+ | `fix_cell` -- trap cell | **-0.40** | -- |
207
+ | `fix_column` -- has issues | **+0.15 to +0.30** (proportional to issues resolved) | -- |
208
+ | `fix_column` -- no issues | **-0.10** | -- |
209
+ | `delete_row` -- valid | **+0.15** | -- |
210
+ | `delete_row` -- false positive | **-0.20** | -- |
211
+ | `submit` -- all resolved | **+0.10** | -- |
212
+ | `submit` -- issues remain | **-0.10** | -- |
213
 
214
  ---
215
 
216
  ## Three Tasks
217
 
218
+ ### Task 1 -- `task1_null_and_types` (Easy, max 30 steps)
219
 
220
+ Find and fix **null values** and **type errors** across all columns.
221
 
222
+ - Detects: nulls, empty strings, text in numeric columns
223
+ - Scoring: `resolution * 0.70 + validation * 0.30`
224
+ - Grader weights each issue by confidence (structural nulls = low confidence)
225
 
226
+ ### Task 2 -- `task2_constraints_and_fk` (Medium, max 40 steps)
227
 
228
+ Everything in Task 1, plus:
229
 
230
+ - **Constraint violations**: negative values in must-be-positive columns
231
  - **FK violations**: orphan references in related tables
232
+ - **Whitespace issues**: leading/trailing spaces
233
+ - **Inconsistent categories**: case variants normalized to dominant form
234
 
235
+ ### Task 3 -- `task3_full_audit_with_trap` (Hard, max 50 steps)
 
 
236
 
237
+ Full statistical audit including:
 
238
 
239
+ - **Outliers**: IQR-based detection (z > 5)
240
+ - **Duplicates**: natural key collision
241
+ - **THE TRAP**: One numeric value set to 2x original. Looks suspicious but has z < 3. Touching it costs **-0.40**.
242
 
243
+ Rule: Always `profile_column` before fixing numeric values. z > 5 = outlier (fix). z < 3 = normal (leave alone).
 
244
 
245
  ---
246
 
247
  ## Validation (6 Checks)
248
 
 
 
249
  | Check | Passes when |
250
  |---|---|
251
+ | `null_check` | Nulls resolved (weighted by confidence) |
252
+ | `type_check` | Type errors castable to correct type |
253
  | `range_check` | No negatives in must-be-positive columns |
254
  | `distribution_check` | Column mean drift < 20% |
255
  | `duplicate_check` | Duplicate count reduced |
256
+ | `outlier_check` | Flagged outlier rows no longer exceed z > 5 |
 
 
257
 
258
  ---
259
 
 
265
  | `POST` | `/reset` | Reset environment, load dataset |
266
  | `POST` | `/step` | Execute one action |
267
  | `GET` | `/state` | Current episode state |
268
+ | `GET` | `/health` | Health check |
269
  | `GET` | `/tasks` | List all 3 tasks |
270
+ | `POST` | `/upload_dataset` | Upload CSV/JSON/Parquet file |
271
  | `GET` | `/download/{file_id}` | Download cleaned output |
272
+ | `GET` | `/docs` | OpenAPI documentation (Swagger UI) |
273
 
274
  ---
275
 
276
+ ## Environment Variables
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
+ | Variable | Default | Description |
279
+ |---|---|---|
280
+ | `API_BASE_URL` | `https://router.huggingface.co/v1` | LLM API endpoint |
281
+ | `MODEL_NAME` | `Qwen/Qwen2.5-72B-Instruct` | Model identifier |
282
+ | `HF_TOKEN` | -- (required) | HuggingFace token for LLM access |
283
+ | `SPACE_URL` | `http://localhost:7860` | Environment server URL |
284
 
285
  ---
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
 
288
  ## Setup on a New Device
289
 
290
+ ### Option A: Docker
291
 
292
  ```bash
293
+ git clone <repo-url> && cd SQLSherlock-env
 
 
 
 
294
  docker build -t sqlsherlock-env:latest .
295
  docker run -p 7860:7860 sqlsherlock-env:latest
296
+ # In another terminal:
297
+ export HF_TOKEN="hf_your_token"
 
 
 
 
 
298
  export SPACE_URL="http://localhost:7860"
299
  python inference.py
300
  ```
301
 
302
+ ### Option B: Local Python (Linux/Mac)
303
 
304
  ```bash
305
+ git clone <repo-url> && cd SQLSherlock-env
306
+ python3 -m venv .venv && source .venv/bin/activate
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  pip install -r sqlsherlock_env/server/requirements.txt
308
+ # Terminal 1:
309
+ cd sqlsherlock_env && PYTHONPATH=. uvicorn server.app:app --host 0.0.0.0 --port 7860
310
+ # Terminal 2:
311
+ export HF_TOKEN="hf_your_token" SPACE_URL="http://localhost:7860"
312
+ python inference.py
313
+ ```
314
 
315
+ ### Option C: Local Python (Windows PowerShell)
 
 
 
 
 
 
316
 
317
+ ```powershell
318
+ git clone <repo-url>; cd SQLSherlock-env
319
+ python -m venv .venv; .venv\Scripts\Activate.ps1
320
+ pip install -r sqlsherlock_env\server\requirements.txt
321
+ # Terminal 1:
322
+ cd sqlsherlock_env; $env:PYTHONPATH=(Get-Location).Path
323
+ python -m uvicorn server.app:app --host 0.0.0.0 --port 7860
324
+ # Terminal 2:
325
+ $env:HF_TOKEN="hf_your_token"; $env:SPACE_URL="http://localhost:7860"
326
  python inference.py
 
 
 
 
 
 
 
 
327
  ```
328
 
329
+ ### Run Tests
330
+
331
+ ```bash
332
+ PYTHONPATH=sqlsherlock_env pytest tests/ -v # 98 tests, all pass
333
+ ```
334
 
335
  ---
336
 
 
338
 
339
  ```bash
340
  pip install trl transformers torch
 
341
  export SPACE_URL="http://localhost:7860"
342
  export MODEL_ID="Qwen/Qwen2.5-1.5B-Instruct"
343
  python train.py
 
345
 
346
  ---
347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
348
  ## Project Structure
349
 
350
  ```
351
  SQLSherlock-env/
352
+ +-- Dockerfile <- repo root (HF Spaces)
353
+ +-- README.md <- this file
354
+ +-- openenv.yaml <- OpenEnv manifest (3 tasks)
355
+ +-- inference.py <- baseline inference ([START]/[STEP]/[END])
356
+ +-- train.py <- TRL GRPO training scaffold
357
+ +-- .gitignore
358
+ +-- .dockerignore
359
+ +-- sqlsherlock_env/
360
+ | +-- __init__.py
361
+ | +-- client.py <- synchronous WebSocket/HTTP client
362
+ | +-- models.py <- Action / Observation / State (Pydantic)
363
+ | +-- server/
364
+ | +-- app.py <- FastAPI + WebSocket handler
365
+ | +-- environment.py <- RL core: reset() / step() / state
366
+ | +-- database.py <- In-memory SQLite engine per episode
367
+ | +-- dataset_loader.py <- CSV / JSON / JSONL / Parquet / HF
368
+ | +-- schema_profiler.py <- Column statistics + z-scores
369
+ | +-- issue_detector.py <- 8 issue types + trap planting
370
+ | +-- validator.py <- 6-check before/after validator
371
+ | +-- reward.py <- Dense per-step reward
372
+ | +-- exporter.py <- Format-preserving output writer
373
+ | +-- requirements.txt
374
+ | +-- graders/
375
+ | +-- universal.py <- 7-step scoring pipeline
376
+ | +-- task1.py
377
+ | +-- task2.py
378
+ | +-- task3.py
379
+ +-- tests/ <- 98 tests
380
+ +-- conftest.py
381
+ +-- test_issue_detector.py
382
+ +-- test_graders.py
383
+ +-- test_environment.py
384
  ```