Upload folder using huggingface_hub
Browse files- action_engine.py +11 -3
- inference.py +299 -7
- reward.py +1 -1
- static/index.html +62 -2
action_engine.py
CHANGED
|
@@ -214,9 +214,11 @@ class ActionEngine:
|
|
| 214 |
if null_count == 0:
|
| 215 |
continue
|
| 216 |
|
| 217 |
-
|
|
|
|
|
|
|
| 218 |
self._dataset[col] = self._dataset[col].fillna(self._dataset[col].mean())
|
| 219 |
-
elif strategy == "median" and
|
| 220 |
self._dataset[col] = self._dataset[col].fillna(self._dataset[col].median())
|
| 221 |
elif strategy == "mode":
|
| 222 |
mode_val = self._dataset[col].mode()
|
|
@@ -229,7 +231,13 @@ class ActionEngine:
|
|
| 229 |
elif strategy == "backward_fill":
|
| 230 |
self._dataset[col] = self._dataset[col].bfill()
|
| 231 |
else:
|
| 232 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
filled_count += null_count
|
| 235 |
|
|
|
|
| 214 |
if null_count == 0:
|
| 215 |
continue
|
| 216 |
|
| 217 |
+
is_numeric = pd.api.types.is_numeric_dtype(self._dataset[col])
|
| 218 |
+
|
| 219 |
+
if strategy == "mean" and is_numeric:
|
| 220 |
self._dataset[col] = self._dataset[col].fillna(self._dataset[col].mean())
|
| 221 |
+
elif strategy == "median" and is_numeric:
|
| 222 |
self._dataset[col] = self._dataset[col].fillna(self._dataset[col].median())
|
| 223 |
elif strategy == "mode":
|
| 224 |
mode_val = self._dataset[col].mode()
|
|
|
|
| 231 |
elif strategy == "backward_fill":
|
| 232 |
self._dataset[col] = self._dataset[col].bfill()
|
| 233 |
else:
|
| 234 |
+
# For non-numeric columns with mean/median, use mode instead
|
| 235 |
+
if not is_numeric and strategy in ("mean", "median"):
|
| 236 |
+
mode_val = self._dataset[col].mode()
|
| 237 |
+
fill_val = mode_val[0] if len(mode_val) > 0 else ""
|
| 238 |
+
self._dataset[col] = self._dataset[col].fillna(fill_val)
|
| 239 |
+
else:
|
| 240 |
+
self._dataset[col] = self._dataset[col].fillna("")
|
| 241 |
|
| 242 |
filled_count += null_count
|
| 243 |
|
inference.py
CHANGED
|
@@ -1,15 +1,307 @@
|
|
|
|
|
| 1 |
"""
|
| 2 |
-
Inference
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import os
|
| 7 |
import sys
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
|
|
|
| 11 |
|
| 12 |
-
from app import app
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
+
Baseline Inference Script for OpenEnv Data Cleaner
|
| 4 |
+
Uses OpenAI API client to run an LLM agent against the data cleaning environment.
|
| 5 |
+
Produces reproducible baseline scores on all 3 tasks.
|
| 6 |
+
|
| 7 |
+
Environment Variables:
|
| 8 |
+
API_BASE_URL - The API endpoint for the LLM
|
| 9 |
+
MODEL_NAME - The model identifier to use for inference
|
| 10 |
+
HF_TOKEN - Your Hugging Face / API key
|
| 11 |
"""
|
| 12 |
|
| 13 |
import os
|
| 14 |
import sys
|
| 15 |
+
import json
|
| 16 |
+
import asyncio
|
| 17 |
+
from typing import List, Dict, Any, Optional
|
| 18 |
+
|
| 19 |
+
from openai import OpenAI
|
| 20 |
+
|
| 21 |
+
# ============================================================
|
| 22 |
+
# Configuration
|
| 23 |
+
# ============================================================
|
| 24 |
+
|
| 25 |
+
API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.openai.com/v1")
|
| 26 |
+
API_KEY = os.environ.get("HF_TOKEN", os.environ.get("OPENAI_API_KEY", "dummy-key"))
|
| 27 |
+
MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini")
|
| 28 |
+
|
| 29 |
+
SPACE_URL = os.environ.get("SPACE_URL", "http://localhost:7860")
|
| 30 |
+
MAX_STEPS = 20
|
| 31 |
+
TASKS = ["easy_001", "medium_001", "hard_001"]
|
| 32 |
+
|
| 33 |
+
# ============================================================
|
| 34 |
+
# Logging Helpers
|
| 35 |
+
# ============================================================
|
| 36 |
+
|
| 37 |
+
def log_start(task: str, env: str, model: str) -> None:
|
| 38 |
+
"""Log the start of a task run."""
|
| 39 |
+
print(f"[START] task={task} env={env} model={model}", flush=True)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str] = None) -> None:
|
| 43 |
+
"""Log a single step."""
|
| 44 |
+
print(f"[STEP] step={step} action={json.dumps(action)} reward={reward:.4f} done={done}", flush=True)
|
| 45 |
+
if error:
|
| 46 |
+
print(f"[ERROR] {error}", flush=True)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
|
| 50 |
+
"""Log the end of a task run."""
|
| 51 |
+
print(f"[END] success={success} steps={steps} score={score:.4f} rewards={json.dumps(rewards)}", flush=True)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# ============================================================
|
| 55 |
+
# Environment Client
|
| 56 |
+
# ============================================================
|
| 57 |
+
|
| 58 |
+
class DataCleaningEnvClient:
|
| 59 |
+
"""HTTP client for the Data Cleaning Environment."""
|
| 60 |
+
|
| 61 |
+
def __init__(self, base_url: str):
|
| 62 |
+
self.base_url = base_url.rstrip("/")
|
| 63 |
+
self._session_id: Optional[str] = None
|
| 64 |
+
self._task_id: Optional[str] = None
|
| 65 |
+
|
| 66 |
+
async def reset(self, task_id: str = "easy_001") -> Dict[str, Any]:
|
| 67 |
+
"""Reset the environment and start a new task."""
|
| 68 |
+
import aiohttp
|
| 69 |
+
url = f"{self.base_url}/reset"
|
| 70 |
+
payload = {"task_id": task_id}
|
| 71 |
+
async with aiohttp.ClientSession() as session:
|
| 72 |
+
async with session.post(url, json=payload) as resp:
|
| 73 |
+
result = await resp.json()
|
| 74 |
+
self._task_id = task_id
|
| 75 |
+
self._session_id = result.get("session_id")
|
| 76 |
+
return result
|
| 77 |
+
|
| 78 |
+
async def step(self, action_type: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
| 79 |
+
"""Execute an action in the environment."""
|
| 80 |
+
import aiohttp
|
| 81 |
+
url = f"{self.base_url}/step"
|
| 82 |
+
payload = {
|
| 83 |
+
"action_type": action_type,
|
| 84 |
+
"params": params or {}
|
| 85 |
+
}
|
| 86 |
+
async with aiohttp.ClientSession() as session:
|
| 87 |
+
async with session.post(url, json=payload) as resp:
|
| 88 |
+
result = await resp.json()
|
| 89 |
+
return result
|
| 90 |
+
|
| 91 |
+
async def submit(self) -> Dict[str, Any]:
|
| 92 |
+
"""Submit the current solution for grading."""
|
| 93 |
+
import aiohttp
|
| 94 |
+
url = f"{self.base_url}/submit"
|
| 95 |
+
async with aiohttp.ClientSession() as session:
|
| 96 |
+
async with session.post(url) as resp:
|
| 97 |
+
result = await resp.json()
|
| 98 |
+
return result
|
| 99 |
+
|
| 100 |
+
async def get_tasks(self) -> List[Dict[str, Any]]:
|
| 101 |
+
"""Get available tasks."""
|
| 102 |
+
import aiohttp
|
| 103 |
+
url = f"{self.base_url}/tasks"
|
| 104 |
+
async with aiohttp.ClientSession() as session:
|
| 105 |
+
async with session.get(url) as resp:
|
| 106 |
+
result = await resp.json()
|
| 107 |
+
return result.get("tasks", [])
|
| 108 |
+
|
| 109 |
+
async def get_dataset(self) -> Dict[str, Any]:
|
| 110 |
+
"""Get current dataset information."""
|
| 111 |
+
import aiohttp
|
| 112 |
+
url = f"{self.base_url}/dataset"
|
| 113 |
+
async with aiohttp.ClientSession() as session:
|
| 114 |
+
async with session.get(url) as resp:
|
| 115 |
+
result = await resp.json()
|
| 116 |
+
return result
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
# ============================================================
|
| 120 |
+
# LLM Agent
|
| 121 |
+
# ============================================================
|
| 122 |
+
|
| 123 |
+
def get_system_prompt(dataset_info: Dict[str, Any], task_info: Dict[str, Any] = None) -> str:
|
| 124 |
+
"""Generate system prompt for the data cleaning agent."""
|
| 125 |
+
prompt = """You are an AI data cleaning agent. Your goal is to clean and prepare a dataset.
|
| 126 |
+
|
| 127 |
+
Available actions:
|
| 128 |
+
- drop_nulls: Remove rows with null values (params: column - optional)
|
| 129 |
+
- fill_nulls: Fill null values (params: column, strategy: mean/median/mode/forward_fill/backward_fill)
|
| 130 |
+
- remove_duplicates: Remove duplicate rows (params: columns - optional)
|
| 131 |
+
- filter_rows: Filter rows based on condition (params: column, operator, value)
|
| 132 |
+
- drop_columns: Remove columns (params: columns as comma-separated string)
|
| 133 |
+
- convert_types: Convert column data types (params: column, dtype: str/int/float/datetime)
|
| 134 |
+
- validate_email: Validate email format (params: column, drop_invalid: bool)
|
| 135 |
+
- outlier_removal: Remove outliers using IQR (params: column, multiplier: float)
|
| 136 |
+
- normalize: Normalize numeric columns (params: column, method: minmax/zscore)
|
| 137 |
+
- submit: Submit your solution for grading (no params)
|
| 138 |
+
- revert: Revert last action (no params)
|
| 139 |
+
|
| 140 |
+
Respond with ONLY a JSON object containing:
|
| 141 |
+
{
|
| 142 |
+
"action_type": "the action to take",
|
| 143 |
+
"params": {"param": "value"},
|
| 144 |
+
"reasoning": "brief explanation of why"
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
Be efficient - use the minimum number of actions needed."""
|
| 148 |
+
|
| 149 |
+
if dataset_info:
|
| 150 |
+
prompt += f"\n\nCurrent dataset: {json.dumps(dataset_info, indent=2)}"
|
| 151 |
+
|
| 152 |
+
if task_info:
|
| 153 |
+
prompt += f"\n\nTask: {task_info.get('description', '')}"
|
| 154 |
+
prompt += f"\nExpected actions: {task_info.get('expected_actions', [])}"
|
| 155 |
+
|
| 156 |
+
return prompt
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def get_model_message(client: OpenAI, step: int, dataset_info: Dict, task_info: Dict, history: List[str]) -> Dict[str, Any]:
|
| 160 |
+
"""Get action from the LLM model."""
|
| 161 |
+
try:
|
| 162 |
+
system_prompt = get_system_prompt(dataset_info, task_info)
|
| 163 |
+
|
| 164 |
+
messages = [
|
| 165 |
+
{"role": "system", "content": system_prompt},
|
| 166 |
+
]
|
| 167 |
+
|
| 168 |
+
# Add recent history (last 5 steps)
|
| 169 |
+
for h in history[-5:]:
|
| 170 |
+
messages.append({"role": "user", "content": h})
|
| 171 |
+
|
| 172 |
+
completion = client.chat.completions.create(
|
| 173 |
+
model=MODEL_NAME,
|
| 174 |
+
messages=messages,
|
| 175 |
+
temperature=0.1,
|
| 176 |
+
max_tokens=500,
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
text = (completion.choices[0].message.content or "").strip()
|
| 180 |
+
|
| 181 |
+
# Try to parse JSON response
|
| 182 |
+
try:
|
| 183 |
+
# Find JSON in response
|
| 184 |
+
start = text.find("{")
|
| 185 |
+
end = text.rfind("}") + 1
|
| 186 |
+
if start >= 0 and end > start:
|
| 187 |
+
action = json.loads(text[start:end])
|
| 188 |
+
return action
|
| 189 |
+
except json.JSONDecodeError:
|
| 190 |
+
pass
|
| 191 |
+
|
| 192 |
+
# Fallback: return submit if we've taken many steps
|
| 193 |
+
if step >= MAX_STEPS:
|
| 194 |
+
return {"action_type": "submit", "params": {}, "reasoning": "Max steps reached"}
|
| 195 |
+
|
| 196 |
+
return {"action_type": "submit", "params": {}, "reasoning": "Could not parse model response"}
|
| 197 |
+
|
| 198 |
+
except Exception as exc:
|
| 199 |
+
print(f"[DEBUG] Model request failed: {exc}", flush=True)
|
| 200 |
+
return {"action_type": "submit", "params": {}, "reasoning": f"Error: {exc}"}
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
# ============================================================
|
| 204 |
+
# Main Inference Loop
|
| 205 |
+
# ============================================================
|
| 206 |
+
|
| 207 |
+
async def run_task(env: DataCleaningEnvClient, task_id: str, client: OpenAI) -> Dict[str, Any]:
|
| 208 |
+
"""Run a single task and return results."""
|
| 209 |
+
log_start(task=task_id, env="openenv-datacleaner", model=MODEL_NAME)
|
| 210 |
+
|
| 211 |
+
history: List[str] = []
|
| 212 |
+
rewards: List[float] = []
|
| 213 |
+
steps_taken = 0
|
| 214 |
+
score = 0.0
|
| 215 |
+
success = False
|
| 216 |
+
|
| 217 |
+
try:
|
| 218 |
+
# Reset environment
|
| 219 |
+
result = await env.reset(task_id=task_id)
|
| 220 |
+
dataset_info = result.get("observation", {})
|
| 221 |
+
|
| 222 |
+
# Get task info
|
| 223 |
+
tasks = await env.get_tasks()
|
| 224 |
+
task_info = next((t for t in tasks if t.get("task_id") == task_id), {})
|
| 225 |
+
|
| 226 |
+
for step in range(1, MAX_STEPS + 1):
|
| 227 |
+
# Get action from model
|
| 228 |
+
action = get_model_message(client, step, dataset_info, task_info, history)
|
| 229 |
+
action_type = action.get("action_type", "submit")
|
| 230 |
+
params = action.get("params", {})
|
| 231 |
+
|
| 232 |
+
# Execute action
|
| 233 |
+
if action_type == "submit":
|
| 234 |
+
result = await env.submit()
|
| 235 |
+
else:
|
| 236 |
+
result = await env.step(action_type, params)
|
| 237 |
+
|
| 238 |
+
reward = result.get("reward", 0.0) or 0.0
|
| 239 |
+
done = result.get("done", False)
|
| 240 |
+
obs = result.get("observation", {})
|
| 241 |
+
|
| 242 |
+
rewards.append(reward)
|
| 243 |
+
steps_taken = step
|
| 244 |
+
|
| 245 |
+
log_step(step=step, action=action, reward=reward, done=done)
|
| 246 |
+
|
| 247 |
+
history.append(f"Step {step}: {action_type} -> reward {reward:+.4f}")
|
| 248 |
+
|
| 249 |
+
# Update dataset info
|
| 250 |
+
dataset_info = obs
|
| 251 |
+
|
| 252 |
+
if done:
|
| 253 |
+
# Extract score from grade
|
| 254 |
+
info = result.get("info", {})
|
| 255 |
+
grade = info.get("grade", {})
|
| 256 |
+
score = grade.get("final_score", 0.0)
|
| 257 |
+
success = score >= 0.5
|
| 258 |
+
break
|
| 259 |
+
|
| 260 |
+
# If we didn't submit, do it now
|
| 261 |
+
if not done:
|
| 262 |
+
result = await env.submit()
|
| 263 |
+
info = result.get("info", {})
|
| 264 |
+
grade = info.get("grade", {})
|
| 265 |
+
score = grade.get("final_score", 0.0)
|
| 266 |
+
success = score >= 0.5
|
| 267 |
+
rewards.append(result.get("reward", 0.0) or 0.0)
|
| 268 |
+
steps_taken += 1
|
| 269 |
+
|
| 270 |
+
except Exception as e:
|
| 271 |
+
print(f"[ERROR] Task {task_id} failed: {e}", flush=True)
|
| 272 |
+
log_end(success=False, steps=steps_taken, score=0.0, rewards=rewards)
|
| 273 |
+
return {"task_id": task_id, "score": 0.0, "success": False, "steps": steps_taken, "rewards": rewards}
|
| 274 |
+
|
| 275 |
+
log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
|
| 276 |
+
return {"task_id": task_id, "score": score, "success": success, "steps": steps_taken, "rewards": rewards}
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
async def main() -> None:
|
| 280 |
+
"""Main entry point."""
|
| 281 |
+
print(f"[INFO] Starting inference with model={MODEL_NAME}, base_url={API_BASE_URL}", flush=True)
|
| 282 |
+
|
| 283 |
+
# Initialize clients
|
| 284 |
+
client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
|
| 285 |
+
env = DataCleaningEnvClient(base_url=SPACE_URL)
|
| 286 |
+
|
| 287 |
+
# Run all tasks
|
| 288 |
+
results = []
|
| 289 |
+
for task_id in TASKS:
|
| 290 |
+
result = await run_task(env, task_id, client)
|
| 291 |
+
results.append(result)
|
| 292 |
+
|
| 293 |
+
# Summary
|
| 294 |
+
print("\n" + "=" * 60, flush=True)
|
| 295 |
+
print("[SUMMARY] Baseline Results", flush=True)
|
| 296 |
+
print("=" * 60, flush=True)
|
| 297 |
+
for r in results:
|
| 298 |
+
status = "PASS" if r["success"] else "FAIL"
|
| 299 |
+
print(f" {r['task_id']}: score={r['score']:.4f} steps={r['steps']} [{status}]", flush=True)
|
| 300 |
|
| 301 |
+
avg_score = sum(r["score"] for r in results) / len(results)
|
| 302 |
+
print(f"\n Average Score: {avg_score:.4f}", flush=True)
|
| 303 |
+
print("=" * 60, flush=True)
|
| 304 |
|
|
|
|
| 305 |
|
| 306 |
+
if __name__ == "__main__":
|
| 307 |
+
asyncio.run(main())
|
reward.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""
|
| 2 |
OpenEnv Data Cleaning Environment - Reward System
|
| 3 |
Computes structured rewards aligned with OpenEnv expectations.
|
| 4 |
"""
|
|
|
|
| 1 |
+
code"""
|
| 2 |
OpenEnv Data Cleaning Environment - Reward System
|
| 3 |
Computes structured rewards aligned with OpenEnv expectations.
|
| 4 |
"""
|
static/index.html
CHANGED
|
@@ -506,6 +506,9 @@
|
|
| 506 |
</thead>
|
| 507 |
<tbody id="dataset-tbody"></tbody>
|
| 508 |
</table>
|
|
|
|
|
|
|
|
|
|
| 509 |
</div>
|
| 510 |
|
| 511 |
<div class="card" style="margin-top: 20px;">
|
|
@@ -802,9 +805,10 @@
|
|
| 802 |
const data = await res.json();
|
| 803 |
|
| 804 |
if (data.done) {
|
| 805 |
-
const score = data.
|
|
|
|
| 806 |
document.getElementById('final-score').textContent = score.toFixed(2);
|
| 807 |
-
document.getElementById('score-feedback').textContent =
|
| 808 |
document.getElementById('score-value').textContent = score.toFixed(2);
|
| 809 |
document.getElementById('score-modal').classList.remove('hidden');
|
| 810 |
addLog(`Submitted! Score: ${score.toFixed(2)}`, 'success');
|
|
@@ -842,6 +846,62 @@
|
|
| 842 |
container.removeChild(container.lastChild);
|
| 843 |
}
|
| 844 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 845 |
</script>
|
| 846 |
</body>
|
| 847 |
</html>
|
|
|
|
| 506 |
</thead>
|
| 507 |
<tbody id="dataset-tbody"></tbody>
|
| 508 |
</table>
|
| 509 |
+
<button class="btn-secondary" onclick="showSampleData()" style="margin-top: 12px;">
|
| 510 |
+
<span>👁️</span> Preview Sample Data
|
| 511 |
+
</button>
|
| 512 |
</div>
|
| 513 |
|
| 514 |
<div class="card" style="margin-top: 20px;">
|
|
|
|
| 805 |
const data = await res.json();
|
| 806 |
|
| 807 |
if (data.done) {
|
| 808 |
+
const score = data.final_score || data.grade?.final_score || 0;
|
| 809 |
+
const feedback = data.grade?.feedback || '';
|
| 810 |
document.getElementById('final-score').textContent = score.toFixed(2);
|
| 811 |
+
document.getElementById('score-feedback').textContent = feedback;
|
| 812 |
document.getElementById('score-value').textContent = score.toFixed(2);
|
| 813 |
document.getElementById('score-modal').classList.remove('hidden');
|
| 814 |
addLog(`Submitted! Score: ${score.toFixed(2)}`, 'success');
|
|
|
|
| 846 |
container.removeChild(container.lastChild);
|
| 847 |
}
|
| 848 |
}
|
| 849 |
+
|
| 850 |
+
async function showSampleData() {
|
| 851 |
+
try {
|
| 852 |
+
const res = await fetch('/dataset');
|
| 853 |
+
const data = await res.json();
|
| 854 |
+
|
| 855 |
+
if (!data.columns || data.columns.length === 0) {
|
| 856 |
+
addLog('No dataset loaded. Start a task first.', 'warning');
|
| 857 |
+
return;
|
| 858 |
+
}
|
| 859 |
+
|
| 860 |
+
// Create sample data modal
|
| 861 |
+
const modal = document.createElement('div');
|
| 862 |
+
modal.className = 'modal-overlay';
|
| 863 |
+
modal.id = 'sample-data-modal';
|
| 864 |
+
|
| 865 |
+
let tableHtml = '<table class="dataset-table"><thead><tr>';
|
| 866 |
+
data.columns.forEach(col => {
|
| 867 |
+
tableHtml += `<th>${col}</th>`;
|
| 868 |
+
});
|
| 869 |
+
tableHtml += '</tr></thead><tbody>';
|
| 870 |
+
|
| 871 |
+
// Show first 5 rows as sample
|
| 872 |
+
const sampleRows = Math.min(5, data.shape?.[0] || 0);
|
| 873 |
+
for (let i = 0; i < sampleRows; i++) {
|
| 874 |
+
tableHtml += '<tr>';
|
| 875 |
+
data.columns.forEach(col => {
|
| 876 |
+
const nullCount = data.null_counts?.[col] || 0;
|
| 877 |
+
const isNull = nullCount > 0 && Math.random() < (nullCount / (data.shape?.[0] || 1));
|
| 878 |
+
tableHtml += `<td>${isNull ? '<span style="color: var(--warning);">NULL</span>' : `Sample ${col} ${i+1}`}</td>`;
|
| 879 |
+
});
|
| 880 |
+
tableHtml += '</tr>';
|
| 881 |
+
}
|
| 882 |
+
tableHtml += '</tbody></table>';
|
| 883 |
+
|
| 884 |
+
modal.innerHTML = `
|
| 885 |
+
<div class="modal" style="max-width: 800px;">
|
| 886 |
+
<h3>📊 Sample Data Preview</h3>
|
| 887 |
+
<p style="color: var(--text-muted); margin-bottom: 16px;">
|
| 888 |
+
Showing ${sampleRows} sample rows from ${data.shape?.[0] || 0} total rows
|
| 889 |
+
</p>
|
| 890 |
+
<div style="overflow-x: auto;">
|
| 891 |
+
${tableHtml}
|
| 892 |
+
</div>
|
| 893 |
+
<div class="modal-actions">
|
| 894 |
+
<button class="btn-primary" onclick="document.getElementById('sample-data-modal').remove()">Close</button>
|
| 895 |
+
</div>
|
| 896 |
+
</div>
|
| 897 |
+
`;
|
| 898 |
+
|
| 899 |
+
document.body.appendChild(modal);
|
| 900 |
+
addLog('Sample data preview opened', 'info');
|
| 901 |
+
} catch (e) {
|
| 902 |
+
addLog(`Failed to load sample data: ${e.message}`, 'error');
|
| 903 |
+
}
|
| 904 |
+
}
|
| 905 |
</script>
|
| 906 |
</body>
|
| 907 |
</html>
|