bpHigh's picture
Round 2 README, Qwen2.5-Coder-3B baseline, missing data_pipeline pullers
4d300ac
Raw
History Blame Contribute Delete
6.34 kB
"""Stratified puller for the FinWorkBench/Finch dataset.
Selects 50 tasks across the most-frequent task_type tags, downloads source
and reference xlsx files, and emits a manifest.jsonl row for each task.
Usage:
python data_pipeline/finch_pull.py
"""
from __future__ import annotations
import argparse
import json
import os
import random
import urllib.request
from collections import defaultdict
from pathlib import Path
from datasets import load_dataset
REPO_ROOT = Path(__file__).resolve().parent.parent
DATA_DIR = REPO_ROOT / "data" / "finch_50"
MANIFEST_PATH = REPO_ROOT / "data" / "manifest.jsonl"
# Per-tag pick budgets. Sum = 50. Web Search tasks have non-xlsx sources
# (web/PDF), so we drop them and reallocate slots to denser tags.
TAG_BUDGET = {
"Calculation": 16,
"Structuring / Formatting": 11,
"Data Entry / Import": 6,
"Validation / Review": 5,
"Cross-sheet/file Retrieval": 5,
"Summary / Visualization": 4,
"Financial Modeling": 3,
}
# Eval holdout per tag (rest go to train). Sum = 10.
EVAL_HOLDOUT = {
"Calculation": 3,
"Structuring / Formatting": 2,
"Data Entry / Import": 1,
"Validation / Review": 1,
"Cross-sheet/file Retrieval": 1,
"Summary / Visualization": 1,
"Financial Modeling": 1,
}
def primary_tag(task_type: str) -> str:
"""Return the first tag in the comma-separated task_type field."""
return task_type.split(",")[0].strip()
def is_xlsx_task(row) -> bool:
"""Pure-xlsx tasks: source files are all xlsx, reference output is xlsx."""
srcs = row["source_files"]
if not srcs or any(not s.lower().endswith(".xlsx") for s in srcs):
return False
refs = row["reference_outputs"].get("files") or []
if refs and any(not r.lower().endswith(".xlsx") for r in refs):
return False
return True
def download(url: str, dest: Path, timeout: float = 30.0, retries: int = 3) -> None:
if dest.exists() and dest.stat().st_size > 0:
return
dest.parent.mkdir(parents=True, exist_ok=True)
last_exc: Exception | None = None
for _ in range(retries):
try:
with urllib.request.urlopen(url, timeout=timeout) as r, open(dest, "wb") as f:
f.write(r.read())
return
except Exception as e:
last_exc = e
raise RuntimeError(f"download failed after {retries} retries: {last_exc}")
def select(ds, seed: int = 17) -> dict:
"""Return {tag: [row, ...]} sized per TAG_BUDGET, xlsx-only, single-source."""
rng = random.Random(seed)
by_primary: dict[str, list] = defaultdict(list)
for row in ds:
if not is_xlsx_task(row):
continue
if len(row["source_files"]) != 1:
continue
if not row["reference_outputs"].get("files"):
# Skip pure-QA for now; MODIFY tasks dominate and grade cleanly.
continue
by_primary[primary_tag(row["task_type"])].append(row)
picked: dict[str, list] = {}
for tag, budget in TAG_BUDGET.items():
pool = by_primary.get(tag, [])
rng.shuffle(pool)
picked[tag] = pool[:budget]
if len(picked[tag]) < budget:
print(f" ⚠ tag {tag!r}: wanted {budget}, got {len(picked[tag])}")
return picked
def emit_manifest(picked: dict) -> list[dict]:
"""Download files and build manifest rows. Returns the list of rows."""
rows: list[dict] = []
rng = random.Random(31)
for tag, items in picked.items():
rng.shuffle(items)
eval_n = EVAL_HOLDOUT.get(tag, 0)
for i, row in enumerate(items):
split = "eval" if i < eval_n else "train"
tid = f"finch_{row['id']}"
task_dir = DATA_DIR / row["id"]
src_name = row["source_files"][0]
src_url = row["source_files_urls"][0]
ref_name = row["reference_outputs"]["files"][0]
ref_url = row["reference_file_urls"][0]
src_path = task_dir / src_name
ref_path = task_dir / ref_name
try:
download(src_url, src_path)
download(ref_url, ref_path)
except Exception as e:
print(f" ✗ {tid}: download failed: {e}")
continue
rows.append({
"id": tid,
"family": "xlsx",
"origin": "finch",
"orig_id": row["id"],
"split": split,
"primary_tag": tag,
"all_tags": [t.strip() for t in row["task_type"].split(",")],
"business_type": row["business_type"],
"instruction": row["instruction_en"],
"constraints": row.get("task_constraints", "") or "",
"source_file": str(src_path.relative_to(REPO_ROOT)),
"reference_file": str(ref_path.relative_to(REPO_ROOT)),
"task_type": "MODIFY",
"max_steps": 15,
})
print(f" ✓ {tid:14s} {split:5s} {tag}")
return rows
def main():
p = argparse.ArgumentParser()
p.add_argument("--dry-run", action="store_true", help="Don't download, just print picks")
args = p.parse_args()
import sys
print("Loading FinWorkBench/Finch …", flush=True)
ds = load_dataset("FinWorkBench/Finch", split="test")
print(f" {len(ds)} rows", flush=True)
picked = select(ds)
total = sum(len(v) for v in picked.values())
print(f"\nSelected {total} tasks across {len(picked)} tags", flush=True)
if args.dry_run:
for tag, items in picked.items():
print(f" {tag}: {[r['id'] for r in items]}", flush=True)
return
DATA_DIR.mkdir(parents=True, exist_ok=True)
sys.stdout.reconfigure(line_buffering=True)
rows = emit_manifest(picked)
rows.sort(key=lambda r: (r["split"], r["primary_tag"], r["orig_id"]))
MANIFEST_PATH.parent.mkdir(parents=True, exist_ok=True)
with open(MANIFEST_PATH, "w") as f:
for r in rows:
f.write(json.dumps(r) + "\n")
train_n = sum(1 for r in rows if r["split"] == "train")
eval_n = sum(1 for r in rows if r["split"] == "eval")
print(f"\nManifest written: {MANIFEST_PATH}", flush=True)
print(f" train: {train_n} | eval: {eval_n}", flush=True)
if __name__ == "__main__":
main()