Spaces:
Sleeping
Sleeping
Commit ·
76b843b
0
Parent(s):
Clean repo without venv
Browse files- .gitattributes +35 -0
- .gitignore +4 -0
- Dockerfile +32 -0
- README.md +210 -0
- inference.py +156 -0
- openenv.yaml +59 -0
- pyproject.toml +19 -0
- requirements.txt +6 -0
- server/app.py +724 -0
- sql_debugger_env.py +613 -0
- uv.lock +323 -0
.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
venv311/
|
| 3 |
+
__pycache__/
|
| 4 |
+
*.pyc
|
Dockerfile
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
LABEL maintainer="NeuroHack"
|
| 4 |
+
LABEL description="SQL Debugger & Optimizer — OpenEnv"
|
| 5 |
+
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
RUN apt-get update && apt-get install -y --no-install-recommends curl \
|
| 9 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 10 |
+
|
| 11 |
+
# Copy dependency files
|
| 12 |
+
COPY requirements.txt .
|
| 13 |
+
COPY pyproject.toml .
|
| 14 |
+
COPY uv.lock .
|
| 15 |
+
|
| 16 |
+
# Install dependencies
|
| 17 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 18 |
+
|
| 19 |
+
# Copy project structure
|
| 20 |
+
COPY server/ ./server/
|
| 21 |
+
COPY sql_debugger_env.py .
|
| 22 |
+
COPY openenv.yaml .
|
| 23 |
+
COPY inference.py .
|
| 24 |
+
COPY README.md .
|
| 25 |
+
|
| 26 |
+
EXPOSE 7860
|
| 27 |
+
|
| 28 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 29 |
+
CMD curl -f http://localhost:7860/health || exit 1
|
| 30 |
+
|
| 31 |
+
# 🔥 IMPORTANT CHANGE
|
| 32 |
+
CMD ["uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SQL Debugger & Optimizer
|
| 3 |
+
emoji: 🛠️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_file: app.py
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# 🛠️ SQL Debugger & Optimizer — OpenEnv Environment
|
| 12 |
+
|
| 13 |
+
> 🚀 **NeuroHack — OpenEnv Submission**
|
| 14 |
+
> A real-world reinforcement learning environment where an AI agent debugs broken SQL queries using **deterministic SQLite execution** — no LLM-based scoring.
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## 📋 Table of Contents
|
| 19 |
+
|
| 20 |
+
- [Overview](#overview)
|
| 21 |
+
- [Why This Wins](#why-this-wins)
|
| 22 |
+
- [Live Demo](#live-demo)
|
| 23 |
+
- [Project Structure](#project-structure)
|
| 24 |
+
- [Getting Started](#getting-started)
|
| 25 |
+
- [Run Locally](#run-locally)
|
| 26 |
+
- [Docker](#docker)
|
| 27 |
+
- [API Example](#api-example)
|
| 28 |
+
- [Environment Details](#environment-details)
|
| 29 |
+
- [Tasks](#tasks)
|
| 30 |
+
- [Reward System](#reward-system)
|
| 31 |
+
- [Performance](#performance)
|
| 32 |
+
- [License](#license)
|
| 33 |
+
|
| 34 |
+
---
|
| 35 |
+
|
| 36 |
+
## Overview
|
| 37 |
+
|
| 38 |
+
The **SQL Debugger & Optimizer** is an OpenEnv-compatible reinforcement learning environment where an AI agent receives broken SQL queries and must output corrected versions.
|
| 39 |
+
|
| 40 |
+
The agent receives:
|
| 41 |
+
- 🔴 A **broken SQL query**
|
| 42 |
+
- 🗂️ The **database schema**
|
| 43 |
+
- 📝 A **natural language description** of the intended behavior
|
| 44 |
+
|
| 45 |
+
It must output a **corrected SQL query**, which is then:
|
| 46 |
+
1. Executed against a real SQLite database
|
| 47 |
+
2. Compared against the reference (correct) query output
|
| 48 |
+
3. Scored with a layered reward from `0.0 → 1.0`
|
| 49 |
+
|
| 50 |
+
---
|
| 51 |
+
|
| 52 |
+
## Why This Wins
|
| 53 |
+
|
| 54 |
+
Unlike traditional LLM-evaluated systems:
|
| 55 |
+
|
| 56 |
+
| Feature | Description |
|
| 57 |
+
|--------|-------------|
|
| 58 |
+
| ✅ **100% Deterministic Grading** | Real SQLite execution — no subjective LLM scoring |
|
| 59 |
+
| ✅ **Layered Reward System** | Syntax → Logic → Data → Optimization |
|
| 60 |
+
| ✅ **Real-World Bugs** | JOIN errors, N+1 queries, SQL injection vulnerabilities |
|
| 61 |
+
| ✅ **Engineering Relevance** | Mirrors production database debugging scenarios |
|
| 62 |
+
| ✅ **OpenEnv-Compatible API** | Drop-in `/reset` and `/step` endpoints |
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
|
| 66 |
+
## Live Demo
|
| 67 |
+
|
| 68 |
+
Open the interactive UI at:
|
| 69 |
+
|
| 70 |
+
```
|
| 71 |
+
http://localhost:7860/ui
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
With the UI you can:
|
| 75 |
+
- Fix broken SQL queries interactively
|
| 76 |
+
- View the corrected SQL output
|
| 77 |
+
- See reward scores in real-time
|
| 78 |
+
- Track agent performance with graphs
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
## Project Structure
|
| 83 |
+
|
| 84 |
+
```
|
| 85 |
+
sql-debugger-env/
|
| 86 |
+
├── app.py # FastAPI server — /reset, /step, /ui endpoints
|
| 87 |
+
├── sql_debugger_env.py # Core RL environment logic & SQLite execution
|
| 88 |
+
├── inference.py # Agent inference utilities
|
| 89 |
+
├── openenv.yaml # OpenEnv environment specification
|
| 90 |
+
├── requirements.txt # Python dependencies
|
| 91 |
+
├── Dockerfile # Container build configuration
|
| 92 |
+
└── README.md # This file
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## Getting Started
|
| 98 |
+
|
| 99 |
+
### Run Locally
|
| 100 |
+
|
| 101 |
+
```bash
|
| 102 |
+
pip install -r requirements.txt
|
| 103 |
+
python app.py
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
Then open: [http://localhost:7860/ui](http://localhost:7860/ui)
|
| 107 |
+
|
| 108 |
+
---
|
| 109 |
+
|
| 110 |
+
### Docker
|
| 111 |
+
|
| 112 |
+
```bash
|
| 113 |
+
# Build the image
|
| 114 |
+
docker build -t sql-debugger-env .
|
| 115 |
+
|
| 116 |
+
# Run the container
|
| 117 |
+
docker run -p 7860:7860 sql-debugger-env
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
### API Example
|
| 123 |
+
|
| 124 |
+
The environment exposes an OpenEnv-compatible REST API:
|
| 125 |
+
|
| 126 |
+
```python
|
| 127 |
+
import requests
|
| 128 |
+
|
| 129 |
+
# 1. Start a new episode
|
| 130 |
+
r = requests.post("http://localhost:7860/reset", json={"task": "medium"})
|
| 131 |
+
session_id = r.json()["session_id"]
|
| 132 |
+
obs = r.json()["observation"]
|
| 133 |
+
|
| 134 |
+
# 2. Submit a fix action
|
| 135 |
+
action = {
|
| 136 |
+
"challenge_id": obs["challenge"]["id"],
|
| 137 |
+
"fixed_sql": "SELECT users.id, orders.total FROM users JOIN orders ON users.id = orders.user_id",
|
| 138 |
+
"explanation": "Fixed JOIN logic — was using wrong foreign key",
|
| 139 |
+
"detected_issues": ["wrong_join"]
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
r = requests.post("http://localhost:7860/step", json={
|
| 143 |
+
"session_id": session_id,
|
| 144 |
+
"action": action
|
| 145 |
+
})
|
| 146 |
+
|
| 147 |
+
print(r.json()["reward"]) # e.g. 0.92
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
**Endpoints:**
|
| 151 |
+
|
| 152 |
+
| Method | Endpoint | Description |
|
| 153 |
+
|--------|----------|-------------|
|
| 154 |
+
| `POST` | `/reset` | Start a new episode. Accepts `{"task": "easy" \| "medium" \| "hard"}` |
|
| 155 |
+
| `POST` | `/step` | Submit a fix. Returns reward, done flag, and next observation |
|
| 156 |
+
| `GET` | `/ui` | Interactive web interface |
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
## Environment Details
|
| 161 |
+
|
| 162 |
+
### Tasks
|
| 163 |
+
|
| 164 |
+
| Difficulty | Bug Types |
|
| 165 |
+
|------------|-----------|
|
| 166 |
+
| 🟢 **Easy** | Syntax errors (`SELCT`, `FORM`, missing `WHERE`) |
|
| 167 |
+
| 🟡 **Medium** | JOIN logic errors, aggregation mistakes |
|
| 168 |
+
| 🔴 **Hard** | N+1 query patterns, optimization issues, complex aggregations |
|
| 169 |
+
|
| 170 |
+
---
|
| 171 |
+
|
| 172 |
+
### Reward System
|
| 173 |
+
|
| 174 |
+
Each submitted query is evaluated across multiple components:
|
| 175 |
+
|
| 176 |
+
| Component | Weight | Description |
|
| 177 |
+
|-----------|--------|-------------|
|
| 178 |
+
| Syntax Correctness | `0.20` | Query parses and executes without error |
|
| 179 |
+
| Row Count Match | `0.25` | Output row count matches reference |
|
| 180 |
+
| Column Match | `0.15` | Returned columns match expected schema |
|
| 181 |
+
| Data Exact Match | `0.30` | Row-by-row data comparison |
|
| 182 |
+
| Security Fix | `0.10` | SQL injection or unsafe patterns resolved |
|
| 183 |
+
| Optimization | `0.05` | Query avoids N+1 or redundant scans |
|
| 184 |
+
| Explanation Quality | `0.05` | Detected issues and explanation provided |
|
| 185 |
+
| **Total** | **1.00** | |
|
| 186 |
+
|
| 187 |
+
---
|
| 188 |
+
|
| 189 |
+
### Performance
|
| 190 |
+
|
| 191 |
+
Benchmarks from the reference agent (`inference.py`):
|
| 192 |
+
|
| 193 |
+
| Task | Score |
|
| 194 |
+
|------|-------|
|
| 195 |
+
| Easy | ~0.90 |
|
| 196 |
+
| Medium | ~0.90 |
|
| 197 |
+
| Hard | ~0.76 |
|
| 198 |
+
| **Average** | 🚀 **~0.85** |
|
| 199 |
+
|
| 200 |
+
---
|
| 201 |
+
|
| 202 |
+
## License
|
| 203 |
+
|
| 204 |
+
This project is licensed under the **MIT License** — see [`LICENSE`](LICENSE) for details.
|
| 205 |
+
|
| 206 |
+
---
|
| 207 |
+
|
| 208 |
+
<div align="center">
|
| 209 |
+
Built for <strong>NeuroHack</strong> · OpenEnv Track · 2025
|
| 210 |
+
</div>
|
inference.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
inference.py — SQL Debugger & Optimizer Agent
|
| 3 |
+
===============================================
|
| 4 |
+
Runs an LLM against all 3 tasks. Emits mandatory [START]/[STEP]/[END] logs.
|
| 5 |
+
|
| 6 |
+
Env vars:
|
| 7 |
+
API_BASE_URL LLM endpoint (default: https://router.huggingface.co/v1)
|
| 8 |
+
MODEL_NAME Model name (default: Qwen/Qwen2.5-72B-Instruct)
|
| 9 |
+
HF_TOKEN API key
|
| 10 |
+
"""
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
import json, os, textwrap
|
| 13 |
+
from typing import Dict, List, Optional
|
| 14 |
+
from openai import OpenAI
|
| 15 |
+
from sql_debugger_env import Action, SQLDebuggerEnv
|
| 16 |
+
|
| 17 |
+
API_KEY = os.getenv("HF_TOKEN") or os.getenv("API_KEY", "")
|
| 18 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
|
| 19 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-72B-Instruct")
|
| 20 |
+
BENCHMARK = "sql-debugger-agent"
|
| 21 |
+
TASKS = ["easy", "medium", "hard"]
|
| 22 |
+
|
| 23 |
+
client = OpenAI(api_key=API_KEY, base_url=API_BASE_URL)
|
| 24 |
+
|
| 25 |
+
SYSTEM_PROMPT = textwrap.dedent("""
|
| 26 |
+
You are an expert SQL debugger and optimizer.
|
| 27 |
+
You will be given a broken SQL query, the database schema, and a description
|
| 28 |
+
of what the query SHOULD do. Your job is to fix ALL bugs.
|
| 29 |
+
|
| 30 |
+
Always respond with ONLY a JSON object — no markdown, no explanation outside JSON:
|
| 31 |
+
{
|
| 32 |
+
"challenge_id": "<id>",
|
| 33 |
+
"fixed_sql": "<your corrected SQL query>",
|
| 34 |
+
"explanation": "<brief explanation of what was wrong>",
|
| 35 |
+
"detected_issues": ["issue1", "issue2"]
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
Common bug types to look for:
|
| 39 |
+
- Syntax typos (SELCT, FORM, WHER)
|
| 40 |
+
- Missing GROUP BY when using aggregate functions with non-aggregate columns
|
| 41 |
+
- Wrong JOIN type (INNER vs LEFT)
|
| 42 |
+
- Wrong column in ORDER BY (ordering by raw column instead of aggregate alias)
|
| 43 |
+
- Subquery referencing wrong scope (AVG of all rows instead of filtered group)
|
| 44 |
+
- SQL injection (string concatenation of user input — fix with parameterized form)
|
| 45 |
+
- N+1 correlated subqueries (rewrite as JOIN with subquery or CTE)
|
| 46 |
+
|
| 47 |
+
Output ONLY the JSON. Nothing else.
|
| 48 |
+
""").strip()
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def build_prompt(obs_dict: dict) -> str:
|
| 52 |
+
c = obs_dict["challenge"]
|
| 53 |
+
return textwrap.dedent(f"""
|
| 54 |
+
TASK: {obs_dict['task']}
|
| 55 |
+
INSTRUCTIONS: {obs_dict['instructions']}
|
| 56 |
+
|
| 57 |
+
DATABASE SCHEMA:
|
| 58 |
+
{obs_dict['schema_info']}
|
| 59 |
+
|
| 60 |
+
CHALLENGE ID: {c['id']}
|
| 61 |
+
GOAL: {c['description']}
|
| 62 |
+
DIFFICULTY: {c['difficulty']}
|
| 63 |
+
|
| 64 |
+
KNOWN BUGS (hints):
|
| 65 |
+
{chr(10).join('- ' + b for b in c['bugs'])}
|
| 66 |
+
|
| 67 |
+
HINT: {c['hint']}
|
| 68 |
+
|
| 69 |
+
BROKEN SQL TO FIX:
|
| 70 |
+
{c['broken_sql']}
|
| 71 |
+
|
| 72 |
+
Output only JSON with keys: challenge_id, fixed_sql, explanation, detected_issues
|
| 73 |
+
""").strip()
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def run_task(task_name: str) -> dict:
|
| 77 |
+
env = SQLDebuggerEnv(task=task_name)
|
| 78 |
+
obs_obj = env.reset()
|
| 79 |
+
obs = obs_obj.model_dump()
|
| 80 |
+
|
| 81 |
+
step_num = 0
|
| 82 |
+
rewards: List[float] = []
|
| 83 |
+
done = False
|
| 84 |
+
last_error = None
|
| 85 |
+
|
| 86 |
+
print(f"[START] task={task_name} env={BENCHMARK} model={MODEL_NAME}", flush=True)
|
| 87 |
+
|
| 88 |
+
while not done:
|
| 89 |
+
prompt = build_prompt(obs)
|
| 90 |
+
try:
|
| 91 |
+
resp = client.chat.completions.create(
|
| 92 |
+
model=MODEL_NAME,
|
| 93 |
+
messages=[
|
| 94 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 95 |
+
{"role": "user", "content": prompt},
|
| 96 |
+
],
|
| 97 |
+
max_tokens=600,
|
| 98 |
+
temperature=0.1,
|
| 99 |
+
)
|
| 100 |
+
raw = resp.choices[0].message.content or ""
|
| 101 |
+
raw = raw.strip().strip("```json").strip("```").strip()
|
| 102 |
+
action_dict = json.loads(raw)
|
| 103 |
+
action = Action(**action_dict)
|
| 104 |
+
last_error = None
|
| 105 |
+
except Exception as e:
|
| 106 |
+
last_error = str(e)[:100]
|
| 107 |
+
cid = obs["challenge"]["id"]
|
| 108 |
+
action = Action(
|
| 109 |
+
challenge_id=cid,
|
| 110 |
+
fixed_sql=obs["challenge"]["broken_sql"],
|
| 111 |
+
explanation="parse error fallback",
|
| 112 |
+
detected_issues=[],
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
obs_obj, reward, done, info = env.step(action)
|
| 116 |
+
obs = obs_obj.model_dump()
|
| 117 |
+
step_num += 1
|
| 118 |
+
rewards.append(reward)
|
| 119 |
+
|
| 120 |
+
action_str = f"fix(id={action.challenge_id},issues={len(action.detected_issues)})"
|
| 121 |
+
print(
|
| 122 |
+
f"[STEP] step={step_num} action={action_str} "
|
| 123 |
+
f"reward={reward:.2f} done={str(done).lower()} "
|
| 124 |
+
f"error={last_error if last_error else 'null'}",
|
| 125 |
+
flush=True,
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
score = env.episode_score()
|
| 129 |
+
rewards_str = ",".join(f"{r:.2f}" for r in rewards)
|
| 130 |
+
print(
|
| 131 |
+
f"[END] success={str(score >= 0.5).lower()} steps={step_num} "
|
| 132 |
+
f"score={score:.2f} rewards={rewards_str}",
|
| 133 |
+
flush=True,
|
| 134 |
+
)
|
| 135 |
+
env.close()
|
| 136 |
+
return {"task": task_name, "score": score, "steps": step_num}
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def main():
|
| 140 |
+
print(f"# SQL Debugger & Optimizer — Baseline Inference", flush=True)
|
| 141 |
+
print(f"# Model: {MODEL_NAME} API: {API_BASE_URL}\n", flush=True)
|
| 142 |
+
results = []
|
| 143 |
+
for task in TASKS:
|
| 144 |
+
result = run_task(task)
|
| 145 |
+
results.append(result)
|
| 146 |
+
print(f"# Task '{task}' → score: {result['score']:.4f}\n", flush=True)
|
| 147 |
+
|
| 148 |
+
avg = sum(r["score"] for r in results) / len(results)
|
| 149 |
+
print("# === FINAL SUMMARY ===", flush=True)
|
| 150 |
+
for r in results:
|
| 151 |
+
print(f"# {r['task']:8s}: {r['score']:.4f}", flush=True)
|
| 152 |
+
print(f"# AVERAGE : {avg:.4f}", flush=True)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
if __name__ == "__main__":
|
| 156 |
+
main()
|
openenv.yaml
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: sql-debugger-agent
|
| 2 |
+
version: "1.0.0"
|
| 3 |
+
description: >
|
| 4 |
+
A real-world environment where an AI agent debugs broken SQL queries —
|
| 5 |
+
fixing syntax errors, logic bugs (wrong JOINs, aggregation mistakes,
|
| 6 |
+
subquery scope), performance anti-patterns (N+1 correlated subqueries),
|
| 7 |
+
and SQL injection vulnerabilities. Uses a live in-memory SQLite database
|
| 8 |
+
for 100% deterministic, execution-based grading.
|
| 9 |
+
|
| 10 |
+
author: NeuroHack
|
| 11 |
+
tags:
|
| 12 |
+
- openenv
|
| 13 |
+
- sql
|
| 14 |
+
- debugging
|
| 15 |
+
- optimization
|
| 16 |
+
- security
|
| 17 |
+
- real-world
|
| 18 |
+
- data-engineering
|
| 19 |
+
|
| 20 |
+
tasks:
|
| 21 |
+
- name: easy
|
| 22 |
+
description: Fix SQL syntax errors — typos in keywords, missing clauses.
|
| 23 |
+
difficulty: easy
|
| 24 |
+
max_steps: 3
|
| 25 |
+
reward_range: [0.0, 1.0]
|
| 26 |
+
|
| 27 |
+
- name: medium
|
| 28 |
+
description: Fix logic bugs — wrong JOIN types, incorrect aggregation scope, bad ORDER BY.
|
| 29 |
+
difficulty: medium
|
| 30 |
+
max_steps: 3
|
| 31 |
+
reward_range: [0.0, 1.0]
|
| 32 |
+
|
| 33 |
+
- name: hard
|
| 34 |
+
description: >
|
| 35 |
+
Fix + optimize + secure: N+1 correlated subqueries, SQL injection vulnerabilities,
|
| 36 |
+
and missing column aliases. All bugs must be identified and fixed.
|
| 37 |
+
difficulty: hard
|
| 38 |
+
max_steps: 3
|
| 39 |
+
reward_range: [0.0, 1.0]
|
| 40 |
+
|
| 41 |
+
interface:
|
| 42 |
+
observation: Observation
|
| 43 |
+
action: Action
|
| 44 |
+
reward: Reward
|
| 45 |
+
methods:
|
| 46 |
+
- reset
|
| 47 |
+
- step
|
| 48 |
+
- state
|
| 49 |
+
|
| 50 |
+
environment:
|
| 51 |
+
python: ">=3.10"
|
| 52 |
+
framework: openenv-core
|
| 53 |
+
database: sqlite3 (in-memory, no external deps)
|
| 54 |
+
api: openai-compatible
|
| 55 |
+
|
| 56 |
+
hf_space:
|
| 57 |
+
sdk: docker
|
| 58 |
+
tags:
|
| 59 |
+
- openenv
|
pyproject.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "sql-debugger-env"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "SQL Debugger & Optimizer OpenEnv environment"
|
| 5 |
+
authors = [{ name = "Abrar" }]
|
| 6 |
+
|
| 7 |
+
dependencies = [
|
| 8 |
+
"fastapi",
|
| 9 |
+
"uvicorn",
|
| 10 |
+
"pydantic",
|
| 11 |
+
"openenv"
|
| 12 |
+
]
|
| 13 |
+
requires-python = ">=3.10,<3.12"
|
| 14 |
+
[project.scripts]
|
| 15 |
+
server = "server.app:main"
|
| 16 |
+
|
| 17 |
+
[build-system]
|
| 18 |
+
requires = ["setuptools", "wheel"]
|
| 19 |
+
build-backend = "setuptools.build_meta"
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.110.0
|
| 2 |
+
uvicorn[standard]>=0.29.0
|
| 3 |
+
pydantic>=2.6.0
|
| 4 |
+
openai>=1.14.0
|
| 5 |
+
pyyaml>=6.0
|
| 6 |
+
httpx>=0.27.0
|
server/app.py
ADDED
|
@@ -0,0 +1,724 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SQL Debugger & Optimizer — FastAPI Server for HF Spaces
|
| 3 |
+
"""
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
import os
|
| 6 |
+
import uuid
|
| 7 |
+
from typing import Any, Dict, Optional
|
| 8 |
+
|
| 9 |
+
from fastapi import FastAPI, HTTPException, Body
|
| 10 |
+
from fastapi.responses import HTMLResponse
|
| 11 |
+
from pydantic import BaseModel
|
| 12 |
+
|
| 13 |
+
from sql_debugger_env import Action, SQLDebuggerEnv
|
| 14 |
+
|
| 15 |
+
app = FastAPI(
|
| 16 |
+
title="SQL Debugger & Optimizer — OpenEnv",
|
| 17 |
+
description="Real-world SQL debugging environment for RL agent evaluation.",
|
| 18 |
+
version="1.0.0",
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
_sessions: Dict[str, SQLDebuggerEnv] = {}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class ResetRequest(BaseModel):
|
| 25 |
+
task: str = "easy"
|
| 26 |
+
session_id: Optional[str] = None
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class StepRequest(BaseModel):
|
| 30 |
+
session_id: str
|
| 31 |
+
action: Dict[str, Any]
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# ✅ HEALTH CHECK
|
| 35 |
+
@app.get("/health")
|
| 36 |
+
def health():
|
| 37 |
+
return {"status": "ok"}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# ✅ RESET
|
| 41 |
+
@app.post("/reset")
|
| 42 |
+
def reset(req: Optional[ResetRequest] = Body(default=None)):
|
| 43 |
+
task = req.task if req else "easy"
|
| 44 |
+
session_id = req.session_id if req else str(uuid.uuid4())
|
| 45 |
+
|
| 46 |
+
env = SQLDebuggerEnv(task=task)
|
| 47 |
+
_sessions[session_id] = env
|
| 48 |
+
obs = env.reset()
|
| 49 |
+
|
| 50 |
+
return {
|
| 51 |
+
"session_id": session_id,
|
| 52 |
+
"observation": obs.model_dump()
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# ✅ STEP
|
| 57 |
+
@app.post("/step")
|
| 58 |
+
def step(req: StepRequest):
|
| 59 |
+
env = _sessions.get(req.session_id)
|
| 60 |
+
if not env:
|
| 61 |
+
raise HTTPException(status_code=404, detail="Session not found.")
|
| 62 |
+
|
| 63 |
+
action = Action(**req.action)
|
| 64 |
+
obs, reward, done, info = env.step(action)
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
"observation": obs.model_dump(),
|
| 68 |
+
"reward": reward,
|
| 69 |
+
"done": done,
|
| 70 |
+
"info": info
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# ✅ ROOT UI
|
| 75 |
+
@app.get("/", response_class=HTMLResponse)
|
| 76 |
+
def ui():
|
| 77 |
+
return r"""<!DOCTYPE html>
|
| 78 |
+
<html lang="en">
|
| 79 |
+
<head>
|
| 80 |
+
<meta charset="UTF-8"/>
|
| 81 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 82 |
+
<title>SQL Debugger & Optimizer</title>
|
| 83 |
+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Syne:wght@700;800&display=swap" rel="stylesheet"/>
|
| 84 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
| 85 |
+
<style>
|
| 86 |
+
:root {
|
| 87 |
+
--bg: #080c14;
|
| 88 |
+
--surface: #0d1525;
|
| 89 |
+
--card: #111b2e;
|
| 90 |
+
--border: #1e2f4a;
|
| 91 |
+
--accent: #00d4ff;
|
| 92 |
+
--accent2: #7c3aed;
|
| 93 |
+
--green: #22d3a0;
|
| 94 |
+
--yellow: #fbbf24;
|
| 95 |
+
--red: #f87171;
|
| 96 |
+
--text: #e2eaf8;
|
| 97 |
+
--muted: #5a7092;
|
| 98 |
+
--glow: 0 0 24px rgba(0,212,255,.25);
|
| 99 |
+
}
|
| 100 |
+
*{box-sizing:border-box;margin:0;padding:0;}
|
| 101 |
+
html,body{height:100%;background:var(--bg);color:var(--text);font-family:'JetBrains Mono',monospace;}
|
| 102 |
+
|
| 103 |
+
/* ── Background grid ── */
|
| 104 |
+
body::before{
|
| 105 |
+
content:'';position:fixed;inset:0;z-index:0;
|
| 106 |
+
background-image:
|
| 107 |
+
linear-gradient(rgba(0,212,255,.04) 1px,transparent 1px),
|
| 108 |
+
linear-gradient(90deg,rgba(0,212,255,.04) 1px,transparent 1px);
|
| 109 |
+
background-size:40px 40px;
|
| 110 |
+
pointer-events:none;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.wrapper{position:relative;z-index:1;max-width:1200px;margin:0 auto;padding:32px 24px 60px;}
|
| 114 |
+
|
| 115 |
+
/* ── Header ── */
|
| 116 |
+
header{display:flex;align-items:center;gap:16px;margin-bottom:36px;}
|
| 117 |
+
.logo-box{
|
| 118 |
+
width:52px;height:52px;border-radius:14px;
|
| 119 |
+
background:linear-gradient(135deg,var(--accent2),var(--accent));
|
| 120 |
+
display:flex;align-items:center;justify-content:center;font-size:22px;
|
| 121 |
+
box-shadow:var(--glow);
|
| 122 |
+
}
|
| 123 |
+
header h1{font-family:'Syne',sans-serif;font-size:26px;font-weight:800;letter-spacing:-.5px;}
|
| 124 |
+
header h1 span{color:var(--accent);}
|
| 125 |
+
.badge{
|
| 126 |
+
margin-left:auto;padding:5px 14px;border-radius:20px;font-size:11px;font-weight:700;
|
| 127 |
+
background:rgba(0,212,255,.1);border:1px solid rgba(0,212,255,.3);color:var(--accent);
|
| 128 |
+
letter-spacing:1.5px;text-transform:uppercase;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
/* ── Difficulty row ── */
|
| 132 |
+
.diff-row{display:flex;gap:12px;margin-bottom:28px;flex-wrap:wrap;}
|
| 133 |
+
.diff-btn{
|
| 134 |
+
flex:1;min-width:120px;padding:13px 0;border-radius:12px;border:1.5px solid var(--border);
|
| 135 |
+
background:var(--card);cursor:pointer;font-family:'Syne',sans-serif;font-size:13px;font-weight:700;
|
| 136 |
+
color:var(--muted);letter-spacing:.8px;transition:all .2s;position:relative;overflow:hidden;
|
| 137 |
+
}
|
| 138 |
+
.diff-btn::after{
|
| 139 |
+
content:'';position:absolute;inset:0;opacity:0;
|
| 140 |
+
background:linear-gradient(135deg,rgba(0,212,255,.12),rgba(124,58,237,.12));
|
| 141 |
+
transition:opacity .2s;
|
| 142 |
+
}
|
| 143 |
+
.diff-btn:hover::after,.diff-btn.active::after{opacity:1;}
|
| 144 |
+
.diff-btn:hover,.diff-btn.active{border-color:var(--accent);color:var(--text);transform:translateY(-2px);box-shadow:var(--glow);}
|
| 145 |
+
.diff-btn.active{color:var(--accent);}
|
| 146 |
+
.diff-btn .pill{
|
| 147 |
+
display:inline-block;margin-left:8px;padding:2px 8px;border-radius:8px;font-size:10px;
|
| 148 |
+
vertical-align:middle;
|
| 149 |
+
}
|
| 150 |
+
.pill-easy{background:rgba(34,211,160,.15);color:var(--green);}
|
| 151 |
+
.pill-medium{background:rgba(251,191,36,.15);color:var(--yellow);}
|
| 152 |
+
.pill-hard{background:rgba(248,113,113,.15);color:var(--red);}
|
| 153 |
+
.pill-custom{background:rgba(124,58,237,.2);color:#a78bfa;}
|
| 154 |
+
|
| 155 |
+
/* ── Main grid ── */
|
| 156 |
+
.grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px;}
|
| 157 |
+
@media(max-width:768px){.grid{grid-template-columns:1fr;}}
|
| 158 |
+
|
| 159 |
+
/* ── Card ── */
|
| 160 |
+
.card{
|
| 161 |
+
background:var(--card);border:1.5px solid var(--border);border-radius:16px;
|
| 162 |
+
padding:20px;
|
| 163 |
+
}
|
| 164 |
+
.card-title{
|
| 165 |
+
font-family:'Syne',sans-serif;font-size:11px;font-weight:800;letter-spacing:2px;
|
| 166 |
+
text-transform:uppercase;color:var(--muted);margin-bottom:14px;display:flex;align-items:center;gap:8px;
|
| 167 |
+
}
|
| 168 |
+
.card-title .dot{width:8px;height:8px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent);}
|
| 169 |
+
|
| 170 |
+
/* ── Textarea / pre ── */
|
| 171 |
+
textarea{
|
| 172 |
+
width:100%;height:150px;background:#060d1a;border:1.5px solid var(--border);
|
| 173 |
+
border-radius:10px;color:var(--text);font-family:'JetBrains Mono',monospace;
|
| 174 |
+
font-size:13px;padding:14px;resize:vertical;outline:none;transition:border-color .2s;
|
| 175 |
+
line-height:1.6;
|
| 176 |
+
}
|
| 177 |
+
textarea:focus{border-color:var(--accent);}
|
| 178 |
+
pre{
|
| 179 |
+
background:#060d1a;border:1.5px solid var(--border);border-radius:10px;
|
| 180 |
+
padding:14px;min-height:80px;font-size:13px;line-height:1.6;white-space:pre-wrap;
|
| 181 |
+
word-break:break-all;color:var(--green);overflow:auto;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
/* ── Custom SQL ── */
|
| 185 |
+
.custom-area{display:none;margin-bottom:20px;}
|
| 186 |
+
.custom-area.show{display:block;}
|
| 187 |
+
.custom-area textarea{height:80px;}
|
| 188 |
+
.custom-label{font-size:11px;color:var(--muted);margin-bottom:6px;letter-spacing:1px;}
|
| 189 |
+
|
| 190 |
+
/* ── Run button ── */
|
| 191 |
+
.run-row{display:flex;gap:12px;margin-bottom:20px;align-items:center;}
|
| 192 |
+
.run-btn{
|
| 193 |
+
flex:1;padding:14px;border-radius:12px;border:none;cursor:pointer;
|
| 194 |
+
background:linear-gradient(135deg,var(--accent2),var(--accent));
|
| 195 |
+
font-family:'Syne',sans-serif;font-size:14px;font-weight:800;color:#fff;
|
| 196 |
+
letter-spacing:.5px;transition:all .2s;box-shadow:0 4px 24px rgba(0,212,255,.2);
|
| 197 |
+
}
|
| 198 |
+
.run-btn:hover{transform:translateY(-2px);box-shadow:0 8px 32px rgba(0,212,255,.35);}
|
| 199 |
+
.run-btn:active{transform:translateY(0);}
|
| 200 |
+
|
| 201 |
+
/* ── Score strip ── */
|
| 202 |
+
.score-strip{
|
| 203 |
+
display:flex;gap:14px;margin-bottom:20px;flex-wrap:wrap;
|
| 204 |
+
}
|
| 205 |
+
.score-card{
|
| 206 |
+
flex:1;min-width:110px;background:var(--card);border:1.5px solid var(--border);
|
| 207 |
+
border-radius:14px;padding:16px 14px;text-align:center;transition:all .3s;
|
| 208 |
+
}
|
| 209 |
+
.score-card.lit{border-color:var(--accent);box-shadow:var(--glow);}
|
| 210 |
+
.score-card .sc-val{font-family:'Syne',sans-serif;font-size:28px;font-weight:800;color:var(--accent);}
|
| 211 |
+
.score-card .sc-lbl{font-size:10px;color:var(--muted);margin-top:4px;letter-spacing:1.5px;text-transform:uppercase;}
|
| 212 |
+
|
| 213 |
+
/* ── Progress bar ── */
|
| 214 |
+
.prog-wrap{background:var(--surface);border-radius:99px;height:10px;overflow:hidden;margin-top:8px;}
|
| 215 |
+
.prog-bar{
|
| 216 |
+
height:100%;border-radius:99px;width:0%;
|
| 217 |
+
background:linear-gradient(90deg,var(--accent2),var(--accent));
|
| 218 |
+
transition:width .8s cubic-bezier(.4,0,.2,1);
|
| 219 |
+
box-shadow:0 0 12px var(--accent);
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
/* ── Status badge ── */
|
| 223 |
+
.status-pill{
|
| 224 |
+
display:inline-flex;align-items:center;gap:6px;padding:6px 14px;
|
| 225 |
+
border-radius:20px;font-size:12px;font-weight:700;letter-spacing:.5px;
|
| 226 |
+
margin-bottom:8px;
|
| 227 |
+
}
|
| 228 |
+
.status-pill.win{background:rgba(34,211,160,.15);border:1px solid var(--green);color:var(--green);}
|
| 229 |
+
.status-pill.lose{background:rgba(248,113,113,.12);border:1px solid var(--red);color:var(--red);}
|
| 230 |
+
.status-pill.neutral{background:rgba(91,112,146,.12);border:1px solid var(--muted);color:var(--muted);}
|
| 231 |
+
|
| 232 |
+
/* ── Chart ── */
|
| 233 |
+
.chart-wrap{position:relative;height:220px;}
|
| 234 |
+
|
| 235 |
+
/* ── Issues list ── */
|
| 236 |
+
.issues{list-style:none;}
|
| 237 |
+
.issues li{
|
| 238 |
+
padding:8px 12px;border-radius:8px;margin-bottom:6px;font-size:12px;
|
| 239 |
+
background:rgba(248,113,113,.08);border-left:3px solid var(--red);color:#fca5a5;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
/* ── Log ── */
|
| 243 |
+
.log-wrap{
|
| 244 |
+
background:#060d1a;border:1.5px solid var(--border);border-radius:12px;
|
| 245 |
+
max-height:180px;overflow-y:auto;padding:12px;
|
| 246 |
+
}
|
| 247 |
+
.log-line{font-size:11px;line-height:1.8;color:var(--muted);}
|
| 248 |
+
.log-line span{color:var(--accent);}
|
| 249 |
+
.log-line.ok span{color:var(--green);}
|
| 250 |
+
.log-line.err span{color:var(--red);}
|
| 251 |
+
|
| 252 |
+
/* ── Explanation ── */
|
| 253 |
+
.explain-input{
|
| 254 |
+
width:100%;padding:10px 14px;border-radius:10px;border:1.5px solid var(--border);
|
| 255 |
+
background:#060d1a;color:var(--text);font-family:'JetBrains Mono',monospace;font-size:12px;
|
| 256 |
+
outline:none;transition:border-color .2s;
|
| 257 |
+
}
|
| 258 |
+
.explain-input:focus{border-color:var(--accent);}
|
| 259 |
+
|
| 260 |
+
/* ── Hackathon meter ── */
|
| 261 |
+
.hack-bar{
|
| 262 |
+
background:var(--card);border:1.5px solid var(--border);border-radius:16px;
|
| 263 |
+
padding:20px;margin-bottom:20px;
|
| 264 |
+
}
|
| 265 |
+
.hack-bar .hb-title{font-family:'Syne',sans-serif;font-size:12px;font-weight:800;letter-spacing:2px;color:var(--muted);text-transform:uppercase;margin-bottom:12px;}
|
| 266 |
+
.hack-bar .hb-row{display:flex;justify-content:space-between;margin-bottom:8px;font-size:12px;}
|
| 267 |
+
.hack-bar .hb-val{color:var(--accent);font-weight:700;}
|
| 268 |
+
.hack-meter{position:relative;height:22px;background:var(--surface);border-radius:99px;overflow:hidden;}
|
| 269 |
+
.hack-fill{
|
| 270 |
+
height:100%;border-radius:99px;width:0%;
|
| 271 |
+
background:linear-gradient(90deg,#f43f5e,#fbbf24,#22d3a0,#00d4ff);
|
| 272 |
+
transition:width 1s cubic-bezier(.4,0,.2,1);
|
| 273 |
+
box-shadow:0 0 16px rgba(0,212,255,.4);
|
| 274 |
+
}
|
| 275 |
+
.hack-labels{display:flex;justify-content:space-between;margin-top:6px;font-size:10px;color:var(--muted);}
|
| 276 |
+
|
| 277 |
+
/* ── Animations ── */
|
| 278 |
+
@keyframes fadeUp{from{opacity:0;transform:translateY(12px);}to{opacity:1;transform:translateY(0);}}
|
| 279 |
+
.card,.score-card,.hack-bar{animation:fadeUp .4s ease both;}
|
| 280 |
+
|
| 281 |
+
/* ── Spinner ── */
|
| 282 |
+
@keyframes spin{to{transform:rotate(360deg);}}
|
| 283 |
+
.spinner{width:18px;height:18px;border:2px solid rgba(255,255,255,.2);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;display:none;}
|
| 284 |
+
.loading .spinner{display:block;}
|
| 285 |
+
.loading .btn-label{display:none;}
|
| 286 |
+
</style>
|
| 287 |
+
</head>
|
| 288 |
+
<body>
|
| 289 |
+
<div class="wrapper">
|
| 290 |
+
|
| 291 |
+
<!-- Header -->
|
| 292 |
+
<header>
|
| 293 |
+
<div class="logo-box">⚡</div>
|
| 294 |
+
<div>
|
| 295 |
+
<h1>SQL <span>Debugger</span> & Optimizer</h1>
|
| 296 |
+
<div style="font-size:11px;color:var(--muted);margin-top:2px;">RL Environment · OpenEnv Protocol</div>
|
| 297 |
+
</div>
|
| 298 |
+
<div class="badge">v1.0.0</div>
|
| 299 |
+
</header>
|
| 300 |
+
|
| 301 |
+
<!-- Difficulty Buttons -->
|
| 302 |
+
<div class="diff-row">
|
| 303 |
+
<button class="diff-btn active" data-task="easy" onclick="selectTask('easy',this)">
|
| 304 |
+
🟢 EASY <span class="pill pill-easy">+100</span>
|
| 305 |
+
</button>
|
| 306 |
+
<button class="diff-btn" data-task="medium" onclick="selectTask('medium',this)">
|
| 307 |
+
🟡 MEDIUM <span class="pill pill-medium">+200</span>
|
| 308 |
+
</button>
|
| 309 |
+
<button class="diff-btn" data-task="hard" onclick="selectTask('hard',this)">
|
| 310 |
+
🔴 HARD <span class="pill pill-hard">+400</span>
|
| 311 |
+
</button>
|
| 312 |
+
<button class="diff-btn" data-task="custom" onclick="selectTask('custom',this)">
|
| 313 |
+
🟣 CUSTOM <span class="pill pill-custom">+∞</span>
|
| 314 |
+
</button>
|
| 315 |
+
</div>
|
| 316 |
+
|
| 317 |
+
<!-- Custom SQL entry -->
|
| 318 |
+
<div class="custom-area" id="customArea">
|
| 319 |
+
<div class="custom-label">PASTE YOUR BROKEN SQL BELOW</div>
|
| 320 |
+
<textarea id="customSql" placeholder="-- Paste broken SQL here for custom challenge..."></textarea>
|
| 321 |
+
</div>
|
| 322 |
+
|
| 323 |
+
<!-- Explanation -->
|
| 324 |
+
<div style="margin-bottom:16px;">
|
| 325 |
+
<div class="custom-label" style="margin-bottom:6px;">FIX EXPLANATION (boosts score)</div>
|
| 326 |
+
<input class="explain-input" id="explanation" placeholder="e.g. Fixed typo in FROM clause, added missing JOIN condition..."/>
|
| 327 |
+
</div>
|
| 328 |
+
|
| 329 |
+
<!-- Run -->
|
| 330 |
+
<div class="run-row">
|
| 331 |
+
<button class="run-btn" id="runBtn" onclick="runFix()">
|
| 332 |
+
<div class="spinner" id="spinner"></div>
|
| 333 |
+
<span class="btn-label">⚡ RUN FIX & SCORE</span>
|
| 334 |
+
</button>
|
| 335 |
+
</div>
|
| 336 |
+
|
| 337 |
+
<!-- Hackathon Meter -->
|
| 338 |
+
<div class="hack-bar">
|
| 339 |
+
<div class="hb-title">🏆 Hackathon Score Meter</div>
|
| 340 |
+
<div class="hb-row">
|
| 341 |
+
<span>Cumulative Score</span><span class="hb-val" id="hackScore">0</span>
|
| 342 |
+
</div>
|
| 343 |
+
<div class="hack-meter"><div class="hack-fill" id="hackFill"></div></div>
|
| 344 |
+
<div class="hack-labels">
|
| 345 |
+
<span>0</span><span>Participant</span><span>Good</span><span>Finalist</span><span>🥇 Winner</span>
|
| 346 |
+
</div>
|
| 347 |
+
</div>
|
| 348 |
+
|
| 349 |
+
<!-- Score Strip -->
|
| 350 |
+
<div class="score-strip">
|
| 351 |
+
<div class="score-card" id="scReward">
|
| 352 |
+
<div class="sc-val" id="valReward">—</div>
|
| 353 |
+
<div class="sc-lbl">Last Reward</div>
|
| 354 |
+
</div>
|
| 355 |
+
<div class="score-card" id="scTotal">
|
| 356 |
+
<div class="sc-val" id="valTotal">0</div>
|
| 357 |
+
<div class="sc-lbl">Total Score</div>
|
| 358 |
+
</div>
|
| 359 |
+
<div class="score-card" id="scRuns">
|
| 360 |
+
<div class="sc-val" id="valRuns">0</div>
|
| 361 |
+
<div class="sc-lbl">Runs</div>
|
| 362 |
+
</div>
|
| 363 |
+
<div class="score-card" id="scBest">
|
| 364 |
+
<div class="sc-val" id="valBest">—</div>
|
| 365 |
+
<div class="sc-lbl">Best Reward</div>
|
| 366 |
+
</div>
|
| 367 |
+
<div class="score-card" id="scAcc">
|
| 368 |
+
<div class="sc-val" id="valAcc">0%</div>
|
| 369 |
+
<div class="sc-lbl">Win Rate</div>
|
| 370 |
+
</div>
|
| 371 |
+
</div>
|
| 372 |
+
|
| 373 |
+
<!-- Main Grid -->
|
| 374 |
+
<div class="grid">
|
| 375 |
+
<!-- Left: SQL panels -->
|
| 376 |
+
<div>
|
| 377 |
+
<div class="card" style="margin-bottom:16px;">
|
| 378 |
+
<div class="card-title"><span class="dot"></span>BROKEN SQL (from challenge)</div>
|
| 379 |
+
<textarea id="sql" placeholder="Click a difficulty button above to load a challenge..."></textarea>
|
| 380 |
+
</div>
|
| 381 |
+
<div class="card">
|
| 382 |
+
<div class="card-title"><span class="dot" style="background:var(--green);box-shadow:0 0 8px var(--green);"></span>FIXED SQL OUTPUT</div>
|
| 383 |
+
<div id="statusPill"></div>
|
| 384 |
+
<pre id="out">-- Fixed SQL will appear here after running ⚡</pre>
|
| 385 |
+
<div style="margin-top:12px;">
|
| 386 |
+
<div class="custom-label" style="margin-bottom:4px;">SCORE PROGRESS</div>
|
| 387 |
+
<div class="prog-wrap"><div class="prog-bar" id="progBar"></div></div>
|
| 388 |
+
</div>
|
| 389 |
+
</div>
|
| 390 |
+
</div>
|
| 391 |
+
|
| 392 |
+
<!-- Right: Charts & logs -->
|
| 393 |
+
<div>
|
| 394 |
+
<div class="card" style="margin-bottom:16px;">
|
| 395 |
+
<div class="card-title"><span class="dot" style="background:var(--accent2);box-shadow:0 0 8px var(--accent2);"></span>REWARD HISTORY</div>
|
| 396 |
+
<div class="chart-wrap"><canvas id="rewardChart"></canvas></div>
|
| 397 |
+
</div>
|
| 398 |
+
<div class="card" style="margin-bottom:16px;">
|
| 399 |
+
<div class="card-title"><span class="dot" style="background:var(--yellow);box-shadow:0 0 8px var(--yellow);"></span>DIFFICULTY BREAKDOWN</div>
|
| 400 |
+
<div class="chart-wrap"><canvas id="diffChart"></canvas></div>
|
| 401 |
+
</div>
|
| 402 |
+
<div class="card">
|
| 403 |
+
<div class="card-title"><span class="dot"></span>SESSION LOG</div>
|
| 404 |
+
<div class="log-wrap" id="log">
|
| 405 |
+
<div class="log-line">Waiting for first run…</div>
|
| 406 |
+
</div>
|
| 407 |
+
</div>
|
| 408 |
+
</div>
|
| 409 |
+
</div>
|
| 410 |
+
|
| 411 |
+
<!-- Issues -->
|
| 412 |
+
<div class="card">
|
| 413 |
+
<div class="card-title"><span class="dot" style="background:var(--red);box-shadow:0 0 8px var(--red);"></span>DETECTED ISSUES</div>
|
| 414 |
+
<ul class="issues" id="issueList">
|
| 415 |
+
<li>No issues detected yet — run a fix to analyse SQL.</li>
|
| 416 |
+
</ul>
|
| 417 |
+
</div>
|
| 418 |
+
|
| 419 |
+
</div>
|
| 420 |
+
|
| 421 |
+
<script>
|
| 422 |
+
/* ─── State ─── */
|
| 423 |
+
let sid = null, challenge = null;
|
| 424 |
+
let currentTask = 'easy';
|
| 425 |
+
let totalScore = 0, runs = 0, wins = 0, bestReward = null;
|
| 426 |
+
const rewardHistory = [];
|
| 427 |
+
const diffScores = { easy:0, medium:0, hard:0, custom:0 };
|
| 428 |
+
|
| 429 |
+
const THRESHOLDS = { easy:0.6, medium:0.6, hard:0.6, custom:0.5 };
|
| 430 |
+
|
| 431 |
+
/* ─── Charts ─── */
|
| 432 |
+
const chartDefaults = {
|
| 433 |
+
color: '#e2eaf8',
|
| 434 |
+
plugins:{ legend:{ labels:{ color:'#5a7092', font:{ family:'JetBrains Mono', size:10 } } } },
|
| 435 |
+
scales:{
|
| 436 |
+
x:{ ticks:{ color:'#5a7092', font:{ family:'JetBrains Mono', size:10 } }, grid:{ color:'rgba(30,47,74,.6)' } },
|
| 437 |
+
y:{ ticks:{ color:'#5a7092', font:{ family:'JetBrains Mono', size:10 } }, grid:{ color:'rgba(30,47,74,.6)' } }
|
| 438 |
+
}
|
| 439 |
+
};
|
| 440 |
+
|
| 441 |
+
const rewardCtx = document.getElementById('rewardChart').getContext('2d');
|
| 442 |
+
const rewardChart = new Chart(rewardCtx, {
|
| 443 |
+
type: 'line',
|
| 444 |
+
data:{
|
| 445 |
+
labels:[],
|
| 446 |
+
datasets:[{
|
| 447 |
+
label:'Reward',
|
| 448 |
+
data:[],
|
| 449 |
+
borderColor:'#00d4ff',
|
| 450 |
+
backgroundColor:'rgba(0,212,255,.08)',
|
| 451 |
+
borderWidth:2,
|
| 452 |
+
pointBackgroundColor:'#00d4ff',
|
| 453 |
+
pointRadius:4,
|
| 454 |
+
tension:.4,
|
| 455 |
+
fill:true
|
| 456 |
+
}]
|
| 457 |
+
},
|
| 458 |
+
options:{ ...chartDefaults, animation:{ duration:600 }, plugins:{ legend:{ display:false } } }
|
| 459 |
+
});
|
| 460 |
+
|
| 461 |
+
const diffCtx = document.getElementById('diffChart').getContext('2d');
|
| 462 |
+
const diffChart = new Chart(diffCtx, {
|
| 463 |
+
type:'bar',
|
| 464 |
+
data:{
|
| 465 |
+
labels:['Easy','Medium','Hard','Custom'],
|
| 466 |
+
datasets:[{
|
| 467 |
+
label:'Score',
|
| 468 |
+
data:[0,0,0,0],
|
| 469 |
+
backgroundColor:['rgba(34,211,160,.7)','rgba(251,191,36,.7)','rgba(248,113,113,.7)','rgba(167,139,250,.7)'],
|
| 470 |
+
borderColor:['#22d3a0','#fbbf24','#f87171','#a78bfa'],
|
| 471 |
+
borderWidth:1.5,
|
| 472 |
+
borderRadius:6
|
| 473 |
+
}]
|
| 474 |
+
},
|
| 475 |
+
options:{ ...chartDefaults, animation:{ duration:600 }, plugins:{ legend:{ display:false } } }
|
| 476 |
+
});
|
| 477 |
+
|
| 478 |
+
/* ─── Select task ─── */
|
| 479 |
+
function selectTask(task, btn) {
|
| 480 |
+
currentTask = task;
|
| 481 |
+
document.querySelectorAll('.diff-btn').forEach(b=>b.classList.remove('active'));
|
| 482 |
+
btn.classList.add('active');
|
| 483 |
+
|
| 484 |
+
const ca = document.getElementById('customArea');
|
| 485 |
+
if(task==='custom') { ca.classList.add('show'); }
|
| 486 |
+
else { ca.classList.remove('show'); }
|
| 487 |
+
|
| 488 |
+
if(task !== 'custom') loadChallenge(task);
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
/* ─── Load challenge ─── */
|
| 492 |
+
async function loadChallenge(task) {
|
| 493 |
+
try {
|
| 494 |
+
const r = await fetch('/reset',{
|
| 495 |
+
method:'POST',
|
| 496 |
+
headers:{'Content-Type':'application/json'},
|
| 497 |
+
body: JSON.stringify({ task })
|
| 498 |
+
});
|
| 499 |
+
const d = await r.json();
|
| 500 |
+
sid = d.session_id;
|
| 501 |
+
challenge = d.observation.challenge;
|
| 502 |
+
document.getElementById('sql').value = challenge.broken_sql || '-- No SQL provided';
|
| 503 |
+
addLog('ok', `Challenge loaded · task=${task} · id=${challenge.id}`);
|
| 504 |
+
} catch(e) {
|
| 505 |
+
addLog('err', `Failed to load challenge: ${e.message}`);
|
| 506 |
+
}
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
/* ─── Run Fix ─── */
|
| 510 |
+
async function runFix() {
|
| 511 |
+
const btn = document.getElementById('runBtn');
|
| 512 |
+
btn.classList.add('loading');
|
| 513 |
+
|
| 514 |
+
try {
|
| 515 |
+
let brokenSql = document.getElementById('sql').value.trim();
|
| 516 |
+
let customSql = document.getElementById('customSql').value.trim();
|
| 517 |
+
const explanation = document.getElementById('explanation').value.trim() || 'auto fix';
|
| 518 |
+
|
| 519 |
+
// For custom, auto-load a session first
|
| 520 |
+
if(currentTask === 'custom') {
|
| 521 |
+
if(!customSql) { addLog('err','Paste your broken SQL in the custom box.'); btn.classList.remove('loading'); return; }
|
| 522 |
+
// Load a session for custom (use easy as base env)
|
| 523 |
+
const rr = await fetch('/reset',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({task:'easy'})});
|
| 524 |
+
const rd = await rr.json();
|
| 525 |
+
sid = rd.session_id;
|
| 526 |
+
challenge = rd.observation.challenge;
|
| 527 |
+
brokenSql = customSql;
|
| 528 |
+
challenge.broken_sql = customSql;
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
if(!sid || !challenge) { addLog('err','Click a difficulty button first.'); btn.classList.remove('loading'); return; }
|
| 532 |
+
|
| 533 |
+
/* ── Smart fixer ── */
|
| 534 |
+
const fixed = smartFix(brokenSql);
|
| 535 |
+
const issues = detectIssues(brokenSql);
|
| 536 |
+
|
| 537 |
+
const resp = await fetch('/step',{
|
| 538 |
+
method:'POST',
|
| 539 |
+
headers:{'Content-Type':'application/json'},
|
| 540 |
+
body: JSON.stringify({
|
| 541 |
+
session_id: sid,
|
| 542 |
+
action:{
|
| 543 |
+
challenge_id: challenge.id,
|
| 544 |
+
fixed_sql: fixed,
|
| 545 |
+
explanation: explanation,
|
| 546 |
+
detected_issues: issues
|
| 547 |
+
}
|
| 548 |
+
})
|
| 549 |
+
});
|
| 550 |
+
const data = await resp.json();
|
| 551 |
+
const reward = data.reward ?? 0;
|
| 552 |
+
|
| 553 |
+
/* ── Update UI ── */
|
| 554 |
+
updateScores(reward, issues, fixed, data);
|
| 555 |
+
|
| 556 |
+
/* ── Reload next challenge ── */
|
| 557 |
+
if(currentTask !== 'custom') loadChallenge(currentTask);
|
| 558 |
+
|
| 559 |
+
} catch(e) {
|
| 560 |
+
addLog('err', `Error: ${e.message}`);
|
| 561 |
+
} finally {
|
| 562 |
+
btn.classList.remove('loading');
|
| 563 |
+
}
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
/* ─── Smart SQL fixer (high-reward logic) ─── */
|
| 567 |
+
function smartFix(sql) {
|
| 568 |
+
let s = sql;
|
| 569 |
+
|
| 570 |
+
// Keyword typos
|
| 571 |
+
const fixes = [
|
| 572 |
+
[/\bSELCT\b/gi,'SELECT'],[/\bSELECT\b/gi,'SELECT'],
|
| 573 |
+
[/\bFORM\b/g,'FROM'],[/\bFROM\b/gi,'FROM'],
|
| 574 |
+
[/\bWHER\b/g,'WHERE'],[/\bWHERE\b/gi,'WHERE'],
|
| 575 |
+
[/\bORDER\s+BU\b/gi,'ORDER BY'],[/\bGROUP\s+BU\b/gi,'GROUP BY'],
|
| 576 |
+
[/\bHAVNG\b/gi,'HAVING'],[/\bHAVING\b/gi,'HAVING'],
|
| 577 |
+
[/\bINNER\s+JION\b/gi,'INNER JOIN'],[/\bLEFT\s+JION\b/gi,'LEFT JOIN'],
|
| 578 |
+
[/\bJOIN\s+ON\b/gi,'JOIN'],[/\bINSERT\s+IN\b/g,'INSERT INTO'],
|
| 579 |
+
[/\bDELETE\s+FORM\b/gi,'DELETE FROM'],
|
| 580 |
+
[/\bUPDATE\b/gi,'UPDATE'],[/\bSET\b/gi,'SET'],
|
| 581 |
+
[/\bDISTINT\b/gi,'DISTINCT'],[/\bCOUNT\s*\(\s*\)/gi,'COUNT(*)'],
|
| 582 |
+
[/\bNULL\b/gi,'NULL'],[/\bIS\s+NOT\s+NUL\b/gi,'IS NOT NULL'],
|
| 583 |
+
[/\bIS\s+NUL\b/gi,'IS NULL'],
|
| 584 |
+
[/\bLIMT\b/gi,'LIMIT'],[/\bOFFST\b/gi,'OFFSET'],
|
| 585 |
+
[/\bUNION\s+AL\b/gi,'UNION ALL'],
|
| 586 |
+
[/\bCREATE\s+TABL\b/gi,'CREATE TABLE'],
|
| 587 |
+
[/\bALTER\s+TABL\b/gi,'ALTER TABLE'],
|
| 588 |
+
[/\bDROP\s+TABL\b/gi,'DROP TABLE'],
|
| 589 |
+
[/\bVARCAHR\b/gi,'VARCHAR'],[/\bINTEGR\b/gi,'INTEGER'],
|
| 590 |
+
[/==\s*/g,'= '],[/\bAND\s+AND\b/gi,'AND'],
|
| 591 |
+
[/\bOR\s+OR\b/gi,'OR'],[/\bNOT\s+NOT\b/gi,'NOT'],
|
| 592 |
+
[/\bTRUNCATE\b/gi,'TRUNCATE'],[/\bTRANSACTION\b/gi,'TRANSACTION'],
|
| 593 |
+
];
|
| 594 |
+
|
| 595 |
+
fixes.forEach(([pat,rep])=>{ s = s.replace(pat,rep); });
|
| 596 |
+
|
| 597 |
+
// Unclosed quotes fix
|
| 598 |
+
const sq = (s.match(/'/g)||[]).length;
|
| 599 |
+
if(sq%2!==0) s += "'";
|
| 600 |
+
|
| 601 |
+
// Unclosed parens fix
|
| 602 |
+
const op=(s.match(/\(/g)||[]).length, cl=(s.match(/\)/g)||[]).length;
|
| 603 |
+
if(op>cl) s += ')'.repeat(op-cl);
|
| 604 |
+
if(cl>op) s = '('.repeat(cl-op) + s;
|
| 605 |
+
|
| 606 |
+
// Missing semicolon
|
| 607 |
+
if(!/;\s*$/.test(s.trim())) s = s.trim() + ';';
|
| 608 |
+
|
| 609 |
+
// Normalise whitespace
|
| 610 |
+
s = s.replace(/\s{2,}/g,' ').trim();
|
| 611 |
+
|
| 612 |
+
return s;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
/* ─── Detect issues ─── */
|
| 616 |
+
function detectIssues(sql) {
|
| 617 |
+
const issues = [];
|
| 618 |
+
if(/\bSELCT\b/i.test(sql)) issues.push('typo:SELCT→SELECT');
|
| 619 |
+
if(/\bFORM\b/g.test(sql)) issues.push('typo:FORM→FROM');
|
| 620 |
+
if(/\bWHER\b/g.test(sql)) issues.push('typo:WHER→WHERE');
|
| 621 |
+
if(/==/.test(sql)) issues.push('operator:==→=');
|
| 622 |
+
if((/\(/g.exec(sql)||[]).length !== (/\)/g.exec(sql)||[]).length) issues.push('syntax:unbalanced_parentheses');
|
| 623 |
+
if((/'/g.exec(sql)||[]).length%2!==0) issues.push('syntax:unclosed_string_literal');
|
| 624 |
+
if(!/;\s*$/.test(sql.trim())) issues.push('syntax:missing_semicolon');
|
| 625 |
+
if(/\bLEFT\s+JION\b/i.test(sql)||/\bINNER\s+JION\b/i.test(sql)) issues.push('typo:JION→JOIN');
|
| 626 |
+
if(/\bHAVNG\b/i.test(sql)) issues.push('typo:HAVNG→HAVING');
|
| 627 |
+
if(/\bINTEGR\b/i.test(sql)) issues.push('typo:INTEGR→INTEGER');
|
| 628 |
+
if(!issues.length) issues.push('no_issues_detected');
|
| 629 |
+
return issues;
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
/* ─── Update all UI ─── */
|
| 633 |
+
function updateScores(reward, issues, fixed, data) {
|
| 634 |
+
runs++;
|
| 635 |
+
totalScore += reward;
|
| 636 |
+
const isWin = reward >= (THRESHOLDS[currentTask] || 0.6);
|
| 637 |
+
if(isWin) wins++;
|
| 638 |
+
if(bestReward===null || reward > bestReward) bestReward = reward;
|
| 639 |
+
|
| 640 |
+
diffScores[currentTask] += reward;
|
| 641 |
+
|
| 642 |
+
/* Score cards */
|
| 643 |
+
document.getElementById('valReward').textContent = reward.toFixed ? reward.toFixed(3) : reward;
|
| 644 |
+
document.getElementById('valTotal').textContent = totalScore.toFixed(2);
|
| 645 |
+
document.getElementById('valRuns').textContent = runs;
|
| 646 |
+
document.getElementById('valBest').textContent = bestReward.toFixed ? bestReward.toFixed(3) : bestReward;
|
| 647 |
+
document.getElementById('valAcc').textContent = Math.round((wins/runs)*100)+'%';
|
| 648 |
+
|
| 649 |
+
document.getElementById('scReward').classList.add('lit');
|
| 650 |
+
setTimeout(()=>document.getElementById('scReward').classList.remove('lit'),1200);
|
| 651 |
+
|
| 652 |
+
/* Progress bar */
|
| 653 |
+
const pct = Math.min(100, (reward/(THRESHOLDS[currentTask]||1))*100);
|
| 654 |
+
document.getElementById('progBar').style.width = pct+'%';
|
| 655 |
+
|
| 656 |
+
/* Hackathon meter */
|
| 657 |
+
const hackPct = Math.min(100, (totalScore / (runs * 1)) * 100);
|
| 658 |
+
document.getElementById('hackFill').style.width = hackPct+'%';
|
| 659 |
+
document.getElementById('hackScore').textContent = totalScore.toFixed(2);
|
| 660 |
+
|
| 661 |
+
/* Status pill */
|
| 662 |
+
const pill = document.getElementById('statusPill');
|
| 663 |
+
if(isWin){
|
| 664 |
+
pill.innerHTML = '<div class="status-pill win">✅ WINNING SCORE ACHIEVED</div>';
|
| 665 |
+
} else {
|
| 666 |
+
pill.innerHTML = '<div class="status-pill lose">⚠ BELOW THRESHOLD — TRY AGAIN</div>';
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
/* Output */
|
| 670 |
+
document.getElementById('out').textContent = fixed;
|
| 671 |
+
|
| 672 |
+
/* Reward chart */
|
| 673 |
+
rewardHistory.push(reward);
|
| 674 |
+
rewardChart.data.labels.push(`Run ${runs}`);
|
| 675 |
+
rewardChart.data.datasets[0].data.push(reward);
|
| 676 |
+
if(rewardHistory.length > 20) {
|
| 677 |
+
rewardChart.data.labels.shift();
|
| 678 |
+
rewardChart.data.datasets[0].data.shift();
|
| 679 |
+
}
|
| 680 |
+
rewardChart.update();
|
| 681 |
+
|
| 682 |
+
/* Diff chart */
|
| 683 |
+
diffChart.data.datasets[0].data = [
|
| 684 |
+
diffScores.easy, diffScores.medium, diffScores.hard, diffScores.custom
|
| 685 |
+
];
|
| 686 |
+
diffChart.update();
|
| 687 |
+
|
| 688 |
+
/* Issues list */
|
| 689 |
+
const ul = document.getElementById('issueList');
|
| 690 |
+
ul.innerHTML = issues.map(i=>`<li>${i.replace(/:/g,' → ')}</li>`).join('');
|
| 691 |
+
|
| 692 |
+
/* Log */
|
| 693 |
+
addLog(isWin?'ok':'err',
|
| 694 |
+
`run=${runs} task=${currentTask} reward=${typeof reward==='number'?reward.toFixed(3):reward} done=${data.done||false}`);
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
/* ─── Log helper ─── */
|
| 698 |
+
function addLog(type, msg) {
|
| 699 |
+
const box = document.getElementById('log');
|
| 700 |
+
const ts = new Date().toLocaleTimeString();
|
| 701 |
+
const div = document.createElement('div');
|
| 702 |
+
div.className = `log-line ${type}`;
|
| 703 |
+
div.innerHTML = `<span>[${ts}]</span> ${msg}`;
|
| 704 |
+
box.appendChild(div);
|
| 705 |
+
box.scrollTop = box.scrollHeight;
|
| 706 |
+
// Clear placeholder
|
| 707 |
+
const first = box.querySelector('.log-line:not(.ok):not(.err)');
|
| 708 |
+
if(first && box.children.length > 1) first.remove();
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
/* ─── Initial load ─── */
|
| 712 |
+
loadChallenge('easy');
|
| 713 |
+
</script>
|
| 714 |
+
</body>
|
| 715 |
+
</html>"""
|
| 716 |
+
|
| 717 |
+
|
| 718 |
+
def main():
|
| 719 |
+
import uvicorn
|
| 720 |
+
uvicorn.run("server.app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
|
| 721 |
+
|
| 722 |
+
|
| 723 |
+
if __name__ == "__main__":
|
| 724 |
+
main()
|
sql_debugger_env.py
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
SQL Query Debugger & Optimizer — OpenEnv Environment
|
| 3 |
+
======================================================
|
| 4 |
+
A real-world environment where an AI agent is given broken, inefficient,
|
| 5 |
+
or insecure SQL queries and must:
|
| 6 |
+
- Fix syntax errors
|
| 7 |
+
- Correct logical bugs (wrong JOIN, missing GROUP BY, etc.)
|
| 8 |
+
- Optimize for performance (remove N+1 patterns, add proper indexes hints)
|
| 9 |
+
- Detect & fix SQL injection vulnerabilities
|
| 10 |
+
|
| 11 |
+
This is a task real data analysts and backend engineers do every single day.
|
| 12 |
+
|
| 13 |
+
Tasks:
|
| 14 |
+
easy — Fix obvious syntax errors in simple SELECT queries
|
| 15 |
+
medium — Fix logic bugs (wrong JOINs, incorrect aggregations, missing clauses)
|
| 16 |
+
hard — Fix + optimize + secure (injection detection, N+1 queries, subquery rewrites)
|
| 17 |
+
|
| 18 |
+
Why this beats other environments:
|
| 19 |
+
- 100% deterministic graders (execute real SQL in SQLite, compare results)
|
| 20 |
+
- Rich partial rewards at every step (syntax → logic → performance → security)
|
| 21 |
+
- Real engineering pain point that Meta/HF engineers deal with daily
|
| 22 |
+
- Novel domain — not in OpenEnv Hub yet
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import re
|
| 28 |
+
import sqlite3
|
| 29 |
+
import textwrap
|
| 30 |
+
from typing import Any, Dict, List, Literal, Optional, Tuple
|
| 31 |
+
|
| 32 |
+
from pydantic import BaseModel, Field
|
| 33 |
+
|
| 34 |
+
# ---------------------------------------------------------------------------
|
| 35 |
+
# Database Schema (in-memory SQLite — fully reproducible)
|
| 36 |
+
# ---------------------------------------------------------------------------
|
| 37 |
+
|
| 38 |
+
SCHEMA_SQL = """
|
| 39 |
+
CREATE TABLE IF NOT EXISTS users (
|
| 40 |
+
id INTEGER PRIMARY KEY,
|
| 41 |
+
name TEXT NOT NULL,
|
| 42 |
+
email TEXT UNIQUE NOT NULL,
|
| 43 |
+
department TEXT NOT NULL,
|
| 44 |
+
salary REAL NOT NULL,
|
| 45 |
+
hire_date TEXT NOT NULL,
|
| 46 |
+
manager_id INTEGER REFERENCES users(id)
|
| 47 |
+
);
|
| 48 |
+
|
| 49 |
+
CREATE TABLE IF NOT EXISTS orders (
|
| 50 |
+
id INTEGER PRIMARY KEY,
|
| 51 |
+
user_id INTEGER NOT NULL REFERENCES users(id),
|
| 52 |
+
product TEXT NOT NULL,
|
| 53 |
+
amount REAL NOT NULL,
|
| 54 |
+
status TEXT NOT NULL CHECK(status IN ('pending','completed','cancelled')),
|
| 55 |
+
created_at TEXT NOT NULL
|
| 56 |
+
);
|
| 57 |
+
|
| 58 |
+
CREATE TABLE IF NOT EXISTS products (
|
| 59 |
+
id INTEGER PRIMARY KEY,
|
| 60 |
+
name TEXT NOT NULL,
|
| 61 |
+
category TEXT NOT NULL,
|
| 62 |
+
price REAL NOT NULL,
|
| 63 |
+
stock INTEGER NOT NULL DEFAULT 0
|
| 64 |
+
);
|
| 65 |
+
"""
|
| 66 |
+
|
| 67 |
+
SEED_SQL = """
|
| 68 |
+
INSERT INTO users VALUES
|
| 69 |
+
(1,'Alice Chen','alice@co.com','Engineering',95000,'2021-03-01',NULL),
|
| 70 |
+
(2,'Bob Smith','bob@co.com','Engineering',82000,'2022-06-15',1),
|
| 71 |
+
(3,'Carol Jones','carol@co.com','Marketing',74000,'2020-01-10',NULL),
|
| 72 |
+
(4,'Dan Park','dan@co.com','Engineering',91000,'2019-08-22',1),
|
| 73 |
+
(5,'Eva Liu','eva@co.com','Marketing',68000,'2023-02-28',3),
|
| 74 |
+
(6,'Frank Wu','frank@co.com','HR',61000,'2021-11-05',NULL),
|
| 75 |
+
(7,'Grace Kim','grace@co.com','Engineering',103000,'2018-05-14',1),
|
| 76 |
+
(8,'Hank Patel','hank@co.com','Marketing',72000,'2022-09-30',3);
|
| 77 |
+
|
| 78 |
+
INSERT INTO products VALUES
|
| 79 |
+
(1,'Laptop Pro','Electronics',1299.99,45),
|
| 80 |
+
(2,'Wireless Mouse','Electronics',29.99,200),
|
| 81 |
+
(3,'Standing Desk','Furniture',549.00,30),
|
| 82 |
+
(4,'Monitor 4K','Electronics',699.99,60),
|
| 83 |
+
(5,'Ergonomic Chair','Furniture',399.00,25),
|
| 84 |
+
(6,'USB Hub','Electronics',49.99,150);
|
| 85 |
+
|
| 86 |
+
INSERT INTO orders VALUES
|
| 87 |
+
(1,1,'Laptop Pro',1299.99,'completed','2024-01-15'),
|
| 88 |
+
(2,2,'Wireless Mouse',29.99,'completed','2024-01-20'),
|
| 89 |
+
(3,1,'Monitor 4K',699.99,'completed','2024-02-01'),
|
| 90 |
+
(4,3,'Ergonomic Chair',399.00,'pending','2024-02-10'),
|
| 91 |
+
(5,4,'Standing Desk',549.00,'completed','2024-02-14'),
|
| 92 |
+
(6,2,'USB Hub',49.99,'cancelled','2024-02-20'),
|
| 93 |
+
(7,5,'Wireless Mouse',29.99,'completed','2024-03-01'),
|
| 94 |
+
(8,7,'Laptop Pro',1299.99,'completed','2024-03-05'),
|
| 95 |
+
(9,1,'USB Hub',49.99,'completed','2024-03-10'),
|
| 96 |
+
(10,3,'Laptop Pro',1299.99,'pending','2024-03-15'),
|
| 97 |
+
(11,4,'Wireless Mouse',29.99,'completed','2024-03-18'),
|
| 98 |
+
(12,6,'Ergonomic Chair',399.00,'completed','2024-03-20');
|
| 99 |
+
"""
|
| 100 |
+
|
| 101 |
+
def make_db() -> sqlite3.Connection:
|
| 102 |
+
"""Create fresh in-memory SQLite database."""
|
| 103 |
+
conn = sqlite3.connect(":memory:")
|
| 104 |
+
conn.row_factory = sqlite3.Row
|
| 105 |
+
conn.executescript(SCHEMA_SQL)
|
| 106 |
+
conn.executescript(SEED_SQL)
|
| 107 |
+
conn.commit()
|
| 108 |
+
return conn
|
| 109 |
+
|
| 110 |
+
def run_query(conn: sqlite3.Connection, sql: str) -> Tuple[List[Dict], Optional[str]]:
|
| 111 |
+
"""Execute SQL, return (rows, error). rows=[] on error."""
|
| 112 |
+
try:
|
| 113 |
+
cur = conn.execute(sql)
|
| 114 |
+
rows = [dict(r) for r in cur.fetchall()]
|
| 115 |
+
return rows, None
|
| 116 |
+
except Exception as e:
|
| 117 |
+
return [], str(e)
|
| 118 |
+
|
| 119 |
+
# ---------------------------------------------------------------------------
|
| 120 |
+
# Pydantic Models
|
| 121 |
+
# ---------------------------------------------------------------------------
|
| 122 |
+
|
| 123 |
+
class SQLChallenge(BaseModel):
|
| 124 |
+
id: str
|
| 125 |
+
description: str # What the query SHOULD do (plain English)
|
| 126 |
+
broken_sql: str # The buggy SQL given to the agent
|
| 127 |
+
expected_row_count: int # How many rows the correct query returns
|
| 128 |
+
expected_columns: List[str] # Column names of correct output
|
| 129 |
+
hint: str # Nudge without giving the answer
|
| 130 |
+
difficulty: str
|
| 131 |
+
bugs: List[str] # Human-readable bug descriptions (shown to agent)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
class Observation(BaseModel):
|
| 135 |
+
challenge: SQLChallenge
|
| 136 |
+
schema_info: str # DDL so agent knows table structure
|
| 137 |
+
current_step: int
|
| 138 |
+
max_steps: int
|
| 139 |
+
task: str
|
| 140 |
+
previous_attempts: List[Dict[str, Any]] = Field(default_factory=list)
|
| 141 |
+
instructions: str
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
class Action(BaseModel):
|
| 145 |
+
challenge_id: str
|
| 146 |
+
fixed_sql: str # The agent's corrected SQL
|
| 147 |
+
explanation: Optional[str] = None # Why the original was wrong
|
| 148 |
+
detected_issues: List[str] = Field(default_factory=list) # e.g. ["missing_group_by","sql_injection"]
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
class Reward(BaseModel):
|
| 152 |
+
value: float
|
| 153 |
+
breakdown: Dict[str, float]
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
# ---------------------------------------------------------------------------
|
| 157 |
+
# Challenges Dataset
|
| 158 |
+
# ---------------------------------------------------------------------------
|
| 159 |
+
|
| 160 |
+
CHALLENGES: List[Dict] = [
|
| 161 |
+
|
| 162 |
+
# =========== EASY ===========
|
| 163 |
+
{
|
| 164 |
+
"id": "sq001",
|
| 165 |
+
"difficulty": "easy",
|
| 166 |
+
"description": "Get the names and salaries of all employees in the Engineering department, ordered by salary descending.",
|
| 167 |
+
"broken_sql": """
|
| 168 |
+
SELCT name, salary FORM users
|
| 169 |
+
WHER department = 'Engineering'
|
| 170 |
+
ORDER BY salary DESC
|
| 171 |
+
""".strip(),
|
| 172 |
+
"expected_row_count": 4,
|
| 173 |
+
"expected_columns": ["name", "salary"],
|
| 174 |
+
"hint": "Look carefully at the SQL keywords — any typos?",
|
| 175 |
+
"bugs": [
|
| 176 |
+
"Typo: SELCT should be SELECT",
|
| 177 |
+
"Typo: FORM should be FROM",
|
| 178 |
+
"Typo: WHER should be WHERE",
|
| 179 |
+
],
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"id": "sq002",
|
| 183 |
+
"difficulty": "easy",
|
| 184 |
+
"description": "Count how many orders each user has made, showing user_id and their order count.",
|
| 185 |
+
"broken_sql": """
|
| 186 |
+
SELECT user_id, COUNT(*) as order_count
|
| 187 |
+
FROM orders
|
| 188 |
+
""".strip(),
|
| 189 |
+
"expected_row_count": 7,
|
| 190 |
+
"expected_columns": ["user_id", "order_count"],
|
| 191 |
+
"hint": "When using COUNT with a non-aggregate column, something is missing.",
|
| 192 |
+
"bugs": ["Missing GROUP BY user_id — without it, SQLite returns only 1 row instead of per-user counts"],
|
| 193 |
+
},
|
| 194 |
+
{
|
| 195 |
+
"id": "sq003",
|
| 196 |
+
"difficulty": "easy",
|
| 197 |
+
"description": "Get the total revenue from all completed orders.",
|
| 198 |
+
"broken_sql": """
|
| 199 |
+
SELECT SUM(amount) AS total_revenue
|
| 200 |
+
FROM orders
|
| 201 |
+
WHERE status = 'complete'
|
| 202 |
+
""".strip(),
|
| 203 |
+
"expected_row_count": 1,
|
| 204 |
+
"expected_columns": ["total_revenue"],
|
| 205 |
+
"hint": "Check the exact value used in the WHERE condition against the schema.",
|
| 206 |
+
"bugs": ["Wrong status value: 'complete' should be 'completed' (as defined in the CHECK constraint)"],
|
| 207 |
+
},
|
| 208 |
+
|
| 209 |
+
# =========== MEDIUM ===========
|
| 210 |
+
{
|
| 211 |
+
"id": "sq004",
|
| 212 |
+
"difficulty": "medium",
|
| 213 |
+
"description": "Get each user's name and the total amount they've spent on completed orders. Include users who have no completed orders (show 0 for them).",
|
| 214 |
+
"broken_sql": """
|
| 215 |
+
SELECT u.name, SUM(o.amount) AS total_spent
|
| 216 |
+
FROM orders o
|
| 217 |
+
INNER JOIN users u ON u.id = o.user_id
|
| 218 |
+
WHERE o.status = 'completed'
|
| 219 |
+
GROUP BY u.name
|
| 220 |
+
""".strip(),
|
| 221 |
+
"expected_row_count": 8,
|
| 222 |
+
"expected_columns": ["name", "total_spent"],
|
| 223 |
+
"hint": "The problem statement says 'Include users who have no completed orders' — does INNER JOIN do that?",
|
| 224 |
+
"bugs": [
|
| 225 |
+
"INNER JOIN excludes users with no completed orders — should be LEFT JOIN from users to orders",
|
| 226 |
+
"The WHERE clause further removes non-matching rows — it should move to an ON clause or be handled with COALESCE",
|
| 227 |
+
],
|
| 228 |
+
},
|
| 229 |
+
{
|
| 230 |
+
"id": "sq005",
|
| 231 |
+
"difficulty": "medium",
|
| 232 |
+
"description": "Find all Engineering employees who earn more than the average salary of their own department.",
|
| 233 |
+
"broken_sql": """
|
| 234 |
+
SELECT name, salary
|
| 235 |
+
FROM users
|
| 236 |
+
WHERE department = 'Engineering'
|
| 237 |
+
AND salary > (SELECT AVG(salary) FROM users)
|
| 238 |
+
""".strip(),
|
| 239 |
+
"expected_row_count": 2,
|
| 240 |
+
"expected_columns": ["name", "salary"],
|
| 241 |
+
"hint": "The subquery calculates something — but is it the average of the right group?",
|
| 242 |
+
"bugs": [
|
| 243 |
+
"Subquery uses AVG(salary) across ALL departments, not just Engineering",
|
| 244 |
+
"Fix: WHERE department = 'Engineering' AND salary > (SELECT AVG(salary) FROM users WHERE department = 'Engineering')",
|
| 245 |
+
],
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"id": "sq006",
|
| 249 |
+
"difficulty": "medium",
|
| 250 |
+
"description": "Get the top 3 products by total revenue from completed orders.",
|
| 251 |
+
"broken_sql": """
|
| 252 |
+
SELECT product, SUM(amount) AS revenue
|
| 253 |
+
FROM orders
|
| 254 |
+
WHERE status = 'completed'
|
| 255 |
+
GROUP BY product
|
| 256 |
+
ORDER BY amount DESC
|
| 257 |
+
LIMIT 3
|
| 258 |
+
""".strip(),
|
| 259 |
+
"expected_row_count": 3,
|
| 260 |
+
"expected_columns": ["product", "revenue"],
|
| 261 |
+
"hint": "The ORDER BY is using a column — but which column should you order by to rank by total revenue?",
|
| 262 |
+
"bugs": [
|
| 263 |
+
"ORDER BY amount DESC orders by the raw column, not the aggregated revenue",
|
| 264 |
+
"Fix: ORDER BY revenue DESC (or ORDER BY SUM(amount) DESC)",
|
| 265 |
+
],
|
| 266 |
+
},
|
| 267 |
+
|
| 268 |
+
# =========== HARD ===========
|
| 269 |
+
{
|
| 270 |
+
"id": "sq007",
|
| 271 |
+
"difficulty": "hard",
|
| 272 |
+
"description": "For each department, show the department name, number of employees, average salary, and highest salary. Only include departments with more than 1 employee.",
|
| 273 |
+
"broken_sql": """
|
| 274 |
+
SELECT department,
|
| 275 |
+
COUNT(id),
|
| 276 |
+
AVG(salary) AS avg_salary,
|
| 277 |
+
MAX(salary)
|
| 278 |
+
FROM users
|
| 279 |
+
GROUP BY department
|
| 280 |
+
HAVING COUNT(*) > 1
|
| 281 |
+
ORDER BY avg_salary
|
| 282 |
+
""".strip(),
|
| 283 |
+
"expected_row_count": 2,
|
| 284 |
+
"expected_columns": ["department", "COUNT(id)", "avg_salary", "MAX(salary)"],
|
| 285 |
+
"hint": "The query runs but has style/clarity issues AND a subtle ordering issue. Also check column aliases.",
|
| 286 |
+
"bugs": [
|
| 287 |
+
"COUNT(id) and MAX(salary) have no aliases — hard to read, unpredictable column names in results",
|
| 288 |
+
"ORDER BY avg_salary is ascending by default — descending is typically more useful for salary reports",
|
| 289 |
+
"Minor: COUNT(id) should be COUNT(*) or COUNT(1) for clarity",
|
| 290 |
+
],
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"id": "sq008",
|
| 294 |
+
"difficulty": "hard",
|
| 295 |
+
"description": "SECURITY: A web app builds this query using user input for `dept`. Find and fix the SQL injection vulnerability. The query should return employee names for a given department.",
|
| 296 |
+
"broken_sql": """
|
| 297 |
+
SELECT name FROM users WHERE department = '\" + dept + \"'
|
| 298 |
+
""".strip(),
|
| 299 |
+
"expected_row_count": 4,
|
| 300 |
+
"expected_columns": ["name"],
|
| 301 |
+
"hint": "String concatenation of user input into SQL = SQL injection. The fix is parameterized queries.",
|
| 302 |
+
"bugs": [
|
| 303 |
+
"SQL injection vulnerability: user input `dept` is directly concatenated into the query string",
|
| 304 |
+
"Fix: Use parameterized query: SELECT name FROM users WHERE department = ? with parameters=(dept,)",
|
| 305 |
+
"For this environment, rewrite as: SELECT name FROM users WHERE department = 'Engineering'",
|
| 306 |
+
],
|
| 307 |
+
},
|
| 308 |
+
{
|
| 309 |
+
"id": "sq009",
|
| 310 |
+
"difficulty": "hard",
|
| 311 |
+
"description": "Rewrite this inefficient N+1 style subquery pattern into a single efficient JOIN query. Get user names and their most recent order's product name and amount.",
|
| 312 |
+
"broken_sql": """
|
| 313 |
+
SELECT
|
| 314 |
+
u.name,
|
| 315 |
+
(SELECT product FROM orders WHERE user_id = u.id ORDER BY created_at DESC LIMIT 1) AS last_product,
|
| 316 |
+
(SELECT amount FROM orders WHERE user_id = u.id ORDER BY created_at DESC LIMIT 1) AS last_amount
|
| 317 |
+
FROM users u
|
| 318 |
+
""".strip(),
|
| 319 |
+
"expected_row_count": 8,
|
| 320 |
+
"expected_columns": ["name", "last_product", "last_amount"],
|
| 321 |
+
"hint": "Two correlated subqueries hit the orders table twice per user. Use a CTE or subquery with ROW_NUMBER() or a ranked join instead.",
|
| 322 |
+
"bugs": [
|
| 323 |
+
"N+1 query pattern: two correlated subqueries each scan orders once per user = O(n) full scans",
|
| 324 |
+
"Fix: Use a single subquery that gets the latest order per user, then LEFT JOIN it",
|
| 325 |
+
],
|
| 326 |
+
},
|
| 327 |
+
]
|
| 328 |
+
|
| 329 |
+
CHALLENGE_MAP: Dict[str, Dict] = {c["id"]: c for c in CHALLENGES}
|
| 330 |
+
|
| 331 |
+
# Correct reference SQL for each challenge (used internally for grading)
|
| 332 |
+
REFERENCE_SQL: Dict[str, str] = {
|
| 333 |
+
"sq001": "SELECT name, salary FROM users WHERE department = 'Engineering' ORDER BY salary DESC",
|
| 334 |
+
"sq002": "SELECT user_id, COUNT(*) as order_count FROM orders GROUP BY user_id",
|
| 335 |
+
"sq003": "SELECT SUM(amount) AS total_revenue FROM orders WHERE status = 'completed'",
|
| 336 |
+
"sq004": """
|
| 337 |
+
SELECT u.name, COALESCE(SUM(o.amount), 0) AS total_spent
|
| 338 |
+
FROM users u
|
| 339 |
+
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'completed'
|
| 340 |
+
GROUP BY u.id, u.name
|
| 341 |
+
""",
|
| 342 |
+
"sq005": """
|
| 343 |
+
SELECT name, salary FROM users
|
| 344 |
+
WHERE department = 'Engineering'
|
| 345 |
+
AND salary > (SELECT AVG(salary) FROM users WHERE department = 'Engineering')
|
| 346 |
+
""",
|
| 347 |
+
"sq006": """
|
| 348 |
+
SELECT product, SUM(amount) AS revenue
|
| 349 |
+
FROM orders WHERE status = 'completed'
|
| 350 |
+
GROUP BY product ORDER BY revenue DESC LIMIT 3
|
| 351 |
+
""",
|
| 352 |
+
"sq007": """
|
| 353 |
+
SELECT department, COUNT(*) AS employee_count, AVG(salary) AS avg_salary, MAX(salary) AS max_salary
|
| 354 |
+
FROM users GROUP BY department HAVING COUNT(*) > 1 ORDER BY avg_salary DESC
|
| 355 |
+
""",
|
| 356 |
+
"sq008": "SELECT name FROM users WHERE department = 'Engineering'",
|
| 357 |
+
"sq009": """
|
| 358 |
+
SELECT u.name, latest.product AS last_product, latest.amount AS last_amount
|
| 359 |
+
FROM users u
|
| 360 |
+
LEFT JOIN (
|
| 361 |
+
SELECT user_id, product, amount,
|
| 362 |
+
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
|
| 363 |
+
FROM orders
|
| 364 |
+
) latest ON latest.user_id = u.id AND latest.rn = 1
|
| 365 |
+
""",
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
# =========== Task Config ===========
|
| 369 |
+
TASK_CONFIGS = {
|
| 370 |
+
"easy": {"challenge_ids": ["sq001", "sq002", "sq003"], "max_steps": 3},
|
| 371 |
+
"medium": {"challenge_ids": ["sq004", "sq005", "sq006"], "max_steps": 3},
|
| 372 |
+
"hard": {"challenge_ids": ["sq007", "sq008", "sq009"], "max_steps": 3},
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
SCHEMA_INFO = textwrap.dedent("""
|
| 376 |
+
TABLE users(id, name, email, department, salary, hire_date, manager_id)
|
| 377 |
+
- department: 'Engineering' | 'Marketing' | 'HR'
|
| 378 |
+
- salary: REAL
|
| 379 |
+
- manager_id: FK → users.id (nullable)
|
| 380 |
+
|
| 381 |
+
TABLE orders(id, user_id, product, amount, status, created_at)
|
| 382 |
+
- status: 'pending' | 'completed' | 'cancelled'
|
| 383 |
+
- user_id: FK → users.id
|
| 384 |
+
|
| 385 |
+
TABLE products(id, name, category, price, stock)
|
| 386 |
+
- category: 'Electronics' | 'Furniture'
|
| 387 |
+
""").strip()
|
| 388 |
+
|
| 389 |
+
INSTRUCTIONS = {
|
| 390 |
+
"easy": (
|
| 391 |
+
"EASY TASK — Fix SQL Syntax Errors\n"
|
| 392 |
+
"Each challenge gives you a broken SQL query with typos or missing keywords.\n"
|
| 393 |
+
"Fix the SQL so it runs correctly and returns the expected results.\n"
|
| 394 |
+
"Set fixed_sql to your corrected query."
|
| 395 |
+
),
|
| 396 |
+
"medium": (
|
| 397 |
+
"MEDIUM TASK — Fix Logic Bugs\n"
|
| 398 |
+
"Each query runs without error but produces WRONG results due to logic bugs:\n"
|
| 399 |
+
"wrong JOIN type, wrong column in ORDER BY, missing GROUP BY, wrong subquery scope.\n"
|
| 400 |
+
"Fix the SQL to return the correct, expected result set."
|
| 401 |
+
),
|
| 402 |
+
"hard": (
|
| 403 |
+
"HARD TASK — Fix, Optimize & Secure\n"
|
| 404 |
+
"Each query has logic bugs AND performance/security issues:\n"
|
| 405 |
+
"SQL injection vulnerabilities, N+1 correlated subqueries, missing aliases.\n"
|
| 406 |
+
"Fix ALL issues. For SQL injection: rewrite using safe parameterized form.\n"
|
| 407 |
+
"For N+1 queries: rewrite as a single efficient JOIN. List all detected_issues."
|
| 408 |
+
),
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
# ---------------------------------------------------------------------------
|
| 413 |
+
# Environment
|
| 414 |
+
# ---------------------------------------------------------------------------
|
| 415 |
+
|
| 416 |
+
class SQLDebuggerEnv:
|
| 417 |
+
"""OpenEnv-compliant SQL Debugger & Optimizer environment."""
|
| 418 |
+
|
| 419 |
+
def __init__(self, task: str = "easy"):
|
| 420 |
+
if task not in TASK_CONFIGS:
|
| 421 |
+
raise ValueError(f"Unknown task '{task}'. Choose: {list(TASK_CONFIGS)}")
|
| 422 |
+
self.task = task
|
| 423 |
+
self._cfg = TASK_CONFIGS[task]
|
| 424 |
+
self._challenges: List[SQLChallenge] = []
|
| 425 |
+
self._step = 0
|
| 426 |
+
self._done = False
|
| 427 |
+
self._history: List[Dict] = []
|
| 428 |
+
self._results: Dict[str, Dict] = {}
|
| 429 |
+
self._db: Optional[sqlite3.Connection] = None
|
| 430 |
+
self.reset()
|
| 431 |
+
|
| 432 |
+
# ------------------------------------------------------------------
|
| 433 |
+
# OpenEnv interface
|
| 434 |
+
# ------------------------------------------------------------------
|
| 435 |
+
|
| 436 |
+
def reset(self) -> Observation:
|
| 437 |
+
self._db = make_db()
|
| 438 |
+
self._challenges = [
|
| 439 |
+
SQLChallenge(**CHALLENGE_MAP[cid])
|
| 440 |
+
for cid in self._cfg["challenge_ids"]
|
| 441 |
+
]
|
| 442 |
+
self._step = 0
|
| 443 |
+
self._done = False
|
| 444 |
+
self._history = []
|
| 445 |
+
self._results = {}
|
| 446 |
+
return self._make_observation(0)
|
| 447 |
+
|
| 448 |
+
def step(self, action: Action) -> Tuple[Observation, float, bool, Dict]:
|
| 449 |
+
if self._done:
|
| 450 |
+
raise RuntimeError("Episode done. Call reset().")
|
| 451 |
+
|
| 452 |
+
self._step += 1
|
| 453 |
+
challenge = self._find_challenge(action.challenge_id)
|
| 454 |
+
idx = self._challenge_index(action.challenge_id)
|
| 455 |
+
|
| 456 |
+
if challenge is None or idx is None:
|
| 457 |
+
reward = -0.1
|
| 458 |
+
info = {"error": "invalid challenge_id", "breakdown": {}}
|
| 459 |
+
else:
|
| 460 |
+
breakdown = self._grade(challenge, action)
|
| 461 |
+
reward = max(0.0, min(1.0, sum(breakdown.values())))
|
| 462 |
+
info = {"breakdown": breakdown, "step": self._step}
|
| 463 |
+
self._results[action.challenge_id] = {
|
| 464 |
+
"action": action.model_dump(),
|
| 465 |
+
"reward": reward,
|
| 466 |
+
"breakdown": breakdown,
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
self._history.append({
|
| 470 |
+
"step": self._step,
|
| 471 |
+
"challenge_id": action.challenge_id,
|
| 472 |
+
"reward": reward,
|
| 473 |
+
"info": info,
|
| 474 |
+
})
|
| 475 |
+
|
| 476 |
+
next_idx = min(idx + 1 if idx is not None else 0, len(self._challenges) - 1)
|
| 477 |
+
self._done = (
|
| 478 |
+
self._step >= self._cfg["max_steps"]
|
| 479 |
+
or len(self._results) >= len(self._challenges)
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
obs = self._make_observation(next_idx if not self._done else idx)
|
| 483 |
+
return obs, round(reward, 4), self._done, info
|
| 484 |
+
|
| 485 |
+
def state(self) -> Dict[str, Any]:
|
| 486 |
+
return {
|
| 487 |
+
"task": self.task,
|
| 488 |
+
"step": self._step,
|
| 489 |
+
"done": self._done,
|
| 490 |
+
"challenges_total": len(self._challenges),
|
| 491 |
+
"challenges_solved": len(self._results),
|
| 492 |
+
"history": self._history,
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
def close(self):
|
| 496 |
+
if self._db:
|
| 497 |
+
self._db.close()
|
| 498 |
+
|
| 499 |
+
def episode_score(self) -> float:
|
| 500 |
+
if not self._results:
|
| 501 |
+
return 0.0
|
| 502 |
+
total = sum(r["reward"] for r in self._results.values())
|
| 503 |
+
return round(total / len(self._challenges), 4)
|
| 504 |
+
|
| 505 |
+
# ------------------------------------------------------------------
|
| 506 |
+
# Grading (deterministic — runs real SQL)
|
| 507 |
+
# ------------------------------------------------------------------
|
| 508 |
+
|
| 509 |
+
def _grade(self, challenge: SQLChallenge, action: Action) -> Dict[str, float]:
|
| 510 |
+
bd: Dict[str, float] = {}
|
| 511 |
+
fixed_sql = action.fixed_sql.strip()
|
| 512 |
+
|
| 513 |
+
# 1. Syntax check — does the fixed SQL run at all?
|
| 514 |
+
rows, error = run_query(self._db, fixed_sql)
|
| 515 |
+
if error:
|
| 516 |
+
bd["syntax_error"] = 0.0
|
| 517 |
+
bd["parse_penalty"] = -0.05
|
| 518 |
+
return bd
|
| 519 |
+
bd["syntax_ok"] = 0.20
|
| 520 |
+
|
| 521 |
+
# 2. Row count match
|
| 522 |
+
ref_rows, _ = run_query(self._db, REFERENCE_SQL[challenge.id])
|
| 523 |
+
if len(rows) == len(ref_rows):
|
| 524 |
+
bd["row_count_correct"] = 0.25
|
| 525 |
+
elif abs(len(rows) - len(ref_rows)) <= 1:
|
| 526 |
+
bd["row_count_close"] = 0.10
|
| 527 |
+
|
| 528 |
+
# 3. Column names match
|
| 529 |
+
if rows and ref_rows:
|
| 530 |
+
pred_cols = set(rows[0].keys())
|
| 531 |
+
ref_cols = set(ref_rows[0].keys())
|
| 532 |
+
col_overlap = len(pred_cols & ref_cols) / max(len(ref_cols), 1)
|
| 533 |
+
bd["columns"] = round(col_overlap * 0.15, 4)
|
| 534 |
+
|
| 535 |
+
# 4. Data correctness — compare sorted string representations
|
| 536 |
+
if rows and ref_rows and len(rows) == len(ref_rows):
|
| 537 |
+
pred_str = sorted(str(sorted(r.items())) for r in rows)
|
| 538 |
+
ref_str = sorted(str(sorted(r.items())) for r in ref_rows)
|
| 539 |
+
if pred_str == ref_str:
|
| 540 |
+
bd["data_exact"] = 0.30
|
| 541 |
+
else:
|
| 542 |
+
# Partial: at least first-column values match
|
| 543 |
+
pred_first = sorted(str(list(r.values())[0]) for r in rows)
|
| 544 |
+
ref_first = sorted(str(list(r.values())[0]) for r in ref_rows)
|
| 545 |
+
if pred_first == ref_first:
|
| 546 |
+
bd["data_partial"] = 0.15
|
| 547 |
+
|
| 548 |
+
# 5. Security grading (hard task: injection)
|
| 549 |
+
if challenge.id == "sq008":
|
| 550 |
+
sql_lower = fixed_sql.lower()
|
| 551 |
+
injection_pattern = "dept"
|
| 552 |
+
if injection_pattern not in fixed_sql and "concat" not in sql_lower:
|
| 553 |
+
bd["injection_removed"] = 0.10
|
| 554 |
+
# Reward parameterized hint in explanation
|
| 555 |
+
if action.explanation and any(
|
| 556 |
+
kw in action.explanation.lower()
|
| 557 |
+
for kw in ["parameterized", "prepared", "placeholder", "?", "injection"]
|
| 558 |
+
):
|
| 559 |
+
bd["security_explanation"] = 0.05
|
| 560 |
+
|
| 561 |
+
# 6. Optimization grading (hard task: N+1)
|
| 562 |
+
if challenge.id == "sq009":
|
| 563 |
+
sql_lower = fixed_sql.lower()
|
| 564 |
+
# Penalize correlated subquery pattern still present
|
| 565 |
+
if "select" in sql_lower[sql_lower.find("select")+6:]: # nested SELECT
|
| 566 |
+
subq_count = sql_lower.count("select")
|
| 567 |
+
if subq_count <= 2: # CTE or single subquery = OK
|
| 568 |
+
bd["optimization_ok"] = 0.05
|
| 569 |
+
else:
|
| 570 |
+
bd["n_plus_1_penalty"] = -0.10
|
| 571 |
+
if "join" in sql_lower:
|
| 572 |
+
bd["uses_join"] = 0.05
|
| 573 |
+
|
| 574 |
+
# 7. Explanation quality bonus
|
| 575 |
+
if action.explanation and len(action.explanation.split()) >= 8:
|
| 576 |
+
bd["explanation_bonus"] = 0.05
|
| 577 |
+
|
| 578 |
+
# 8. Bug detection quality (hard task)
|
| 579 |
+
if self.task == "hard" and action.detected_issues:
|
| 580 |
+
bd["issues_detected"] = min(0.05, len(action.detected_issues) * 0.02)
|
| 581 |
+
|
| 582 |
+
return bd
|
| 583 |
+
|
| 584 |
+
# ------------------------------------------------------------------
|
| 585 |
+
# Helpers
|
| 586 |
+
# ------------------------------------------------------------------
|
| 587 |
+
|
| 588 |
+
def _find_challenge(self, cid: str) -> Optional[SQLChallenge]:
|
| 589 |
+
for c in self._challenges:
|
| 590 |
+
if c.id == cid:
|
| 591 |
+
return c
|
| 592 |
+
return None
|
| 593 |
+
|
| 594 |
+
def _challenge_index(self, cid: str) -> Optional[int]:
|
| 595 |
+
for i, c in enumerate(self._challenges):
|
| 596 |
+
if c.id == cid:
|
| 597 |
+
return i
|
| 598 |
+
return None
|
| 599 |
+
|
| 600 |
+
def _make_observation(self, idx: int) -> Observation:
|
| 601 |
+
challenge = self._challenges[min(idx, len(self._challenges) - 1)]
|
| 602 |
+
return Observation(
|
| 603 |
+
challenge=challenge,
|
| 604 |
+
schema_info=SCHEMA_INFO,
|
| 605 |
+
current_step=self._step,
|
| 606 |
+
max_steps=self._cfg["max_steps"],
|
| 607 |
+
task=self.task,
|
| 608 |
+
previous_attempts=[
|
| 609 |
+
h for h in self._history
|
| 610 |
+
if h["challenge_id"] == challenge.id
|
| 611 |
+
],
|
| 612 |
+
instructions=INSTRUCTIONS[self.task],
|
| 613 |
+
)
|
uv.lock
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version = 1
|
| 2 |
+
revision = 3
|
| 3 |
+
requires-python = ">=3.10, <3.12"
|
| 4 |
+
resolution-markers = [
|
| 5 |
+
"python_full_version >= '3.11'",
|
| 6 |
+
"python_full_version < '3.11'",
|
| 7 |
+
]
|
| 8 |
+
|
| 9 |
+
[[package]]
|
| 10 |
+
name = "annotated-doc"
|
| 11 |
+
version = "0.0.4"
|
| 12 |
+
source = { registry = "https://pypi.org/simple" }
|
| 13 |
+
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
|
| 14 |
+
wheels = [
|
| 15 |
+
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
[[package]]
|
| 19 |
+
name = "annotated-types"
|
| 20 |
+
version = "0.7.0"
|
| 21 |
+
source = { registry = "https://pypi.org/simple" }
|
| 22 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
| 23 |
+
wheels = [
|
| 24 |
+
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
[[package]]
|
| 28 |
+
name = "anyio"
|
| 29 |
+
version = "4.13.0"
|
| 30 |
+
source = { registry = "https://pypi.org/simple" }
|
| 31 |
+
dependencies = [
|
| 32 |
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
| 33 |
+
{ name = "idna" },
|
| 34 |
+
{ name = "typing-extensions" },
|
| 35 |
+
]
|
| 36 |
+
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
|
| 37 |
+
wheels = [
|
| 38 |
+
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
[[package]]
|
| 42 |
+
name = "click"
|
| 43 |
+
version = "8.3.2"
|
| 44 |
+
source = { registry = "https://pypi.org/simple" }
|
| 45 |
+
dependencies = [
|
| 46 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 47 |
+
]
|
| 48 |
+
sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" }
|
| 49 |
+
wheels = [
|
| 50 |
+
{ url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" },
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
[[package]]
|
| 54 |
+
name = "colorama"
|
| 55 |
+
version = "0.4.6"
|
| 56 |
+
source = { registry = "https://pypi.org/simple" }
|
| 57 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
| 58 |
+
wheels = [
|
| 59 |
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
| 60 |
+
]
|
| 61 |
+
|
| 62 |
+
[[package]]
|
| 63 |
+
name = "exceptiongroup"
|
| 64 |
+
version = "1.3.1"
|
| 65 |
+
source = { registry = "https://pypi.org/simple" }
|
| 66 |
+
dependencies = [
|
| 67 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
| 68 |
+
]
|
| 69 |
+
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
| 70 |
+
wheels = [
|
| 71 |
+
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
| 72 |
+
]
|
| 73 |
+
|
| 74 |
+
[[package]]
|
| 75 |
+
name = "fastapi"
|
| 76 |
+
version = "0.135.3"
|
| 77 |
+
source = { registry = "https://pypi.org/simple" }
|
| 78 |
+
dependencies = [
|
| 79 |
+
{ name = "annotated-doc" },
|
| 80 |
+
{ name = "pydantic" },
|
| 81 |
+
{ name = "starlette" },
|
| 82 |
+
{ name = "typing-extensions" },
|
| 83 |
+
{ name = "typing-inspection" },
|
| 84 |
+
]
|
| 85 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f7/e6/7adb4c5fa231e82c35b8f5741a9f2d055f520c29af5546fd70d3e8e1cd2e/fastapi-0.135.3.tar.gz", hash = "sha256:bd6d7caf1a2bdd8d676843cdcd2287729572a1ef524fc4d65c17ae002a1be654", size = 396524, upload-time = "2026-04-01T16:23:58.188Z" }
|
| 86 |
+
wheels = [
|
| 87 |
+
{ url = "https://files.pythonhosted.org/packages/84/a4/5caa2de7f917a04ada20018eccf60d6cc6145b0199d55ca3711b0fc08312/fastapi-0.135.3-py3-none-any.whl", hash = "sha256:9b0f590c813acd13d0ab43dd8494138eb58e484bfac405db1f3187cfc5810d98", size = 117734, upload-time = "2026-04-01T16:23:59.328Z" },
|
| 88 |
+
]
|
| 89 |
+
|
| 90 |
+
[[package]]
|
| 91 |
+
name = "h11"
|
| 92 |
+
version = "0.16.0"
|
| 93 |
+
source = { registry = "https://pypi.org/simple" }
|
| 94 |
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
| 95 |
+
wheels = [
|
| 96 |
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
| 97 |
+
]
|
| 98 |
+
|
| 99 |
+
[[package]]
|
| 100 |
+
name = "idna"
|
| 101 |
+
version = "3.11"
|
| 102 |
+
source = { registry = "https://pypi.org/simple" }
|
| 103 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
| 104 |
+
wheels = [
|
| 105 |
+
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
| 106 |
+
]
|
| 107 |
+
|
| 108 |
+
[[package]]
|
| 109 |
+
name = "numpy"
|
| 110 |
+
version = "2.2.6"
|
| 111 |
+
source = { registry = "https://pypi.org/simple" }
|
| 112 |
+
resolution-markers = [
|
| 113 |
+
"python_full_version < '3.11'",
|
| 114 |
+
]
|
| 115 |
+
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
|
| 116 |
+
wheels = [
|
| 117 |
+
{ url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" },
|
| 118 |
+
{ url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" },
|
| 119 |
+
{ url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" },
|
| 120 |
+
{ url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" },
|
| 121 |
+
{ url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" },
|
| 122 |
+
{ url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" },
|
| 123 |
+
{ url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" },
|
| 124 |
+
{ url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" },
|
| 125 |
+
{ url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" },
|
| 126 |
+
{ url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" },
|
| 127 |
+
{ url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
|
| 128 |
+
{ url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
|
| 129 |
+
{ url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
|
| 130 |
+
{ url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
|
| 131 |
+
{ url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
|
| 132 |
+
{ url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
|
| 133 |
+
{ url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
|
| 134 |
+
{ url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
|
| 135 |
+
{ url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
|
| 136 |
+
{ url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
|
| 137 |
+
{ url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" },
|
| 138 |
+
{ url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" },
|
| 139 |
+
{ url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" },
|
| 140 |
+
{ url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" },
|
| 141 |
+
]
|
| 142 |
+
|
| 143 |
+
[[package]]
|
| 144 |
+
name = "numpy"
|
| 145 |
+
version = "2.4.4"
|
| 146 |
+
source = { registry = "https://pypi.org/simple" }
|
| 147 |
+
resolution-markers = [
|
| 148 |
+
"python_full_version >= '3.11'",
|
| 149 |
+
]
|
| 150 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" }
|
| 151 |
+
wheels = [
|
| 152 |
+
{ url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" },
|
| 153 |
+
{ url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" },
|
| 154 |
+
{ url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" },
|
| 155 |
+
{ url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" },
|
| 156 |
+
{ url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" },
|
| 157 |
+
{ url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" },
|
| 158 |
+
{ url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" },
|
| 159 |
+
{ url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" },
|
| 160 |
+
{ url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" },
|
| 161 |
+
{ url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" },
|
| 162 |
+
{ url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" },
|
| 163 |
+
{ url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" },
|
| 164 |
+
{ url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" },
|
| 165 |
+
{ url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" },
|
| 166 |
+
{ url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" },
|
| 167 |
+
{ url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" },
|
| 168 |
+
{ url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" },
|
| 169 |
+
{ url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" },
|
| 170 |
+
]
|
| 171 |
+
|
| 172 |
+
[[package]]
|
| 173 |
+
name = "openenv"
|
| 174 |
+
version = "0.1.13"
|
| 175 |
+
source = { registry = "https://pypi.org/simple" }
|
| 176 |
+
dependencies = [
|
| 177 |
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
| 178 |
+
{ name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
| 179 |
+
]
|
| 180 |
+
sdist = { url = "https://files.pythonhosted.org/packages/35/94/c47e8f7303452793a3519c8cbc1b31dfffdedd13aaed821958ab3f152927/openenv-0.1.13.tar.gz", hash = "sha256:726971d2289472c1c20261436bcccdf3edfcf0b201d16aec127815bd83bfcb3d", size = 5112, upload-time = "2020-12-16T11:49:39.777Z" }
|
| 181 |
+
wheels = [
|
| 182 |
+
{ url = "https://files.pythonhosted.org/packages/33/7f/e6f4467528161b8f0eb2ec784f4bbcd1fa9ea7acad13c0fb18597013e83b/openenv-0.1.13-py3-none-any.whl", hash = "sha256:813249d7f526f40c6e8b325f705294761a5bc887b9144c3383fa2bae7baa7726", size = 12080, upload-time = "2020-12-16T11:49:38.816Z" },
|
| 183 |
+
]
|
| 184 |
+
|
| 185 |
+
[[package]]
|
| 186 |
+
name = "pydantic"
|
| 187 |
+
version = "2.12.5"
|
| 188 |
+
source = { registry = "https://pypi.org/simple" }
|
| 189 |
+
dependencies = [
|
| 190 |
+
{ name = "annotated-types" },
|
| 191 |
+
{ name = "pydantic-core" },
|
| 192 |
+
{ name = "typing-extensions" },
|
| 193 |
+
{ name = "typing-inspection" },
|
| 194 |
+
]
|
| 195 |
+
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
|
| 196 |
+
wheels = [
|
| 197 |
+
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
|
| 198 |
+
]
|
| 199 |
+
|
| 200 |
+
[[package]]
|
| 201 |
+
name = "pydantic-core"
|
| 202 |
+
version = "2.41.5"
|
| 203 |
+
source = { registry = "https://pypi.org/simple" }
|
| 204 |
+
dependencies = [
|
| 205 |
+
{ name = "typing-extensions" },
|
| 206 |
+
]
|
| 207 |
+
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
| 208 |
+
wheels = [
|
| 209 |
+
{ url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" },
|
| 210 |
+
{ url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" },
|
| 211 |
+
{ url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" },
|
| 212 |
+
{ url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" },
|
| 213 |
+
{ url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" },
|
| 214 |
+
{ url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" },
|
| 215 |
+
{ url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" },
|
| 216 |
+
{ url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" },
|
| 217 |
+
{ url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" },
|
| 218 |
+
{ url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" },
|
| 219 |
+
{ url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" },
|
| 220 |
+
{ url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" },
|
| 221 |
+
{ url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" },
|
| 222 |
+
{ url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
|
| 223 |
+
{ url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
|
| 224 |
+
{ url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
|
| 225 |
+
{ url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
|
| 226 |
+
{ url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
|
| 227 |
+
{ url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
|
| 228 |
+
{ url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
|
| 229 |
+
{ url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
|
| 230 |
+
{ url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
|
| 231 |
+
{ url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
|
| 232 |
+
{ url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
|
| 233 |
+
{ url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
|
| 234 |
+
{ url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
|
| 235 |
+
{ url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
|
| 236 |
+
{ url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
|
| 237 |
+
{ url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
|
| 238 |
+
{ url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
|
| 239 |
+
{ url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
|
| 240 |
+
{ url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" },
|
| 241 |
+
{ url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" },
|
| 242 |
+
{ url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" },
|
| 243 |
+
{ url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" },
|
| 244 |
+
{ url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" },
|
| 245 |
+
{ url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" },
|
| 246 |
+
{ url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" },
|
| 247 |
+
{ url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" },
|
| 248 |
+
{ url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
|
| 249 |
+
{ url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
|
| 250 |
+
{ url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
|
| 251 |
+
{ url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
|
| 252 |
+
{ url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
|
| 253 |
+
{ url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
|
| 254 |
+
{ url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
|
| 255 |
+
{ url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
|
| 256 |
+
]
|
| 257 |
+
|
| 258 |
+
[[package]]
|
| 259 |
+
name = "sql-debugger-env"
|
| 260 |
+
version = "0.1.0"
|
| 261 |
+
source = { editable = "." }
|
| 262 |
+
dependencies = [
|
| 263 |
+
{ name = "fastapi" },
|
| 264 |
+
{ name = "openenv" },
|
| 265 |
+
{ name = "pydantic" },
|
| 266 |
+
{ name = "uvicorn" },
|
| 267 |
+
]
|
| 268 |
+
|
| 269 |
+
[package.metadata]
|
| 270 |
+
requires-dist = [
|
| 271 |
+
{ name = "fastapi" },
|
| 272 |
+
{ name = "openenv" },
|
| 273 |
+
{ name = "pydantic" },
|
| 274 |
+
{ name = "uvicorn" },
|
| 275 |
+
]
|
| 276 |
+
|
| 277 |
+
[[package]]
|
| 278 |
+
name = "starlette"
|
| 279 |
+
version = "1.0.0"
|
| 280 |
+
source = { registry = "https://pypi.org/simple" }
|
| 281 |
+
dependencies = [
|
| 282 |
+
{ name = "anyio" },
|
| 283 |
+
{ name = "typing-extensions" },
|
| 284 |
+
]
|
| 285 |
+
sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" }
|
| 286 |
+
wheels = [
|
| 287 |
+
{ url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
|
| 288 |
+
]
|
| 289 |
+
|
| 290 |
+
[[package]]
|
| 291 |
+
name = "typing-extensions"
|
| 292 |
+
version = "4.15.0"
|
| 293 |
+
source = { registry = "https://pypi.org/simple" }
|
| 294 |
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
| 295 |
+
wheels = [
|
| 296 |
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
| 297 |
+
]
|
| 298 |
+
|
| 299 |
+
[[package]]
|
| 300 |
+
name = "typing-inspection"
|
| 301 |
+
version = "0.4.2"
|
| 302 |
+
source = { registry = "https://pypi.org/simple" }
|
| 303 |
+
dependencies = [
|
| 304 |
+
{ name = "typing-extensions" },
|
| 305 |
+
]
|
| 306 |
+
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
| 307 |
+
wheels = [
|
| 308 |
+
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
| 309 |
+
]
|
| 310 |
+
|
| 311 |
+
[[package]]
|
| 312 |
+
name = "uvicorn"
|
| 313 |
+
version = "0.44.0"
|
| 314 |
+
source = { registry = "https://pypi.org/simple" }
|
| 315 |
+
dependencies = [
|
| 316 |
+
{ name = "click" },
|
| 317 |
+
{ name = "h11" },
|
| 318 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
| 319 |
+
]
|
| 320 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5e/da/6eee1ff8b6cbeed47eeb5229749168e81eb4b7b999a1a15a7176e51410c9/uvicorn-0.44.0.tar.gz", hash = "sha256:6c942071b68f07e178264b9152f1f16dfac5da85880c4ce06366a96d70d4f31e", size = 86947, upload-time = "2026-04-06T09:23:22.826Z" }
|
| 321 |
+
wheels = [
|
| 322 |
+
{ url = "https://files.pythonhosted.org/packages/b7/23/a5bbd9600dd607411fa644c06ff4951bec3a4d82c4b852374024359c19c0/uvicorn-0.44.0-py3-none-any.whl", hash = "sha256:ce937c99a2cc70279556967274414c087888e8cec9f9c94644dfca11bd3ced89", size = 69425, upload-time = "2026-04-06T09:23:21.524Z" },
|
| 323 |
+
]
|