Spaces:
Running
Running
annotation review app (per-user queues, Hub-backed rulings, static-safe direct commit)
53ea208 verified | #!/usr/bin/env python3 | |
| """Group scored flat pool items into passage examples (multi-mention). | |
| Reads the flat rescored queue (build_pool_queue.py → rescore_singlepass.py) | |
| and emits one JSON object per distinct (origin, ctx): | |
| {queue, origin, ctx, n, band, scored_by, | |
| mentions: [{key, surface, start, end, head_score, band}...]} # by start | |
| Passages with one mention stay as-is; multi stays multi — the UI drives | |
| them as one example with a per-mention ruling target. The passage-level | |
| band is the most-informative mention state: | |
| unscored any unscored | |
| confusion any confusion | |
| mixed keep + drop, no confusion | |
| else the single uniform band | |
| uv run python human_labeling/build_passage_queue.py \ | |
| [--input human_labeling/queue.json] [--out human_labeling/queue_passages.json] | |
| """ | |
| import argparse | |
| import json | |
| from collections import defaultdict | |
| from pathlib import Path | |
| HERE = Path(__file__).resolve().parent | |
| def passage_band(mentions: list[dict]) -> str: | |
| bands = {m.get("band") for m in mentions} | |
| if "unscored" in bands: | |
| return "unscored" | |
| if "confusion" in bands: | |
| return "confusion" | |
| if len(bands) == 1: | |
| return bands.pop() | |
| return "mixed" | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--input", default=str(HERE / "queue.json")) | |
| ap.add_argument("--out", default=str(HERE / "queue_passages.json")) | |
| a = ap.parse_args() | |
| groups: dict[tuple, list[dict]] = defaultdict(list) | |
| for line in Path(a.input).read_text().splitlines(): | |
| if not line.strip(): | |
| continue | |
| r = json.loads(line) | |
| if r.get("ctx"): | |
| groups[(r.get("origin"), r["ctx"])].append(r) | |
| passages = [] | |
| for (origin, ctx), rows in groups.items(): | |
| mentions = sorted( | |
| ({"key": r["key"], "surface": r.get("surface"), | |
| "start": r.get("start"), "end": r.get("end"), | |
| "head_score": r.get("head_score"), "band": r.get("band")} | |
| for r in rows), | |
| key=lambda m: (m["start"] if isinstance(m["start"], int) else 0, | |
| m["surface"] or "")) | |
| passages.append({ | |
| "queue": "probe_candidates/passages", | |
| "origin": origin, "ctx": ctx, "n": len(mentions), | |
| "band": passage_band(mentions), "mentions": mentions, | |
| "scored_by": rows[0].get("scored_by"), | |
| }) | |
| passages.sort(key=lambda p: (p["origin"] or "", p["ctx"] or "")) | |
| Path(a.out).write_text( | |
| "\n".join(json.dumps(p) for p in passages) + "\n") | |
| n_multi = sum(1 for p in passages if p["n"] > 1) | |
| from collections import Counter | |
| print(f"passages={len(passages)} multi={n_multi} " | |
| f"{Counter(p['band'] for p in passages)} -> {a.out}") | |
| if __name__ == "__main__": | |
| main() |