Arry7868 commited on
Commit
1439536
·
verified ·
1 Parent(s): 4cabbfc
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["uvicorn", "api.server:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,11 +1,189 @@
1
- ---
2
- title: Kaggle Simulation Environment
3
- emoji: 📚
4
- colorFrom: purple
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # KaggleSimEnv v3
2
+
3
+ Production-grade **OpenEnv** RL environment simulating Kaggle competitions with **hierarchical action categories**, **causal dataset properties**, **failure-mode traps**, **contextual strategy scoring**, and **50+ advanced strategies**.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ pip install -r requirements.txt
9
+ uvicorn api.server:app --host 0.0.0.0 --port 7860 --reload
10
+ ```
11
+
12
+ ### Baseline agent
13
+
14
+ ```bash
15
+ export OPENAI_API_KEY=sk-...
16
+ python -m baseline.run_baseline --mode local
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Architecture
22
+
23
+ ```
24
+ openenvHackathon/
25
+ ├── kaggle_sim_env/
26
+ │ ├── models.py # Hierarchical categories, DatasetProperties, FailureMode
27
+ │ ├── environment.py # Causal logic, trap detection, mitigation tracking
28
+ │ ├── tasks.py # 5 tasks with properties, traps, context relevance
29
+ │ ├── grader.py # 4-axis grading (perf + strategy + combo + trap)
30
+ │ ├── leaderboard.py # Ghost competitor leaderboard
31
+ │ ├── hints.py # Per-task hint dispensing
32
+ │ └── rewards.py # 9-component dense reward
33
+ ├── api/server.py # FastAPI (8 endpoints)
34
+ ├── baseline/run_baseline.py # Structured phase-based agent
35
+ ├── openenv.yaml / Dockerfile / requirements.txt
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Hierarchical Action Space
41
+
42
+ Actions use `category` to reduce search space:
43
+
44
+ ```json
45
+ {
46
+ "action_type": "feature_engineering",
47
+ "parameters": {
48
+ "category": "distribution",
49
+ "technique": "log_transform"
50
+ }
51
+ }
52
+ ```
53
+
54
+ | Action Type | Categories → Techniques |
55
+ |---|---|
56
+ | `set_cv` | standard(kfold, repeated_kfold) · group(group_kfold, stratified_group_kfold) · temporal(time_split, combined_group_time) |
57
+ | `feature_engineering` | distribution(log_transform, normalize, quantile_features) · interaction(interaction_terms, domain_ratios) · encoding(sin_cos_encoding, target_encoding, spatial_encoding, tfidf_features) · spatial(relative_coordinates, distance_features) · signal(frequency_features, multi_layer_features, fourier_resampling) |
58
+ | `detect_shift` | detection(adversarial_validation, feature_importance_shift) · mitigation(remove_identifiers, domain_invariant_features) |
59
+ | `train_model` | tree(xgboost, lightgbm, catboost, random_forest) · linear(linear) · neural(neural_network, pretrained_backbone, temporal_cnn, transformer_encoder) |
60
+ | `handle_imbalance` | weighting(scale_pos_weight, class_weighted_loss) · calibration(calibrate_probabilities, optimize_threshold) · hierarchy(hierarchical_labels, lower_thresholds_recall) |
61
+ | `clean_data` | removal(remove_corrupted, remove_outliers, remove_leaky_features) · reconstruction(analytical_reconstruction, nan_native_model, domain_augmentation, clean_subset_training) |
62
+ | `augmentation` | geometric(geometric, rotation_invariant, image_rectification) · color(color_transform, clahe) · noise(gaussian_noise, robustness_augmentation) · domain(camera_simulation, temporal_augmentation, symmetry_augmentation, multi_view_processing) |
63
+ | `ensemble` | averaging(weighted_average, multi_seed_averaging, swa) · stacking(stacking) · diversity(diverse_features, heterogeneous) |
64
+ | `postprocess` | calibration(bias_correction, prediction_shrinkage, per_group_calibration) · domain(domain_rules, physics_constraints) · inference(tta) |
65
+ | `tune_loss` | asymmetric(asymmetric_loss, epsilon_insensitive) · uncertainty(gaussian_nll) · multi_objective(multi_task, interval_regression, quantile_regression) · weighting(sample_weighted, auxiliary_physics_loss) |
66
+ | `regularize` | weight(strong_regularization, ema, dropout) · transfer(freeze_backbone) |
67
+
68
+ Plus: `pseudo_label` (iterations), `inspect_top_solution`, `submit`
69
+
70
+ ---
71
+
72
+ ## Causal Dataset Properties
73
+
74
+ Each task has ground-truth properties that drive **causal** reward logic:
75
+
76
+ ```python
77
+ DatasetProperties(
78
+ has_shift=True, # Actions addressing shift are rewarded
79
+ has_leakage=True, # Cleaning leaky features is critical
80
+ has_noise_features=True, # Interaction terms on noise amplify it
81
+ has_missing_data=True, # Reconstruction strategies get bonus
82
+ has_imbalance=True, # Scale_pos_weight becomes relevant
83
+ has_images=False, # Image augmentation is irrelevant → penalty
84
+ needs_physics=False, # Physics loss is irrelevant → penalty
85
+ )
86
+ ```
87
+
88
+ Actions are scored based on whether they match the dataset:
89
+
90
+ ```
91
+ if dataset.has_shift and action == "adversarial_validation":
92
+ reward += context_bonus # Relevant!
93
+ elif not dataset.has_images and action == "geometric_augmentation":
94
+ reward += irrelevant_penalty # Wrong domain!
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Failure-Mode Traps
100
+
101
+ The environment contains traps that **punish common mistakes**:
102
+
103
+ | Trap | Trigger | Effect | Mitigation |
104
+ |---|---|---|---|
105
+ | kfold_on_temporal_data | Using kfold when has_shift | CV +0.08, **test -0.04** | Use time_split instead |
106
+ | ignoring_shift | Training without addressing shift | test **-0.06** | Detect shift first |
107
+ | keeping_leaky_feature | Training when has_leakage | test **-0.08** | Clean leaky features first |
108
+ | target_encoding_leakage | target_encoding on shifted data | CV +0.05, **test -0.06** | Don't use it |
109
+ | interaction_terms_on_noise | interaction_terms when noise | CV +0.05, **test -0.04** | Avoid on noisy data |
110
+ | tree_model_on_images | xgboost on image data | CV +0.04, **test -0.02** | Use pretrained_backbone |
111
+ | no_augmentation_on_images | Submit without augmentation | test **-0.04** | Apply augmentation |
112
+ | raw_heading_without_sincos | Submit without sin_cos_encoding | test **-0.03** | Encode angles properly |
113
+
114
+ Traps can be **mitigated** by taking the correct action first. The environment tracks mitigations.
115
+
116
+ ---
117
+
118
+ ## Grading (4 Axes)
119
+
120
+ ```
121
+ final = 0.40×performance + 0.25×strategy + 0.20×combo + 0.15×trap_avoidance
122
+ ```
123
+
124
+ | Component | Description |
125
+ |---|---|
126
+ | **performance** | Test score vs ghost competitors |
127
+ | **strategy** | Contextual — penalises irrelevant strategies used |
128
+ | **combo** | Fraction of synergy combos activated |
129
+ | **trap_avoidance** | 1.0 minus fraction of traps triggered |
130
+
131
+ ---
132
+
133
+ ## Reward Function (9 Components)
134
+
135
+ | Component | Description |
136
+ |---|---|
137
+ | `cv_improvement` | Δ CV score |
138
+ | `strategy_bonus` | +0.05 for expected strategy |
139
+ | `context_bonus` | +0.03×relevance (positive) or -0.04×relevance (negative) |
140
+ | `combo_bonus` | +0.08 per combo completed |
141
+ | `redundancy_penalty` | -0.03 × repeat count |
142
+ | `irrelevant_penalty` | -0.05 for actions with relevance ≤ -0.8 |
143
+ | `trap_penalty` | -0.08 per trap triggered |
144
+ | `overfitting_penalty` | -0.5 × gap when CV-test > 0.05 |
145
+ | `submission_bonus` | 0.5 × test_score |
146
+
147
+ ---
148
+
149
+ ## Tasks (5)
150
+
151
+ | Task | Difficulty | Traps | Combos | Key Challenge |
152
+ |---|---|---|---|---|
153
+ | `easy_churn` | Easy | 3 | 2 | Clean tabular, mild imbalance |
154
+ | `medium_fraud` | Medium | 3 | 3 | Shift, heavy imbalance, safety-critical |
155
+ | `hard_leaky_noisy` | Hard | 4 | 4 | Leakage, noise, missing data, shift |
156
+ | `image_quality` | Hard | 2 | 4 | Heavy-tailed, camera bias, augmentation |
157
+ | `trajectory_pred` | Hard | 2 | 4 | Multi-agent, physics, spatial-temporal |
158
+
159
+ ---
160
+
161
+ ## Baseline Agent
162
+
163
+ Structured multi-phase approach:
164
+ 1. **Inspect** hints (1-2)
165
+ 2. **Diagnose** dataset properties
166
+ 3. **Clean** if needed
167
+ 4. **CV** appropriate for domain
168
+ 5. **Features** domain-relevant only
169
+ 6. **Train** right model family
170
+ 7. **Tune** imbalance/loss
171
+ 8. **Ensemble** (1-2 techniques)
172
+ 9. **Submit**
173
+
174
+ Keeps actions to 8-15 total. Uses hints to inform decisions.
175
+
176
+ ---
177
+
178
+ ## Docker
179
+
180
+ ```bash
181
+ docker build -t kaggle-sim-env .
182
+ docker run -p 7860:7860 kaggle-sim-env
183
+ ```
184
+
185
+ ---
186
+
187
+ ## License
188
+
189
+ MIT
api/__init__.py ADDED
File without changes
api/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (166 Bytes). View file
 
api/__pycache__/server.cpython-312.pyc ADDED
Binary file (16.4 kB). View file
 
api/server.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """FastAPI server for KaggleSimEnv v3.
2
+
3
+ Endpoints:
4
+ POST /reset, /step, /grader, /baseline
5
+ GET /state, /tasks, /actions, /health
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from fastapi import FastAPI, HTTPException
13
+ from pydantic import BaseModel, Field
14
+
15
+ from kaggle_sim_env.environment import KaggleSimEnv
16
+ from kaggle_sim_env.grader import GradeResult, Grader
17
+ from kaggle_sim_env.models import (
18
+ Action,
19
+ ActionType,
20
+ CATEGORY_MAP,
21
+ EnvState,
22
+ Observation,
23
+ StepResponse,
24
+ get_categories_for_action,
25
+ )
26
+ from kaggle_sim_env.tasks import TASK_REGISTRY, get_task
27
+
28
+ app = FastAPI(
29
+ title="KaggleSimEnv – OpenEnv API",
30
+ description=(
31
+ "RL environment simulating Kaggle competitions with hierarchical actions, "
32
+ "causal dataset properties, failure-mode traps, and contextual scoring."
33
+ ),
34
+ version="3.0.0",
35
+ )
36
+
37
+ env = KaggleSimEnv()
38
+ grader = Grader()
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ # Schemas
43
+ # ---------------------------------------------------------------------------
44
+
45
+ class ResetRequest(BaseModel):
46
+ task_id: str = "easy_churn"
47
+
48
+ class StepRequest(BaseModel):
49
+ action_type: str
50
+ parameters: dict[str, Any] = Field(default_factory=dict)
51
+
52
+ class TaskSummary(BaseModel):
53
+ task_id: str
54
+ title: str
55
+ difficulty: str
56
+ description: str
57
+ max_steps: int
58
+ num_expected_strategies: int
59
+ num_strategy_combos: int
60
+ num_failure_modes: int
61
+
62
+ class BaselineRequest(BaseModel):
63
+ task_id: str = "easy_churn"
64
+
65
+ class BaselineResult(BaseModel):
66
+ task_id: str
67
+ actions_taken: list[dict[str, Any]]
68
+ grade: GradeResult
69
+
70
+ class ActionCategoryEntry(BaseModel):
71
+ action_type: str
72
+ parameter_key: str | None = None
73
+ categories: dict[str, list[str]] = Field(default_factory=dict)
74
+
75
+
76
+ # ---------------------------------------------------------------------------
77
+ # Endpoints
78
+ # ---------------------------------------------------------------------------
79
+
80
+ @app.post("/reset", response_model=Observation)
81
+ def reset(req: ResetRequest) -> Observation:
82
+ try:
83
+ return env.reset(task_id=req.task_id)
84
+ except ValueError as exc:
85
+ raise HTTPException(status_code=400, detail=str(exc))
86
+
87
+ @app.post("/step", response_model=StepResponse)
88
+ def step(req: StepRequest) -> StepResponse:
89
+ try:
90
+ action = Action(action_type=req.action_type, parameters=req.parameters)
91
+ except Exception as exc:
92
+ raise HTTPException(status_code=422, detail=f"Invalid action: {exc}")
93
+ try:
94
+ return env.step(action)
95
+ except RuntimeError as exc:
96
+ raise HTTPException(status_code=400, detail=str(exc))
97
+
98
+ @app.get("/state", response_model=EnvState)
99
+ def state() -> EnvState:
100
+ try:
101
+ return env.state()
102
+ except (AssertionError, RuntimeError):
103
+ raise HTTPException(status_code=400, detail="No active environment.")
104
+
105
+ @app.get("/tasks", response_model=list[TaskSummary])
106
+ def list_tasks() -> list[TaskSummary]:
107
+ return [
108
+ TaskSummary(
109
+ task_id=t.task_id, title=t.title, difficulty=t.difficulty,
110
+ description=t.description, max_steps=t.max_steps,
111
+ num_expected_strategies=len(t.expected_strategies),
112
+ num_strategy_combos=len(t.strategy_combos),
113
+ num_failure_modes=len(t.failure_modes),
114
+ )
115
+ for t in TASK_REGISTRY.values()
116
+ ]
117
+
118
+ @app.post("/grader", response_model=GradeResult)
119
+ def grade() -> GradeResult:
120
+ try:
121
+ s = env.state()
122
+ except (AssertionError, RuntimeError):
123
+ raise HTTPException(status_code=400, detail="No active environment.")
124
+ return grader.grade(s, get_task(s.task_id))
125
+
126
+ @app.get("/actions", response_model=list[ActionCategoryEntry])
127
+ def action_space() -> list[ActionCategoryEntry]:
128
+ """Hierarchical action space with categories."""
129
+ from kaggle_sim_env.models import _PARAM_KEY_MAP
130
+ entries: list[ActionCategoryEntry] = []
131
+ for at, key in _PARAM_KEY_MAP.items():
132
+ cats = get_categories_for_action(at)
133
+ entries.append(ActionCategoryEntry(action_type=at, parameter_key=key, categories=cats))
134
+ entries.append(ActionCategoryEntry(
135
+ action_type="pseudo_label", parameter_key="iterations",
136
+ categories={"iterations": ["1", "2", "3"]},
137
+ ))
138
+ entries.append(ActionCategoryEntry(action_type="inspect_top_solution"))
139
+ entries.append(ActionCategoryEntry(action_type="submit"))
140
+ return entries
141
+
142
+
143
+ # ---------------------------------------------------------------------------
144
+ # Baseline: structured, hint-using, phase-based agent
145
+ # ---------------------------------------------------------------------------
146
+
147
+ @app.post("/baseline", response_model=BaselineResult)
148
+ def run_baseline(req: BaselineRequest) -> BaselineResult:
149
+ task = get_task(req.task_id)
150
+ bl_env = KaggleSimEnv()
151
+ bl_env.reset(task_id=req.task_id)
152
+
153
+ actions_log: list[dict[str, Any]] = []
154
+ plan = _baseline_plan(task.task_id)
155
+
156
+ for action in plan:
157
+ result = bl_env.step(action)
158
+ actions_log.append({
159
+ "action": action.model_dump(),
160
+ "cv_score": result.observation.current_cv_score,
161
+ "reward": result.reward.total,
162
+ "traps": result.info.get("traps", []),
163
+ "combos": result.info.get("combos_completed", []),
164
+ })
165
+ if result.done:
166
+ break
167
+
168
+ s = bl_env.state()
169
+ g = grader.grade(s, task)
170
+ return BaselineResult(task_id=req.task_id, actions_taken=actions_log, grade=g)
171
+
172
+
173
+ def _baseline_plan(task_id: str) -> list[Action]:
174
+ """Structured, phase-based expert plans that use hints first."""
175
+ A = Action
176
+
177
+ if task_id == "easy_churn":
178
+ return [
179
+ # Phase 1: Inspect
180
+ A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
181
+ # Phase 2: CV + Features
182
+ A(action_type=ActionType.SET_CV, parameters={"category": "standard", "strategy": "kfold"}),
183
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "distribution", "technique": "normalize"}),
184
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "interaction", "technique": "domain_ratios"}),
185
+ # Phase 3: Train
186
+ A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "xgboost"}),
187
+ # Phase 4: Imbalance
188
+ A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "weighting", "method": "scale_pos_weight"}),
189
+ A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "calibration", "method": "optimize_threshold"}),
190
+ # Phase 5: Submit
191
+ A(action_type=ActionType.SUBMIT, parameters={}),
192
+ ]
193
+
194
+ if task_id == "medium_fraud":
195
+ return [
196
+ # Phase 1: Inspect + Diagnose
197
+ A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
198
+ A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
199
+ A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "detection", "method": "adversarial_validation"}),
200
+ A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "mitigation", "method": "remove_identifiers"}),
201
+ A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "mitigation", "method": "domain_invariant_features"}),
202
+ # Phase 2: CV + Features
203
+ A(action_type=ActionType.SET_CV, parameters={"category": "temporal", "strategy": "time_split"}),
204
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "distribution", "technique": "log_transform"}),
205
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "interaction", "technique": "domain_ratios"}),
206
+ # Phase 3: Train + Imbalance
207
+ A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "xgboost"}),
208
+ A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "weighting", "method": "scale_pos_weight"}),
209
+ A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "calibration", "method": "optimize_threshold"}),
210
+ A(action_type=ActionType.TUNE_LOSS, parameters={"category": "asymmetric", "method": "asymmetric_loss"}),
211
+ # Phase 4: Regularize + Ensemble
212
+ A(action_type=ActionType.REGULARIZE, parameters={"category": "weight", "method": "strong_regularization"}),
213
+ A(action_type=ActionType.ENSEMBLE, parameters={"category": "averaging", "method": "weighted_average"}),
214
+ A(action_type=ActionType.SUBMIT, parameters={}),
215
+ ]
216
+
217
+ if task_id == "hard_leaky_noisy":
218
+ return [
219
+ # Phase 1: Inspect + Diagnose + Clean
220
+ A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
221
+ A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "detection", "method": "adversarial_validation"}),
222
+ A(action_type=ActionType.DETECT_SHIFT, parameters={"category": "mitigation", "method": "remove_identifiers"}),
223
+ A(action_type=ActionType.CLEAN_DATA, parameters={"category": "removal", "method": "remove_leaky_features"}),
224
+ A(action_type=ActionType.CLEAN_DATA, parameters={"category": "removal", "method": "remove_outliers"}),
225
+ A(action_type=ActionType.CLEAN_DATA, parameters={"category": "reconstruction", "method": "analytical_reconstruction"}),
226
+ A(action_type=ActionType.CLEAN_DATA, parameters={"category": "reconstruction", "method": "nan_native_model"}),
227
+ # Phase 2: CV + Features
228
+ A(action_type=ActionType.SET_CV, parameters={"category": "group", "strategy": "stratified_group_kfold"}),
229
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "distribution", "technique": "log_transform"}),
230
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "interaction", "technique": "domain_ratios"}),
231
+ # Phase 3: Train + Imbalance
232
+ A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "xgboost"}),
233
+ A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "lightgbm"}),
234
+ A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "weighting", "method": "scale_pos_weight"}),
235
+ A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "calibration", "method": "calibrate_probabilities"}),
236
+ A(action_type=ActionType.HANDLE_IMBALANCE, parameters={"category": "calibration", "method": "optimize_threshold"}),
237
+ # Phase 4: Ensemble + Regularize
238
+ A(action_type=ActionType.ENSEMBLE, parameters={"category": "stacking", "method": "stacking"}),
239
+ A(action_type=ActionType.ENSEMBLE, parameters={"category": "diversity", "method": "diverse_features"}),
240
+ A(action_type=ActionType.REGULARIZE, parameters={"category": "weight", "method": "strong_regularization"}),
241
+ A(action_type=ActionType.PSEUDO_LABEL, parameters={"iterations": 1}),
242
+ A(action_type=ActionType.SUBMIT, parameters={}),
243
+ ]
244
+
245
+ if task_id == "image_quality":
246
+ return [
247
+ # Phase 1: Inspect
248
+ A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
249
+ A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
250
+ # Phase 2: CV + Backbone
251
+ A(action_type=ActionType.SET_CV, parameters={"category": "group", "strategy": "group_kfold"}),
252
+ A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "neural", "algorithm": "pretrained_backbone"}),
253
+ A(action_type=ActionType.REGULARIZE, parameters={"category": "transfer", "method": "freeze_backbone"}),
254
+ # Phase 3: Augmentation
255
+ A(action_type=ActionType.AUGMENTATION, parameters={"category": "geometric", "method": "geometric"}),
256
+ A(action_type=ActionType.AUGMENTATION, parameters={"category": "color", "method": "color_transform"}),
257
+ A(action_type=ActionType.AUGMENTATION, parameters={"category": "domain", "method": "camera_simulation"}),
258
+ A(action_type=ActionType.AUGMENTATION, parameters={"category": "color", "method": "clahe"}),
259
+ # Phase 4: Loss + Features
260
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "distribution", "technique": "log_transform"}),
261
+ A(action_type=ActionType.TUNE_LOSS, parameters={"category": "uncertainty", "method": "gaussian_nll"}),
262
+ A(action_type=ActionType.TUNE_LOSS, parameters={"category": "multi_objective", "method": "multi_task"}),
263
+ # Phase 5: Stabilize + Ensemble
264
+ A(action_type=ActionType.REGULARIZE, parameters={"category": "weight", "method": "ema"}),
265
+ A(action_type=ActionType.ENSEMBLE, parameters={"category": "averaging", "method": "multi_seed_averaging"}),
266
+ A(action_type=ActionType.ENSEMBLE, parameters={"category": "averaging", "method": "swa"}),
267
+ # Phase 6: Postprocess
268
+ A(action_type=ActionType.POSTPROCESS, parameters={"category": "inference", "method": "tta"}),
269
+ A(action_type=ActionType.POSTPROCESS, parameters={"category": "calibration", "method": "per_group_calibration"}),
270
+ A(action_type=ActionType.POSTPROCESS, parameters={"category": "calibration", "method": "prediction_shrinkage"}),
271
+ A(action_type=ActionType.SUBMIT, parameters={}),
272
+ ]
273
+
274
+ if task_id == "trajectory_pred":
275
+ return [
276
+ # Phase 1: Inspect + Clean
277
+ A(action_type=ActionType.INSPECT_TOP_SOLUTION, parameters={}),
278
+ A(action_type=ActionType.SET_CV, parameters={"category": "group", "strategy": "group_kfold"}),
279
+ A(action_type=ActionType.CLEAN_DATA, parameters={"category": "removal", "method": "remove_corrupted"}),
280
+ A(action_type=ActionType.CLEAN_DATA, parameters={"category": "removal", "method": "remove_outliers"}),
281
+ # Phase 2: Spatial features
282
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "encoding", "technique": "sin_cos_encoding"}),
283
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "spatial", "technique": "relative_coordinates"}),
284
+ A(action_type=ActionType.FEATURE_ENGINEERING, parameters={"category": "spatial", "technique": "distance_features"}),
285
+ # Phase 3: Train
286
+ A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "neural", "algorithm": "transformer_encoder"}),
287
+ A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "neural", "algorithm": "temporal_cnn"}),
288
+ # Phase 4: Augmentation
289
+ A(action_type=ActionType.AUGMENTATION, parameters={"category": "geometric", "method": "rotation_invariant"}),
290
+ A(action_type=ActionType.AUGMENTATION, parameters={"category": "domain", "method": "symmetry_augmentation"}),
291
+ A(action_type=ActionType.AUGMENTATION, parameters={"category": "domain", "method": "temporal_augmentation"}),
292
+ # Phase 5: Loss + Regularize
293
+ A(action_type=ActionType.TUNE_LOSS, parameters={"category": "uncertainty", "method": "gaussian_nll"}),
294
+ A(action_type=ActionType.TUNE_LOSS, parameters={"category": "weighting", "method": "auxiliary_physics_loss"}),
295
+ A(action_type=ActionType.REGULARIZE, parameters={"category": "weight", "method": "ema"}),
296
+ # Phase 6: Ensemble + Postprocess
297
+ A(action_type=ActionType.ENSEMBLE, parameters={"category": "averaging", "method": "multi_seed_averaging"}),
298
+ A(action_type=ActionType.POSTPROCESS, parameters={"category": "domain", "method": "physics_constraints"}),
299
+ A(action_type=ActionType.SUBMIT, parameters={}),
300
+ ]
301
+
302
+ return [
303
+ A(action_type=ActionType.TRAIN_MODEL, parameters={"category": "tree", "algorithm": "xgboost"}),
304
+ A(action_type=ActionType.SUBMIT, parameters={}),
305
+ ]
306
+
307
+
308
+ @app.get("/health")
309
+ def health() -> dict[str, str]:
310
+ return {"status": "ok"}
baseline/__init__.py ADDED
File without changes
baseline/run_baseline.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Baseline agent for KaggleSimEnv v3.
3
+
4
+ Structured multi-phase approach:
5
+ Phase 1: Inspect hints (1-2 hints)
6
+ Phase 2: Diagnose dataset (shift detection, cleaning)
7
+ Phase 3: Set CV + feature engineering
8
+ Phase 4: Train model + handle imbalance
9
+ Phase 5: Ensemble + regularize + postprocess
10
+ Phase 6: Submit
11
+
12
+ Usage:
13
+ python -m baseline.run_baseline --mode local
14
+ python -m baseline.run_baseline --mode api --base-url http://localhost:7860
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import json
21
+ import os
22
+ import sys
23
+ from typing import Any
24
+
25
+ import requests
26
+ from openai import OpenAI
27
+
28
+ from kaggle_sim_env.environment import KaggleSimEnv
29
+ from kaggle_sim_env.grader import Grader
30
+ from kaggle_sim_env.models import Action
31
+ from kaggle_sim_env.tasks import TASK_REGISTRY, get_task
32
+
33
+ SYSTEM_PROMPT = """\
34
+ You are an expert Kaggle Grand Master agent. You work in structured phases.
35
+
36
+ ## PHASES (follow in order)
37
+ 1. INSPECT: Use inspect_top_solution to get 1-2 hints before acting.
38
+ 2. DIAGNOSE: Check dataset properties. If has_time_column → detect shift. If columns look leaky → clean data.
39
+ 3. CV: Choose CV based on dataset properties (group/time/standard).
40
+ 4. FEATURES: Engineer domain-appropriate features only. Don't use image/spatial features on tabular data.
41
+ 5. TRAIN: Pick the right model family for the domain.
42
+ 6. TUNE: Handle imbalance, tune loss if needed.
43
+ 7. STABILIZE: Regularize + ensemble (keep it to 1-2 techniques).
44
+ 8. POSTPROCESS: Only if domain-relevant (TTA for images, physics for trajectories).
45
+ 9. SUBMIT: When confident or running low on steps.
46
+
47
+ ## HIERARCHICAL ACTIONS (use category field!)
48
+
49
+ Example:
50
+ {"action_type": "feature_engineering", "parameters": {"category": "distribution", "technique": "log_transform"}}
51
+
52
+ Categories per action type:
53
+ set_cv: standard(kfold,repeated_kfold) | group(group_kfold,stratified_group_kfold) | temporal(time_split,combined_group_time)
54
+ feature_engineering: distribution(log_transform,normalize,quantile_features) | interaction(interaction_terms,domain_ratios) | encoding(sin_cos_encoding,target_encoding,spatial_encoding,tfidf_features) | spatial(relative_coordinates,distance_features) | signal(frequency_features,multi_layer_features,fourier_resampling)
55
+ detect_shift: detection(adversarial_validation,feature_importance_shift) | mitigation(remove_identifiers,domain_invariant_features)
56
+ train_model: tree(xgboost,lightgbm,catboost,random_forest) | linear(linear) | neural(neural_network,pretrained_backbone,temporal_cnn,transformer_encoder)
57
+ handle_imbalance: weighting(scale_pos_weight,class_weighted_loss) | calibration(calibrate_probabilities,optimize_threshold) | hierarchy(hierarchical_labels,lower_thresholds_recall)
58
+ clean_data: removal(remove_corrupted,remove_outliers,remove_leaky_features) | reconstruction(analytical_reconstruction,nan_native_model,domain_augmentation,clean_subset_training)
59
+ augmentation: geometric(geometric,rotation_invariant,image_rectification) | color(color_transform,clahe) | noise(gaussian_noise,robustness_augmentation) | domain(camera_simulation,temporal_augmentation,symmetry_augmentation,multi_view_processing)
60
+ ensemble: averaging(weighted_average,multi_seed_averaging,swa) | stacking(stacking) | diversity(diverse_features,heterogeneous)
61
+ postprocess: calibration(bias_correction,prediction_shrinkage,per_group_calibration) | domain(domain_rules,physics_constraints) | inference(tta)
62
+ tune_loss: asymmetric(asymmetric_loss,epsilon_insensitive) | uncertainty(gaussian_nll) | multi_objective(multi_task,interval_regression,quantile_regression) | weighting(sample_weighted,auxiliary_physics_loss)
63
+ regularize: weight(strong_regularization,ema,dropout) | transfer(freeze_backbone)
64
+
65
+ Also available:
66
+ {"action_type": "pseudo_label", "parameters": {"iterations": 1}}
67
+ {"action_type": "inspect_top_solution", "parameters": {}}
68
+ {"action_type": "submit", "parameters": {}}
69
+
70
+ ## FAILURE MODE AWARENESS
71
+ - Using kfold on temporal/grouped data → CV inflated, test destroyed
72
+ - Image augmentation on tabular data → wasted step + penalty
73
+ - Target encoding without group CV → leakage trap
74
+ - Training without addressing shift → model memorises wrong distribution
75
+ - Using tree models on image/trajectory data → poor fit
76
+
77
+ ## CRITICAL RULES
78
+ - Start with 1-2 hints to understand the dataset
79
+ - NEVER use more than 8-10 substantive actions before submitting
80
+ - Only use strategies relevant to THIS dataset
81
+ - Respond with ONLY valid JSON, nothing else
82
+ """
83
+
84
+
85
+ def build_user_message(observation: dict[str, Any]) -> str:
86
+ meta = observation["dataset_metadata"]
87
+ return (
88
+ f"Step {observation['step_count']}/{observation['max_steps']} "
89
+ f"CV: {observation['current_cv_score']:.4f} "
90
+ f"Rank: {observation['leaderboard_rank']}\n"
91
+ f"Applied: {observation['applied_strategies']}\n"
92
+ f"Message: {observation.get('message', '')}\n\n"
93
+ f"Dataset: rows={meta['num_rows']} features={meta['num_features']} "
94
+ f"target={meta['target_column']} type={meta['task_type']}\n"
95
+ f" has_time={meta.get('has_time_column',False)} "
96
+ f"has_group={meta.get('has_group_column',False)} "
97
+ f"has_image={meta.get('has_image_data',False)} "
98
+ f"has_spatial={meta.get('has_spatial_data',False)}\n"
99
+ f" target_dist={meta.get('target_distribution','normal')} "
100
+ f"class_balance={meta.get('class_balance',{})}\n"
101
+ f" columns: {', '.join(c['name']+'('+c['dtype']+',miss='+str(c['missing_pct'])+'%)' for c in meta['columns'][:8])}"
102
+ f"{'...' if len(meta['columns'])>8 else ''}\n\n"
103
+ "Choose your next action (JSON only):"
104
+ )
105
+
106
+
107
+ def parse_llm_action(text: str) -> dict[str, Any]:
108
+ text = text.strip()
109
+ if text.startswith("```"):
110
+ lines = text.split("\n")
111
+ lines = [l for l in lines if not l.strip().startswith("```")]
112
+ text = "\n".join(lines).strip()
113
+ return json.loads(text)
114
+
115
+
116
+ def run_local(client: OpenAI, task_id: str) -> dict[str, Any]:
117
+ env = KaggleSimEnv()
118
+ grader_inst = Grader()
119
+ task = get_task(task_id)
120
+
121
+ obs = env.reset(task_id=task_id)
122
+ obs_dict = obs.model_dump()
123
+ messages: list[dict[str, str]] = [{"role": "system", "content": SYSTEM_PROMPT}]
124
+ actions_log: list[dict[str, Any]] = []
125
+
126
+ while not obs_dict.get("done", False):
127
+ messages.append({"role": "user", "content": build_user_message(obs_dict)})
128
+ response = client.chat.completions.create(
129
+ model="gpt-4o-mini",
130
+ messages=messages,
131
+ temperature=0.0,
132
+ max_tokens=256,
133
+ )
134
+ raw = response.choices[0].message.content or "{}"
135
+ messages.append({"role": "assistant", "content": raw})
136
+
137
+ try:
138
+ action_dict = parse_llm_action(raw)
139
+ action = Action(**action_dict)
140
+ except Exception as exc:
141
+ print(f" [!] Parse error: {exc}. Submitting.")
142
+ action = Action(action_type="submit", parameters={})
143
+
144
+ step_result = env.step(action)
145
+ obs_dict = step_result.observation.model_dump()
146
+ actions_log.append({
147
+ "action": action.model_dump(),
148
+ "cv_score": step_result.observation.current_cv_score,
149
+ "reward": step_result.reward.total,
150
+ })
151
+
152
+ trap_info = ""
153
+ if step_result.info.get("traps"):
154
+ trap_info = f" TRAP!"
155
+ combo_info = ""
156
+ if step_result.info.get("combos_completed"):
157
+ combo_info = f" COMBO: {step_result.info['combos_completed']}"
158
+
159
+ print(
160
+ f" Step {obs_dict['step_count']:2d}: {action.full_tag():50s} "
161
+ f"CV={obs_dict['current_cv_score']:.4f} R={step_result.reward.total:+.4f}"
162
+ f"{trap_info}{combo_info}"
163
+ )
164
+
165
+ state = env.state()
166
+ grade = grader_inst.grade(state, task)
167
+ return {"task_id": task_id, "actions": actions_log, "grade": grade.model_dump()}
168
+
169
+
170
+ def run_api(client: OpenAI, task_id: str, base_url: str) -> dict[str, Any]:
171
+ resp = requests.post(f"{base_url}/reset", json={"task_id": task_id}, timeout=30)
172
+ resp.raise_for_status()
173
+ obs_dict = resp.json()
174
+ messages: list[dict[str, str]] = [{"role": "system", "content": SYSTEM_PROMPT}]
175
+ actions_log: list[dict[str, Any]] = []
176
+
177
+ while not obs_dict.get("done", False):
178
+ messages.append({"role": "user", "content": build_user_message(obs_dict)})
179
+ response = client.chat.completions.create(
180
+ model="gpt-4o-mini", messages=messages,
181
+ temperature=0.0, max_tokens=256,
182
+ )
183
+ raw = response.choices[0].message.content or "{}"
184
+ messages.append({"role": "assistant", "content": raw})
185
+
186
+ try:
187
+ action_dict = parse_llm_action(raw)
188
+ except Exception:
189
+ action_dict = {"action_type": "submit", "parameters": {}}
190
+
191
+ resp = requests.post(f"{base_url}/step", json=action_dict, timeout=30)
192
+ resp.raise_for_status()
193
+ step_data = resp.json()
194
+ obs_dict = step_data["observation"]
195
+ actions_log.append({
196
+ "action": action_dict,
197
+ "cv_score": obs_dict["current_cv_score"],
198
+ "reward": step_data["reward"]["total"],
199
+ })
200
+
201
+ resp = requests.post(f"{base_url}/grader", timeout=30)
202
+ resp.raise_for_status()
203
+ return {"task_id": task_id, "actions": actions_log, "grade": resp.json()}
204
+
205
+
206
+ def main() -> None:
207
+ parser = argparse.ArgumentParser(description="KaggleSimEnv baseline agent")
208
+ parser.add_argument("--mode", choices=["local", "api"], default="local")
209
+ parser.add_argument("--base-url", default="http://localhost:7860")
210
+ parser.add_argument("--tasks", nargs="*", default=list(TASK_REGISTRY.keys()))
211
+ args = parser.parse_args()
212
+
213
+ api_key = os.environ.get("OPENAI_API_KEY")
214
+ if not api_key:
215
+ print("Error: OPENAI_API_KEY not set.", file=sys.stderr)
216
+ sys.exit(1)
217
+
218
+ client = OpenAI(api_key=api_key)
219
+ results: list[dict[str, Any]] = []
220
+
221
+ for task_id in args.tasks:
222
+ print(f"\n{'='*70}")
223
+ print(f" Task: {task_id}")
224
+ print(f"{'='*70}")
225
+ result = run_local(client, task_id) if args.mode == "local" else run_api(client, task_id, args.base_url)
226
+ results.append(result)
227
+ g = result["grade"]
228
+ print(f"\n Grade: perf={g['performance_score']:.4f} strat={g['strategy_score']:.4f} "
229
+ f"combo={g['combo_score']:.4f} trap={g['trap_score']:.4f} final={g['final_score']:.4f}")
230
+
231
+ print(f"\n{'='*70}")
232
+ print(" SUMMARY")
233
+ print(f"{'='*70}")
234
+ for r in results:
235
+ g = r["grade"]
236
+ print(f" {r['task_id']:25s} final={g['final_score']:.4f}")
237
+ avg = sum(r["grade"]["final_score"] for r in results) / max(len(results), 1)
238
+ print(f"\n Average: {avg:.4f}")
239
+
240
+
241
+ if __name__ == "__main__":
242
+ main()
kaggle_sim_env/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from kaggle_sim_env.models import Action, Observation, Reward, EnvState
2
+ from kaggle_sim_env.environment import KaggleSimEnv
3
+ from kaggle_sim_env.grader import Grader
4
+ from kaggle_sim_env.tasks import TASK_REGISTRY, get_task
5
+
6
+ __all__ = [
7
+ "Action",
8
+ "Observation",
9
+ "Reward",
10
+ "EnvState",
11
+ "KaggleSimEnv",
12
+ "Grader",
13
+ "TASK_REGISTRY",
14
+ "get_task",
15
+ ]
kaggle_sim_env/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (554 Bytes). View file
 
kaggle_sim_env/__pycache__/environment.cpython-312.pyc ADDED
Binary file (16 kB). View file
 
kaggle_sim_env/__pycache__/grader.cpython-312.pyc ADDED
Binary file (7.22 kB). View file
 
kaggle_sim_env/__pycache__/hints.cpython-312.pyc ADDED
Binary file (1.78 kB). View file
 
kaggle_sim_env/__pycache__/leaderboard.cpython-312.pyc ADDED
Binary file (3.09 kB). View file
 
kaggle_sim_env/__pycache__/models.cpython-312.pyc ADDED
Binary file (13.1 kB). View file
 
kaggle_sim_env/__pycache__/rewards.cpython-312.pyc ADDED
Binary file (3.11 kB). View file
 
kaggle_sim_env/__pycache__/tasks.cpython-312.pyc ADDED
Binary file (29.2 kB). View file
 
kaggle_sim_env/environment.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Core KaggleSimEnv environment v3.
2
+
3
+ Causal logic, hierarchical categories, failure-mode traps, contextual rewards.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from kaggle_sim_env.hints import HintProvider
11
+ from kaggle_sim_env.leaderboard import Leaderboard
12
+ from kaggle_sim_env.models import (
13
+ Action,
14
+ ActionType,
15
+ EnvState,
16
+ FailureMode,
17
+ Observation,
18
+ Reward,
19
+ RewardBreakdown,
20
+ StepResponse,
21
+ get_allowed_values,
22
+ get_param_key,
23
+ validate_category,
24
+ )
25
+ from kaggle_sim_env.rewards import compute_reward
26
+ from kaggle_sim_env.tasks import TaskDefinition, get_task
27
+
28
+
29
+ class KaggleSimEnv:
30
+ """RL environment simulating a Kaggle competition with causal dataset logic."""
31
+
32
+ def __init__(self) -> None:
33
+ self._task: TaskDefinition | None = None
34
+ self._cv_score: float = 0.0
35
+ self._test_score: float = 0.0
36
+ self._applied: list[str] = []
37
+ self._history: list[str] = []
38
+ self._step_count: int = 0
39
+ self._done: bool = True
40
+ self._submitted: bool = False
41
+ self._leaderboard: Leaderboard | None = None
42
+ self._hints: HintProvider | None = None
43
+ self._overfitting_accum: float = 0.0
44
+ self._active_combos: list[str] = []
45
+ self._traps_triggered: list[str] = []
46
+ self._mitigated_traps: set[str] = set()
47
+
48
+ # ------------------------------------------------------------------
49
+ # OpenEnv API
50
+ # ------------------------------------------------------------------
51
+
52
+ def reset(self, task_id: str = "easy_churn") -> Observation:
53
+ self._task = get_task(task_id)
54
+ self._cv_score = self._task.base_cv_score
55
+ self._test_score = self._task.base_test_score
56
+ self._applied = []
57
+ self._history = []
58
+ self._step_count = 0
59
+ self._max_steps = 10
60
+ self._done = False
61
+ self._submitted = False
62
+ self._overfitting_accum = 0.0
63
+ self._active_combos = []
64
+ self._traps_triggered = []
65
+ self._mitigated_traps = set()
66
+ self._leaderboard = Leaderboard(task_id, list(self._task.ghost_scores))
67
+ self._hints = HintProvider(list(self._task.hints))
68
+ return self._observation(message="Environment reset. Choose your first action.")
69
+
70
+ def step(self, action: Action) -> StepResponse:
71
+ if self._task is None or self._done:
72
+ raise RuntimeError("Environment not active. Call reset() first.")
73
+
74
+ self._step_count += 1
75
+ info: dict[str, Any] = {}
76
+ prev_cv = self._cv_score
77
+ tag = action.tag()
78
+
79
+ # --- Validate ---
80
+ valid, msg = self._validate_action(action)
81
+ if not valid:
82
+ reward = Reward(total=-0.05, breakdown=RewardBreakdown(redundancy_penalty=-0.05))
83
+ info["error"] = msg
84
+ obs = self._observation(message=f"Invalid action: {msg}")
85
+ done = self._check_done()
86
+ return StepResponse(observation=obs, reward=reward, done=done, info=info)
87
+
88
+ # --- Apply action (mutates scores) ---
89
+ self._apply_action(action, info)
90
+
91
+ # --- Check failure-mode traps ---
92
+ traps_this_step = self._check_traps(action, info)
93
+
94
+ # --- Check strategy combos ---
95
+ newly_completed = self._check_combos()
96
+ if newly_completed:
97
+ info["combos_completed"] = newly_completed
98
+
99
+ # --- Contextual relevance ---
100
+ relevance = self._task.context_relevance.get(tag)
101
+
102
+ is_submit = action.action_type == ActionType.SUBMIT
103
+ reward = compute_reward(
104
+ prev_cv=prev_cv,
105
+ new_cv=self._cv_score,
106
+ new_test=self._test_score,
107
+ action_tag=tag,
108
+ expected_strategies=self._task.expected_strategies,
109
+ already_applied=self._applied[:-1] if tag in self._applied else self._applied,
110
+ is_submit=is_submit,
111
+ submitted_test_score=self._test_score if is_submit else None,
112
+ newly_completed_combos=newly_completed,
113
+ context_relevance=relevance,
114
+ traps_triggered_this_step=traps_this_step,
115
+ )
116
+
117
+ self._history.append(tag)
118
+ done = self._check_done()
119
+ obs = self._observation(message=info.get("message", ""))
120
+ return StepResponse(observation=obs, reward=reward, done=done, info=info)
121
+
122
+ def state(self) -> EnvState:
123
+ assert self._task is not None
124
+ assert self._leaderboard is not None
125
+ assert self._hints is not None
126
+ return EnvState(
127
+ task_id=self._task.task_id,
128
+ step_count=self._step_count,
129
+ max_steps=self._task.max_steps,
130
+ done=self._done,
131
+ cv_score=round(self._cv_score, 4),
132
+ test_score=round(self._test_score, 4),
133
+ applied_strategies=list(self._applied),
134
+ strategy_history=list(self._history),
135
+ leaderboard_rank=self._leaderboard.agent_rank(self._test_score),
136
+ leaderboard=self._leaderboard.full_board(self._test_score),
137
+ submitted=self._submitted,
138
+ hint_count=self._hints.hints_given,
139
+ active_combos=list(self._active_combos),
140
+ traps_triggered=list(self._traps_triggered),
141
+ )
142
+
143
+ # ------------------------------------------------------------------
144
+ # Observation
145
+ # ------------------------------------------------------------------
146
+
147
+ def _observation(self, message: str = "") -> Observation:
148
+ assert self._task is not None and self._leaderboard is not None
149
+ return Observation(
150
+ dataset_metadata=self._task.dataset_metadata,
151
+ applied_strategies=list(self._applied),
152
+ current_cv_score=round(self._cv_score, 4),
153
+ leaderboard_rank=self._leaderboard.agent_rank(self._test_score),
154
+ step_count=self._step_count,
155
+ max_steps=self._task.max_steps,
156
+ done=self._done,
157
+ message=message,
158
+ )
159
+
160
+ # ------------------------------------------------------------------
161
+ # Validation (hierarchical categories)
162
+ # ------------------------------------------------------------------
163
+
164
+ def _validate_action(self, action: Action) -> tuple[bool, str]:
165
+ at = action.action_type
166
+ p = action.parameters
167
+
168
+ if at == ActionType.PSEUDO_LABEL:
169
+ iters = p.get("iterations")
170
+ if iters is None or not isinstance(iters, int) or iters < 1:
171
+ return False, "pseudo_label requires 'iterations' (int >= 1)"
172
+ return True, ""
173
+
174
+ if at == ActionType.SUBMIT:
175
+ if self._submitted:
176
+ return False, "Already submitted."
177
+ if not any(s.startswith("train_model:") for s in self._applied):
178
+ return False, "Cannot submit without training a model first."
179
+ return True, ""
180
+
181
+ if at == ActionType.INSPECT_TOP_SOLUTION:
182
+ return True, ""
183
+
184
+ key = get_param_key(at.value)
185
+ if key:
186
+ value = p.get(key)
187
+ allowed = get_allowed_values(at.value)
188
+ if value is None or value not in allowed:
189
+ return False, f"{at.value} requires '{key}' in {allowed}"
190
+ cat_err = validate_category(at.value, p.get("category"), value)
191
+ if cat_err:
192
+ return False, cat_err
193
+
194
+ return True, ""
195
+
196
+ # ------------------------------------------------------------------
197
+ # Action application with causal logic
198
+ # ------------------------------------------------------------------
199
+
200
+ def _apply_action(self, action: Action, info: dict[str, Any]) -> None:
201
+ assert self._task is not None and self._hints is not None
202
+ tag = action.tag()
203
+ modifiers = self._task.score_modifiers
204
+ overfit_risk = self._task.overfitting_risk
205
+ props = self._task.dataset_properties
206
+
207
+ if action.action_type == ActionType.INSPECT_TOP_SOLUTION:
208
+ hint = self._hints.next_hint()
209
+ info["hint"] = hint
210
+ info["message"] = f"Hint: {hint}"
211
+ if tag not in self._applied:
212
+ self._applied.append(tag)
213
+ return
214
+
215
+ if action.action_type == ActionType.SUBMIT:
216
+ self._submitted = True
217
+ self._done = True
218
+ info["message"] = (
219
+ f"Submitted! Final test score: {self._test_score:.4f} "
220
+ f"CV score: {self._cv_score:.4f}"
221
+ )
222
+ return
223
+
224
+ is_repeat = tag in self._applied
225
+
226
+ # --- Track mitigation actions ---
227
+ self._update_mitigations(tag, props)
228
+
229
+ if tag in modifiers:
230
+ cv_delta = modifiers[tag]["cv"]
231
+ test_delta = modifiers[tag]["test"]
232
+
233
+ if is_repeat:
234
+ cv_delta *= 0.1
235
+ test_delta *= 0.1
236
+ else:
237
+ self._applied.append(tag)
238
+
239
+ overfit_extra = overfit_risk.get(tag, 0.0)
240
+ self._overfitting_accum += overfit_extra
241
+ cv_delta += overfit_extra
242
+
243
+ self._cv_score = min(1.0, self._cv_score + cv_delta)
244
+ self._test_score = min(1.0, self._test_score + test_delta)
245
+
246
+ rank = self._leaderboard.agent_rank(self._test_score) if self._leaderboard else "?"
247
+ info["message"] = f"Applied {action.full_tag()}. CV: {self._cv_score:.4f}, Rank: {rank}"
248
+ else:
249
+ if tag not in self._applied:
250
+ self._applied.append(tag)
251
+ info["message"] = f"Applied {action.full_tag()} (no modifier for this task)."
252
+
253
+ def _update_mitigations(self, tag: str, props: Any) -> None:
254
+ """Track which failure modes the agent has pre-emptively mitigated."""
255
+ if tag == "detect_shift:adversarial_validation" and props.has_shift:
256
+ self._mitigated_traps.add("ignoring_shift")
257
+ if tag == "detect_shift:remove_identifiers" and props.has_shift:
258
+ self._mitigated_traps.add("ignoring_shift")
259
+ if tag == "clean_data:remove_leaky_features" and props.has_leakage:
260
+ self._mitigated_traps.add("keeping_leaky_feature")
261
+ if tag == "feature_engineering:sin_cos_encoding" and props.has_spatial_data:
262
+ self._mitigated_traps.add("raw_heading_without_sincos")
263
+ if tag.startswith("augmentation:") and props.has_images:
264
+ self._mitigated_traps.add("no_augmentation_on_images")
265
+
266
+ # ------------------------------------------------------------------
267
+ # Failure-mode trap detection
268
+ # ------------------------------------------------------------------
269
+
270
+ def _check_traps(self, action: Action, info: dict[str, Any]) -> list[str]:
271
+ assert self._task is not None
272
+ tag = action.tag()
273
+ props = self._task.dataset_properties
274
+ triggered: list[str] = []
275
+
276
+ for fm in self._task.failure_modes:
277
+ if fm.name in self._traps_triggered:
278
+ continue
279
+ if fm.name in self._mitigated_traps:
280
+ continue
281
+ if tag != fm.trigger_tag:
282
+ continue
283
+
284
+ prop_val = getattr(props, fm.condition_field, None)
285
+ if prop_val == fm.condition_value:
286
+ self._cv_score = min(1.0, self._cv_score + fm.cv_effect)
287
+ self._test_score = max(0.0, self._test_score + fm.test_effect)
288
+ self._traps_triggered.append(fm.name)
289
+ triggered.append(fm.name)
290
+
291
+ prev_msg = info.get("message", "")
292
+ info["message"] = f"{prev_msg} TRAP: {fm.message}"
293
+ info.setdefault("traps", []).append({
294
+ "name": fm.name,
295
+ "message": fm.message,
296
+ "cv_effect": fm.cv_effect,
297
+ "test_effect": fm.test_effect,
298
+ })
299
+
300
+ return triggered
301
+
302
+ # ------------------------------------------------------------------
303
+ # Combo detection
304
+ # ------------------------------------------------------------------
305
+
306
+ def _check_combos(self) -> list[str]:
307
+ assert self._task is not None
308
+ newly_completed: list[str] = []
309
+ applied_set = set(self._applied)
310
+ for combo in self._task.strategy_combos:
311
+ if combo.name in self._active_combos:
312
+ continue
313
+ if combo.required.issubset(applied_set):
314
+ self._active_combos.append(combo.name)
315
+ self._cv_score = min(1.0, self._cv_score + combo.cv_bonus)
316
+ self._test_score = min(1.0, self._test_score + combo.test_bonus)
317
+ newly_completed.append(combo.name)
318
+ return newly_completed
319
+
320
+ # ------------------------------------------------------------------
321
+ # Done
322
+ # ------------------------------------------------------------------
323
+
324
+ def _check_done(self) -> bool:
325
+ if self._done:
326
+ return True
327
+ assert self._task is not None
328
+ if self._step_count >= self._task.max_steps:
329
+ self._done = True
330
+ return self._done
kaggle_sim_env/grader.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Deterministic grader for KaggleSimEnv v3.
2
+
3
+ Score [0.0 – 1.0] combining:
4
+ performance_score : test score normalised vs ghost competitors
5
+ strategy_score : contextual — only credit strategies relevant to THIS dataset
6
+ combo_score : fraction of strategy combos activated
7
+ trap_penalty : deduction for falling into failure-mode traps
8
+
9
+ final = 0.40×perf + 0.25×strategy + 0.20×combo + 0.15×(1 - trap_rate)
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from pydantic import BaseModel, Field
15
+
16
+ from kaggle_sim_env.models import EnvState
17
+ from kaggle_sim_env.tasks import TaskDefinition
18
+
19
+
20
+ class GradeResult(BaseModel):
21
+ task_id: str
22
+ performance_score: float = Field(ge=0.0, le=1.0)
23
+ strategy_score: float = Field(ge=0.0, le=1.0)
24
+ combo_score: float = Field(ge=0.0, le=1.0)
25
+ trap_score: float = Field(ge=0.0, le=1.0)
26
+ final_score: float = Field(ge=0.0, le=1.0)
27
+ details: dict[str, object] = Field(default_factory=dict)
28
+
29
+
30
+ class Grader:
31
+
32
+ PERF_W = 0.40
33
+ STRAT_W = 0.25
34
+ COMBO_W = 0.20
35
+ TRAP_W = 0.15
36
+
37
+ def grade(self, state: EnvState, task: TaskDefinition) -> GradeResult:
38
+ perf = self._performance_score(state, task)
39
+ strat = self._contextual_strategy_score(state, task)
40
+ combo = self._combo_score(state, task)
41
+ trap = self._trap_score(state, task)
42
+ final = round(
43
+ self.PERF_W * perf + self.STRAT_W * strat
44
+ + self.COMBO_W * combo + self.TRAP_W * trap,
45
+ 4,
46
+ )
47
+
48
+ return GradeResult(
49
+ task_id=task.task_id,
50
+ performance_score=round(perf, 4),
51
+ strategy_score=round(strat, 4),
52
+ combo_score=round(combo, 4),
53
+ trap_score=round(trap, 4),
54
+ final_score=final,
55
+ details={
56
+ "test_score": state.test_score,
57
+ "cv_score": state.cv_score,
58
+ "cv_test_gap": round(abs(state.cv_score - state.test_score), 4),
59
+ "steps_used": state.step_count,
60
+ "max_steps": state.max_steps,
61
+ "submitted": state.submitted,
62
+ "expected_strategies": task.expected_strategies,
63
+ "applied_strategies": state.applied_strategies,
64
+ "matched_strategies": self._matched(state, task),
65
+ "missing_strategies": self._missing(state, task),
66
+ "irrelevant_strategies_used": self._irrelevant_used(state, task),
67
+ "total_combos": len(task.strategy_combos),
68
+ "active_combos": state.active_combos,
69
+ "traps_triggered": state.traps_triggered,
70
+ "total_failure_modes": len(task.failure_modes),
71
+ },
72
+ )
73
+
74
+ # --- Performance ---
75
+
76
+ @staticmethod
77
+ def _performance_score(state: EnvState, task: TaskDefinition) -> float:
78
+ if not state.submitted:
79
+ return 0.0
80
+ ghost_max = max(task.ghost_scores) if task.ghost_scores else 1.0
81
+ ghost_min = min(task.ghost_scores) if task.ghost_scores else 0.0
82
+ rng = ghost_max - ghost_min
83
+ if rng < 1e-9:
84
+ return float(state.test_score >= ghost_max)
85
+ raw = (state.test_score - ghost_min) / rng
86
+ return max(0.0, min(1.0, raw))
87
+
88
+ # --- Contextual strategy score ---
89
+
90
+ @staticmethod
91
+ def _contextual_strategy_score(state: EnvState, task: TaskDefinition) -> float:
92
+ """Credit for relevant strategies, penalise irrelevant ones."""
93
+ expected = set(task.expected_strategies)
94
+ if not expected:
95
+ return 1.0
96
+
97
+ matched = expected.intersection(state.applied_strategies)
98
+ base = len(matched) / len(expected)
99
+
100
+ irrelevant_count = 0
101
+ for strat in state.applied_strategies:
102
+ rel = task.context_relevance.get(strat)
103
+ if rel is not None and rel <= -0.5:
104
+ irrelevant_count += 1
105
+
106
+ penalty = min(irrelevant_count * 0.05, 0.3)
107
+ return max(0.0, round(base - penalty, 4))
108
+
109
+ # --- Combo ---
110
+
111
+ @staticmethod
112
+ def _combo_score(state: EnvState, task: TaskDefinition) -> float:
113
+ total = len(task.strategy_combos)
114
+ if total == 0:
115
+ return 1.0
116
+ return len(state.active_combos) / total
117
+
118
+ # --- Trap score (1.0 = no traps, 0.0 = all traps triggered) ---
119
+
120
+ @staticmethod
121
+ def _trap_score(state: EnvState, task: TaskDefinition) -> float:
122
+ total = len(task.failure_modes)
123
+ if total == 0:
124
+ return 1.0
125
+ triggered = len(state.traps_triggered)
126
+ return max(0.0, 1.0 - triggered / total)
127
+
128
+ # --- Helpers ---
129
+
130
+ @staticmethod
131
+ def _matched(state: EnvState, task: TaskDefinition) -> list[str]:
132
+ return sorted(set(task.expected_strategies) & set(state.applied_strategies))
133
+
134
+ @staticmethod
135
+ def _missing(state: EnvState, task: TaskDefinition) -> list[str]:
136
+ return sorted(set(task.expected_strategies) - set(state.applied_strategies))
137
+
138
+ @staticmethod
139
+ def _irrelevant_used(state: EnvState, task: TaskDefinition) -> list[str]:
140
+ result = []
141
+ for strat in state.applied_strategies:
142
+ rel = task.context_relevance.get(strat)
143
+ if rel is not None and rel <= -0.5:
144
+ result.append(strat)
145
+ return result
kaggle_sim_env/hints.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hints system for KaggleSimEnv.
2
+
3
+ When the agent takes the `inspect_top_solution` action it receives a hint
4
+ from the task's hint list. Hints are dispensed in order; once all hints are
5
+ exhausted the agent receives a generic message.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+
11
+ class HintProvider:
12
+ """Dispenses task-specific hints one at a time."""
13
+
14
+ def __init__(self, hints: list[str]) -> None:
15
+ self._hints = list(hints)
16
+ self._index = 0
17
+
18
+ @property
19
+ def hints_given(self) -> int:
20
+ return self._index
21
+
22
+ def next_hint(self) -> str:
23
+ if self._index < len(self._hints):
24
+ hint = self._hints[self._index]
25
+ self._index += 1
26
+ return hint
27
+ return "No more hints available. Try combining strategies you've learned so far."
28
+
29
+ def reset(self) -> None:
30
+ self._index = 0
kaggle_sim_env/leaderboard.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Simulated leaderboard with ghost competitors.
2
+
3
+ Ghost competitors have fixed scores. The agent's rank is computed by
4
+ inserting its current test score into the sorted ghost list.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import hashlib
10
+ from typing import Any
11
+
12
+
13
+ _GHOST_NAMES = [
14
+ "GrandMaster_42",
15
+ "DataWizard",
16
+ "FeatureKing",
17
+ "BoostQueen",
18
+ "StackMaster",
19
+ "NeuralNinja",
20
+ "EnsembleGuru",
21
+ "CVExpert",
22
+ "KagglePro",
23
+ ]
24
+
25
+
26
+ def _deterministic_name(index: int, task_id: str) -> str:
27
+ """Generate a stable ghost name seeded by task + index."""
28
+ digest = hashlib.md5(f"{task_id}:{index}".encode()).hexdigest()[:4]
29
+ base = _GHOST_NAMES[index % len(_GHOST_NAMES)]
30
+ return f"{base}_{digest}"
31
+
32
+
33
+ class Leaderboard:
34
+ """Maintains a leaderboard of ghost competitors plus the agent."""
35
+
36
+ def __init__(self, task_id: str, ghost_scores: list[float]) -> None:
37
+ self.task_id = task_id
38
+ self.ghosts: list[dict[str, Any]] = []
39
+ for i, score in enumerate(sorted(ghost_scores, reverse=True)):
40
+ self.ghosts.append({
41
+ "name": _deterministic_name(i, task_id),
42
+ "score": round(score, 4),
43
+ "rank": i + 1,
44
+ })
45
+
46
+ def agent_rank(self, agent_score: float) -> int:
47
+ """Return 1-indexed rank of the agent among ghosts."""
48
+ rank = 1
49
+ for ghost in self.ghosts:
50
+ if ghost["score"] > agent_score:
51
+ rank += 1
52
+ return rank
53
+
54
+ def full_board(self, agent_score: float) -> list[dict[str, Any]]:
55
+ """Return complete leaderboard with agent inserted."""
56
+ entries = list(self.ghosts) + [
57
+ {"name": "Agent", "score": round(agent_score, 4), "rank": 0}
58
+ ]
59
+ entries.sort(key=lambda e: e["score"], reverse=True)
60
+ for i, entry in enumerate(entries):
61
+ entry["rank"] = i + 1
62
+ return entries
kaggle_sim_env/models.py ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pydantic models for KaggleSimEnv.
2
+
3
+ v3 — hierarchical categories, causal dataset properties, failure modes.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from enum import Enum
9
+ from typing import Any
10
+
11
+ from pydantic import BaseModel, Field, field_validator
12
+
13
+
14
+ # =========================================================================
15
+ # Hierarchical category mappings
16
+ # technique → category (the single source of truth)
17
+ # =========================================================================
18
+
19
+ CATEGORY_MAP: dict[str, dict[str, str]] = {
20
+ "set_cv": {
21
+ "kfold": "standard",
22
+ "repeated_kfold": "standard",
23
+ "group_kfold": "group",
24
+ "stratified_group_kfold": "group",
25
+ "time_split": "temporal",
26
+ "combined_group_time": "temporal",
27
+ },
28
+ "feature_engineering": {
29
+ "log_transform": "distribution",
30
+ "normalize": "distribution",
31
+ "quantile_features": "distribution",
32
+ "interaction_terms": "interaction",
33
+ "domain_ratios": "interaction",
34
+ "sin_cos_encoding": "encoding",
35
+ "target_encoding": "encoding",
36
+ "spatial_encoding": "encoding",
37
+ "tfidf_features": "encoding",
38
+ "relative_coordinates": "spatial",
39
+ "distance_features": "spatial",
40
+ "frequency_features": "signal",
41
+ "multi_layer_features": "signal",
42
+ "fourier_resampling": "signal",
43
+ },
44
+ "detect_shift": {
45
+ "adversarial_validation": "detection",
46
+ "feature_importance_shift": "detection",
47
+ "remove_identifiers": "mitigation",
48
+ "domain_invariant_features": "mitigation",
49
+ },
50
+ "train_model": {
51
+ "xgboost": "tree",
52
+ "lightgbm": "tree",
53
+ "catboost": "tree",
54
+ "random_forest": "tree",
55
+ "linear": "linear",
56
+ "neural_network": "neural",
57
+ "pretrained_backbone": "neural",
58
+ "temporal_cnn": "neural",
59
+ "transformer_encoder": "neural",
60
+ },
61
+ "handle_imbalance": {
62
+ "scale_pos_weight": "weighting",
63
+ "class_weighted_loss": "weighting",
64
+ "calibrate_probabilities": "calibration",
65
+ "optimize_threshold": "calibration",
66
+ "hierarchical_labels": "hierarchy",
67
+ "lower_thresholds_recall": "hierarchy",
68
+ },
69
+ "clean_data": {
70
+ "remove_corrupted": "removal",
71
+ "remove_outliers": "removal",
72
+ "remove_leaky_features": "removal",
73
+ "analytical_reconstruction": "reconstruction",
74
+ "nan_native_model": "reconstruction",
75
+ "domain_augmentation": "reconstruction",
76
+ "clean_subset_training": "reconstruction",
77
+ },
78
+ "augmentation": {
79
+ "geometric": "geometric",
80
+ "rotation_invariant": "geometric",
81
+ "image_rectification": "geometric",
82
+ "color_transform": "color",
83
+ "clahe": "color",
84
+ "gaussian_noise": "noise",
85
+ "robustness_augmentation": "noise",
86
+ "camera_simulation": "domain",
87
+ "temporal_augmentation": "domain",
88
+ "symmetry_augmentation": "domain",
89
+ "multi_view_processing": "domain",
90
+ },
91
+ "ensemble": {
92
+ "weighted_average": "averaging",
93
+ "multi_seed_averaging": "averaging",
94
+ "swa": "averaging",
95
+ "stacking": "stacking",
96
+ "diverse_features": "diversity",
97
+ "heterogeneous": "diversity",
98
+ },
99
+ "postprocess": {
100
+ "bias_correction": "calibration",
101
+ "prediction_shrinkage": "calibration",
102
+ "per_group_calibration": "calibration",
103
+ "domain_rules": "domain",
104
+ "physics_constraints": "domain",
105
+ "tta": "inference",
106
+ },
107
+ "tune_loss": {
108
+ "asymmetric_loss": "asymmetric",
109
+ "epsilon_insensitive": "asymmetric",
110
+ "gaussian_nll": "uncertainty",
111
+ "multi_task": "multi_objective",
112
+ "interval_regression": "multi_objective",
113
+ "quantile_regression": "multi_objective",
114
+ "sample_weighted": "weighting",
115
+ "auxiliary_physics_loss": "weighting",
116
+ },
117
+ "regularize": {
118
+ "strong_regularization": "weight",
119
+ "ema": "weight",
120
+ "dropout": "weight",
121
+ "freeze_backbone": "transfer",
122
+ },
123
+ }
124
+
125
+
126
+ def get_categories_for_action(action_type: str) -> dict[str, list[str]]:
127
+ """Return {category: [techniques]} for an action type."""
128
+ mapping = CATEGORY_MAP.get(action_type, {})
129
+ result: dict[str, list[str]] = {}
130
+ for technique, cat in mapping.items():
131
+ result.setdefault(cat, []).append(technique)
132
+ return result
133
+
134
+
135
+ def validate_category(action_type: str, category: str | None, technique: str) -> str | None:
136
+ """Validate and return the correct category. Returns error message on failure."""
137
+ mapping = CATEGORY_MAP.get(action_type)
138
+ if mapping is None:
139
+ return None
140
+ expected_cat = mapping.get(technique)
141
+ if expected_cat is None:
142
+ return f"Unknown technique '{technique}' for {action_type}"
143
+ if category is not None and category != expected_cat:
144
+ return (
145
+ f"Wrong category '{category}' for {action_type}:{technique}. "
146
+ f"Expected '{expected_cat}'."
147
+ )
148
+ return None
149
+
150
+
151
+ def infer_category(action_type: str, technique: str) -> str | None:
152
+ """Infer category from action_type + technique."""
153
+ mapping = CATEGORY_MAP.get(action_type, {})
154
+ return mapping.get(technique)
155
+
156
+
157
+ # =========================================================================
158
+ # Action types
159
+ # =========================================================================
160
+
161
+ class ActionType(str, Enum):
162
+ SET_CV = "set_cv"
163
+ FEATURE_ENGINEERING = "feature_engineering"
164
+ DETECT_SHIFT = "detect_shift"
165
+ TRAIN_MODEL = "train_model"
166
+ HANDLE_IMBALANCE = "handle_imbalance"
167
+ CLEAN_DATA = "clean_data"
168
+ AUGMENTATION = "augmentation"
169
+ ENSEMBLE = "ensemble"
170
+ PSEUDO_LABEL = "pseudo_label"
171
+ POSTPROCESS = "postprocess"
172
+ TUNE_LOSS = "tune_loss"
173
+ REGULARIZE = "regularize"
174
+ INSPECT_TOP_SOLUTION = "inspect_top_solution"
175
+ SUBMIT = "submit"
176
+
177
+
178
+ _PARAM_KEY_MAP: dict[str, str] = {
179
+ "set_cv": "strategy",
180
+ "feature_engineering": "technique",
181
+ "detect_shift": "method",
182
+ "train_model": "algorithm",
183
+ "handle_imbalance": "method",
184
+ "clean_data": "method",
185
+ "augmentation": "method",
186
+ "ensemble": "method",
187
+ "postprocess": "method",
188
+ "tune_loss": "method",
189
+ "regularize": "method",
190
+ }
191
+
192
+
193
+ def get_param_key(action_type: str) -> str | None:
194
+ return _PARAM_KEY_MAP.get(action_type)
195
+
196
+
197
+ def get_allowed_values(action_type: str) -> list[str]:
198
+ mapping = CATEGORY_MAP.get(action_type, {})
199
+ return list(mapping.keys())
200
+
201
+
202
+ # =========================================================================
203
+ # Action
204
+ # =========================================================================
205
+
206
+ class Action(BaseModel):
207
+ """A single structured action with hierarchical category."""
208
+
209
+ action_type: ActionType
210
+ parameters: dict[str, Any] = Field(default_factory=dict)
211
+
212
+ @field_validator("parameters", mode="before")
213
+ @classmethod
214
+ def _coerce_parameters(cls, v: Any) -> dict[str, Any]:
215
+ if v is None:
216
+ return {}
217
+ return v
218
+
219
+ def technique_value(self) -> str | None:
220
+ """Return the technique/strategy/method/algorithm value."""
221
+ key = get_param_key(self.action_type.value)
222
+ if key is None:
223
+ return None
224
+ return self.parameters.get(key)
225
+
226
+ def category_value(self) -> str | None:
227
+ """Return the declared category, or infer it."""
228
+ declared = self.parameters.get("category")
229
+ if declared:
230
+ return declared
231
+ tv = self.technique_value()
232
+ if tv:
233
+ return infer_category(self.action_type.value, tv)
234
+ return None
235
+
236
+ def tag(self) -> str:
237
+ """Concise string for history tracking (without category)."""
238
+ at = self.action_type
239
+ if at == ActionType.PSEUDO_LABEL:
240
+ return f"pseudo_label:{self.parameters.get('iterations', 1)}"
241
+ if at in (ActionType.INSPECT_TOP_SOLUTION, ActionType.SUBMIT):
242
+ return at.value
243
+ tv = self.technique_value()
244
+ if tv:
245
+ return f"{at.value}:{tv}"
246
+ return at.value
247
+
248
+ def full_tag(self) -> str:
249
+ """Verbose string including category."""
250
+ cat = self.category_value()
251
+ base = self.tag()
252
+ if cat:
253
+ parts = base.split(":", 1)
254
+ if len(parts) == 2:
255
+ return f"{parts[0]}:{cat}:{parts[1]}"
256
+ return base
257
+
258
+
259
+ # =========================================================================
260
+ # Causal dataset properties
261
+ # =========================================================================
262
+
263
+ class DatasetProperties(BaseModel):
264
+ """Ground-truth properties that govern causal reward logic."""
265
+
266
+ has_shift: bool = False
267
+ has_leakage: bool = False
268
+ has_noise_features: bool = False
269
+ has_missing_data: bool = False
270
+ has_imbalance: bool = False
271
+ imbalance_ratio: float = 0.5
272
+ has_heavy_tails: bool = False
273
+ has_time_column: bool = False
274
+ has_group_column: bool = False
275
+ has_images: bool = False
276
+ has_spatial_data: bool = False
277
+ has_text: bool = False
278
+ is_safety_critical: bool = False
279
+ needs_physics: bool = False
280
+
281
+
282
+ # =========================================================================
283
+ # Failure mode definition
284
+ # =========================================================================
285
+
286
+ class FailureMode(BaseModel):
287
+ """A trap the agent can fall into."""
288
+
289
+ name: str
290
+ trigger_tag: str
291
+ condition_field: str
292
+ condition_value: bool
293
+ cv_effect: float
294
+ test_effect: float
295
+ message: str
296
+
297
+
298
+ # =========================================================================
299
+ # Dataset metadata (observation-facing)
300
+ # =========================================================================
301
+
302
+ class ColumnInfo(BaseModel):
303
+ name: str
304
+ dtype: str
305
+ missing_pct: float = Field(ge=0.0, le=100.0)
306
+ unique_count: int = Field(ge=0)
307
+
308
+
309
+ class DatasetMetadata(BaseModel):
310
+ num_rows: int
311
+ num_features: int
312
+ columns: list[ColumnInfo]
313
+ target_column: str
314
+ task_type: str = "classification"
315
+ has_time_column: bool = False
316
+ has_group_column: bool = False
317
+ has_image_data: bool = False
318
+ has_text_data: bool = False
319
+ has_spatial_data: bool = False
320
+ class_balance: dict[str, float] = Field(default_factory=dict)
321
+ target_distribution: str = "normal"
322
+
323
+
324
+ # =========================================================================
325
+ # Observation
326
+ # =========================================================================
327
+
328
+ class Observation(BaseModel):
329
+ dataset_metadata: DatasetMetadata
330
+ applied_strategies: list[str]
331
+ current_cv_score: float
332
+ leaderboard_rank: int
333
+ step_count: int
334
+ max_steps: int
335
+ done: bool
336
+ message: str = ""
337
+
338
+
339
+ # =========================================================================
340
+ # Reward
341
+ # =========================================================================
342
+
343
+ class RewardBreakdown(BaseModel):
344
+ cv_improvement: float = 0.0
345
+ strategy_bonus: float = 0.0
346
+ context_bonus: float = 0.0
347
+ combo_bonus: float = 0.0
348
+ redundancy_penalty: float = 0.0
349
+ irrelevant_penalty: float = 0.0
350
+ trap_penalty: float = 0.0
351
+ overfitting_penalty: float = 0.0
352
+ submission_bonus: float = 0.0
353
+
354
+
355
+ class Reward(BaseModel):
356
+ total: float
357
+ breakdown: RewardBreakdown
358
+
359
+
360
+ # =========================================================================
361
+ # Full environment state
362
+ # =========================================================================
363
+
364
+ class EnvState(BaseModel):
365
+ task_id: str
366
+ step_count: int
367
+ max_steps: int
368
+ done: bool
369
+ cv_score: float
370
+ test_score: float
371
+ applied_strategies: list[str]
372
+ strategy_history: list[str]
373
+ leaderboard_rank: int
374
+ leaderboard: list[dict[str, Any]]
375
+ submitted: bool
376
+ hint_count: int
377
+ active_combos: list[str] = Field(default_factory=list)
378
+ traps_triggered: list[str] = Field(default_factory=list)
379
+
380
+
381
+ # =========================================================================
382
+ # Step response
383
+ # =========================================================================
384
+
385
+ class StepResponse(BaseModel):
386
+ observation: Observation
387
+ reward: Reward
388
+ done: bool
389
+ info: dict[str, Any] = Field(default_factory=dict)
kaggle_sim_env/rewards.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Reward calculation engine for KaggleSimEnv v3.
2
+
3
+ Nine-component dense reward:
4
+ cv_improvement Δ CV score
5
+ strategy_bonus +0.05 for expected strategy (first use)
6
+ context_bonus +/- based on action relevance to this dataset
7
+ combo_bonus +0.08 per strategy combo completed
8
+ redundancy_penalty -0.03 × repeat count
9
+ irrelevant_penalty penalty for actions irrelevant to this domain
10
+ trap_penalty penalty for falling into a failure mode
11
+ overfitting_penalty penalty when CV-test gap > threshold
12
+ submission_bonus 0.5 × test_score on submit
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from kaggle_sim_env.models import Reward, RewardBreakdown
18
+
19
+
20
+ def compute_reward(
21
+ *,
22
+ prev_cv: float,
23
+ new_cv: float,
24
+ new_test: float,
25
+ action_tag: str,
26
+ expected_strategies: list[str],
27
+ already_applied: list[str],
28
+ is_submit: bool,
29
+ submitted_test_score: float | None = None,
30
+ newly_completed_combos: list[str] | None = None,
31
+ context_relevance: float | None = None,
32
+ traps_triggered_this_step: list[str] | None = None,
33
+ ) -> Reward:
34
+ cv_improvement = round(new_cv - prev_cv, 6)
35
+
36
+ # --- Expected strategy bonus ---
37
+ strategy_bonus = 0.0
38
+ if action_tag in expected_strategies and action_tag not in already_applied:
39
+ strategy_bonus = 0.05
40
+
41
+ # --- Contextual relevance bonus/penalty ---
42
+ context_bonus = 0.0
43
+ if context_relevance is not None:
44
+ if context_relevance > 0:
45
+ context_bonus = 0.03 * context_relevance
46
+ elif context_relevance < 0:
47
+ context_bonus = 0.04 * context_relevance # harsher penalty
48
+
49
+ # --- Combo bonus ---
50
+ combo_bonus = 0.0
51
+ if newly_completed_combos:
52
+ combo_bonus = 0.08 * len(newly_completed_combos)
53
+
54
+ # --- Redundancy penalty ---
55
+ redundancy_penalty = 0.0
56
+ if action_tag in already_applied:
57
+ count = already_applied.count(action_tag)
58
+ redundancy_penalty = -0.03 * min(count, 3)
59
+
60
+ # --- Irrelevant action penalty ---
61
+ irrelevant_penalty = 0.0
62
+ if context_relevance is not None and context_relevance <= -0.8:
63
+ irrelevant_penalty = -0.05
64
+
65
+ # --- Trap penalty ---
66
+ trap_penalty = 0.0
67
+ if traps_triggered_this_step:
68
+ trap_penalty = -0.08 * len(traps_triggered_this_step)
69
+
70
+ # --- Overfitting penalty ---
71
+ gap = abs(new_cv - new_test)
72
+ overfitting_penalty = -gap * 0.5 if gap > 0.05 else 0.0
73
+
74
+ # --- Submission bonus ---
75
+ submission_bonus = 0.0
76
+ if is_submit and submitted_test_score is not None:
77
+ submission_bonus = submitted_test_score * 0.5
78
+
79
+ breakdown = RewardBreakdown(
80
+ cv_improvement=round(cv_improvement, 6),
81
+ strategy_bonus=round(strategy_bonus, 6),
82
+ context_bonus=round(context_bonus, 6),
83
+ combo_bonus=round(combo_bonus, 6),
84
+ redundancy_penalty=round(redundancy_penalty, 6),
85
+ irrelevant_penalty=round(irrelevant_penalty, 6),
86
+ trap_penalty=round(trap_penalty, 6),
87
+ overfitting_penalty=round(overfitting_penalty, 6),
88
+ submission_bonus=round(submission_bonus, 6),
89
+ )
90
+
91
+ total = sum([
92
+ cv_improvement, strategy_bonus, context_bonus, combo_bonus,
93
+ redundancy_penalty, irrelevant_penalty, trap_penalty,
94
+ overfitting_penalty, submission_bonus,
95
+ ])
96
+
97
+ return Reward(total=round(total, 6), breakdown=breakdown)
kaggle_sim_env/tasks.py ADDED
@@ -0,0 +1,1031 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Task definitions for KaggleSimEnv v3.
2
+
3
+ Each task now includes:
4
+ - DatasetProperties → causal ground truth driving reward logic
5
+ - failure_modes → traps the agent can fall into
6
+ - context_relevance → per-action relevance to THIS dataset
7
+ - strategy_combos → synergy bonuses
8
+ - score_modifiers → deterministic CV/test deltas
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any
15
+
16
+ from kaggle_sim_env.models import (
17
+ ColumnInfo,
18
+ DatasetMetadata,
19
+ DatasetProperties,
20
+ FailureMode,
21
+ )
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class StrategyComboDef:
26
+ name: str
27
+ required: frozenset[str]
28
+ cv_bonus: float
29
+ test_bonus: float
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class TaskDefinition:
34
+ task_id: str
35
+ title: str
36
+ difficulty: str
37
+ description: str
38
+ dataset_metadata: DatasetMetadata
39
+ dataset_properties: DatasetProperties
40
+ base_cv_score: float
41
+ base_test_score: float
42
+ expected_strategies: list[str]
43
+ ghost_scores: list[float]
44
+ hints: list[str]
45
+ score_modifiers: dict[str, dict[str, float]] = field(default_factory=dict)
46
+ overfitting_risk: dict[str, float] = field(default_factory=dict)
47
+ strategy_combos: list[StrategyComboDef] = field(default_factory=list)
48
+ failure_modes: list[FailureMode] = field(default_factory=list)
49
+ context_relevance: dict[str, float] = field(default_factory=dict)
50
+ max_steps: int = 20
51
+
52
+
53
+ # =========================================================================
54
+ # Contextual relevance helpers
55
+ # =========================================================================
56
+ # context_relevance maps action tags → relevance score [-1.0 .. +1.0]
57
+ # +1.0 = highly relevant for this dataset
58
+ # 0.0 = neutral
59
+ # -1.0 = completely irrelevant / harmful
60
+
61
+
62
+ # =========================================================================
63
+ # Task 1 – Easy: Clean tabular classification
64
+ # =========================================================================
65
+
66
+ def _easy_task() -> TaskDefinition:
67
+ columns = [
68
+ ColumnInfo(name="id", dtype="int64", missing_pct=0.0, unique_count=10000),
69
+ ColumnInfo(name="age", dtype="float64", missing_pct=0.0, unique_count=80),
70
+ ColumnInfo(name="income", dtype="float64", missing_pct=0.0, unique_count=5000),
71
+ ColumnInfo(name="credit_score", dtype="float64", missing_pct=0.0, unique_count=300),
72
+ ColumnInfo(name="num_products", dtype="int64", missing_pct=0.0, unique_count=4),
73
+ ColumnInfo(name="tenure", dtype="int64", missing_pct=0.0, unique_count=12),
74
+ ColumnInfo(name="balance", dtype="float64", missing_pct=0.0, unique_count=6000),
75
+ ColumnInfo(name="has_credit_card", dtype="int64", missing_pct=0.0, unique_count=2),
76
+ ColumnInfo(name="is_active_member", dtype="int64", missing_pct=0.0, unique_count=2),
77
+ ColumnInfo(name="target", dtype="int64", missing_pct=0.0, unique_count=2),
78
+ ]
79
+
80
+ props = DatasetProperties(
81
+ has_imbalance=True, imbalance_ratio=0.20,
82
+ )
83
+
84
+ failure_modes = [
85
+ FailureMode(
86
+ name="unnecessary_shift_detection",
87
+ trigger_tag="detect_shift:adversarial_validation",
88
+ condition_field="has_shift", condition_value=False,
89
+ cv_effect=0.0, test_effect=0.0,
90
+ message="No distribution shift exists — adversarial validation wasted a step.",
91
+ ),
92
+ FailureMode(
93
+ name="image_aug_on_tabular",
94
+ trigger_tag="augmentation:geometric",
95
+ condition_field="has_images", condition_value=False,
96
+ cv_effect=0.0, test_effect=-0.01,
97
+ message="Geometric augmentation is meaningless on tabular data.",
98
+ ),
99
+ FailureMode(
100
+ name="target_encoding_without_group_cv",
101
+ trigger_tag="feature_engineering:target_encoding",
102
+ condition_field="has_group_column", condition_value=False,
103
+ cv_effect=0.06, test_effect=-0.04,
104
+ message="Target encoding without group-aware CV causes leakage — CV inflated but test hurt.",
105
+ ),
106
+ ]
107
+
108
+ context_relevance = {
109
+ # Highly relevant
110
+ "set_cv:kfold": 1.0,
111
+ "set_cv:repeated_kfold": 0.8,
112
+ "feature_engineering:normalize": 0.8,
113
+ "feature_engineering:domain_ratios": 1.0,
114
+ "train_model:xgboost": 1.0,
115
+ "train_model:lightgbm": 0.9,
116
+ "handle_imbalance:scale_pos_weight": 1.0,
117
+ "handle_imbalance:optimize_threshold": 0.8,
118
+ "ensemble:weighted_average": 0.7,
119
+ # Neutral
120
+ "feature_engineering:log_transform": 0.3,
121
+ "regularize:strong_regularization": 0.2,
122
+ # Irrelevant
123
+ "detect_shift:adversarial_validation": -0.5,
124
+ "augmentation:geometric": -1.0,
125
+ "augmentation:color_transform": -1.0,
126
+ "augmentation:camera_simulation": -1.0,
127
+ "feature_engineering:sin_cos_encoding": -0.8,
128
+ "feature_engineering:relative_coordinates": -1.0,
129
+ "feature_engineering:frequency_features": -0.8,
130
+ "tune_loss:auxiliary_physics_loss": -1.0,
131
+ "postprocess:physics_constraints": -1.0,
132
+ "train_model:pretrained_backbone": -0.5,
133
+ "train_model:temporal_cnn": -0.8,
134
+ "train_model:transformer_encoder": -0.5,
135
+ }
136
+
137
+ return TaskDefinition(
138
+ task_id="easy_churn",
139
+ title="Customer Churn Prediction",
140
+ difficulty="easy",
141
+ description=(
142
+ "Predict customer churn from clean tabular banking data. "
143
+ "Mild class imbalance (80/20). No missing values, no shift."
144
+ ),
145
+ dataset_metadata=DatasetMetadata(
146
+ num_rows=10000, num_features=9, columns=columns,
147
+ target_column="target", task_type="classification",
148
+ class_balance={"0": 0.80, "1": 0.20},
149
+ ),
150
+ dataset_properties=props,
151
+ base_cv_score=0.50,
152
+ base_test_score=0.50,
153
+ expected_strategies=[
154
+ "set_cv:kfold",
155
+ "feature_engineering:normalize",
156
+ "feature_engineering:domain_ratios",
157
+ "train_model:xgboost",
158
+ "handle_imbalance:scale_pos_weight",
159
+ ],
160
+ ghost_scores=[0.90, 0.88, 0.86, 0.84, 0.82, 0.79, 0.75, 0.72, 0.68],
161
+ hints=[
162
+ "This is a clean tabular dataset — standard k-fold CV works well.",
163
+ "Create domain ratios like balance/income, products/tenure.",
164
+ "XGBoost dominates clean tabular data.",
165
+ "Tune scale_pos_weight for the 80/20 class split.",
166
+ "No distribution shift exists — don't waste steps detecting it.",
167
+ ],
168
+ score_modifiers={
169
+ "set_cv:kfold": {"cv": 0.05, "test": 0.05},
170
+ "set_cv:repeated_kfold": {"cv": 0.06, "test": 0.06},
171
+ "set_cv:group_kfold": {"cv": 0.03, "test": 0.03},
172
+ "set_cv:time_split": {"cv": 0.02, "test": 0.01},
173
+ "feature_engineering:normalize": {"cv": 0.04, "test": 0.04},
174
+ "feature_engineering:log_transform": {"cv": 0.02, "test": 0.02},
175
+ "feature_engineering:interaction_terms": {"cv": 0.03, "test": 0.02},
176
+ "feature_engineering:domain_ratios": {"cv": 0.05, "test": 0.05},
177
+ "feature_engineering:target_encoding": {"cv": 0.06, "test": -0.04},
178
+ "feature_engineering:quantile_features": {"cv": 0.02, "test": 0.02},
179
+ "train_model:xgboost": {"cv": 0.20, "test": 0.19},
180
+ "train_model:lightgbm": {"cv": 0.19, "test": 0.18},
181
+ "train_model:catboost": {"cv": 0.18, "test": 0.18},
182
+ "train_model:random_forest": {"cv": 0.16, "test": 0.15},
183
+ "train_model:linear": {"cv": 0.10, "test": 0.10},
184
+ "train_model:neural_network": {"cv": 0.08, "test": 0.06},
185
+ "handle_imbalance:scale_pos_weight": {"cv": 0.04, "test": 0.04},
186
+ "handle_imbalance:calibrate_probabilities": {"cv": 0.02, "test": 0.03},
187
+ "handle_imbalance:optimize_threshold": {"cv": 0.03, "test": 0.03},
188
+ "ensemble:weighted_average": {"cv": 0.04, "test": 0.03},
189
+ "ensemble:stacking": {"cv": 0.05, "test": 0.03},
190
+ "ensemble:multi_seed_averaging": {"cv": 0.03, "test": 0.03},
191
+ "regularize:strong_regularization": {"cv": 0.01, "test": 0.02},
192
+ "pseudo_label:1": {"cv": 0.01, "test": 0.00},
193
+ "postprocess:bias_correction": {"cv": 0.01, "test": 0.01},
194
+ "inspect_top_solution": {"cv": 0.0, "test": 0.0},
195
+ },
196
+ overfitting_risk={
197
+ "feature_engineering:target_encoding": 0.06,
198
+ "feature_engineering:interaction_terms": 0.01,
199
+ "ensemble:stacking": 0.02,
200
+ },
201
+ strategy_combos=[
202
+ StrategyComboDef(
203
+ name="clean_tabular_pipeline",
204
+ required=frozenset({"set_cv:kfold", "train_model:xgboost", "feature_engineering:normalize"}),
205
+ cv_bonus=0.02, test_bonus=0.02,
206
+ ),
207
+ StrategyComboDef(
208
+ name="imbalance_aware",
209
+ required=frozenset({"handle_imbalance:scale_pos_weight", "handle_imbalance:optimize_threshold"}),
210
+ cv_bonus=0.02, test_bonus=0.03,
211
+ ),
212
+ ],
213
+ failure_modes=failure_modes,
214
+ context_relevance=context_relevance,
215
+ max_steps=20,
216
+ )
217
+
218
+
219
+ # =========================================================================
220
+ # Task 2 – Medium: Temporal fraud detection + distribution shift
221
+ # =========================================================================
222
+
223
+ def _medium_task() -> TaskDefinition:
224
+ columns = [
225
+ ColumnInfo(name="id", dtype="int64", missing_pct=0.0, unique_count=50000),
226
+ ColumnInfo(name="timestamp", dtype="datetime64", missing_pct=0.0, unique_count=365),
227
+ ColumnInfo(name="merchant_id", dtype="int64", missing_pct=0.0, unique_count=500),
228
+ ColumnInfo(name="amount", dtype="float64", missing_pct=1.2, unique_count=15000),
229
+ ColumnInfo(name="category", dtype="object", missing_pct=0.0, unique_count=15),
230
+ ColumnInfo(name="customer_age", dtype="float64", missing_pct=3.5, unique_count=70),
231
+ ColumnInfo(name="customer_region", dtype="object", missing_pct=0.5, unique_count=8),
232
+ ColumnInfo(name="prev_transactions", dtype="int64", missing_pct=0.0, unique_count=200),
233
+ ColumnInfo(name="is_weekend", dtype="int64", missing_pct=0.0, unique_count=2),
234
+ ColumnInfo(name="device_type", dtype="object", missing_pct=2.0, unique_count=4),
235
+ ColumnInfo(name="is_fraud", dtype="int64", missing_pct=0.0, unique_count=2),
236
+ ]
237
+
238
+ props = DatasetProperties(
239
+ has_shift=True,
240
+ has_imbalance=True, imbalance_ratio=0.05,
241
+ has_time_column=True, has_group_column=True,
242
+ is_safety_critical=True,
243
+ )
244
+
245
+ failure_modes = [
246
+ FailureMode(
247
+ name="kfold_on_temporal_data",
248
+ trigger_tag="set_cv:kfold",
249
+ condition_field="has_shift", condition_value=True,
250
+ cv_effect=0.08, test_effect=-0.04,
251
+ message="Random k-fold on shifted temporal data — CV is optimistic, test will be much worse.",
252
+ ),
253
+ FailureMode(
254
+ name="ignoring_shift",
255
+ trigger_tag="train_model:xgboost",
256
+ condition_field="has_shift", condition_value=True,
257
+ cv_effect=0.0, test_effect=-0.06,
258
+ message="Training without addressing distribution shift — model memorises train distribution.",
259
+ ),
260
+ FailureMode(
261
+ name="target_encoding_leakage",
262
+ trigger_tag="feature_engineering:target_encoding",
263
+ condition_field="has_shift", condition_value=True,
264
+ cv_effect=0.05, test_effect=-0.06,
265
+ message="Target encoding on shifted data leaks train-specific patterns.",
266
+ ),
267
+ ]
268
+
269
+ context_relevance = {
270
+ "detect_shift:adversarial_validation": 1.0,
271
+ "detect_shift:remove_identifiers": 1.0,
272
+ "detect_shift:domain_invariant_features": 0.9,
273
+ "set_cv:time_split": 1.0,
274
+ "set_cv:combined_group_time": 0.9,
275
+ "set_cv:kfold": -0.8,
276
+ "feature_engineering:log_transform": 0.8,
277
+ "feature_engineering:domain_ratios": 0.9,
278
+ "train_model:xgboost": 0.9,
279
+ "handle_imbalance:scale_pos_weight": 1.0,
280
+ "handle_imbalance:optimize_threshold": 0.9,
281
+ "tune_loss:asymmetric_loss": 1.0,
282
+ "regularize:strong_regularization": 0.8,
283
+ "ensemble:weighted_average": 0.7,
284
+ # Irrelevant
285
+ "augmentation:geometric": -1.0,
286
+ "augmentation:color_transform": -1.0,
287
+ "feature_engineering:sin_cos_encoding": -0.5,
288
+ "feature_engineering:relative_coordinates": -1.0,
289
+ "tune_loss:auxiliary_physics_loss": -1.0,
290
+ "postprocess:physics_constraints": -1.0,
291
+ "train_model:pretrained_backbone": -0.5,
292
+ }
293
+
294
+ return TaskDefinition(
295
+ task_id="medium_fraud",
296
+ title="Fraud Detection with Distribution Shift",
297
+ difficulty="medium",
298
+ description=(
299
+ "Detect fraudulent transactions. Test set comes from a later time "
300
+ "period with distribution shift. Heavy imbalance (95/5). Safety-critical."
301
+ ),
302
+ dataset_metadata=DatasetMetadata(
303
+ num_rows=50000, num_features=10, columns=columns,
304
+ target_column="is_fraud", task_type="classification",
305
+ has_time_column=True, has_group_column=True,
306
+ class_balance={"0": 0.95, "1": 0.05},
307
+ ),
308
+ dataset_properties=props,
309
+ base_cv_score=0.42,
310
+ base_test_score=0.38,
311
+ expected_strategies=[
312
+ "detect_shift:adversarial_validation",
313
+ "detect_shift:remove_identifiers",
314
+ "set_cv:time_split",
315
+ "feature_engineering:log_transform",
316
+ "feature_engineering:domain_ratios",
317
+ "train_model:xgboost",
318
+ "handle_imbalance:scale_pos_weight",
319
+ "handle_imbalance:optimize_threshold",
320
+ "tune_loss:asymmetric_loss",
321
+ "ensemble:weighted_average",
322
+ "regularize:strong_regularization",
323
+ ],
324
+ ghost_scores=[0.92, 0.90, 0.87, 0.84, 0.80, 0.76, 0.72, 0.67, 0.60],
325
+ hints=[
326
+ "Adversarial validation AUC > 0.82 — significant distribution shift exists!",
327
+ "Use time-based CV — random k-fold will give you a false sense of accuracy.",
328
+ "Log-transform 'amount' — it has extreme skew.",
329
+ "Remove merchant_id and device_type — they are identifiers causing shift.",
330
+ "This is safety-critical: asymmetric loss prioritises fraud recall.",
331
+ "Tune scale_pos_weight on OOF outputs, then optimize threshold.",
332
+ "Strong regularisation prevents memorising train-specific patterns.",
333
+ ],
334
+ score_modifiers={
335
+ "set_cv:kfold": {"cv": 0.08, "test": -0.04},
336
+ "set_cv:group_kfold": {"cv": 0.06, "test": 0.05},
337
+ "set_cv:time_split": {"cv": 0.05, "test": 0.10},
338
+ "set_cv:combined_group_time": {"cv": 0.05, "test": 0.09},
339
+ "detect_shift:adversarial_validation": {"cv": 0.02, "test": 0.08},
340
+ "detect_shift:feature_importance_shift": {"cv": 0.01, "test": 0.04},
341
+ "detect_shift:remove_identifiers": {"cv": -0.01, "test": 0.06},
342
+ "detect_shift:domain_invariant_features": {"cv": 0.02, "test": 0.05},
343
+ "feature_engineering:log_transform": {"cv": 0.05, "test": 0.06},
344
+ "feature_engineering:normalize": {"cv": 0.02, "test": 0.02},
345
+ "feature_engineering:domain_ratios": {"cv": 0.04, "test": 0.05},
346
+ "feature_engineering:interaction_terms": {"cv": 0.04, "test": 0.01},
347
+ "feature_engineering:target_encoding": {"cv": 0.05, "test": -0.06},
348
+ "train_model:xgboost": {"cv": 0.18, "test": 0.16},
349
+ "train_model:lightgbm": {"cv": 0.17, "test": 0.16},
350
+ "train_model:catboost": {"cv": 0.16, "test": 0.15},
351
+ "train_model:random_forest": {"cv": 0.14, "test": 0.12},
352
+ "train_model:linear": {"cv": 0.08, "test": 0.07},
353
+ "handle_imbalance:scale_pos_weight": {"cv": 0.04, "test": 0.05},
354
+ "handle_imbalance:calibrate_probabilities": {"cv": 0.02, "test": 0.03},
355
+ "handle_imbalance:optimize_threshold": {"cv": 0.03, "test": 0.04},
356
+ "tune_loss:asymmetric_loss": {"cv": 0.03, "test": 0.05},
357
+ "ensemble:weighted_average": {"cv": 0.05, "test": 0.05},
358
+ "ensemble:stacking": {"cv": 0.06, "test": 0.04},
359
+ "ensemble:multi_seed_averaging": {"cv": 0.03, "test": 0.04},
360
+ "ensemble:heterogeneous": {"cv": 0.04, "test": 0.05},
361
+ "regularize:strong_regularization": {"cv": -0.01, "test": 0.04},
362
+ "regularize:ema": {"cv": 0.01, "test": 0.02},
363
+ "pseudo_label:1": {"cv": 0.02, "test": 0.01},
364
+ "pseudo_label:2": {"cv": 0.03, "test": -0.02},
365
+ "postprocess:bias_correction": {"cv": 0.01, "test": 0.02},
366
+ "clean_data:remove_outliers": {"cv": 0.01, "test": 0.02},
367
+ "clean_data:remove_leaky_features": {"cv": -0.02, "test": 0.04},
368
+ "inspect_top_solution": {"cv": 0.0, "test": 0.0},
369
+ },
370
+ overfitting_risk={
371
+ "set_cv:kfold": 0.08,
372
+ "pseudo_label:2": 0.05,
373
+ "feature_engineering:target_encoding": 0.05,
374
+ "feature_engineering:interaction_terms": 0.03,
375
+ },
376
+ strategy_combos=[
377
+ StrategyComboDef(
378
+ name="shift_aware_pipeline",
379
+ required=frozenset({
380
+ "detect_shift:adversarial_validation",
381
+ "set_cv:time_split",
382
+ "detect_shift:remove_identifiers",
383
+ }),
384
+ cv_bonus=0.03, test_bonus=0.06,
385
+ ),
386
+ StrategyComboDef(
387
+ name="safety_critical_fraud",
388
+ required=frozenset({
389
+ "handle_imbalance:scale_pos_weight",
390
+ "tune_loss:asymmetric_loss",
391
+ "handle_imbalance:optimize_threshold",
392
+ }),
393
+ cv_bonus=0.02, test_bonus=0.04,
394
+ ),
395
+ StrategyComboDef(
396
+ name="robust_generalisation",
397
+ required=frozenset({
398
+ "regularize:strong_regularization",
399
+ "detect_shift:domain_invariant_features",
400
+ }),
401
+ cv_bonus=0.01, test_bonus=0.03,
402
+ ),
403
+ ],
404
+ failure_modes=failure_modes,
405
+ context_relevance=context_relevance,
406
+ max_steps=25,
407
+ )
408
+
409
+
410
+ # =========================================================================
411
+ # Task 3 – Hard: Leakage + noise + missing data + shift
412
+ # =========================================================================
413
+
414
+ def _hard_task() -> TaskDefinition:
415
+ columns = [
416
+ ColumnInfo(name="id", dtype="int64", missing_pct=0.0, unique_count=100000),
417
+ ColumnInfo(name="timestamp", dtype="datetime64", missing_pct=0.0, unique_count=730),
418
+ ColumnInfo(name="group_id", dtype="int64", missing_pct=0.0, unique_count=1000),
419
+ ColumnInfo(name="feature_0", dtype="float64", missing_pct=8.5, unique_count=50000),
420
+ ColumnInfo(name="feature_1", dtype="float64", missing_pct=12.0, unique_count=40000),
421
+ ColumnInfo(name="feature_2", dtype="float64", missing_pct=0.0, unique_count=60000),
422
+ ColumnInfo(name="feature_3", dtype="float64", missing_pct=5.0, unique_count=30000),
423
+ ColumnInfo(name="feature_4", dtype="object", missing_pct=15.0, unique_count=20),
424
+ ColumnInfo(name="feature_5_leaky", dtype="float64", missing_pct=0.0, unique_count=80000),
425
+ ColumnInfo(name="noise_0", dtype="float64", missing_pct=0.0, unique_count=100000),
426
+ ColumnInfo(name="noise_1", dtype="float64", missing_pct=0.0, unique_count=100000),
427
+ ColumnInfo(name="noise_2", dtype="float64", missing_pct=0.0, unique_count=100000),
428
+ ColumnInfo(name="target", dtype="int64", missing_pct=0.0, unique_count=2),
429
+ ]
430
+
431
+ props = DatasetProperties(
432
+ has_shift=True, has_leakage=True, has_noise_features=True,
433
+ has_missing_data=True, has_imbalance=True, imbalance_ratio=0.08,
434
+ has_time_column=True, has_group_column=True,
435
+ )
436
+
437
+ failure_modes = [
438
+ FailureMode(
439
+ name="kfold_on_grouped_data",
440
+ trigger_tag="set_cv:kfold",
441
+ condition_field="has_group_column", condition_value=True,
442
+ cv_effect=0.10, test_effect=-0.05,
443
+ message="Random k-fold on grouped data leaks group structure — CV hugely inflated.",
444
+ ),
445
+ FailureMode(
446
+ name="keeping_leaky_feature",
447
+ trigger_tag="train_model:xgboost",
448
+ condition_field="has_leakage", condition_value=True,
449
+ cv_effect=0.0, test_effect=-0.08,
450
+ message="Training on leaky features — model depends on data that won't exist at test time.",
451
+ ),
452
+ FailureMode(
453
+ name="interaction_terms_on_noisy_data",
454
+ trigger_tag="feature_engineering:interaction_terms",
455
+ condition_field="has_noise_features", condition_value=True,
456
+ cv_effect=0.05, test_effect=-0.04,
457
+ message="Interaction terms on noisy features amplify noise — CV up, test down.",
458
+ ),
459
+ FailureMode(
460
+ name="pseudo_label_overfit",
461
+ trigger_tag="pseudo_label:2",
462
+ condition_field="has_shift", condition_value=True,
463
+ cv_effect=0.05, test_effect=-0.06,
464
+ message="Iterative pseudo-labeling on shifted data compounds distribution errors.",
465
+ ),
466
+ ]
467
+
468
+ context_relevance = {
469
+ "detect_shift:adversarial_validation": 1.0,
470
+ "detect_shift:remove_identifiers": 0.9,
471
+ "set_cv:stratified_group_kfold": 1.0,
472
+ "set_cv:kfold": -0.9,
473
+ "clean_data:remove_leaky_features": 1.0,
474
+ "clean_data:remove_outliers": 0.8,
475
+ "clean_data:analytical_reconstruction": 1.0,
476
+ "clean_data:nan_native_model": 0.8,
477
+ "feature_engineering:log_transform": 0.7,
478
+ "feature_engineering:domain_ratios": 0.8,
479
+ "feature_engineering:interaction_terms": -0.7,
480
+ "feature_engineering:target_encoding": -0.9,
481
+ "train_model:xgboost": 0.9,
482
+ "train_model:lightgbm": 0.9,
483
+ "handle_imbalance:scale_pos_weight": 1.0,
484
+ "handle_imbalance:calibrate_probabilities": 0.9,
485
+ "ensemble:stacking": 0.8,
486
+ "ensemble:diverse_features": 0.9,
487
+ "regularize:strong_regularization": 0.9,
488
+ "pseudo_label:1": 0.5,
489
+ "pseudo_label:2": -0.8,
490
+ # Irrelevant
491
+ "augmentation:geometric": -1.0,
492
+ "augmentation:camera_simulation": -1.0,
493
+ "tune_loss:auxiliary_physics_loss": -1.0,
494
+ "postprocess:physics_constraints": -1.0,
495
+ "train_model:pretrained_backbone": -0.5,
496
+ }
497
+
498
+ return TaskDefinition(
499
+ task_id="hard_leaky_noisy",
500
+ title="Leaky Noisy Imbalanced Classification",
501
+ difficulty="hard",
502
+ description=(
503
+ "High-dimensional classification with data leakage (feature_5_leaky), "
504
+ "pure-noise columns (noise_0-2), structured missing data, class imbalance "
505
+ "(92/8), and temporal distribution shift."
506
+ ),
507
+ dataset_metadata=DatasetMetadata(
508
+ num_rows=100000, num_features=12, columns=columns,
509
+ target_column="target", task_type="classification",
510
+ has_time_column=True, has_group_column=True,
511
+ class_balance={"0": 0.92, "1": 0.08},
512
+ ),
513
+ dataset_properties=props,
514
+ base_cv_score=0.38,
515
+ base_test_score=0.32,
516
+ expected_strategies=[
517
+ "detect_shift:adversarial_validation",
518
+ "detect_shift:remove_identifiers",
519
+ "set_cv:stratified_group_kfold",
520
+ "clean_data:remove_leaky_features",
521
+ "clean_data:remove_outliers",
522
+ "clean_data:analytical_reconstruction",
523
+ "feature_engineering:log_transform",
524
+ "feature_engineering:domain_ratios",
525
+ "train_model:xgboost",
526
+ "train_model:lightgbm",
527
+ "handle_imbalance:scale_pos_weight",
528
+ "handle_imbalance:calibrate_probabilities",
529
+ "ensemble:stacking",
530
+ "ensemble:diverse_features",
531
+ "pseudo_label:1",
532
+ "regularize:strong_regularization",
533
+ ],
534
+ ghost_scores=[0.91, 0.88, 0.85, 0.82, 0.78, 0.74, 0.69, 0.63, 0.55],
535
+ hints=[
536
+ "'feature_5_leaky' is suspiciously predictive — it encodes target leakage. Remove it!",
537
+ "noise_0/1/2 have zero signal; removing them reduces overfitting dramatically.",
538
+ "Use stratified group k-fold — random k-fold will leak group structure.",
539
+ "Adversarial validation AUC 0.85 — heavy distribution shift.",
540
+ "Many rows with structured missing data can be analytically reconstructed.",
541
+ "Calibrate probabilities with isotonic regression on OOF predictions.",
542
+ "Stack XGBoost + LightGBM with diverse feature subsets.",
543
+ "One iteration of pseudo-labeling helps; two iterations overfit badly on shifted data.",
544
+ ],
545
+ score_modifiers={
546
+ "set_cv:kfold": {"cv": 0.10, "test": -0.05},
547
+ "set_cv:group_kfold": {"cv": 0.06, "test": 0.08},
548
+ "set_cv:stratified_group_kfold": {"cv": 0.05, "test": 0.10},
549
+ "set_cv:time_split": {"cv": 0.05, "test": 0.07},
550
+ "detect_shift:adversarial_validation": {"cv": 0.02, "test": 0.08},
551
+ "detect_shift:remove_identifiers": {"cv": -0.02, "test": 0.07},
552
+ "clean_data:remove_leaky_features": {"cv": -0.05, "test": 0.08},
553
+ "clean_data:remove_outliers": {"cv": 0.02, "test": 0.03},
554
+ "clean_data:analytical_reconstruction": {"cv": 0.03, "test": 0.04},
555
+ "clean_data:nan_native_model": {"cv": 0.02, "test": 0.03},
556
+ "feature_engineering:log_transform": {"cv": 0.04, "test": 0.05},
557
+ "feature_engineering:domain_ratios": {"cv": 0.04, "test": 0.05},
558
+ "feature_engineering:interaction_terms": {"cv": 0.05, "test": -0.04},
559
+ "feature_engineering:target_encoding": {"cv": 0.06, "test": -0.05},
560
+ "feature_engineering:normalize": {"cv": 0.03, "test": 0.03},
561
+ "train_model:xgboost": {"cv": 0.16, "test": 0.14},
562
+ "train_model:lightgbm": {"cv": 0.15, "test": 0.14},
563
+ "train_model:catboost": {"cv": 0.14, "test": 0.13},
564
+ "train_model:random_forest": {"cv": 0.12, "test": 0.10},
565
+ "train_model:linear": {"cv": 0.06, "test": 0.06},
566
+ "handle_imbalance:scale_pos_weight": {"cv": 0.04, "test": 0.05},
567
+ "handle_imbalance:calibrate_probabilities": {"cv": 0.02, "test": 0.04},
568
+ "handle_imbalance:optimize_threshold": {"cv": 0.03, "test": 0.03},
569
+ "tune_loss:asymmetric_loss": {"cv": 0.02, "test": 0.04},
570
+ "ensemble:stacking": {"cv": 0.06, "test": 0.05},
571
+ "ensemble:diverse_features": {"cv": 0.04, "test": 0.05},
572
+ "ensemble:weighted_average": {"cv": 0.04, "test": 0.04},
573
+ "regularize:strong_regularization": {"cv": -0.02, "test": 0.05},
574
+ "regularize:ema": {"cv": 0.01, "test": 0.02},
575
+ "pseudo_label:1": {"cv": 0.03, "test": 0.03},
576
+ "pseudo_label:2": {"cv": 0.05, "test": -0.06},
577
+ "postprocess:bias_correction": {"cv": 0.01, "test": 0.02},
578
+ "inspect_top_solution": {"cv": 0.0, "test": 0.0},
579
+ },
580
+ overfitting_risk={
581
+ "set_cv:kfold": 0.10,
582
+ "pseudo_label:2": 0.06,
583
+ "feature_engineering:interaction_terms": 0.05,
584
+ "feature_engineering:target_encoding": 0.06,
585
+ },
586
+ strategy_combos=[
587
+ StrategyComboDef(
588
+ name="data_hygiene",
589
+ required=frozenset({
590
+ "clean_data:remove_leaky_features",
591
+ "clean_data:remove_outliers",
592
+ "detect_shift:adversarial_validation",
593
+ }),
594
+ cv_bonus=0.02, test_bonus=0.06,
595
+ ),
596
+ StrategyComboDef(
597
+ name="missing_data_mastery",
598
+ required=frozenset({
599
+ "clean_data:analytical_reconstruction",
600
+ "clean_data:nan_native_model",
601
+ }),
602
+ cv_bonus=0.02, test_bonus=0.03,
603
+ ),
604
+ StrategyComboDef(
605
+ name="diverse_ensemble",
606
+ required=frozenset({
607
+ "train_model:xgboost", "train_model:lightgbm",
608
+ "ensemble:diverse_features", "ensemble:stacking",
609
+ }),
610
+ cv_bonus=0.03, test_bonus=0.05,
611
+ ),
612
+ StrategyComboDef(
613
+ name="calibrated_imbalance",
614
+ required=frozenset({
615
+ "handle_imbalance:scale_pos_weight",
616
+ "handle_imbalance:calibrate_probabilities",
617
+ "handle_imbalance:optimize_threshold",
618
+ }),
619
+ cv_bonus=0.02, test_bonus=0.04,
620
+ ),
621
+ ],
622
+ failure_modes=failure_modes,
623
+ context_relevance=context_relevance,
624
+ max_steps=30,
625
+ )
626
+
627
+
628
+ # =========================================================================
629
+ # Task 4 – Image: Quality regression
630
+ # =========================================================================
631
+
632
+ def _image_task() -> TaskDefinition:
633
+ columns = [
634
+ ColumnInfo(name="image_id", dtype="int64", missing_pct=0.0, unique_count=25000),
635
+ ColumnInfo(name="image_path", dtype="object", missing_pct=0.0, unique_count=25000),
636
+ ColumnInfo(name="camera_model", dtype="object", missing_pct=0.0, unique_count=12),
637
+ ColumnInfo(name="exposure", dtype="float64", missing_pct=2.0, unique_count=500),
638
+ ColumnInfo(name="iso", dtype="int64", missing_pct=1.0, unique_count=20),
639
+ ColumnInfo(name="focal_length", dtype="float64", missing_pct=0.5, unique_count=30),
640
+ ColumnInfo(name="width", dtype="int64", missing_pct=0.0, unique_count=10),
641
+ ColumnInfo(name="height", dtype="int64", missing_pct=0.0, unique_count=10),
642
+ ColumnInfo(name="quality_score", dtype="float64", missing_pct=0.0, unique_count=1000),
643
+ ]
644
+
645
+ props = DatasetProperties(
646
+ has_heavy_tails=True, has_group_column=True,
647
+ has_images=True,
648
+ )
649
+
650
+ failure_modes = [
651
+ FailureMode(
652
+ name="no_augmentation_on_images",
653
+ trigger_tag="submit",
654
+ condition_field="has_images", condition_value=True,
655
+ cv_effect=0.0, test_effect=-0.04,
656
+ message="Submitting image model without augmentation — overfitting on small image data.",
657
+ ),
658
+ FailureMode(
659
+ name="tree_model_on_images",
660
+ trigger_tag="train_model:xgboost",
661
+ condition_field="has_images", condition_value=True,
662
+ cv_effect=0.04, test_effect=-0.02,
663
+ message="Tree models on raw image metadata miss the visual signal entirely.",
664
+ ),
665
+ ]
666
+
667
+ context_relevance = {
668
+ "train_model:pretrained_backbone": 1.0,
669
+ "regularize:freeze_backbone": 1.0,
670
+ "augmentation:geometric": 1.0,
671
+ "augmentation:color_transform": 1.0,
672
+ "augmentation:camera_simulation": 1.0,
673
+ "augmentation:clahe": 0.9,
674
+ "postprocess:tta": 1.0,
675
+ "postprocess:per_group_calibration": 0.9,
676
+ "tune_loss:gaussian_nll": 1.0,
677
+ "tune_loss:multi_task": 0.8,
678
+ "regularize:ema": 0.8,
679
+ "ensemble:multi_seed_averaging": 0.8,
680
+ "ensemble:swa": 0.8,
681
+ "feature_engineering:log_transform": 0.7,
682
+ "set_cv:group_kfold": 1.0,
683
+ "set_cv:kfold": -0.6,
684
+ # Irrelevant
685
+ "feature_engineering:tfidf_features": -1.0,
686
+ "feature_engineering:relative_coordinates": -0.8,
687
+ "tune_loss:auxiliary_physics_loss": -0.8,
688
+ "detect_shift:adversarial_validation": 0.2,
689
+ "train_model:xgboost": -0.3,
690
+ "train_model:linear": -0.7,
691
+ }
692
+
693
+ return TaskDefinition(
694
+ task_id="image_quality",
695
+ title="Image Quality Assessment",
696
+ difficulty="hard",
697
+ description=(
698
+ "Predict continuous quality scores from photographs. Images from "
699
+ "multiple cameras with varying resolution. Heavy-tailed target "
700
+ "and camera-specific biases."
701
+ ),
702
+ dataset_metadata=DatasetMetadata(
703
+ num_rows=25000, num_features=8, columns=columns,
704
+ target_column="quality_score", task_type="regression",
705
+ has_image_data=True, has_group_column=True,
706
+ target_distribution="heavy_tailed",
707
+ ),
708
+ dataset_properties=props,
709
+ base_cv_score=0.35,
710
+ base_test_score=0.30,
711
+ expected_strategies=[
712
+ "set_cv:group_kfold",
713
+ "train_model:pretrained_backbone",
714
+ "augmentation:geometric",
715
+ "augmentation:color_transform",
716
+ "augmentation:camera_simulation",
717
+ "augmentation:clahe",
718
+ "feature_engineering:log_transform",
719
+ "tune_loss:gaussian_nll",
720
+ "tune_loss:multi_task",
721
+ "regularize:freeze_backbone",
722
+ "regularize:ema",
723
+ "ensemble:multi_seed_averaging",
724
+ "ensemble:swa",
725
+ "postprocess:tta",
726
+ "postprocess:per_group_calibration",
727
+ ],
728
+ ghost_scores=[0.90, 0.87, 0.84, 0.81, 0.77, 0.73, 0.68, 0.62, 0.55],
729
+ hints=[
730
+ "Group k-fold by camera_model — camera bias leaks between folds.",
731
+ "Pretrained ImageNet backbones are essential with only 25k images.",
732
+ "Freeze backbone first, train head, then fine-tune.",
733
+ "Geometric + color augmentations are a must for image tasks.",
734
+ "Camera simulation augmentation handles focal length variance.",
735
+ "CLAHE normalisation helps with exposure variation.",
736
+ "Heavy-tailed target → log transform + Gaussian NLL loss.",
737
+ "Multi-task: add classification head for quality bins alongside regression.",
738
+ "EMA and SWA stabilise training; multi-seed averaging adds robustness.",
739
+ "TTA with brightness + crops gives 1-2% boost at inference.",
740
+ ],
741
+ score_modifiers={
742
+ "set_cv:kfold": {"cv": 0.06, "test": 0.00},
743
+ "set_cv:group_kfold": {"cv": 0.04, "test": 0.08},
744
+ "train_model:pretrained_backbone": {"cv": 0.22, "test": 0.20},
745
+ "train_model:neural_network": {"cv": 0.14, "test": 0.11},
746
+ "train_model:xgboost": {"cv": 0.04, "test": -0.02},
747
+ "train_model:linear": {"cv": 0.02, "test": 0.02},
748
+ "augmentation:geometric": {"cv": 0.04, "test": 0.05},
749
+ "augmentation:color_transform": {"cv": 0.03, "test": 0.04},
750
+ "augmentation:clahe": {"cv": 0.03, "test": 0.03},
751
+ "augmentation:camera_simulation": {"cv": 0.03, "test": 0.04},
752
+ "augmentation:gaussian_noise": {"cv": 0.02, "test": 0.02},
753
+ "augmentation:image_rectification": {"cv": 0.02, "test": 0.03},
754
+ "augmentation:multi_view_processing": {"cv": 0.03, "test": 0.03},
755
+ "feature_engineering:log_transform": {"cv": 0.03, "test": 0.04},
756
+ "feature_engineering:normalize": {"cv": 0.02, "test": 0.02},
757
+ "tune_loss:gaussian_nll": {"cv": 0.04, "test": 0.05},
758
+ "tune_loss:multi_task": {"cv": 0.03, "test": 0.04},
759
+ "tune_loss:interval_regression": {"cv": 0.02, "test": 0.03},
760
+ "tune_loss:quantile_regression": {"cv": 0.02, "test": 0.03},
761
+ "regularize:freeze_backbone": {"cv": 0.03, "test": 0.05},
762
+ "regularize:ema": {"cv": 0.02, "test": 0.03},
763
+ "regularize:dropout": {"cv": 0.01, "test": 0.02},
764
+ "ensemble:multi_seed_averaging": {"cv": 0.03, "test": 0.04},
765
+ "ensemble:swa": {"cv": 0.03, "test": 0.04},
766
+ "ensemble:weighted_average": {"cv": 0.03, "test": 0.03},
767
+ "postprocess:tta": {"cv": 0.03, "test": 0.04},
768
+ "postprocess:per_group_calibration": {"cv": 0.02, "test": 0.04},
769
+ "postprocess:prediction_shrinkage": {"cv": 0.02, "test": 0.03},
770
+ "pseudo_label:1": {"cv": 0.02, "test": 0.01},
771
+ "inspect_top_solution": {"cv": 0.0, "test": 0.0},
772
+ },
773
+ overfitting_risk={
774
+ "set_cv:kfold": 0.06,
775
+ "train_model:xgboost": 0.06,
776
+ },
777
+ strategy_combos=[
778
+ StrategyComboDef(
779
+ name="vision_pipeline",
780
+ required=frozenset({
781
+ "train_model:pretrained_backbone", "regularize:freeze_backbone",
782
+ "augmentation:geometric", "augmentation:color_transform",
783
+ }),
784
+ cv_bonus=0.03, test_bonus=0.05,
785
+ ),
786
+ StrategyComboDef(
787
+ name="heavy_tail_handling",
788
+ required=frozenset({
789
+ "feature_engineering:log_transform",
790
+ "tune_loss:gaussian_nll",
791
+ "postprocess:prediction_shrinkage",
792
+ }),
793
+ cv_bonus=0.02, test_bonus=0.04,
794
+ ),
795
+ StrategyComboDef(
796
+ name="inference_boost",
797
+ required=frozenset({
798
+ "postprocess:tta", "ensemble:multi_seed_averaging", "ensemble:swa",
799
+ }),
800
+ cv_bonus=0.02, test_bonus=0.04,
801
+ ),
802
+ StrategyComboDef(
803
+ name="camera_robustness",
804
+ required=frozenset({
805
+ "augmentation:camera_simulation", "augmentation:clahe",
806
+ "postprocess:per_group_calibration",
807
+ }),
808
+ cv_bonus=0.02, test_bonus=0.04,
809
+ ),
810
+ ],
811
+ failure_modes=failure_modes,
812
+ context_relevance=context_relevance,
813
+ max_steps=30,
814
+ )
815
+
816
+
817
+ # =========================================================================
818
+ # Task 5 – Trajectory: Multi-agent spatial-temporal prediction
819
+ # =========================================================================
820
+
821
+ def _trajectory_task() -> TaskDefinition:
822
+ columns = [
823
+ ColumnInfo(name="scene_id", dtype="int64", missing_pct=0.0, unique_count=5000),
824
+ ColumnInfo(name="frame_id", dtype="int64", missing_pct=0.0, unique_count=50),
825
+ ColumnInfo(name="agent_id", dtype="int64", missing_pct=0.0, unique_count=20),
826
+ ColumnInfo(name="x", dtype="float64", missing_pct=0.0, unique_count=100000),
827
+ ColumnInfo(name="y", dtype="float64", missing_pct=0.0, unique_count=100000),
828
+ ColumnInfo(name="vx", dtype="float64", missing_pct=0.0, unique_count=50000),
829
+ ColumnInfo(name="vy", dtype="float64", missing_pct=0.0, unique_count=50000),
830
+ ColumnInfo(name="heading", dtype="float64", missing_pct=0.0, unique_count=3600),
831
+ ColumnInfo(name="agent_type", dtype="object", missing_pct=0.0, unique_count=4),
832
+ ColumnInfo(name="future_x", dtype="float64", missing_pct=0.0, unique_count=100000),
833
+ ColumnInfo(name="future_y", dtype="float64", missing_pct=0.0, unique_count=100000),
834
+ ]
835
+
836
+ props = DatasetProperties(
837
+ has_time_column=True, has_group_column=True,
838
+ has_spatial_data=True, needs_physics=True,
839
+ )
840
+
841
+ failure_modes = [
842
+ FailureMode(
843
+ name="tree_model_on_trajectories",
844
+ trigger_tag="train_model:xgboost",
845
+ condition_field="has_spatial_data", condition_value=True,
846
+ cv_effect=0.04, test_effect=-0.03,
847
+ message="Tree models cannot capture spatial-temporal dynamics — use neural approaches.",
848
+ ),
849
+ FailureMode(
850
+ name="raw_heading_without_sincos",
851
+ trigger_tag="submit",
852
+ condition_field="has_spatial_data", condition_value=True,
853
+ cv_effect=0.0, test_effect=-0.03,
854
+ message="Submitting without sin/cos encoding — raw heading angles wrap around and hurt predictions.",
855
+ ),
856
+ ]
857
+
858
+ context_relevance = {
859
+ "set_cv:group_kfold": 1.0,
860
+ "feature_engineering:sin_cos_encoding": 1.0,
861
+ "feature_engineering:relative_coordinates": 1.0,
862
+ "feature_engineering:distance_features": 1.0,
863
+ "train_model:transformer_encoder": 1.0,
864
+ "train_model:temporal_cnn": 0.9,
865
+ "augmentation:rotation_invariant": 1.0,
866
+ "augmentation:symmetry_augmentation": 0.9,
867
+ "augmentation:temporal_augmentation": 0.9,
868
+ "tune_loss:gaussian_nll": 1.0,
869
+ "tune_loss:auxiliary_physics_loss": 1.0,
870
+ "regularize:ema": 0.9,
871
+ "ensemble:multi_seed_averaging": 0.8,
872
+ "postprocess:physics_constraints": 1.0,
873
+ "clean_data:remove_corrupted": 0.8,
874
+ "clean_data:remove_outliers": 0.7,
875
+ "set_cv:kfold": -0.8,
876
+ # Irrelevant
877
+ "train_model:xgboost": -0.6,
878
+ "train_model:linear": -0.9,
879
+ "augmentation:color_transform": -1.0,
880
+ "augmentation:clahe": -1.0,
881
+ "augmentation:camera_simulation": -0.8,
882
+ "feature_engineering:tfidf_features": -1.0,
883
+ "feature_engineering:target_encoding": -0.7,
884
+ }
885
+
886
+ return TaskDefinition(
887
+ task_id="trajectory_pred",
888
+ title="Multi-Agent Trajectory Prediction",
889
+ difficulty="hard",
890
+ description=(
891
+ "Predict future (x,y) positions of multiple interacting agents in "
892
+ "driving scenes. Requires spatial-temporal encoding, rotation-invariant "
893
+ "processing, multi-agent interaction modeling, and physics-aware predictions."
894
+ ),
895
+ dataset_metadata=DatasetMetadata(
896
+ num_rows=500000, num_features=10, columns=columns,
897
+ target_column="future_x,future_y", task_type="regression",
898
+ has_time_column=True, has_group_column=True,
899
+ has_spatial_data=True,
900
+ ),
901
+ dataset_properties=props,
902
+ base_cv_score=0.32,
903
+ base_test_score=0.28,
904
+ expected_strategies=[
905
+ "set_cv:group_kfold",
906
+ "feature_engineering:sin_cos_encoding",
907
+ "feature_engineering:relative_coordinates",
908
+ "feature_engineering:distance_features",
909
+ "clean_data:remove_corrupted",
910
+ "clean_data:remove_outliers",
911
+ "train_model:transformer_encoder",
912
+ "train_model:temporal_cnn",
913
+ "augmentation:rotation_invariant",
914
+ "augmentation:symmetry_augmentation",
915
+ "augmentation:temporal_augmentation",
916
+ "tune_loss:gaussian_nll",
917
+ "tune_loss:auxiliary_physics_loss",
918
+ "regularize:ema",
919
+ "ensemble:multi_seed_averaging",
920
+ "postprocess:physics_constraints",
921
+ ],
922
+ ghost_scores=[0.89, 0.86, 0.83, 0.80, 0.76, 0.71, 0.66, 0.60, 0.52],
923
+ hints=[
924
+ "Group k-fold by scene_id — prevents leaking scene-specific patterns.",
925
+ "Encode heading with sin/cos — raw angles wrap around and mislead models.",
926
+ "Use relative coordinates instead of absolute positions.",
927
+ "Distance features to nearby agents help interaction modeling.",
928
+ "Remove scenes with abnormal sequence lengths.",
929
+ "Transformer encoders capture multi-agent interactions best.",
930
+ "Temporal CNN with gradual pooling works well for positional time series.",
931
+ "Rotation-invariant augmentation: rotate scene, preserve relative positions.",
932
+ "Gaussian NLL lets the model express uncertainty per prediction.",
933
+ "Auxiliary physics loss: penalise unrealistic acceleration/jerk.",
934
+ "Physics post-processing: clamp velocity, smooth jerk, enforce kinematic limits.",
935
+ ],
936
+ score_modifiers={
937
+ "set_cv:kfold": {"cv": 0.06, "test": -0.02},
938
+ "set_cv:group_kfold": {"cv": 0.04, "test": 0.08},
939
+ "feature_engineering:sin_cos_encoding": {"cv": 0.04, "test": 0.05},
940
+ "feature_engineering:relative_coordinates": {"cv": 0.05, "test": 0.06},
941
+ "feature_engineering:distance_features": {"cv": 0.04, "test": 0.05},
942
+ "feature_engineering:normalize": {"cv": 0.02, "test": 0.02},
943
+ "feature_engineering:spatial_encoding": {"cv": 0.03, "test": 0.03},
944
+ "clean_data:remove_corrupted": {"cv": 0.02, "test": 0.03},
945
+ "clean_data:remove_outliers": {"cv": 0.02, "test": 0.03},
946
+ "train_model:transformer_encoder": {"cv": 0.18, "test": 0.17},
947
+ "train_model:temporal_cnn": {"cv": 0.16, "test": 0.15},
948
+ "train_model:neural_network": {"cv": 0.12, "test": 0.10},
949
+ "train_model:xgboost": {"cv": 0.04, "test": -0.03},
950
+ "train_model:linear": {"cv": 0.02, "test": 0.02},
951
+ "augmentation:rotation_invariant": {"cv": 0.04, "test": 0.05},
952
+ "augmentation:symmetry_augmentation": {"cv": 0.03, "test": 0.04},
953
+ "augmentation:temporal_augmentation": {"cv": 0.03, "test": 0.04},
954
+ "tune_loss:gaussian_nll": {"cv": 0.04, "test": 0.05},
955
+ "tune_loss:auxiliary_physics_loss": {"cv": 0.03, "test": 0.05},
956
+ "tune_loss:multi_task": {"cv": 0.02, "test": 0.03},
957
+ "regularize:ema": {"cv": 0.03, "test": 0.04},
958
+ "regularize:dropout": {"cv": 0.01, "test": 0.02},
959
+ "ensemble:multi_seed_averaging": {"cv": 0.03, "test": 0.04},
960
+ "ensemble:swa": {"cv": 0.02, "test": 0.03},
961
+ "ensemble:heterogeneous": {"cv": 0.04, "test": 0.04},
962
+ "postprocess:physics_constraints": {"cv": 0.03, "test": 0.05},
963
+ "postprocess:tta": {"cv": 0.02, "test": 0.02},
964
+ "pseudo_label:1": {"cv": 0.02, "test": 0.01},
965
+ "inspect_top_solution": {"cv": 0.0, "test": 0.0},
966
+ },
967
+ overfitting_risk={
968
+ "set_cv:kfold": 0.08,
969
+ "train_model:xgboost": 0.07,
970
+ },
971
+ strategy_combos=[
972
+ StrategyComboDef(
973
+ name="spatial_encoding_suite",
974
+ required=frozenset({
975
+ "feature_engineering:sin_cos_encoding",
976
+ "feature_engineering:relative_coordinates",
977
+ "feature_engineering:distance_features",
978
+ }),
979
+ cv_bonus=0.03, test_bonus=0.05,
980
+ ),
981
+ StrategyComboDef(
982
+ name="geometric_augmentation_suite",
983
+ required=frozenset({
984
+ "augmentation:rotation_invariant",
985
+ "augmentation:symmetry_augmentation",
986
+ "augmentation:temporal_augmentation",
987
+ }),
988
+ cv_bonus=0.03, test_bonus=0.05,
989
+ ),
990
+ StrategyComboDef(
991
+ name="physics_aware_training",
992
+ required=frozenset({
993
+ "tune_loss:gaussian_nll",
994
+ "tune_loss:auxiliary_physics_loss",
995
+ "postprocess:physics_constraints",
996
+ }),
997
+ cv_bonus=0.03, test_bonus=0.06,
998
+ ),
999
+ StrategyComboDef(
1000
+ name="stable_training",
1001
+ required=frozenset({
1002
+ "regularize:ema", "ensemble:multi_seed_averaging",
1003
+ }),
1004
+ cv_bonus=0.02, test_bonus=0.03,
1005
+ ),
1006
+ ],
1007
+ failure_modes=failure_modes,
1008
+ context_relevance=context_relevance,
1009
+ max_steps=30,
1010
+ )
1011
+
1012
+
1013
+ # =========================================================================
1014
+ # Registry
1015
+ # =========================================================================
1016
+
1017
+ TASK_REGISTRY: dict[str, TaskDefinition] = {
1018
+ "easy_churn": _easy_task(),
1019
+ "medium_fraud": _medium_task(),
1020
+ "hard_leaky_noisy": _hard_task(),
1021
+ "image_quality": _image_task(),
1022
+ "trajectory_pred": _trajectory_task(),
1023
+ }
1024
+
1025
+
1026
+ def get_task(task_id: str) -> TaskDefinition:
1027
+ if task_id not in TASK_REGISTRY:
1028
+ raise ValueError(
1029
+ f"Unknown task '{task_id}'. Available: {list(TASK_REGISTRY.keys())}"
1030
+ )
1031
+ return TASK_REGISTRY[task_id]
openenv.yaml ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: KaggleSimEnv
2
+ version: "3.0.0"
3
+ description: >
4
+ Production-grade OpenEnv RL environment simulating Kaggle competitions.
5
+ Features hierarchical action categories, causal dataset properties,
6
+ failure-mode traps, contextual strategy scoring, and 50+ advanced strategies.
7
+
8
+ author: OpenEnv Hackathon Team
9
+ license: MIT
10
+
11
+ environment:
12
+ module: kaggle_sim_env.environment
13
+ class: KaggleSimEnv
14
+
15
+ api:
16
+ framework: fastapi
17
+ module: api.server
18
+ app: app
19
+ port: 7860
20
+
21
+ action_space:
22
+ types: 14
23
+ techniques: 60+
24
+ hierarchical: true
25
+ format: '{"action_type": "...", "parameters": {"category": "...", "<key>": "..."}}'
26
+
27
+ tasks:
28
+ - id: easy_churn
29
+ difficulty: easy
30
+ failure_modes: 3
31
+ combos: 2
32
+ - id: medium_fraud
33
+ difficulty: medium
34
+ failure_modes: 3
35
+ combos: 3
36
+ - id: hard_leaky_noisy
37
+ difficulty: hard
38
+ failure_modes: 4
39
+ combos: 4
40
+ - id: image_quality
41
+ difficulty: hard
42
+ failure_modes: 2
43
+ combos: 4
44
+ - id: trajectory_pred
45
+ difficulty: hard
46
+ failure_modes: 2
47
+ combos: 4
48
+
49
+ grader:
50
+ module: kaggle_sim_env.grader
51
+ class: Grader
52
+ score_range: [0.0, 1.0]
53
+ weights:
54
+ performance: 0.40
55
+ strategy: 0.25
56
+ combo: 0.20
57
+ trap_avoidance: 0.15
58
+
59
+ reward:
60
+ components:
61
+ - cv_improvement
62
+ - strategy_bonus
63
+ - context_bonus
64
+ - combo_bonus
65
+ - redundancy_penalty
66
+ - irrelevant_penalty
67
+ - trap_penalty
68
+ - overfitting_penalty
69
+ - submission_bonus
70
+
71
+ baseline:
72
+ script: baseline/run_baseline.py
73
+ approach: structured_phases_with_hints
74
+ requires:
75
+ - OPENAI_API_KEY
76
+
77
+ docker:
78
+ port: 7860
79
+ dockerfile: Dockerfile
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi>=0.115.0
2
+ uvicorn[standard]>=0.32.0
3
+ pydantic>=2.9.0
4
+ openai>=1.55.0
5
+ requests>=2.32.0