uchihamadara1816 commited on
Commit
d8797e8
·
verified ·
1 Parent(s): d66d765

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +48 -0
  2. README.md +391 -6
  3. inference.py +54 -0
  4. validate_submission.py +135 -0
Dockerfile ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AutoDataLab — Hugging Face Spaces (Docker SDK)
2
+ # Runs the FastAPI / OpenEnv server on port 7860.
3
+ #
4
+ # This is the SUBMISSION entrypoint — it exposes the OpenEnv HTTP API:
5
+ # POST /reset POST /step GET /state GET /health
6
+ #
7
+ # For the interactive Gradio demo, run locally: python app.py
8
+ #
9
+ # Secrets (set in HF Space Settings → Repository secrets):
10
+ # GROQ_API_KEY optional — only needed if running inference from the Space
11
+ # HF_TOKEN optional — same as above
12
+ #
13
+ # Build context: repository root
14
+ FROM python:3.10-slim
15
+
16
+ WORKDIR /app
17
+
18
+ # System deps
19
+ RUN apt-get update && apt-get install -y --no-install-recommends \
20
+ curl git \
21
+ && rm -rf /var/lib/apt/lists/*
22
+
23
+ # Copy project files
24
+ COPY data_cleaning_env /app/data_cleaning_env
25
+ COPY inference.py validate_submission.py app.py /app/
26
+
27
+ # Install env package + extras (openai = LLM client + python-docx)
28
+ RUN pip install --no-cache-dir \
29
+ -e "/app/data_cleaning_env[openai]" \
30
+ gradio>=4.0.0 \
31
+ matplotlib>=3.7.0
32
+
33
+ # Non-root user (HF Spaces requirement)
34
+ RUN useradd -m appuser && chown -R appuser /app
35
+ USER appuser
36
+
37
+ ENV PYTHONUNBUFFERED=1
38
+ ENV PORT=7860
39
+
40
+ EXPOSE 7860
41
+
42
+ # Health check hits the FastAPI /health endpoint
43
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
44
+ CMD curl -fsS http://127.0.0.1:7860/health || exit 1
45
+
46
+ # Run the FastAPI server — exposes /reset /step /state for OpenEnv validators
47
+ CMD ["python", "-m", "uvicorn", "data_cleaning_env.server.app:app", \
48
+ "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,11 +1,396 @@
1
  ---
2
- title: Autodatalab Env
3
- emoji: 💻
4
- colorFrom: purple
5
- colorTo: purple
6
  sdk: docker
7
  pinned: false
8
- short_description: An OpenEnv based Data Laboratory
 
 
 
 
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: AutoDataLab Data Cleaning Env
3
+ emoji: 🛒
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
+ tags:
9
+ - openenv
10
+ - data-cleaning
11
+ - ecommerce
12
+ - reinforcement-learning
13
  ---
14
 
15
+ # AutoDataLab E-Commerce Data Analyst Environment
16
+
17
+ A **hackathon-ready** [OpenEnv](https://github.com/meta-pytorch/OpenEnv) environment that simulates a real e-commerce data analyst workflow.
18
+
19
+ Clean order-level data, compute business metrics, and declare insightful charts — all scored deterministically against a ground-truth CSV.
20
+
21
+ ---
22
+
23
+ ## What Is This?
24
+
25
+ Data teams spend a huge fraction of their time on:
26
+
27
+ - Deduplication
28
+ - Missing value handling
29
+ - Outlier detection and removal
30
+ - Derived columns and rollups
31
+ - Visualization declarations
32
+
33
+ This environment turns that daily workflow into a **step / reset / state** RL-style API with **deterministic graders** and **dense reward shaping**.
34
+
35
+ ---
36
+
37
+ ## Example Dataset
38
+
39
+ Columns: `OrderID`, `CustomerID`, `Age`, `Product`, `Category`, `Price`, `Quantity`, `OrderDate`
40
+
41
+ | OrderID | CustomerID | Age | Product | Category | Price | Quantity | OrderDate | |
42
+ |---------|------------|-----------|---------|-------------|-----------|----------|------------|------------|
43
+ | 101 | C001 | 25 | Shoes | Fashion | 2000 | 1 | 2023-01-01 | |
44
+ | 101 | C001 | 25 | Shoes | Fashion | 2000 | 1 | 2023-01-01 | ← duplicate |
45
+ | 102 | C002 | *(empty)* | Laptop | Electronics | 60000 | 1 | 2023-01-02 | ← missing age |
46
+ | 103 | C003 | 200 | T-shirt | Fashion | 500 | 2 | 2023-01-03 | ← outlier age |
47
+ | 104 | C004 | 30 | Phone | Electronics | *(empty)* | 1 | 2023-01-04 | ← missing price |
48
+
49
+ ---
50
+
51
+ ## Tasks
52
+
53
+ | Task | Difficulty | Analyst Goal |
54
+ |------|------------|--------------|
55
+ | `easy` | 🟢 Easy | **Data Cleaning** — dedupe, remove bogus Age outliers, impute Age and Price (mean). 450-row dataset. |
56
+ | `medium` | 🟡 Medium | **Business Metrics** — pre-cleaned data given; run `compute_metrics` → category-level revenue table. |
57
+ | `medium_plus` | 🟠 Medium+ | **Full KPIs** — run `compute_kpis` → `Metric / Value` table with TotalRevenue + AvgOrderValue. |
58
+ | `hard` | 🔴 Hard | **Cleaning + Insight** — full cleaning as in `easy`, then `derive_revenue`, then two declared plots. |
59
+ | `expert` | 🟣 Expert | **Full Pipeline** — cleaning + revenue derivation + both plots (higher step cap). |
60
+
61
+ Grading uses **cell-wise equality** (with float tolerance for imputed values) vs `tasks/<task>/ground_truth.csv`, weighted with plot correctness when `metadata.json` lists `expected_plots`.
62
+
63
+ ---
64
+
65
+ ## Action Space
66
+
67
+ `DataCleaningAction` fields:
68
+
69
+ | Field | Meaning |
70
+ |-------|---------|
71
+ | `action_type` | `remove_duplicates`, `fill_missing`, `drop_column`, `normalize`, `remove_outliers`, `derive_revenue`, `compute_metrics`, `compute_kpis`, `plot`, `export_csv`, `submit`, `noop` |
72
+ | `column` | Target column for column-wise ops |
73
+ | `method` | `mean` / `median` / `mode` — for `fill_missing` |
74
+ | `z_threshold` | Cutoff for robust outlier removal (modified z-score on `log1p` values) |
75
+ | `x`, `y`, `plot_type` | `scatter` / `bar` / `histogram` — for `plot` |
76
+
77
+ - **`derive_revenue`** adds `Revenue = Price × Quantity`
78
+ - **`compute_metrics`** produces category-level aggregates
79
+ - **`compute_kpis`** computes TotalRevenue + AvgOrderValue
80
+ - **`submit`** finalises the episode; `terminal_grader_score` in `[0, 1]` is returned
81
+
82
+ ---
83
+
84
+ ## Observation Space
85
+
86
+ `DataCleaningObservation` includes:
87
+
88
+ | Field | Description |
89
+ |-------|-------------|
90
+ | `preview` | First rows of the working dataframe |
91
+ | `issues` | Heuristic tags (`duplicates`, `missing_values`, …) |
92
+ | `task_name`, `task_difficulty` | Task metadata |
93
+ | `instruction` | Human-readable task description |
94
+ | `history` | Serialised actions taken so far |
95
+ | `reward`, `reward_breakdown` | Immediate, cumulative, and terminal grader score |
96
+ | `terminal_grader_score` | Final score when `done=True` |
97
+
98
+ ---
99
+
100
+ ## Reward Design
101
+
102
+ **Shaping rewards** — applied at each step:
103
+
104
+ - Small positive reward for productive actions (rows removed, nulls filled, etc.)
105
+ - Penalties for `noop`, destructive `drop_column`, and repeated identical actions
106
+
107
+ **Terminal reward** — applied on `submit` (or when `max_steps` is hit):
108
+
109
+ - `terminal_grader_score` in `[0, 1]` is added to the final step's reward
110
+ - This ensures the last transition carries the primary learning signal
111
+
112
+ ---
113
+
114
+ ## Gradio Web UI
115
+
116
+ An interactive demo is included at `app.py`:
117
+
118
+ ```bash
119
+ pip install -e "./data_cleaning_env[openai]"
120
+ pip install gradio matplotlib
121
+
122
+ python app.py # opens http://127.0.0.1:7861
123
+ python app.py --share # public Gradio link
124
+ ```
125
+
126
+ **Features:**
127
+
128
+ - Live **data preview** — table updates after each action
129
+ - **Issue detector** panel — duplicates, missing values, outliers
130
+ - **Manual action form** — dropdowns + fields → JSON preview
131
+ - **Oracle step / Run full oracle** buttons
132
+ - **Score badge** on submit
133
+ - **Plot gallery** — renders charts inline when plot actions are taken
134
+ - **Download Word report** (`.docx`) button
135
+ - Task selector with pipeline hints (dark mode compatible)
136
+
137
+ ---
138
+
139
+ ## Setup
140
+
141
+ ```bash
142
+ cd data_cleaning_env
143
+ python -m venv .venv && source .venv/bin/activate
144
+ pip install -e ".[dev]"
145
+ ```
146
+
147
+ Validate the OpenEnv layout:
148
+
149
+ ```bash
150
+ openenv validate --verbose
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Run the FastAPI Server
156
+
157
+ ```bash
158
+ cd data_cleaning_env
159
+ uvicorn server.app:app --host 0.0.0.0 --port 7860
160
+ ```
161
+
162
+ - Web UI: `/web`
163
+ - API docs: `/docs`
164
+ - Health check: `/health`
165
+
166
+ ---
167
+
168
+ ## Baseline Inference (Reproducible Scores)
169
+
170
+ ### Oracle (no API key — deterministic 1.0 on all tasks)
171
+
172
+ ```bash
173
+ cd data_cleaning_env
174
+
175
+ python baseline_inference.py --oracle
176
+
177
+ python baseline_inference.py --oracle --tasks easy,medium,medium_plus,hard,expert
178
+ ```
179
+
180
+ ### Word Reports
181
+
182
+ Install `python-docx`:
183
+
184
+ ```bash
185
+ pip install -e ".[report]"
186
+ # or
187
+ pip install -e ".[openai]"
188
+ ```
189
+
190
+ By default, reports are written to `./reports/` (`session_report.docx` plus per-task episode reports).
191
+
192
+ Override with `--report-dir DIR` or `AUTODATALAB_REPORT_DIR`. Disable with `--no-report`.
193
+
194
+ ---
195
+
196
+ ## LLM Baseline
197
+
198
+ ### OpenAI
199
+
200
+ ```bash
201
+ export OPENAI_API_KEY=sk-...
202
+ # optional: OPENAI_BASE_URL, MODEL_NAME (default: gpt-4o-mini)
203
+
204
+ python baseline_inference.py --provider openai
205
+ ```
206
+
207
+ ### Groq
208
+
209
+ ```bash
210
+ export GROQ_API_KEY=gsk_...
211
+
212
+ export OPENAI_BASE_URL=https://api.groq.com/openai/v1
213
+ export MODEL_NAME=llama-3.1-8b-instant
214
+
215
+ python baseline_inference.py --provider groq
216
+ ```
217
+
218
+ ### Google Gemini
219
+
220
+ ```bash
221
+ export GEMINI_API_KEY=... # from Google AI Studio
222
+
223
+ # optional: GEMINI_MODEL=gemini-1.5-flash (default)
224
+
225
+ python baseline_inference.py --provider gemini
226
+ ```
227
+
228
+ ### Auto Provider Detection
229
+
230
+ `LLM_PROVIDER=auto` (or unset) checks keys in this order: `OPENAI_API_KEY` → `GROQ_API_KEY` → `GEMINI_API_KEY`.
231
+
232
+ > **Tip:** if `OPENAI_API_KEY` looks like a Groq key (`gsk_`) and `GEMINI_API_KEY` is also set, Gemini is used automatically. Use `--provider groq` or `--provider gemini` to force a specific backend.
233
+
234
+ ### Tips for Faster LLM Runs
235
+
236
+ - Use `--tasks easy` (or `easy,medium`) for quick single-task checks
237
+ - Pass `--no-report` to skip Word export overhead
238
+ - Use `--provider groq` with `llama-3.1-8b-instant` for the fastest completions
239
+ - Set `LLM_JSON_MODE=0` if JSON mode causes extra round-trips on your backend
240
+ - Lower `--llm-retry-delay` only if you are not hitting 429 rate limits
241
+
242
+ ---
243
+
244
+ ## Baseline Scores (Oracle)
245
+
246
+ | Task | Terminal Grader |
247
+ |------|:---------------:|
248
+ | easy | **1.0** |
249
+ | medium | **1.0** |
250
+ | hard | **1.0** |
251
+ | **mean** | **1.0** |
252
+
253
+ ---
254
+
255
+ ## Docker (Local)
256
+
257
+ From the repository root:
258
+
259
+ ```bash
260
+ docker build -t autodatalab-openenv .
261
+ docker run --rm -p 7860:7860 autodatalab-openenv
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Hugging Face Spaces
267
+
268
+ 1. Push this repo to a **Docker** Space on Hugging Face.
269
+ 2. Use the root **`Dockerfile`** (build context = repo root).
270
+ 3. The server listens on **`PORT`** (default `7860`; matches `openenv.yaml`).
271
+ 4. Tag the Space with **`openenv`** (see `data_cleaning_env/README.md` frontmatter).
272
+ 5. Health check: `GET /health` should return **200**.
273
+
274
+ Or push directly from `data_cleaning_env/`:
275
+
276
+ ```bash
277
+ openenv push
278
+ ```
279
+
280
+ *(requires `huggingface-cli` login)*
281
+
282
+ ---
283
+
284
+ ## Submission / Course Alignment
285
+
286
+ This repo follows the OpenEnv environment pattern from [**Building RL Environments with OpenEnv**](https://github.com/raun/openenv-course).
287
+
288
+ **Root `inference.py`** — required entry point for hackathons:
289
+
290
+ | Variable | Role |
291
+ |----------|------|
292
+ | `API_BASE_URL` | OpenAI-compatible base URL |
293
+ | `MODEL_NAME` | Model ID |
294
+ | `HF_TOKEN` | API key (mapped to `OPENAI_API_KEY`) |
295
+
296
+ Copy **`.env.example`** to `.env` and fill values. Run `--oracle` to validate without an API key.
297
+
298
+ **Pre-flight checks:**
299
+
300
+ ```bash
301
+ pip install -e ./data_cleaning_env
302
+ pip install -e "./data_cleaning_env[dev]" # optional: pytest
303
+
304
+ python validate_submission.py
305
+ ```
306
+
307
+ This runs `openenv validate`, `pytest`, grader checks on easy / medium / hard, and `python inference.py --oracle`. Add `--docker` to also run `docker build`.
308
+
309
+ ---
310
+
311
+ ## Project Layout
312
+
313
+ ```
314
+ .
315
+ ├── data_cleaning_env/ # OpenEnv package (models, server, client, tasks, openenv.yaml)
316
+ ├── app.py # Gradio Web UI
317
+ ├── Dockerfile # HF Spaces / container entrypoint
318
+ ├── inference.py # Root inference script (delegates to baseline_inference.py)
319
+ ├── validate_submission.py # Pre-submission checks
320
+ └── .env.example # Template for API_BASE_URL, MODEL_NAME, HF_TOKEN
321
+ ```
322
+
323
+ ---
324
+
325
+ ---
326
+
327
+ ## Submission Checklist
328
+
329
+ Use this before pasting your HF Spaces URL.
330
+
331
+ ### Step 1 — Set environment variables
332
+
333
+ Copy `.env.example` to `.env` and fill in your values:
334
+
335
+ ```bash
336
+ cp .env.example .env
337
+ ```
338
+
339
+ | Variable | Required | Description |
340
+ |----------|----------|-------------|
341
+ | `API_BASE_URL` | Yes | OpenAI-compatible base URL (e.g. Groq, OpenAI) |
342
+ | `MODEL_NAME` | Yes | Model ID (e.g. `llama-3.1-8b-instant`) |
343
+ | `HF_TOKEN` | Yes | Your API key (mapped to `OPENAI_API_KEY`) |
344
+
345
+ ### Step 2 — Run the validator
346
+
347
+ ```bash
348
+ python validate_submission.py
349
+ ```
350
+
351
+ This automatically checks all of the following:
352
+
353
+ | Check | What it validates |
354
+ |-------|------------------|
355
+ | `openenv validate` | `openenv.yaml` spec, typed models, endpoint layout |
356
+ | `pytest` | Unit tests pass |
357
+ | Grader identity check | `easy`, `medium`, `hard` graders return scores in `[0.0, 1.0]` |
358
+ | Oracle inference | `inference.py --oracle` completes without error and produces scores |
359
+ | Docker build *(optional)* | `python validate_submission.py --docker` |
360
+
361
+ ### Step 3 — Deploy to HF Spaces
362
+
363
+ ```bash
364
+ # From data_cleaning_env/
365
+ openenv push --repo-id your-username/my-env
366
+ ```
367
+
368
+ Or push the repo to a **Docker** Space manually (see Hugging Face Spaces section above).
369
+
370
+ ### Step 4 — Verify the live Space
371
+
372
+ ```bash
373
+ # Must return HTTP 200
374
+ curl https://your-username-my-env.hf.space/health
375
+
376
+ # Must accept reset()
377
+ curl -X POST https://your-username-my-env.hf.space/reset \
378
+ -H "Content-Type: application/json" \
379
+ -d '{"task": "easy"}'
380
+ ```
381
+
382
+ ### Step 5 — Final pre-submit check
383
+
384
+ - [ ] `inference.py` is in the **root directory**
385
+ - [ ] `API_BASE_URL`, `MODEL_NAME`, `HF_TOKEN` are defined in your environment / Space secrets
386
+ - [ ] All LLM calls go through the **OpenAI Python client** (`from openai import OpenAI`)
387
+ - [ ] Oracle inference finishes in **< 20 minutes** on 2 vCPU / 8 GB RAM
388
+ - [ ] `python validate_submission.py` exits with code **0**
389
+ - [ ] HF Space URL returns **200** and responds to **`POST /reset`**
390
+ - [ ] At least **3 tasks** are registered in `openenv.yaml` with working graders
391
+
392
+ ---
393
+
394
+ ## License
395
+
396
+ Environment scaffold includes Meta/OpenEnv BSD-style headers; see files inside `data_cleaning_env/`.
inference.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Root inference entrypoint (hackathon / HF submission).
4
+
5
+ Required environment variables (see also ``.env.example``):
6
+ API_BASE_URL OpenAI-compatible API base URL (e.g. Groq or OpenAI).
7
+ MODEL_NAME Chat model id for the OpenAI client.
8
+ HF_TOKEN API key passed to the OpenAI client (``OPENAI_API_KEY`` is set from this).
9
+
10
+ Word reports: by default writes under ``./reports`` (override with ``--report-dir`` or
11
+ ``AUTODATALAB_REPORT_DIR``; disable with ``--no-report``). Requires ``python-docx``
12
+ (``pip install -e "./data_cleaning_env[openai]"`` or ``[report]``).
13
+
14
+ All LLM calls use the ``openai`` Python package (``OpenAI`` client) against ``API_BASE_URL``.
15
+
16
+ Designed to finish within typical contest limits (e.g. <20 minutes) on modest hardware (e.g. 2 vCPU / 8GB RAM).
17
+
18
+ Course context: `Building RL Environments with OpenEnv <https://github.com/raun/openenv-course>`_.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import os
24
+ import sys
25
+ from pathlib import Path
26
+
27
+
28
+ def _apply_submission_env() -> None:
29
+ """Map submission variable names onto what ``baseline_inference`` / OpenAI client expect."""
30
+ token = (os.environ.get("HF_TOKEN") or "").strip().strip('"').strip("'")
31
+ if token and not (os.environ.get("OPENAI_API_KEY") or "").strip():
32
+ os.environ["OPENAI_API_KEY"] = token
33
+ base = (os.environ.get("API_BASE_URL") or "").strip().rstrip("/")
34
+ if base:
35
+ os.environ["OPENAI_BASE_URL"] = base
36
+ os.environ.setdefault("API_BASE_URL", base)
37
+ model = (os.environ.get("MODEL_NAME") or "").strip()
38
+ if model:
39
+ os.environ["MODEL_NAME"] = model
40
+
41
+
42
+ def main() -> None:
43
+ _apply_submission_env()
44
+ repo = Path(__file__).resolve().parent
45
+ if str(repo) not in sys.path:
46
+ sys.path.insert(0, str(repo))
47
+
48
+ from data_cleaning_env.baseline_inference import main as baseline_main
49
+
50
+ baseline_main()
51
+
52
+
53
+ if __name__ == "__main__":
54
+ main()
validate_submission.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Pre-submission checks: OpenEnv layout, unit tests, oracle inference, grader scores in [0,1].
4
+
5
+ Run from repository root (after ``pip install -e data_cleaning_env`` or ``pip install -e ./data_cleaning_env``):
6
+
7
+ python validate_submission.py
8
+
9
+ Optional:
10
+
11
+ python validate_submission.py --docker # also ``docker build`` (requires Docker)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import json
18
+ import shutil
19
+ import subprocess
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ ROOT = Path(__file__).resolve().parent
24
+ ENV_DIR = ROOT / "data_cleaning_env"
25
+
26
+
27
+ def _openenv_cli() -> str | None:
28
+ """Resolve ``openenv`` executable (same venv as this script, then PATH)."""
29
+ sibling = Path(sys.executable).resolve().parent / "openenv"
30
+ if sibling.is_file():
31
+ return str(sibling)
32
+ return shutil.which("openenv")
33
+
34
+
35
+ def run(cmd: list[str], *, cwd: Path | None = None) -> int:
36
+ print(f"$ {' '.join(cmd)}", flush=True)
37
+ return subprocess.call(cmd, cwd=cwd or ROOT)
38
+
39
+
40
+ def check_graders_three_tasks() -> int:
41
+ """Run bundled easy/medium/hard graders; scores must be in [0, 1]."""
42
+ import pandas as pd
43
+
44
+ sys.path.insert(0, str(ROOT))
45
+ from data_cleaning_env.graders import grade_task
46
+
47
+ for name in ("easy", "medium", "hard"):
48
+ tdir = ENV_DIR / "tasks" / name
49
+ gt = pd.read_csv(tdir / "ground_truth.csv")
50
+ with open(tdir / "metadata.json", encoding="utf-8") as f:
51
+ meta = json.load(f)
52
+ # Build a synthetic perfect plot action matching the first expected plot (if any)
53
+ plot_action = None
54
+ expected = meta.get("expected_plots")
55
+ if expected:
56
+ ep = expected[0]
57
+ plot_action = {
58
+ "plot_type": ep.get("type", "scatter"),
59
+ "x": ep.get("x", "OrderDate"),
60
+ "y": ep.get("y", "Revenue"),
61
+ }
62
+ s = grade_task(gt, gt, meta, plot_action, None)
63
+ if s is None:
64
+ print(f"FAIL: grader returned None for task={name}", file=sys.stderr)
65
+ return 1
66
+ if not (0.0 <= float(s) <= 1.0):
67
+ print(f"FAIL: task={name} score {s} not in [0,1]", file=sys.stderr)
68
+ return 1
69
+ print(f"ok: task={name} identity grader score={s:.4f}", flush=True)
70
+ return 0
71
+
72
+
73
+ def main() -> int:
74
+ ap = argparse.ArgumentParser(description="Pre-submission validation")
75
+ ap.add_argument(
76
+ "--docker",
77
+ action="store_true",
78
+ help="Run docker build from repo root (requires Docker daemon)",
79
+ )
80
+ args = ap.parse_args()
81
+ code = 0
82
+
83
+ if not ENV_DIR.is_dir():
84
+ print(f"Missing {ENV_DIR}", file=sys.stderr)
85
+ return 1
86
+
87
+ oe = _openenv_cli()
88
+ if not oe:
89
+ print(
90
+ "openenv CLI not found. Install with: pip install 'openenv-core[core]'",
91
+ file=sys.stderr,
92
+ )
93
+ return 1
94
+
95
+ r = run([oe, "validate", "--verbose", str(ENV_DIR)])
96
+ code |= r
97
+ if r != 0:
98
+ return code
99
+
100
+ r = run([sys.executable, "-m", "pytest", "tests/", "-q"], cwd=ENV_DIR)
101
+ code |= r
102
+ if r != 0:
103
+ return code
104
+
105
+ r = check_graders_three_tasks()
106
+ code |= r
107
+ if r != 0:
108
+ return code
109
+
110
+ r = run([sys.executable, str(ROOT / "inference.py"), "--oracle", "--no-report"])
111
+ code |= r
112
+ if r != 0:
113
+ return code
114
+
115
+ if args.docker:
116
+ r = run(
117
+ [
118
+ "docker",
119
+ "build",
120
+ "-t",
121
+ "autodatalab-openenv",
122
+ "-f",
123
+ str(ROOT / "Dockerfile"),
124
+ str(ROOT),
125
+ ]
126
+ )
127
+ code |= r
128
+
129
+ if code == 0:
130
+ print("validate_submission: all checks passed.", flush=True)
131
+ return code
132
+
133
+
134
+ if __name__ == "__main__":
135
+ raise SystemExit(main())