selfheal-sql / src /selfheal_sql /make_gate_large.py
HideIron's picture
Self-healing text-to-SQL: landing page + full source
f636e22 verified
Raw History Blame Contribute Delete
4.38 kB
"""Enlarge the held-out gate split (100 -> N) so small accuracy differences are measurable.
The original gate has 100 questions (std. error ~ +/-5pp), too noisy to tell a 2pp
regression from chance. This keeps the original 100 rows (same qids, so old results stay
comparable) and adds more questions drawn from the SAME databases the gate already uses
(Spider-train DB group B), so:
* no database appears in pool or test (asserted),
* every database is already in the HF dataset repo (no DB upload needed),
* gold queries run and return >=1 row (same rule as make_splits.py).
Usage (from repo root):
python src/selfheal_sql/make_gate_large.py [--total 400] [--seed 1]
Writes data/splits/gate_large.jsonl (absolute db_path, like the other local splits) and
data/hf_dataset/splits/gate_large.jsonl (relative db_path, ready to upload).
"""
from __future__ import annotations
import argparse
import json
import os
import random
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from execcheck import check, schema_text # noqa: E402
from make_splits import usable # noqa: E402
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
SPIDER = os.path.join(ROOT, "data", "spider", "spider_data")
SPLITS = os.path.join(ROOT, "data", "splits")
STAGE = os.path.join(ROOT, "data", "hf_dataset", "splits")
def read(path):
with open(path, encoding="utf-8") as f:
return [json.loads(line) for line in f]
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--total", type=int, default=400)
ap.add_argument("--seed", type=int, default=1)
args = ap.parse_args()
pool, gate, test = (read(os.path.join(SPLITS, f"{n}.jsonl")) for n in ("pool", "gate", "test"))
gate_dbs = {r["db_id"] for r in gate}
seen = {(r["db_id"], r["question"]) for r in gate}
with open(os.path.join(SPIDER, "train_spider.json"), encoding="utf-8") as f:
train = json.load(f)
# Candidates: train questions on the gate's databases, not already in the gate.
cands = [x for x in train if x["db_id"] in gate_dbs and (x["db_id"], x["question"]) not in seen]
random.Random(args.seed).shuffle(cands)
rows = [dict(r, split="gate_large") for r in gate] # keep original 100, same qids
schemas = {r["db_id"]: r["schema"] for r in gate}
n_keep = len(rows)
for it in cands:
if len(rows) >= args.total:
break
db_id = it["db_id"]
db_path = os.path.join(SPIDER, "database", db_id, f"{db_id}.sqlite")
if not os.path.exists(db_path) or not usable(db_path, it["query"]):
continue
schemas.setdefault(db_id, schema_text(db_path))
rows.append({
"qid": f"gate_{len(rows):04d}", "split": "gate_large", "db_id": db_id,
"question": it["question"], "gold_sql": it["query"],
"db_path": db_path, "schema": schemas[db_id],
})
# Hard guarantees, asserted not assumed.
dbs = {r["db_id"] for r in rows}
assert dbs == gate_dbs, "gate_large introduced a new database"
assert not dbs & {r["db_id"] for r in pool}, "gate_large shares a DB with pool"
assert not dbs & {r["db_id"] for r in test}, "gate_large shares a DB with test"
assert len({r["qid"] for r in rows}) == len(rows), "duplicate qid"
assert len({(r["db_id"], r["question"]) for r in rows}) == len(rows), "duplicate question"
bad = [r["qid"] for r in rows if not check(r["db_path"], r["gold_sql"], r["gold_sql"]).correct]
print(f"gate_large: {len(rows)} questions ({n_keep} original + {len(rows) - n_keep} new) "
f"over {len(dbs)} databases; gold-vs-gold failures: {len(bad)}")
assert not bad, bad[:5]
os.makedirs(SPLITS, exist_ok=True)
with open(os.path.join(SPLITS, "gate_large.jsonl"), "w", encoding="utf-8") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
os.makedirs(STAGE, exist_ok=True)
with open(os.path.join(STAGE, "gate_large.jsonl"), "w", encoding="utf-8") as f:
for r in rows:
r2 = dict(r, db_path=f"databases/{r['db_id']}/{r['db_id']}.sqlite")
f.write(json.dumps(r2, ensure_ascii=False) + "\n")
print("wrote data/splits/gate_large.jsonl and data/hf_dataset/splits/gate_large.jsonl")
print("db-disjoint from pool/test: OK")
if __name__ == "__main__":
main()