data-use-annotate / merge.py
rafmacalaba's picture
annotation review app (per-user queues, Hub-backed rulings, static-safe direct commit)
53ea208 verified
Raw History Blame Contribute Delete
2.33 kB
#!/usr/bin/env python3
"""Reconcile annotation files into one adjudicated row per key.
uv run python human_labeling/merge.py [--out annotations/adjudicated.jsonl]
Reads every annotations/rulings*.jsonl + annotations/*-export.json
(export format: {"items": [...]}, same ruling objects), majority-votes
ties -> "REVIEW", and writes adjudicated.jsonl:
{"key", "verdict", "n_keep", "n_drop", "annotators", "origins"?, ...}
Kept inputs are never modified; rerun any time.
"""
import argparse
import glob
import json
from pathlib import Path
HERE = Path(__file__).resolve().parent
ANNOT = HERE / "annotations"
def load() -> list[dict]:
rulings: list[dict] = []
for f in sorted(glob.glob(str(ANNOT / "rulings*.jsonl"))):
for line in open(f):
line = line.strip()
if line:
rulings.append(json.loads(line))
for f in sorted(glob.glob(str(ANNOT / "*-export.json"))):
try:
payload = json.load(open(f))
except json.JSONDecodeError:
continue
items = payload.get("items", payload if isinstance(payload, list) else [])
rulings.extend(i for i in items if isinstance(i, dict) and i.get("key"))
return rulings
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--out", default=str(ANNOT / "adjudicated.jsonl"))
a = ap.parse_args()
by_key: dict[str, list[dict]] = {}
for r in load():
by_key.setdefault(r["key"], []).append(r)
n_tie = 0
with open(a.out, "w") as f:
for key in sorted(by_key):
rs = by_key[key]
keep = sum(1 for r in rs if r["ruling"] == "DATA_MENTION")
drop = sum(1 for r in rs if r["ruling"] == "NON_MENTION")
verdict = "DATA_MENTION" if keep > drop else "NON_MENTION" if drop > keep else "REVIEW"
n_tie += verdict == "REVIEW"
f.write(json.dumps({
"key": key, "verdict": verdict, "n_keep": keep, "n_drop": drop,
"n": len(rs), "annotators": sorted({r.get("annotator", "?") for r in rs}),
"surface": rs[0].get("surface"), "origin": rs[0].get("origin"),
"head_score": rs[0].get("head_score"),
}) + "\n")
print(f"keys={len(by_key)} ties={n_tie} -> {a.out}")
if __name__ == "__main__":
main()