textmachine/eval/exp15/assemble_traps.py

111 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""exp15 — assemble the FINAL 30-节 trap set: merge deterministic straddles (straddle_ledger.json:
23 T-ent control + 7 T-ana) with kimi enrichment (ana_enrichment.json: T-ana/T-ell), dedup by
(type, boundary), require each to STRADDLE a flat-A0-30 greedy cut (common denominator), and map each
to the dependent chunk (i+1) so the judge can score each arm's rendering of that chunk.
Output final_traps.json: the trap set for Q1b (oracle vs flat) and Q3a (carryover) judging.
Run: python assemble_traps.py
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from chunker import greedy_chunks
SD = Path("/home/ubuntu/books/gu-zhenren/exp15")
FLAT = SD / "S2prime_big_flat.txt"
OUT = SD / "final_traps.json"
def flat_boundaries(text):
ch = greedy_chunks(text)
offs, starts, pos = [], [0], 0
for i, c in enumerate(ch):
idx = text.find(c[:25], max(0, pos - 5))
if idx < 0:
idx = text.find(c[:25])
if i > 0 and idx >= 0:
offs.append(idx); starts.append(idx)
pos = idx + len(c) if idx >= 0 else pos + len(c)
return ch, offs, starts
def chunk_of_offset(starts, off):
c = 0
for i, s in enumerate(starts):
if off >= s:
c = i
return c
def straddled_boundary(text, offs, a_span, d_span):
ai = text.find(a_span[:25]); di = text.find(d_span[:25])
if ai < 0 or di < 0:
return None, None, None
lo, hi = min(ai, di), max(ai + len(a_span), di + len(d_span))
crossed = [k for k, cut in enumerate(offs) if lo < cut < hi]
if not crossed:
return None, ai, di
return crossed[0], ai, di # boundary index (0-based cut = between chunk k and k+1)
def main():
text = FLAT.read_text(encoding="utf-8")
ch, offs, starts = flat_boundaries(text)
traps = []
seen = set() # (type, boundary_cut)
def add(t, typ, control, a_span, d_span, phen, exp):
bcut, ai, di = straddled_boundary(text, offs, a_span, d_span)
if bcut is None:
return "not_straddling"
key = (typ, bcut)
if key in seen:
return "dup"
seen.add(key)
dep_chunk = chunk_of_offset(starts, di) # chunk containing the dependent (scored chunk)
anc_chunk = chunk_of_offset(starts, ai)
traps.append({"id": f"{typ}-{len(traps)+1}", "type": typ, "control": control,
"boundary_cut": bcut, "anc_chunk": anc_chunk, "dep_chunk": dep_chunk,
"anchor_span": a_span, "dependent_span": d_span,
"phenomenon": phen, "expected_ru": exp})
return "ok"
stats = {"ok": 0, "dup": 0, "not_straddling": 0}
# deterministic
det = json.load(open(SD / "straddle_ledger.json"))["traps"]
for t in det:
r = add(t, t["type"], t.get("control", False), t["anchor_span"], t["dependent_span"],
t["phenomenon"], t["expected_ru"])
stats[r] += 1
# enrichment
enr = json.load(open(SD / "ana_enrichment.json"))["straddles"]
for t in enr:
r = add(t, t.get("type", "T-ana"), False, t.get("antecedent_span", ""), t.get("dependent_span", ""),
t.get("phenomenon", ""), t.get("expected_ru", ""))
stats[r] += 1
from collections import Counter
by = Counter(t["type"] for t in traps)
prim = Counter(t["type"] for t in traps if not t["control"])
OUT.write_text(json.dumps({
"n": len(traps), "by_type": dict(by),
"quota": {"T-ana": {"have": prim.get("T-ana", 0), "need": 20, "meets": prim.get("T-ana", 0) >= 20},
"T-ent": {"have": by.get("T-ent", 0), "need": 20, "meets": by.get("T-ent", 0) >= 20, "role": "negative-control"},
"T-ell": {"have": by.get("T-ell", 0), "need": 10, "meets": by.get("T-ell", 0) >= 10, "note": "secondary; short = honest-power"}},
"undecidable_a_priori": ["T-tense", "T-cat", "T-gen", "T-reg"],
"merge_stats": stats,
"traps": traps,
}, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"final trap set: {dict(by)} (total {len(traps)}) merge {stats}")
print(f" T-ana (primary): {prim.get('T-ana',0)} {'MEETS>=20' if prim.get('T-ana',0)>=20 else 'SHORT'}")
print(f" T-ent (control): {by.get('T-ent',0)} {'MEETS>=20' if by.get('T-ent',0)>=20 else 'SHORT'}")
print(f" T-ell (secondary): {by.get('T-ell',0)} (>=10? {by.get('T-ell',0)>=10})")
print(f" undecidable a-priori: T-tense/T-cat/T-gen/T-reg -> {OUT}")
if __name__ == "__main__":
main()