Files
TheLadder/tools/story-to-pack/probe/merge_dupes.py
T
JesseMarkowitzandClaude Opus 5 fa3769d0fe Add story-to-pack research and a structured situation review
Research toward building a content pack from a story corpus, kept on its own
branch and independent of the game. Records the selection experiments against
blind labels, and settles selection as gate G2 followed by a human review:
review.py writes REVIEW.md and a review.json form, apply_review.py checks the
filled form and writes situations.json for the next stage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6UDQ9o6L6Ey173U7XVou6
2026-09-15 06:59:35 -04:00

39 lines
2.0 KiB
Python

"""Near-duplicate situations among accepted clusters (SELECTION.md protocol step 6).
python3 merge_dupes.py dev # list every accepted pair's centroid cosine, with label names
python3 merge_dupes.py test 0.93 # merge pairs at or above a threshold chosen on dev
Centroids are the mean unit summary vector of each cluster. On dev the threshold is chosen by
reading: the lowest cosine that still separates pairs whose names say "same situation" from pairs
that do not. On test it is applied as given, and each merge is reported with both names so it can
be checked.
"""
import json, pathlib, sys
set_name = sys.argv[1]
threshold = float(sys.argv[2]) if len(sys.argv) > 2 else None
sys.argv = ['measure.py', 'summary_embeddings.json']
exec(pathlib.Path('measure.py').read_text(encoding='utf-8').split("print(f'{path.name}")[0])
sys.argv = ['select_rule.py']
exec(pathlib.Path('select_rule.py').read_text(encoding='utf-8').split("mode = sys.argv")[0])
candidates = json.loads(pathlib.Path(f'candidates-{set_name}.json').read_text())
frozen = json.loads(pathlib.Path('rule-frozen.json').read_text())
terms = [tuple(t) for t in frozen['terms']]
rows = {r['id']: r for r in load(set_name)}
accepted = [cid for cid, r in rows.items() if all(passes(r, t) for t in terms)]
cent = {cid: norm([sum(V[i][d] for i in candidates[cid]) / len(candidates[cid]) for d in range(dim)])
for cid in accepted}
pairs = sorted(((dot(cent[a], cent[b]), a, b) for x, a in enumerate(accepted) for b in accepted[x + 1:]),
reverse=True)
print(f'{set_name}: {len(accepted)} accepted clusters, {len(pairs)} pairs\n')
for cos, a, b in pairs[:25]:
mark = ' MERGE' if threshold is not None and cos >= threshold else ''
print(f"{cos:.4f} {a}({rows[a]['label']}) {rows[a]['name'] or '-'}\n {b}({rows[b]['label']}) {rows[b]['name'] or '-'}{mark}")
if threshold is not None:
merged = [(a, b) for cos, a, b in pairs if cos >= threshold]
print(f'\nthreshold {threshold}: {len(merged)} merges')