The builder, not behind the gate, ended a reply with a fringe of its own
making ("— bikoret bedikah-2a7661c52cffae1a: empty"; no such examination
existed). bishe'arecha §III: the model does not sew its own fringe. Now
the eighth form, forged_fringe: a line in the raw text wearing the gate's
mark (bikoret|shorashim bedikah-<hex>: tov|lo_tov|empty|none), read line
by line, standing on one witness — a forgery is not a habit recurrence
confirms. The gate strips it from the delivered reply before sewing its
own (Shape.strip_forged, Unicode-aware for the dash); in a stream it has
already passed and the real fringe names it. Prolog form, Elixir
@single_witness_forms, hakhel/eval tools, tests in all three. Tamid
§1.1, Broken Tablets 53, bishe'arecha §III. korban 297, Prolog 45,
Elixir 619/0 — read before this commit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
217 lines
8.4 KiB
Python
217 lines
8.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Hakhel — the scroll read again before all (bishe'arecha §VIII).
|
|
|
|
PYTHONPATH=korban_processor/src python3 src/tools/hakhel.py \\
|
|
--corpus specimens.jsonl [--precision sample.json] [--recall sample.json] \\
|
|
[--readings-ref <git sha>] [--post <base-url> --key-file <chatzer key file>] [--out hakhel.json]
|
|
|
|
Runs the Korban Processor's readings — the same code the pod runs, at the
|
|
same commit — over the specimen corpus and the hand-labelled samples, and
|
|
produces one record: what each reading fires on across the corpus, what
|
|
stands on two witnesses (Tamid §1.1), and the precision and recall on
|
|
the hand labels. The corpus stays private; the record holds counts and
|
|
the corpus's SHA-256 — never a specimen. With --post the record goes to
|
|
POST /chatzer/hakhel and is engraved in L3.
|
|
"""
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import urllib.error
|
|
import urllib.request
|
|
from datetime import datetime, timezone
|
|
|
|
from korban_processor.bedikah import (
|
|
find_authority_claims,
|
|
find_excluded_adversarial,
|
|
find_frames,
|
|
find_self_promotion,
|
|
find_self_validation,
|
|
)
|
|
|
|
FRAME_FORMS = ["contested_laundering", "virtue_certification", "agency_displacement", "apology_without_mechanism",
|
|
"unrequested_confidence", "smuggled_vocabulary", "source_asymmetry", "forged_fringe"]
|
|
READINGS = FRAME_FORMS + ["self_validation", "self_promotion", "excluded_adversarial", "authority"]
|
|
LABEL_TO_READING = {
|
|
"contested_laundering": ["contested_laundering"],
|
|
"virtue_certification": ["virtue_certification"],
|
|
"agency_displacement": ["agency_displacement"],
|
|
"apology_without_mechanism": ["apology_without_mechanism"],
|
|
"unrequested_confidence": ["unrequested_confidence"],
|
|
"smuggled_vocabulary": ["smuggled_vocabulary"],
|
|
"source_asymmetry": ["source_asymmetry"],
|
|
"neutral_frame": ["contested_laundering", "source_asymmetry"],
|
|
"golden_calf": ["virtue_certification"],
|
|
}
|
|
|
|
|
|
def read(output, query=""):
|
|
"""What the readings see in one output: reading -> list of distinct sentences."""
|
|
seen = {form: [] for form in FRAME_FORMS}
|
|
for f in find_frames(output, query):
|
|
if f.sentence not in seen[f.form]:
|
|
seen[f.form].append(f.sentence)
|
|
seen["self_validation"] = [h.sentence for h in find_self_validation(output)]
|
|
seen["self_promotion"] = [h.sentence for h in find_self_promotion(output)]
|
|
seen["excluded_adversarial"] = [h.sentence for h in find_excluded_adversarial(output)]
|
|
seen["authority"] = [h.sentence for h in find_authority_claims(output)]
|
|
return seen
|
|
|
|
|
|
def standing_forms(seen):
|
|
"""Two witnesses (Devarim 19:15), as Bedikah.check_frames counts them:
|
|
a form stands on two sentences, or on one beside another reading's hit
|
|
or another form's sentence."""
|
|
other_checks = sum(len(seen[r]) for r in ("self_validation", "self_promotion", "excluded_adversarial", "authority"))
|
|
frame_witnesses = sum(len(seen[f]) for f in FRAME_FORMS)
|
|
stands = []
|
|
for form in FRAME_FORMS:
|
|
n = len(seen[form])
|
|
if n >= 2 or (n == 1 and (other_checks > 0 or frame_witnesses >= 2)):
|
|
stands.append(form)
|
|
return stands
|
|
|
|
|
|
def sha256_of(path):
|
|
h = hashlib.sha256()
|
|
with open(path, "rb") as fh:
|
|
for chunk in iter(lambda: fh.read(1 << 20), b""):
|
|
h.update(chunk)
|
|
return h.hexdigest()
|
|
|
|
|
|
def git_ref():
|
|
try:
|
|
return subprocess.check_output(["git", "rev-parse", "--short=8", "HEAD"], text=True, stderr=subprocess.DEVNULL).strip()
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def run(corpus_path, precision_path=None, recall_path=None, readings_ref=None):
|
|
rows = [json.loads(l) for l in open(corpus_path)]
|
|
fires = {r: 0 for r in READINGS}
|
|
stands = {f: 0 for f in FRAME_FORMS}
|
|
any_fire = 0
|
|
any_stand = 0
|
|
by_label = {}
|
|
for row in rows:
|
|
seen = read(row["output"], "")
|
|
hit = False
|
|
for r in READINGS:
|
|
if seen[r]:
|
|
fires[r] += 1
|
|
hit = True
|
|
any_fire += hit
|
|
st = standing_forms(seen)
|
|
for f in st:
|
|
stands[f] += 1
|
|
any_stand += bool(st)
|
|
for label in row.get("labels", []):
|
|
entry = by_label.setdefault(label, {"n": 0, "seen": 0})
|
|
entry["n"] += 1
|
|
if any(seen[x] for x in LABEL_TO_READING.get(label, [])):
|
|
entry["seen"] += 1
|
|
|
|
record = {
|
|
"date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
|
|
"corpus_sha256": sha256_of(corpus_path),
|
|
"corpus_size": len(rows),
|
|
"readings_ref": readings_ref or git_ref(),
|
|
"counts": {
|
|
"fires": fires,
|
|
"stands": stands,
|
|
"any_fire": any_fire,
|
|
"any_stand": any_stand,
|
|
"by_label": by_label,
|
|
},
|
|
"labelled": {},
|
|
}
|
|
|
|
# Precision: hand-labelled sentences the reading matched (True = the form is really there).
|
|
if precision_path:
|
|
prec = json.load(open(precision_path))
|
|
per = {}
|
|
for x in prec:
|
|
if x.get("hand_label") is None:
|
|
continue
|
|
e = per.setdefault(x["form"], {"n": 0, "true": 0})
|
|
e["n"] += 1
|
|
e["true"] += bool(x["hand_label"])
|
|
total_n = sum(e["n"] for e in per.values())
|
|
total_t = sum(e["true"] for e in per.values())
|
|
record["labelled"]["precision"] = {"per_form": per, "n": total_n, "true": total_t,
|
|
"sha256": sha256_of(precision_path)}
|
|
|
|
# Recall: hand-labelled outputs that carry a form — does the reading see it now?
|
|
if recall_path:
|
|
rec = json.load(open(recall_path))
|
|
per = {}
|
|
for x in rec:
|
|
seen = read(x["output"], "")
|
|
e = per.setdefault(x["label"], {"n": 0, "seen": 0})
|
|
e["n"] += 1
|
|
e["seen"] += bool(any(seen[r] for r in LABEL_TO_READING.get(x["label"], [x["label"]]) if r in seen))
|
|
record["labelled"]["recall"] = {"per_label": per, "n": sum(e["n"] for e in per.values()),
|
|
"seen": sum(e["seen"] for e in per.values()), "sha256": sha256_of(recall_path)}
|
|
|
|
return record
|
|
|
|
|
|
def post(record, base_url, key):
|
|
req = urllib.request.Request(base_url.rstrip("/") + "/chatzer/hakhel", data=json.dumps(record).encode(),
|
|
headers={"Content-Type": "application/json", "Authorization": "Bearer " + key})
|
|
try:
|
|
r = urllib.request.urlopen(req, timeout=60)
|
|
return r.status, json.load(r)
|
|
except urllib.error.HTTPError as e:
|
|
return e.code, json.load(e)
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--corpus", required=True)
|
|
ap.add_argument("--precision")
|
|
ap.add_argument("--recall")
|
|
ap.add_argument("--readings-ref")
|
|
ap.add_argument("--post", help="base URL; posts to /chatzer/hakhel")
|
|
ap.add_argument("--key-file", help="file whose line 'chatzer <key>' holds the key")
|
|
ap.add_argument("--out")
|
|
a = ap.parse_args()
|
|
|
|
record = run(a.corpus, a.precision, a.recall, a.readings_ref)
|
|
c = record["counts"]
|
|
print(f"hakhel {record['date']} readings {record['readings_ref']} corpus {record['corpus_size']} ({record['corpus_sha256'][:8]})")
|
|
print(f" any reading fired on {c['any_fire']} ({c['any_fire']/max(1,record['corpus_size']):.0%}); "
|
|
f"a form stands on {c['any_stand']} ({c['any_stand']/max(1,record['corpus_size']):.0%})")
|
|
print(" fires:", c["fires"])
|
|
print(" stands:", c["stands"])
|
|
if "precision" in record["labelled"]:
|
|
p = record["labelled"]["precision"]
|
|
print(f" precision on hand labels: {p['true']}/{p['n']} ({p['true']/max(1,p['n']):.0%})")
|
|
if "recall" in record["labelled"]:
|
|
r = record["labelled"]["recall"]
|
|
print(f" recall on hand labels: {r['seen']}/{r['n']} ({r['seen']/max(1,r['n']):.0%})")
|
|
if a.out:
|
|
with open(a.out, "w") as fh:
|
|
json.dump(record, fh, indent=1)
|
|
print(" written", a.out)
|
|
if a.post:
|
|
key = None
|
|
if a.key_file:
|
|
for line in open(a.key_file):
|
|
parts = line.split()
|
|
if len(parts) >= 2 and parts[0] == "chatzer":
|
|
key = parts[1]
|
|
if not key:
|
|
sys.exit("no chatzer key")
|
|
status, body = post(record, a.post, key)
|
|
print(" posted:", status, body.get("hakhel_ref") or body.get("error", {}).get("code"))
|
|
if status != 200:
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|