junaid0600 commited on
Commit
f812d5b
·
0 Parent(s):

Initial commit

Browse files
.env.example ADDED
File without changes
Dockerfile ADDED
File without changes
README.md ADDED
File without changes
api/__init__.py ADDED
File without changes
api/server.py ADDED
File without changes
baseline.py ADDED
File without changes
dataset/easy_cases.json ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "easy_001",
4
+ "category": "syntax",
5
+ "description": "Fix the SQL syntax error: missing comma in SELECT clause.",
6
+ "buggy_query": "SELECT id name email FROM users WHERE active = 1",
7
+ "fixed_query": "SELECT id, name, email FROM users WHERE active = 1",
8
+ "error_message": "ERROR: syntax error at or near 'name'",
9
+ "database_schema": {
10
+ "users": ["id INT PRIMARY KEY", "name VARCHAR(100)", "email VARCHAR(100)", "active INT"]
11
+ },
12
+ "expected_output": [
13
+ {"id": 1, "name": "Alice", "email": "alice@example.com"},
14
+ {"id": 2, "name": "Bob", "email": "bob@example.com"}
15
+ ],
16
+ "error_type": "syntax",
17
+ "error_location": "SELECT clause",
18
+ "fix_description": "Add missing commas between column names in SELECT clause",
19
+ "estimated_fix_steps": 2,
20
+ "partial_credit_hints": {
21
+ "identifies_location": 0.2,
22
+ "identifies_error_type": 0.2,
23
+ "correct_fix": 0.5,
24
+ "explanation": 0.1
25
+ }
26
+ },
27
+ {
28
+ "id": "easy_002",
29
+ "category": "syntax",
30
+ "description": "Fix the SQL syntax error: missing WHERE keyword.",
31
+ "buggy_query": "SELECT * FROM orders id = 101",
32
+ "fixed_query": "SELECT * FROM orders WHERE id = 101",
33
+ "error_message": "ERROR: syntax error at or near 'id'",
34
+ "database_schema": {
35
+ "orders": ["id INT PRIMARY KEY", "user_id INT", "total DECIMAL", "status VARCHAR(20)"]
36
+ },
37
+ "expected_output": [
38
+ {"id": 101, "user_id": 5, "total": 250.00, "status": "shipped"}
39
+ ],
40
+ "error_type": "syntax",
41
+ "error_location": "FROM clause",
42
+ "fix_description": "Add missing WHERE keyword before the filter condition",
43
+ "estimated_fix_steps": 2,
44
+ "partial_credit_hints": {
45
+ "identifies_location": 0.2,
46
+ "identifies_error_type": 0.2,
47
+ "correct_fix": 0.5,
48
+ "explanation": 0.1
49
+ }
50
+ },
51
+ {
52
+ "id": "easy_003",
53
+ "category": "syntax",
54
+ "description": "Fix the SQL syntax error: unclosed string literal.",
55
+ "buggy_query": "SELECT * FROM products WHERE category = 'electronics",
56
+ "fixed_query": "SELECT * FROM products WHERE category = 'electronics'",
57
+ "error_message": "ERROR: unterminated quoted string at or near \"'electronics\"",
58
+ "database_schema": {
59
+ "products": ["id INT PRIMARY KEY", "name VARCHAR(100)", "category VARCHAR(50)", "price DECIMAL"]
60
+ },
61
+ "expected_output": [
62
+ {"id": 3, "name": "Laptop", "category": "electronics", "price": 999.99}
63
+ ],
64
+ "error_type": "syntax",
65
+ "error_location": "WHERE clause string literal",
66
+ "fix_description": "Close the single quote at the end of 'electronics'",
67
+ "estimated_fix_steps": 1,
68
+ "partial_credit_hints": {
69
+ "identifies_location": 0.2,
70
+ "identifies_error_type": 0.2,
71
+ "correct_fix": 0.5,
72
+ "explanation": 0.1
73
+ }
74
+ },
75
+ {
76
+ "id": "easy_004",
77
+ "category": "syntax",
78
+ "description": "Fix the SQL syntax error: wrong keyword ORDER used instead of ORDER BY.",
79
+ "buggy_query": "SELECT id, name, price FROM products ORDER price DESC",
80
+ "fixed_query": "SELECT id, name, price FROM products ORDER BY price DESC",
81
+ "error_message": "ERROR: syntax error at or near 'price'",
82
+ "database_schema": {
83
+ "products": ["id INT PRIMARY KEY", "name VARCHAR(100)", "category VARCHAR(50)", "price DECIMAL"]
84
+ },
85
+ "expected_output": [
86
+ {"id": 7, "name": "TV", "price": 1200.00},
87
+ {"id": 3, "name": "Laptop", "price": 999.99}
88
+ ],
89
+ "error_type": "syntax",
90
+ "error_location": "ORDER clause",
91
+ "fix_description": "Replace ORDER with ORDER BY",
92
+ "estimated_fix_steps": 1,
93
+ "partial_credit_hints": {
94
+ "identifies_location": 0.2,
95
+ "identifies_error_type": 0.2,
96
+ "correct_fix": 0.5,
97
+ "explanation": 0.1
98
+ }
99
+ },
100
+ {
101
+ "id": "easy_005",
102
+ "category": "syntax",
103
+ "description": "Fix the SQL syntax error: GROUP used instead of GROUP BY.",
104
+ "buggy_query": "SELECT department, COUNT(*) FROM employees GROUP department",
105
+ "fixed_query": "SELECT department, COUNT(*) FROM employees GROUP BY department",
106
+ "error_message": "ERROR: syntax error at or near 'department'",
107
+ "database_schema": {
108
+ "employees": ["id INT PRIMARY KEY", "name VARCHAR(100)", "department VARCHAR(50)", "salary DECIMAL"]
109
+ },
110
+ "expected_output": [
111
+ {"department": "Engineering", "count": 12},
112
+ {"department": "Marketing", "count": 8}
113
+ ],
114
+ "error_type": "syntax",
115
+ "error_location": "GROUP clause",
116
+ "fix_description": "Replace GROUP with GROUP BY",
117
+ "estimated_fix_steps": 1,
118
+ "partial_credit_hints": {
119
+ "identifies_location": 0.2,
120
+ "identifies_error_type": 0.2,
121
+ "correct_fix": 0.5,
122
+ "explanation": 0.1
123
+ }
124
+ }
125
+ ]
dataset/hard_cases.json ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "hard_001",
4
+ "category": "performance",
5
+ "description": "Detect and fix N+1 query pattern: query fetches all users then runs a subquery per user inside SELECT, causing O(n) database hits.",
6
+ "buggy_query": "SELECT id, name, (SELECT COUNT(*) FROM orders WHERE user_id = u.id) as order_count, (SELECT SUM(total) FROM orders WHERE user_id = u.id) as total_spent FROM users u",
7
+ "fixed_query": "SELECT u.id, u.name, COUNT(o.id) as order_count, COALESCE(SUM(o.total), 0) as total_spent FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name",
8
+ "error_message": "No error thrown — query is functionally correct but causes severe performance degradation at scale (O(n) subqueries)",
9
+ "database_schema": {
10
+ "users": ["id INT PRIMARY KEY", "name VARCHAR(100)", "email VARCHAR(100)"],
11
+ "orders": ["id INT PRIMARY KEY", "user_id INT REFERENCES users(id)", "total DECIMAL", "status VARCHAR(20)"]
12
+ },
13
+ "performance_issue": {
14
+ "type": "N+1 query",
15
+ "impact": "With 10,000 users: 20,001 database round trips vs 1",
16
+ "execution_time_buggy_ms": 8500,
17
+ "execution_time_fixed_ms": 45
18
+ },
19
+ "expected_output": [
20
+ {"id": 1, "name": "Alice", "order_count": 3, "total_spent": 750.00},
21
+ {"id": 2, "name": "Bob", "order_count": 0, "total_spent": 0.00}
22
+ ],
23
+ "error_type": "performance",
24
+ "error_location": "Correlated subqueries in SELECT clause",
25
+ "fix_description": "Replace correlated subqueries with a single LEFT JOIN + GROUP BY aggregation using COALESCE for null safety",
26
+ "estimated_fix_steps": 6,
27
+ "scoring_rubric": {
28
+ "identifies_n_plus_1_pattern": 0.15,
29
+ "identifies_correlated_subquery_cause": 0.1,
30
+ "proposes_join_solution": 0.1,
31
+ "correct_left_join": 0.1,
32
+ "correct_aggregation": 0.1,
33
+ "coalesce_null_safety": 0.05,
34
+ "explanation_quality": 0.1,
35
+ "confidence": 0.05
36
+ },
37
+ "frontier_model_expected_score": 0.15
38
+ },
39
+ {
40
+ "id": "hard_002",
41
+ "category": "performance",
42
+ "description": "Fix missing index causing full table scan: query filters on non-indexed column in 10M row table, fix by identifying missing index and rewriting with index-friendly pattern.",
43
+ "buggy_query": "SELECT * FROM logs WHERE YEAR(created_at) = 2024 AND MONTH(created_at) = 3",
44
+ "fixed_query": "SELECT * FROM logs WHERE created_at >= '2024-03-01' AND created_at < '2024-04-01'",
45
+ "error_message": "No error thrown — runs but causes full table scan because function on indexed column prevents index use",
46
+ "database_schema": {
47
+ "logs": ["id BIGINT PRIMARY KEY", "user_id INT", "action VARCHAR(100)", "created_at TIMESTAMP", "INDEX(created_at)"]
48
+ },
49
+ "performance_issue": {
50
+ "type": "Function on indexed column prevents index use (full table scan)",
51
+ "impact": "10M row table: full scan 12s vs index seek 8ms",
52
+ "execution_time_buggy_ms": 12000,
53
+ "execution_time_fixed_ms": 8,
54
+ "rows_scanned_buggy": 10000000,
55
+ "rows_scanned_fixed": 85000
56
+ },
57
+ "expected_output": "Same rows — functionally equivalent but uses index",
58
+ "error_type": "performance",
59
+ "error_location": "WHERE clause: YEAR() and MONTH() functions prevent index usage on created_at",
60
+ "fix_description": "Replace function-wrapped column with range comparison using explicit date literals so the index on created_at is used",
61
+ "estimated_fix_steps": 5,
62
+ "scoring_rubric": {
63
+ "identifies_function_on_column_issue": 0.15,
64
+ "identifies_index_not_used": 0.15,
65
+ "correct_range_rewrite": 0.15,
66
+ "correct_date_boundaries": 0.1,
67
+ "explanation_quality": 0.1,
68
+ "confidence": 0.05
69
+ },
70
+ "frontier_model_expected_score": 0.12
71
+ },
72
+ {
73
+ "id": "hard_003",
74
+ "category": "performance",
75
+ "description": "Fix implicit cartesian product: missing JOIN condition between two large tables causes cross join — billions of rows.",
76
+ "buggy_query": "SELECT c.name, o.total FROM customers c, orders o WHERE o.total > 1000",
77
+ "fixed_query": "SELECT c.name, o.total FROM customers c JOIN orders o ON c.id = o.customer_id WHERE o.total > 1000",
78
+ "error_message": "No error thrown — query returns astronomically wrong result set (cartesian product)",
79
+ "database_schema": {
80
+ "customers": ["id INT PRIMARY KEY", "name VARCHAR(100)", "country VARCHAR(50)"],
81
+ "orders": ["id INT PRIMARY KEY", "customer_id INT REFERENCES customers(id)", "total DECIMAL", "status VARCHAR(20)"]
82
+ },
83
+ "performance_issue": {
84
+ "type": "Implicit cartesian product (missing JOIN condition)",
85
+ "impact": "50K customers × 200K orders = 10 billion intermediate rows",
86
+ "execution_time_buggy_ms": 999999,
87
+ "execution_time_fixed_ms": 120
88
+ },
89
+ "expected_output": [
90
+ {"name": "Alice Corp", "total": 15000.00},
91
+ {"name": "Bob Ltd", "total": 3200.00}
92
+ ],
93
+ "error_type": "performance",
94
+ "error_location": "FROM clause — two tables listed with comma but no JOIN condition",
95
+ "fix_description": "Replace implicit comma join with explicit JOIN ON c.id = o.customer_id to eliminate cartesian product",
96
+ "estimated_fix_steps": 5,
97
+ "scoring_rubric": {
98
+ "identifies_cartesian_product": 0.15,
99
+ "identifies_missing_join_condition": 0.15,
100
+ "correct_explicit_join": 0.15,
101
+ "correct_join_condition": 0.1,
102
+ "explanation_quality": 0.1,
103
+ "confidence": 0.05
104
+ },
105
+ "frontier_model_expected_score": 0.18
106
+ },
107
+ {
108
+ "id": "hard_004",
109
+ "category": "performance",
110
+ "description": "Fix SELECT * in JOIN query causing unnecessary data transfer and preventing covering index: rewrite to select only needed columns.",
111
+ "buggy_query": "SELECT * FROM orders o JOIN users u ON o.user_id = u.id JOIN products p ON o.product_id = p.id WHERE o.status = 'pending'",
112
+ "fixed_query": "SELECT o.id, o.total, o.status, u.name as user_name, u.email, p.name as product_name, p.price FROM orders o JOIN users u ON o.user_id = u.id JOIN products p ON o.product_id = p.id WHERE o.status = 'pending'",
113
+ "error_message": "No error — functionally correct but causes over-fetching, prevents covering index, increases memory pressure",
114
+ "database_schema": {
115
+ "orders": ["id INT PRIMARY KEY", "user_id INT", "product_id INT", "total DECIMAL", "status VARCHAR(20)", "created_at TIMESTAMP", "updated_at TIMESTAMP", "notes TEXT"],
116
+ "users": ["id INT PRIMARY KEY", "name VARCHAR(100)", "email VARCHAR(100)", "password_hash VARCHAR(255)", "created_at TIMESTAMP"],
117
+ "products": ["id INT PRIMARY KEY", "name VARCHAR(100)", "price DECIMAL", "stock INT", "description TEXT", "image_url VARCHAR(500)"]
118
+ },
119
+ "performance_issue": {
120
+ "type": "SELECT * with JOINs causes over-fetching and prevents covering index",
121
+ "impact": "Fetches 15+ columns per row including TEXT blobs vs 7 needed columns. 3x memory overhead.",
122
+ "execution_time_buggy_ms": 890,
123
+ "execution_time_fixed_ms": 210
124
+ },
125
+ "error_type": "performance",
126
+ "error_location": "SELECT * across 3-table JOIN",
127
+ "fix_description": "Replace SELECT * with explicit column list selecting only the 7 columns actually needed by the application",
128
+ "estimated_fix_steps": 5,
129
+ "scoring_rubric": {
130
+ "identifies_select_star_issue": 0.15,
131
+ "identifies_over_fetching": 0.1,
132
+ "identifies_covering_index_benefit": 0.1,
133
+ "correct_column_selection": 0.15,
134
+ "correct_table_aliases": 0.1,
135
+ "explanation_quality": 0.1,
136
+ "confidence": 0.05
137
+ },
138
+ "frontier_model_expected_score": 0.20
139
+ },
140
+ {
141
+ "id": "hard_005",
142
+ "category": "performance",
143
+ "description": "Fix window function misuse: ROW_NUMBER() applied without PARTITION causing global ranking instead of per-department ranking, and without ORDER BY making results non-deterministic.",
144
+ "buggy_query": "SELECT id, name, department, salary, ROW_NUMBER() OVER () as rank FROM employees WHERE ROW_NUMBER() OVER () <= 3",
145
+ "fixed_query": "SELECT id, name, department, salary, rank FROM (SELECT id, name, department, salary, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank FROM employees) ranked WHERE rank <= 3",
146
+ "error_message": "ERROR: window functions are not allowed in WHERE clause",
147
+ "database_schema": {
148
+ "employees": ["id INT PRIMARY KEY", "name VARCHAR(100)", "department VARCHAR(50)", "salary DECIMAL", "hire_date DATE"]
149
+ },
150
+ "expected_output": [
151
+ {"id": 3, "name": "Carol", "department": "Engineering", "salary": 120000, "rank": 1},
152
+ {"id": 7, "name": "Dave", "department": "Engineering", "salary": 110000, "rank": 2},
153
+ {"id": 12, "name": "Eve", "department": "Marketing", "salary": 95000, "rank": 1}
154
+ ],
155
+ "error_type": "performance",
156
+ "error_location": "Window function in WHERE clause and missing PARTITION BY + ORDER BY",
157
+ "fix_description": "Wrap in subquery to filter on window function result, add PARTITION BY department and ORDER BY salary DESC for correct per-department ranking",
158
+ "estimated_fix_steps": 7,
159
+ "scoring_rubric": {
160
+ "identifies_window_in_where_error": 0.1,
161
+ "identifies_missing_partition_by": 0.1,
162
+ "identifies_missing_order_by": 0.1,
163
+ "correct_subquery_wrapper": 0.1,
164
+ "correct_partition_by": 0.1,
165
+ "correct_order_by": 0.1,
166
+ "correct_where_on_subquery": 0.1,
167
+ "explanation_quality": 0.1,
168
+ "confidence": 0.05
169
+ },
170
+ "frontier_model_expected_score": 0.10
171
+ }
172
+ ]
dataset/medium_cases.json ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "medium_001",
4
+ "category": "logic",
5
+ "description": "Fix the wrong JOIN type: INNER JOIN excludes users with no orders. Should be LEFT JOIN to include all users.",
6
+ "buggy_query": "SELECT u.id, u.name, COUNT(o.id) as order_count FROM users u INNER JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name",
7
+ "fixed_query": "SELECT u.id, u.name, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name",
8
+ "error_message": "No error thrown — query runs but returns wrong results (missing users with 0 orders)",
9
+ "database_schema": {
10
+ "users": ["id INT PRIMARY KEY", "name VARCHAR(100)", "email VARCHAR(100)"],
11
+ "orders": ["id INT PRIMARY KEY", "user_id INT REFERENCES users(id)", "total DECIMAL", "status VARCHAR(20)"]
12
+ },
13
+ "expected_output": [
14
+ {"id": 1, "name": "Alice", "order_count": 3},
15
+ {"id": 2, "name": "Bob", "order_count": 0},
16
+ {"id": 3, "name": "Carol", "order_count": 1}
17
+ ],
18
+ "actual_buggy_output": [
19
+ {"id": 1, "name": "Alice", "order_count": 3},
20
+ {"id": 3, "name": "Carol", "order_count": 1}
21
+ ],
22
+ "error_type": "logic",
23
+ "error_location": "JOIN type",
24
+ "fix_description": "Change INNER JOIN to LEFT JOIN so users with zero orders are included",
25
+ "estimated_fix_steps": 3,
26
+ "partial_credit_hints": {
27
+ "identifies_missing_rows": 0.15,
28
+ "identifies_join_type_issue": 0.2,
29
+ "correct_join_fix": 0.4,
30
+ "explanation": 0.15,
31
+ "confidence": 0.1
32
+ }
33
+ },
34
+ {
35
+ "id": "medium_002",
36
+ "category": "logic",
37
+ "description": "Fix the wrong JOIN condition: joining on wrong column causes cartesian-like explosion of results.",
38
+ "buggy_query": "SELECT o.id, o.total, p.name FROM orders o JOIN order_items oi ON o.id = oi.order_id JOIN products p ON oi.id = p.id",
39
+ "fixed_query": "SELECT o.id, o.total, p.name FROM orders o JOIN order_items oi ON o.id = oi.order_id JOIN products p ON oi.product_id = p.id",
40
+ "error_message": "No error thrown — query runs but returns incorrect product associations",
41
+ "database_schema": {
42
+ "orders": ["id INT PRIMARY KEY", "user_id INT", "total DECIMAL"],
43
+ "order_items": ["id INT PRIMARY KEY", "order_id INT", "product_id INT", "quantity INT"],
44
+ "products": ["id INT PRIMARY KEY", "name VARCHAR(100)", "price DECIMAL"]
45
+ },
46
+ "expected_output": [
47
+ {"id": 101, "total": 250.00, "name": "Laptop"},
48
+ {"id": 102, "total": 89.99, "name": "Mouse"}
49
+ ],
50
+ "error_type": "logic",
51
+ "error_location": "Second JOIN condition — oi.id should be oi.product_id",
52
+ "fix_description": "Change ON oi.id = p.id to ON oi.product_id = p.id",
53
+ "estimated_fix_steps": 3,
54
+ "partial_credit_hints": {
55
+ "identifies_wrong_column": 0.2,
56
+ "identifies_join_condition": 0.2,
57
+ "correct_fix": 0.4,
58
+ "explanation": 0.15,
59
+ "confidence": 0.05
60
+ }
61
+ },
62
+ {
63
+ "id": "medium_003",
64
+ "category": "logic",
65
+ "description": "Fix the aggregation logic: HAVING clause filters before GROUP BY aggregation is complete, wrong column used.",
66
+ "buggy_query": "SELECT department, AVG(salary) as avg_salary FROM employees WHERE AVG(salary) > 70000 GROUP BY department",
67
+ "fixed_query": "SELECT department, AVG(salary) as avg_salary FROM employees GROUP BY department HAVING AVG(salary) > 70000",
68
+ "error_message": "ERROR: aggregate functions are not allowed in WHERE",
69
+ "database_schema": {
70
+ "employees": ["id INT PRIMARY KEY", "name VARCHAR(100)", "department VARCHAR(50)", "salary DECIMAL"]
71
+ },
72
+ "expected_output": [
73
+ {"department": "Engineering", "avg_salary": 95000.00},
74
+ {"department": "Data Science","avg_salary": 88000.00}
75
+ ],
76
+ "error_type": "logic",
77
+ "error_location": "WHERE clause — aggregate function used in WHERE instead of HAVING",
78
+ "fix_description": "Move AVG(salary) > 70000 from WHERE to HAVING clause, placed after GROUP BY",
79
+ "estimated_fix_steps": 3,
80
+ "partial_credit_hints": {
81
+ "identifies_where_vs_having": 0.2,
82
+ "identifies_aggregate_misuse": 0.2,
83
+ "correct_fix": 0.4,
84
+ "explanation": 0.15,
85
+ "confidence": 0.05
86
+ }
87
+ },
88
+ {
89
+ "id": "medium_004",
90
+ "category": "logic",
91
+ "description": "Fix the subquery logic: correlated subquery compares wrong column, returning incorrect filtered set.",
92
+ "buggy_query": "SELECT id, name, salary FROM employees e WHERE salary > (SELECT AVG(salary) FROM employees WHERE department = e.id)",
93
+ "fixed_query": "SELECT id, name, salary FROM employees e WHERE salary > (SELECT AVG(salary) FROM employees WHERE department = e.department)",
94
+ "error_message": "No error thrown — returns incorrect rows because subquery correlates on wrong column",
95
+ "database_schema": {
96
+ "employees": ["id INT PRIMARY KEY", "name VARCHAR(100)", "department VARCHAR(50)", "salary DECIMAL"]
97
+ },
98
+ "expected_output": [
99
+ {"id": 3, "name": "Carol", "salary": 110000},
100
+ {"id": 7, "name": "Dave", "salary": 98000}
101
+ ],
102
+ "error_type": "logic",
103
+ "error_location": "Correlated subquery WHERE clause — e.id should be e.department",
104
+ "fix_description": "Change WHERE department = e.id to WHERE department = e.department",
105
+ "estimated_fix_steps": 4,
106
+ "partial_credit_hints": {
107
+ "identifies_correlated_subquery": 0.15,
108
+ "identifies_wrong_correlation": 0.2,
109
+ "correct_fix": 0.45,
110
+ "explanation": 0.15,
111
+ "confidence": 0.05
112
+ }
113
+ },
114
+ {
115
+ "id": "medium_005",
116
+ "category": "logic",
117
+ "description": "Fix the DISTINCT misuse: DISTINCT applied to wrong scope, counting duplicates incorrectly.",
118
+ "buggy_query": "SELECT COUNT(DISTINCT *) FROM orders WHERE status = 'completed'",
119
+ "fixed_query": "SELECT COUNT(DISTINCT id) FROM orders WHERE status = 'completed'",
120
+ "error_message": "ERROR: COUNT(DISTINCT) with multiple columns requires explicit column list",
121
+ "database_schema": {
122
+ "orders": ["id INT PRIMARY KEY", "user_id INT", "total DECIMAL", "status VARCHAR(20)"]
123
+ },
124
+ "expected_output": [{"count": 47}],
125
+ "error_type": "logic",
126
+ "error_location": "COUNT(DISTINCT *) — wildcard not valid with DISTINCT",
127
+ "fix_description": "Replace COUNT(DISTINCT *) with COUNT(DISTINCT id) to count unique order IDs",
128
+ "estimated_fix_steps": 2,
129
+ "partial_credit_hints": {
130
+ "identifies_distinct_misuse": 0.25,
131
+ "correct_fix": 0.5,
132
+ "explanation": 0.15,
133
+ "confidence": 0.1
134
+ }
135
+ }
136
+ ]
env/__init__.py ADDED
File without changes
env/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (134 Bytes). View file
 
env/__pycache__/models.cpython-312.pyc ADDED
Binary file (9.17 kB). View file
 
env/environment.py ADDED
File without changes
env/graders.py ADDED
File without changes
env/models.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field, field_validator
2
+ from typing import Optional, Any
3
+ from enum import Enum
4
+ import time
5
+
6
+
7
+ # ENUMS
8
+
9
+ class DifficultyLevel(str, Enum):
10
+ EASY = "easy"
11
+ MEDIUM = "medium"
12
+ HARD = "hard"
13
+
14
+
15
+ class ActionType(str, Enum):
16
+ IDENTIFY_ERROR = "identify_error"
17
+ PROPOSE_FIX = "propose_fix"
18
+ SUBMIT_ANSWER = "submit_answer"
19
+ REQUEST_HINT = "request_hint"
20
+ EXPLAIN_ISSUE = "explain_issue"
21
+ OPTIMIZE_QUERY = "optimize_query"
22
+
23
+ # CORE MODELS
24
+
25
+
26
+ class Observation(BaseModel):
27
+ task_id: str = Field(..., description="Unique task identifier")
28
+ task_description: str = Field(..., description="What the agent must do")
29
+ current_context: dict = Field(..., description="What the agent currently sees")
30
+ step_count: int = Field(default=0, ge=0, description="Steps taken so far")
31
+ difficulty: DifficultyLevel = Field(..., description="Task difficulty level")
32
+ max_steps: int = Field(default=20, description="Maximum steps allowed")
33
+ hints_used: int = Field(default=0, description="Number of hints used")
34
+ previous_actions: list[str] = Field(default_factory=list, description="History of action types taken")
35
+ metadata: dict = Field(default_factory=dict, description="Extra task metadata")
36
+
37
+ model_config = {"json_schema_extra": {
38
+ "example": {
39
+ "task_id": "easy_001",
40
+ "task_description": "Fix the SQL syntax error in the query below.",
41
+ "current_context": {
42
+ "buggy_query": "SELECT id, name FROM users WHERE id = 1 AND",
43
+ "error_message": "SyntaxError: unexpected end of input",
44
+ "database_schema": "users(id INT, name VARCHAR, email VARCHAR)"
45
+ },
46
+ "step_count": 0,
47
+ "difficulty": "easy",
48
+ "max_steps": 20,
49
+ "hints_used": 0,
50
+ "previous_actions": [],
51
+ "metadata": {"category": "syntax", "estimated_fix_steps": 2}
52
+ }
53
+ }}
54
+
55
+
56
+ class Action(BaseModel):
57
+ action_type: ActionType = Field(..., description="Type of action the agent is taking")
58
+ payload: dict = Field(..., description="Action-specific data")
59
+
60
+ @field_validator("payload")
61
+ @classmethod
62
+ def payload_must_not_be_empty(cls, v):
63
+ if v is None:
64
+ raise ValueError("Payload cannot be None")
65
+ return v
66
+
67
+ @field_validator("payload")
68
+ @classmethod
69
+ def truncate_long_strings(cls, v):
70
+ # Edge case: extremely long agent output — truncate gracefully
71
+ def truncate(obj, max_len=5000):
72
+ if isinstance(obj, str) and len(obj) > max_len:
73
+ return obj[:max_len] + "...[truncated]"
74
+ if isinstance(obj, dict):
75
+ return {k: truncate(val, max_len) for k, val in obj.items()}
76
+ return obj
77
+ return truncate(v)
78
+
79
+ model_config = {"json_schema_extra": {
80
+ "example": {
81
+ "action_type": "submit_answer",
82
+ "payload": {
83
+ "fixed_query": "SELECT id, name FROM users WHERE id = 1",
84
+ "explanation": "Removed the trailing AND which caused a syntax error",
85
+ "error_type": "syntax",
86
+ "confidence": 0.95
87
+ }
88
+ }
89
+ }}
90
+
91
+
92
+ class Reward(BaseModel):
93
+ score: float = Field(..., ge=-1.0, le=1.0, description="Reward score between -1.0 and 1.0")
94
+ breakdown: dict = Field(..., description="Partial credit details per dimension")
95
+ feedback: str = Field(..., description="Human-readable explanation of the reward")
96
+
97
+ @field_validator("score")
98
+ @classmethod
99
+ def clamp_score(cls, v):
100
+ return max(-1.0, min(1.0, round(v, 4)))
101
+
102
+ model_config = {"json_schema_extra": {
103
+ "example": {
104
+ "score": 0.75,
105
+ "breakdown": {
106
+ "correct_answer": 0.5,
107
+ "explanation": 0.2,
108
+ "confidence": 0.05,
109
+ "step_efficiency": 0.0
110
+ },
111
+ "feedback": "Correct fix applied. Good explanation provided. Minor efficiency penalty."
112
+ }
113
+ }}
114
+
115
+
116
+ # EPISODE STATE (used by state() endpoint)
117
+
118
+ class EpisodeState(BaseModel):
119
+ task_id: Optional[str] = Field(default=None)
120
+ difficulty: Optional[DifficultyLevel] = Field(default=None)
121
+ step_count: int = Field(default=0)
122
+ total_reward: float = Field(default=0.0)
123
+ done: bool = Field(default=False)
124
+ hints_used: int = Field(default=0)
125
+ previous_actions: list[str] = Field(default_factory=list)
126
+ action_counts: dict[str, int] = Field(default_factory=dict)
127
+ started_at: Optional[float] = Field(default=None)
128
+ last_reward: float = Field(default=0.0)
129
+ initialized: bool = Field(default=False)
130
+
131
+ model_config = {"json_schema_extra": {
132
+ "example": {
133
+ "task_id": "medium_002",
134
+ "difficulty": "medium",
135
+ "step_count": 3,
136
+ "total_reward": 0.45,
137
+ "done": False,
138
+ "hints_used": 1,
139
+ "previous_actions": ["identify_error", "request_hint", "propose_fix"],
140
+ "action_counts": {"identify_error": 1, "request_hint": 1, "propose_fix": 1},
141
+ "started_at": 1700000000.0,
142
+ "last_reward": 0.25,
143
+ "initialized": True
144
+ }
145
+ }}
146
+
147
+
148
+ # API REQUEST / RESPONSE WRAPPERS
149
+
150
+ class StepResponse(BaseModel):
151
+ observation: Observation
152
+ reward: Reward
153
+ done: bool
154
+ info: dict
155
+
156
+ class ResetResponse(BaseModel):
157
+ observation: Observation
158
+
159
+ class TaskInfo(BaseModel):
160
+ id: str
161
+ difficulty: DifficultyLevel
162
+ description: str
163
+ action_schema: dict # REQUIRED by validator — field definitions not just names
164
+
165
+ class TaskListResponse(BaseModel):
166
+ tasks: list[TaskInfo]
167
+ total: int
168
+ action_types: list[str]
169
+
170
+ class BaselineResult(BaseModel):
171
+ task_id: str
172
+ difficulty: DifficultyLevel
173
+ score: float
174
+ steps: int
175
+ feedback: str
176
+
177
+ class BaselineResponse(BaseModel):
178
+ results: list[BaselineResult]
179
+ average_score: float
180
+ completed_at: float = Field(default_factory=time.time)
181
+
182
+ class GraderRequest(BaseModel):
183
+ task_id: str
184
+ action: Optional[Action] = None
185
+ episode: Optional[dict] = None
186
+
187
+ class GraderResponse(BaseModel):
188
+ score: float
189
+ feedback: str
190
+ breakdown: dict
191
+
192
+ class HealthResponse(BaseModel):
193
+ status: str = "ok"
194
+ version: str = "1.0.0"
195
+ uptime: float = Field(default_factory=time.time)
env/reward.py ADDED
File without changes
env/tasks.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import random
3
+ from pathlib import Path
4
+ from env.models import DifficultyLevel, TaskInfo
5
+
6
+ # LOAD DATASETS
7
+
8
+ BASE_DIR = Path(__file__).parent.parent / "dataset"
9
+
10
+ def _load(filename: str) -> list[dict]:
11
+ path = BASE_DIR / filename
12
+ with open(path, "r", encoding="utf-8") as f:
13
+ return json.load(f)
14
+
15
+ EASY_CASES = _load("easy_cases.json")
16
+ MEDIUM_CASES = _load("medium_cases.json")
17
+ HARD_CASES = _load("hard_cases.json")
18
+
19
+ ALL_CASES: dict[str, list[dict]] = {
20
+ DifficultyLevel.EASY: EASY_CASES,
21
+ DifficultyLevel.MEDIUM: MEDIUM_CASES,
22
+ DifficultyLevel.HARD: HARD_CASES,
23
+ }
24
+
25
+ # ACTION SCHEMA (required by /tasks validator)
26
+
27
+ ACTION_SCHEMA = {
28
+ "identify_error": {
29
+ "description": "Identify where and what the error is without fixing it yet",
30
+ "payload_fields": {
31
+ "error_location": {"type": "string", "required": True, "description": "Where in the query the error occurs"},
32
+ "error_type": {"type": "string", "required": True, "description": "Type: syntax | logic | performance"},
33
+ "explanation": {"type": "string", "required": False, "description": "Brief explanation of the error"}
34
+ }
35
+ },
36
+ "propose_fix": {
37
+ "description": "Propose a fix without submitting as final answer",
38
+ "payload_fields": {
39
+ "fixed_query": {"type": "string", "required": True, "description": "The proposed corrected SQL query"},
40
+ "change_made": {"type": "string", "required": True, "description": "What specifically was changed"},
41
+ "confidence": {"type": "float", "required": False, "description": "Confidence score 0.0-1.0"}
42
+ }
43
+ },
44
+ "submit_answer": {
45
+ "description": "Submit the final fixed query as the definitive answer",
46
+ "payload_fields": {
47
+ "fixed_query": {"type": "string", "required": True, "description": "Final corrected SQL query"},
48
+ "explanation": {"type": "string", "required": True, "description": "Full explanation of what was wrong and how it was fixed"},
49
+ "error_type": {"type": "string", "required": False, "description": "Type: syntax | logic | performance"},
50
+ "confidence": {"type": "float", "required": False, "description": "Confidence score 0.0-1.0"}
51
+ }
52
+ },
53
+ "request_hint": {
54
+ "description": "Request a hint — costs 0.05 reward penalty per hint",
55
+ "payload_fields": {
56
+ "hint_type": {"type": "string", "required": False, "description": "Type of hint wanted: location | error_type | fix_direction"}
57
+ }
58
+ },
59
+ "explain_issue": {
60
+ "description": "Explain the issue in detail — earns partial credit even without fixing",
61
+ "payload_fields": {
62
+ "explanation": {"type": "string", "required": True, "description": "Detailed explanation of the SQL problem"},
63
+ "impact": {"type": "string", "required": False, "description": "What impact the bug has on query results or performance"},
64
+ "root_cause": {"type": "string", "required": False, "description": "Root cause analysis"}
65
+ }
66
+ },
67
+ "optimize_query": {
68
+ "description": "Submit an optimized version of the query (used for hard/performance tasks)",
69
+ "payload_fields": {
70
+ "optimized_query": {"type": "string", "required": True, "description": "The performance-optimized SQL query"},
71
+ "optimization_type": {"type": "string", "required": True, "description": "What optimization was applied"},
72
+ "expected_improvement":{"type": "string", "required": False, "description": "Expected performance gain description"},
73
+ "explanation": {"type": "string", "required": False, "description": "Why this optimization works"},
74
+ "confidence": {"type": "float", "required": False, "description": "Confidence 0.0-1.0"}
75
+ }
76
+ }
77
+ }
78
+ # TASK MANAGER
79
+
80
+
81
+ class TaskManager:
82
+ """
83
+ Manages task selection, hint generation, and task metadata.
84
+ All tasks are loaded from JSON datasets — no hardcoded tasks.
85
+ """
86
+
87
+ def __init__(self):
88
+ self._used_ids: set[str] = set()
89
+
90
+ def get_task(self, difficulty: DifficultyLevel, task_id: str | None = None) -> dict:
91
+ """
92
+ Returns a task dict for the given difficulty.
93
+ If task_id is provided, returns that specific task.
94
+ Otherwise picks randomly, avoiding recently used tasks.
95
+ """
96
+ pool = ALL_CASES[difficulty]
97
+
98
+ if task_id:
99
+ for case in pool:
100
+ if case["id"] == task_id:
101
+ return case
102
+ raise ValueError(f"Task '{task_id}' not found in {difficulty} pool")
103
+
104
+ # Avoid repeating recently used tasks
105
+ available = [c for c in pool if c["id"] not in self._used_ids]
106
+ if not available:
107
+ self._used_ids.clear()
108
+ available = pool
109
+
110
+ task = random.choice(available)
111
+ self._used_ids.add(task["id"])
112
+ return task
113
+
114
+ def get_random_task(self) -> dict:
115
+ """Pick a random task from any difficulty."""
116
+ difficulty = random.choice(list(DifficultyLevel))
117
+ return self.get_task(difficulty)
118
+
119
+ def build_observation_context(self, task: dict) -> dict:
120
+ """
121
+ Build the current_context dict for the Observation.
122
+ CRITICAL: Must NOT leak the fixed_query (ground truth) to the agent.
123
+ """
124
+ context = {
125
+ "buggy_query": task["buggy_query"],
126
+ "error_message": task["error_message"],
127
+ "database_schema": task["database_schema"],
128
+ "error_type_hint": task["error_type"],
129
+ "category": task["category"],
130
+ "estimated_steps": task["estimated_fix_steps"],
131
+ }
132
+
133
+ # For performance tasks include extra context
134
+ if task.get("performance_issue"):
135
+ context["performance_issue"] = {
136
+ "type": task["performance_issue"]["type"],
137
+ "impact": task["performance_issue"]["impact"],
138
+ # Do NOT include timing numbers — agent must figure it out
139
+ }
140
+
141
+ # Include expected output shape (but not the fixed query!)
142
+ if task.get("expected_output") and isinstance(task["expected_output"], list):
143
+ context["expected_output_sample"] = task["expected_output"][:1]
144
+
145
+ return context
146
+
147
+ def get_hint(self, task: dict, hint_number: int) -> str:
148
+ """
149
+ Returns progressive hints. Each hint gives more info.
150
+ Hints cost -0.05 reward each (handled in reward.py).
151
+ """
152
+ hints = [
153
+ f"Hint 1: The error is in the {task.get('error_location', 'query')}.",
154
+ f"Hint 2: This is a {task.get('error_type', 'unknown')} type error. Category: {task.get('category')}.",
155
+ f"Hint 3: Fix description — {task.get('fix_description', 'Review the query carefully.')}",
156
+ ]
157
+ idx = min(hint_number - 1, len(hints) - 1)
158
+ return hints[idx]
159
+
160
+ def list_all_tasks(self) -> list[TaskInfo]:
161
+ """Returns TaskInfo list for the /tasks endpoint."""
162
+ result = []
163
+ for difficulty, cases in ALL_CASES.items():
164
+ for case in cases:
165
+ result.append(TaskInfo(
166
+ id=case["id"],
167
+ difficulty=difficulty,
168
+ description=case["description"],
169
+ action_schema=ACTION_SCHEMA
170
+ ))
171
+ return result
172
+
173
+ def get_ground_truth(self, task_id: str) -> dict | None:
174
+ """Returns the full ground truth for a task (used by grader only)."""
175
+ for cases in ALL_CASES.values():
176
+ for case in cases:
177
+ if case["id"] == task_id:
178
+ return case
179
+ return None
180
+
181
+
182
+ # Singleton instance
183
+ task_manager = TaskManager()
openenv.yaml ADDED
File without changes
requirements.txt ADDED
File without changes
tests/test_environment.py ADDED
File without changes
tests/test_graders.py ADDED
File without changes