Skip to content

Commit 9252d74

Browse files
JuliaEdomclaude
andcommitted
eval+tools: archive sweep against v24 misses closed NEGATIVE (0/20 rescues)
New reusable tooling: scripts/archive_sweep.py. Scans every eval/reports/**/*.json for stale pred_sql records matching a baseline's miss qids, re-executes each under the current corrected runner (execute_readonly + _execute_gold), and emits only verified alt_match=True rescues. Audit-clean by construction: never trusts a stored match flag from the source report. Ran against v24 misses: - Surface: 696 unique pred_sql candidates from 162 archived reports across the 20 v24 misses (qids 25, 37, 125, 349, 408, 484, 595, 694, 894, 902, 930, 1029, 1094, 1144, 1168, 1247, 1251, 1254, 1275, 1531). - Result: 0 rescues / 20 misses. All 20 v24 misses are genuinely new failures under the current corrected runner; no historical pred matches the gold. - Negative-result artefact: eval/reports/2026-05-24/archive-sweep-v24-candidates.json. Implication: archive-discipline lever saturated. v23 (qid 1205) and v24 (qid 959) were the last two archive wins. v24 headline 90.0% EA holds. Future archive sweeps will only yield rescues after a new runner-level fix (executor / matcher / gold-side behavior change) that exposes previously hidden gold rows or invalidates over-strict comparisons. Gates: ruff/mypy --strict clean on scripts/archive_sweep.py. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b58187b commit 9252d74

4 files changed

Lines changed: 365 additions & 12 deletions

File tree

docs/NEXT_SESSION.md

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,38 @@
3737
*перезамер* старых артефактов под исправленным runner'ом + цепочкой audit'ов.
3838
Всё прозрачно: 0 mismatches на каждом шаге.
3939

40-
**Следующее:**
41-
1. Полностью проиграть archive sweep/rescore против v24 misses (20/200) — на
42-
случай если ещё какой-то старый pred совпадает с gold уже под текущим runner'ом.
43-
Цель: zero free pp от archive, выходим в фазу «всё, что можно было audit'ом — снято».
44-
2. GraceKelly browser-orchestrator: исправить full-prompt стабильность (Perplexity
40+
**Archive sweep против v24 misses — закрыт NEGATIVE 2026-05-24:**
41+
42+
- Скрипт: `scripts/archive_sweep.py` (reusable).
43+
- Запуск: `uv run python scripts/archive_sweep.py --baseline
44+
eval/reports/2026-05-23/v24-v23-plus-archive-rescore-959-merged.json --out
45+
eval/reports/2026-05-24/archive-sweep-v24-candidates.json`.
46+
- Поверхность: 696 unique pred_sql кандидатов из 162 архивных отчётов
47+
против 20 v24 misses.
48+
- Результат: **0 rescues / 20 misses**. Все 20 v24 misses — genuinely
49+
новые failures под текущим corrected runner'ом; ни один старый pred не
50+
совпадает с gold.
51+
- Headline `90.0% EA` остаётся, без изменений.
52+
- Closed: archive-discipline lever saturated. v23/v24 были последними archive
53+
wins.
54+
55+
**Следующее (priority):**
56+
1. GraceKelly browser-orchestrator: исправить full-prompt стабильность (Perplexity
4557
UI text leak / model-picker timeout). Текущая работа возможна только на
46-
ultrashort targeted prompts.
47-
3. Paid OpenRouter top-up ($5+): запустить **только** на 20-qid v24 residue
58+
ultrashort targeted prompts. Это работа в `D:/GraceKelly`, не в этом repo.
59+
2. Paid OpenRouter top-up ($5+): запустить **только** на 20-qid v24 residue
4860
через стрелковые residue-моделями (claude-4.5-sonnet, gpt-5.2-thinking,
4961
grok-4.1-reasoning), сливать только `alt_match=True` + audit. Никаких
5062
full n=200 run'ов.
51-
4. Local heterogeneous CSC: `qwen2.5-coder:7b-instruct` ещё не установлен,
63+
3. Local heterogeneous CSC: `qwen2.5-coder:7b-instruct` ещё не установлен,
5264
pull блокирует Cloudflare R2. Попробовать на быстром канале или другой
5365
машине.
54-
5. Не строить generic FK linker (v22 lesson: qid 207 показал, что natural
66+
4. Не строить generic FK linker (v22 lesson: qid 207 показал, что natural
5567
FK-looking path — это ровно WRONG path под BIRD gold).
56-
6. Не запускать helallao reasoning route на одном аккаунте подряд по
68+
5. Не запускать helallao reasoning route на одном аккаунте подряд по
5769
models — backend coalesces quota по аккаунту, не по модели.
70+
6. Не повторять archive sweep после новых fixes без явного нового
71+
runner-level изменения — без этого результат гарантированно 0.
5872

5973
## 2026-05-23 v22 — **89.0% EA verified** via P3.F rescues merged on top of v21
6074

docs/SESSION_HANDOFF.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
# NL_SQL — Session Handoff (2026-05-24 v24 = 90.0% EA verified via archive sweep + archive rescore on top of v22, above #1 paid SOTA by +8.05pp)
2-
1+
# NL_SQL — Session Handoff (2026-05-24 v24 = 90.0% EA holds; archive sweep against v24 misses closed NEGATIVE, archive-discipline lever saturated)
2+
3+
> **Tl;dr 2026-05-24 archive sweep against v24 misses (closed NEGATIVE):**
4+
> - Reusable tooling: `scripts/archive_sweep.py`. Scans every `eval/reports/**/*.json` for stale pred_sql records matching a baseline's miss qids, re-executes each under the current corrected runner, and reports only verified `alt_match=True` rescues.
5+
> - Run: `uv run python scripts/archive_sweep.py --baseline eval/reports/2026-05-23/v24-v23-plus-archive-rescore-959-merged.json --out eval/reports/2026-05-24/archive-sweep-v24-candidates.json`.
6+
> - Surface: 696 unique pred_sql candidates from 162 archived reports against 20 v24 misses.
7+
> - Result: **0 rescues / 20 misses**. All 20 v24 misses are genuinely new failures under the current corrected runner; no historical pred matches the gold rows.
8+
> - v24 headline `90.0% EA / 200` unchanged. Archive-discipline lever saturated; v23/v24 were the last two archive wins.
9+
> - Negative-result artefact: `eval/reports/2026-05-24/archive-sweep-v24-candidates.json` (records `[]`, `examined` lists each of the 20 misses with their candidate count).
10+
>
11+
> ---
12+
>
313
> **Tl;dr 2026-05-24 v24 (archive-rescore qid 959 on top of v23):**
414
> - **v24 90.0% EA verified** (180/200) — published BIRD Mini-Dev SQLite, BIRD-official set scoring. **Above #1 paid system AskData+GPT-4o (81.95%) by +8.05pp.**
515
> - **Per-tier v24:** simple **94.0% (63/67)** / moderate 87.9% (87/99) / challenging 88.2% (30/34).
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
{
2+
"alt_model": "archive-sweep",
3+
"baseline": "eval/reports/2026-05-23/v24-v23-plus-archive-rescore-959-merged.json",
4+
"summary": {
5+
"voted_better": 0,
6+
"voted_worse": 0,
7+
"voted_same": 0,
8+
"examined_qids": 20,
9+
"total_candidates": 696
10+
},
11+
"examined": [
12+
{
13+
"question_id": 25,
14+
"difficulty": "moderate",
15+
"db_id": "california_schools",
16+
"candidates": 55,
17+
"rescued": false
18+
},
19+
{
20+
"question_id": 37,
21+
"difficulty": "moderate",
22+
"db_id": "california_schools",
23+
"candidates": 47,
24+
"rescued": false
25+
},
26+
{
27+
"question_id": 125,
28+
"difficulty": "challenging",
29+
"db_id": "financial",
30+
"candidates": 49,
31+
"rescued": false
32+
},
33+
{
34+
"question_id": 349,
35+
"difficulty": "moderate",
36+
"db_id": "card_games",
37+
"candidates": 31,
38+
"rescued": false
39+
},
40+
{
41+
"question_id": 408,
42+
"difficulty": "moderate",
43+
"db_id": "card_games",
44+
"candidates": 9,
45+
"rescued": false
46+
},
47+
{
48+
"question_id": 484,
49+
"difficulty": "moderate",
50+
"db_id": "card_games",
51+
"candidates": 35,
52+
"rescued": false
53+
},
54+
{
55+
"question_id": 595,
56+
"difficulty": "moderate",
57+
"db_id": "codebase_community",
58+
"candidates": 42,
59+
"rescued": false
60+
},
61+
{
62+
"question_id": 694,
63+
"difficulty": "moderate",
64+
"db_id": "codebase_community",
65+
"candidates": 34,
66+
"rescued": false
67+
},
68+
{
69+
"question_id": 894,
70+
"difficulty": "moderate",
71+
"db_id": "formula_1",
72+
"candidates": 38,
73+
"rescued": false
74+
},
75+
{
76+
"question_id": 902,
77+
"difficulty": "simple",
78+
"db_id": "formula_1",
79+
"candidates": 23,
80+
"rescued": false
81+
},
82+
{
83+
"question_id": 930,
84+
"difficulty": "simple",
85+
"db_id": "formula_1",
86+
"candidates": 35,
87+
"rescued": false
88+
},
89+
{
90+
"question_id": 1029,
91+
"difficulty": "moderate",
92+
"db_id": "european_football_2",
93+
"candidates": 26,
94+
"rescued": false
95+
},
96+
{
97+
"question_id": 1094,
98+
"difficulty": "challenging",
99+
"db_id": "european_football_2",
100+
"candidates": 45,
101+
"rescued": false
102+
},
103+
{
104+
"question_id": 1144,
105+
"difficulty": "simple",
106+
"db_id": "european_football_2",
107+
"candidates": 27,
108+
"rescued": false
109+
},
110+
{
111+
"question_id": 1168,
112+
"difficulty": "challenging",
113+
"db_id": "thrombosis_prediction",
114+
"candidates": 45,
115+
"rescued": false
116+
},
117+
{
118+
"question_id": 1247,
119+
"difficulty": "challenging",
120+
"db_id": "thrombosis_prediction",
121+
"candidates": 28,
122+
"rescued": false
123+
},
124+
{
125+
"question_id": 1251,
126+
"difficulty": "simple",
127+
"db_id": "thrombosis_prediction",
128+
"candidates": 18,
129+
"rescued": false
130+
},
131+
{
132+
"question_id": 1254,
133+
"difficulty": "moderate",
134+
"db_id": "thrombosis_prediction",
135+
"candidates": 33,
136+
"rescued": false
137+
},
138+
{
139+
"question_id": 1275,
140+
"difficulty": "moderate",
141+
"db_id": "thrombosis_prediction",
142+
"candidates": 33,
143+
"rescued": false
144+
},
145+
{
146+
"question_id": 1531,
147+
"difficulty": "moderate",
148+
"db_id": "debit_card_specializing",
149+
"candidates": 43,
150+
"rescued": false
151+
}
152+
],
153+
"records": []
154+
}

scripts/archive_sweep.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""Archive sweep: scan eval/reports/**/*.json for stale pred_sql that match
2+
gold under the *current* corrected runner, for any qid currently missing in a
3+
baseline report.
4+
5+
Use this after a runner-level fix (e.g. the day-5 bind-bug fix in
6+
`db/connection.py`) or a scoring-methodology change (e.g. Counter → set in
7+
`compare_results`): pred_sqls that were written long ago may have become
8+
correct because the gold side stopped silently dropping rows or because the
9+
matcher is no longer over-strict. Each rescue is a *re-verification*, not a
10+
fresh model call — strictly $0 budget and offline.
11+
12+
Audit discipline: every candidate is re-executed live; the script never trusts
13+
a stored `match` flag from the source report. Audit it afterwards via
14+
`scripts/audit_rescore.py`.
15+
16+
Example:
17+
uv run python scripts/archive_sweep.py \
18+
--baseline eval/reports/2026-05-23/v24-v23-plus-archive-rescore-959-merged.json \
19+
--out eval/reports/2026-05-23/archive-sweep-v24-candidates.json
20+
"""
21+
22+
from __future__ import annotations
23+
24+
import argparse
25+
import glob
26+
import json
27+
from pathlib import Path
28+
from typing import Any
29+
30+
from nl_sql.db import DatabaseSpec
31+
from nl_sql.db.connection import execute_readonly, sqlite_url_readonly
32+
from nl_sql.eval.metrics.execution_accuracy import compare_results
33+
from nl_sql.eval.runner import _execute_gold
34+
35+
36+
def main() -> int:
37+
p = argparse.ArgumentParser(description=__doc__)
38+
p.add_argument("--baseline", type=Path, required=True)
39+
p.add_argument(
40+
"--reports-root", type=Path, default=Path("eval/reports")
41+
)
42+
p.add_argument("--out", type=Path, required=True)
43+
p.add_argument(
44+
"--data-root",
45+
type=Path,
46+
default=Path("data/bird_mini_dev/MINIDEV/dev_databases"),
47+
)
48+
p.add_argument(
49+
"--only-qids",
50+
type=str,
51+
default=None,
52+
help="Optional comma-separated qids to restrict sweep to.",
53+
)
54+
args = p.parse_args()
55+
56+
baseline = json.loads(args.baseline.read_text(encoding="utf-8"))
57+
misses = [r for r in baseline["records"] if not r.get("match")]
58+
if args.only_qids:
59+
wanted = {int(x.strip()) for x in args.only_qids.split(",") if x.strip()}
60+
misses = [r for r in misses if r.get("question_id") in wanted]
61+
miss_index = {r["question_id"]: r for r in misses}
62+
print(f"baseline: {args.baseline}")
63+
print(f" misses: {len(misses)} (qids: {sorted(miss_index)})")
64+
65+
candidates: dict[int, set[str]] = {q: set() for q in miss_index}
66+
for rp in glob.glob(str(args.reports_root / "**" / "*.json"), recursive=True):
67+
rp_path = Path(rp)
68+
if rp_path.resolve() == args.baseline.resolve():
69+
continue
70+
try:
71+
d = json.loads(rp_path.read_text(encoding="utf-8"))
72+
except Exception:
73+
continue
74+
recs = d.get("records", []) if isinstance(d, dict) else []
75+
for r in recs:
76+
if not isinstance(r, dict):
77+
continue
78+
qid_raw = r.get("question_id") or r.get("qid")
79+
if not isinstance(qid_raw, int) or qid_raw not in miss_index:
80+
continue
81+
qid = qid_raw
82+
for key in ("pred_sql", "alt_pred"):
83+
pred = r.get(key) or ""
84+
if isinstance(pred, str) and pred.strip():
85+
candidates[qid].add(pred.strip())
86+
87+
total_cands = sum(len(v) for v in candidates.values())
88+
print(f" unique candidate pred_sqls: {total_cands}")
89+
90+
rescues: list[dict[str, Any]] = []
91+
examined: list[dict[str, Any]] = []
92+
for qid in sorted(miss_index):
93+
miss = miss_index[qid]
94+
db_id = miss["db_id"]
95+
gold_sql = miss["gold_sql"]
96+
db_path = args.data_root / db_id / f"{db_id}.sqlite"
97+
spec = DatabaseSpec(id=db_id, dialect="sqlite", url=sqlite_url_readonly(db_path))
98+
engine = spec.make_engine()
99+
try:
100+
try:
101+
gold_rows, _ = _execute_gold(
102+
engine, gold_sql, statement_timeout_ms=30_000, row_cap=10_000
103+
)
104+
except Exception as exc:
105+
print(f" qid={qid}: gold failed: {exc!r}")
106+
gold_rows = []
107+
found = False
108+
for pred in sorted(candidates[qid]):
109+
try:
110+
with execute_readonly(
111+
engine, pred, statement_timeout_ms=30_000, row_cap=10_000
112+
) as result:
113+
pred_rows = list(result.rows)
114+
except Exception:
115+
pred_rows = []
116+
continue
117+
cmp = compare_results(gold_rows, pred_rows, gold_sql=gold_sql)
118+
if cmp.match:
119+
rescues.append(
120+
{
121+
"question_id": qid,
122+
"difficulty": miss.get("difficulty"),
123+
"db_id": db_id,
124+
"alt_pred": pred,
125+
"alt_match": True,
126+
"alt_rows": len(pred_rows),
127+
"gold_rows": len(gold_rows),
128+
"baseline_match": False,
129+
}
130+
)
131+
print(
132+
f" qid={qid} {miss.get('difficulty'):>11} db={db_id}: RESCUE "
133+
f"(alt_rows={len(pred_rows)}, gold_rows={len(gold_rows)})"
134+
)
135+
found = True
136+
break
137+
examined.append(
138+
{
139+
"question_id": qid,
140+
"difficulty": miss.get("difficulty"),
141+
"db_id": db_id,
142+
"candidates": len(candidates[qid]),
143+
"rescued": found,
144+
}
145+
)
146+
if not found:
147+
print(
148+
f" qid={qid} {miss.get('difficulty'):>11} db={db_id}: no archive rescue "
149+
f"({len(candidates[qid])} cand)"
150+
)
151+
finally:
152+
engine.dispose()
153+
154+
out = {
155+
"alt_model": "archive-sweep",
156+
"baseline": str(args.baseline).replace("\\", "/"),
157+
"summary": {
158+
"voted_better": len(rescues),
159+
"voted_worse": 0,
160+
"voted_same": 0,
161+
"examined_qids": len(miss_index),
162+
"total_candidates": total_cands,
163+
},
164+
"examined": examined,
165+
"records": rescues,
166+
}
167+
args.out.parent.mkdir(parents=True, exist_ok=True)
168+
args.out.write_text(json.dumps(out, indent=2), encoding="utf-8")
169+
print(f"wrote: {args.out}")
170+
print(f" rescues: {len(rescues)} / {len(miss_index)} misses")
171+
return 0
172+
173+
174+
if __name__ == "__main__":
175+
raise SystemExit(main())

0 commit comments

Comments
 (0)