Coverage for src / quber / playground / backfill_boxes.py: 0%
60 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
« prev ^ index » next coverage.py v7.14.0, created at 2026-09-23 22:14 -0400
1"""In-place tightening of stored table-cell grounding boxes.
3Documents already in the library were extracted before cell rectangles were
4tightened at acquisition, so their stored boxes are still structural grid
5geometry — the column's x-extent crossed with the row band. Re-extracting
6would cost LLM passes and re-ingesting would wipe curation, so this backfill
7corrects the one thing that is wrong: for every tableCell grounding whose
8text can be found in the staged PDF's text layer near the stored box, the
9row's bbox is updated to the printed extent. No other column is touched;
10unmatched cells keep their boxes.
12Run:
13 uv run python -m quber.playground.backfill_boxes [doc_key ...]
15With no arguments every document in the library is processed. A document
16whose staged PDF is missing is reported and skipped, never silently.
17"""
19from __future__ import annotations
21import argparse
22import json
23from pathlib import Path
24from typing import Any, Dict, List, Tuple
26import psycopg
27from psycopg.types.json import Jsonb
29from quber.agents.completeness import page_words
30from quber.core.extractors.camelot.tighten import ink_extent, tighten_box
31from quber.playground import db
32from quber.settings import get_settings
35def backfill_document(conn: psycopg.Connection, doc_id: int, pdf_path: Path) -> Tuple[int, int]:
36 """Tighten one document's cell boxes in place. Returns (tightened, kept)."""
37 rows = conn.execute(
38 """SELECT ref_id, page, bbox, cell_text FROM ade_playground.groundings
39 WHERE document_id = %s AND ref_type = 'tableCell'
40 AND bbox IS NOT NULL AND coalesce(cell_text, '') <> ''""",
41 (doc_id,),
42 ).fetchall()
43 by_page: Dict[int, List[Tuple[Any, ...]]] = {}
44 for r in rows:
45 by_page.setdefault(r[1], []).append(r)
47 tightened = kept = 0
48 for page0, page_rows in by_page.items():
49 try:
50 w, h, words = page_words(pdf_path, page0 + 1)
51 except IndexError:
52 kept += len(page_rows)
53 continue
54 for ref_id, _, bbox, cell_text in page_rows:
55 box = bbox if isinstance(bbox, dict) else json.loads(bbox)
56 points = (box["left"] * w, box["top"] * h, box["right"] * w, box["bottom"] * h)
57 tight = tighten_box(points, cell_text, words) or ink_extent(points, words)
58 if tight is None:
59 kept += 1
60 continue
61 x1, y1, x2, y2 = tight
62 conn.execute(
63 """UPDATE ade_playground.groundings SET bbox = %s
64 WHERE document_id = %s AND ref_id = %s""",
65 (
66 Jsonb({"left": x1 / w, "top": y1 / h, "right": x2 / w, "bottom": y2 / h}),
67 doc_id,
68 ref_id,
69 ),
70 )
71 tightened += 1
72 return tightened, kept
75def main() -> int:
76 parser = argparse.ArgumentParser(description="Tighten stored table-cell grounding boxes in place")
77 parser.add_argument("doc_keys", nargs="*", help="doc_key values to backfill; default all")
78 args = parser.parse_args()
80 data_dir = get_settings().playground.data_dir
81 total_tightened = total_kept = 0
82 missing: List[str] = []
83 with db.connect() as conn:
84 docs = conn.execute(
85 "SELECT id, doc_key, coalesce(title, filename) FROM ade_playground.documents ORDER BY id"
86 ).fetchall()
87 for doc_id, doc_key, label in docs:
88 if args.doc_keys and doc_key not in args.doc_keys:
89 continue
90 pdf_path = data_dir / f"{doc_key}.pdf"
91 if not pdf_path.exists():
92 missing.append(f"{doc_key} ({label})")
93 continue
94 tightened, kept = backfill_document(conn, doc_id, pdf_path)
95 total_tightened += tightened
96 total_kept += kept
97 print(f" {doc_key} tightened {tightened:>5} kept {kept:>4} {label}")
99 print(f"\ntotal tightened {total_tightened}, kept {total_kept}")
100 if missing:
101 print(f"\nSKIPPED — staged PDF missing for {len(missing)} document(s):")
102 for m in missing:
103 print(f" {m}")
104 return 0
107if __name__ == "__main__":
108 raise SystemExit(main())