textmachine/eval/cold_run_b/measure.py

137 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""measure.py — the money instrument of cold run B, pre-registered before the first cent.
It answers ONE question the call-level numbers cannot: what share of a book's UNITS was paid for and
not kept. A unit is `(chapter, chunk_idx, stage, role)` and it SPANS attempts on purpose — the
attempt number is deliberately not in the key, because "we bought this cell twice" is a statement
about the cell, not about a retry.
Why it is validated against run A before it is pointed at run B: an instrument that has never
reproduced a known number is indistinguishable from one that reads the wrong column. `--selfcheck`
re-derives run A's published figures from run A's own database and refuses if any of them moves.
⚠ TWO THINGS THIS FILE REFUSES TO DO, both because a previous shift got a beautiful false number
from doing them:
* it never decides "which answer was accepted" by ORDER IN TIME. `ts` in this schema is second-
granular while `started_at` carries microseconds, so every checkpoint substitution is formally
"before" the attempt that made it. The attempt number is NAMED in `trace_id`; it is read, not
computed.
* it never calls the second numerator "over-payment". On run A the second terminology pass changed
the rendering of 12 terms out of 69 and 11 of those 12 are what the reader actually got. That is
the PRICE OF RE-MINING THE BANK AFTER A HUMAN SIGNATURE, not money bought for nothing.
"""
import argparse, collections, json, sqlite3, sys
def load(db):
c = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
c.row_factory = sqlite3.Row
rows = [dict(r) for r in c.execute("select * from request_log order by id")]
c.close()
return rows
def attempt_of(row):
"""The attempt is NAMED in the row. `trace_id` ends with it (tm-stream-<run>-<n>)."""
t = row.get("trace_id") or ""
tail = t.rsplit("-", 1)[-1]
return tail if tail.isdigit() else "?"
def measure(rows):
total = sum(r["cost_usd"] for r in rows)
units = collections.defaultdict(list)
for r in rows:
units[(r["chapter"], r["chunk_idx"], r["stage"], r["role"])].append(r)
paid = {k: v for k, v in units.items() if sum(x["cost_usd"] for x in v) > 0}
n1 = {k: v for k, v in units.items() if any(x["ok"] == 0 and x["cost_usd"] > 0 for x in v)}
n1_money = sum(x["cost_usd"] for v in n1.values() for x in v if x["ok"] == 0 and x["cost_usd"] > 0)
n2 = {k: v for k, v in units.items()
if sum(1 for x in v if x["ok"] == 1 and x["cost_usd"] > 0) > 1}
# Both orientations are printed. Neither is derived from time: rows are ordered by the primary
# key, and which one is called "the extra" is a DECLARED choice, not a discovered fact.
but_first = 0.0
but_last = 0.0
for v in n2.values():
acc = sorted([y for y in v if y["ok"] == 1 and y["cost_usd"] > 0], key=lambda z: z["id"])
but_first += sum(x["cost_usd"] for x in acc[1:])
but_last += sum(x["cost_usd"] for x in acc[:-1])
by_degraded = sum(r["cost_usd"] for r in rows if (r["degraded"] or "") != "")
by_ok0 = sum(r["cost_usd"] for r in rows if r["ok"] == 0 and r["cost_usd"] > 0)
free = [r for r in rows if r["tm_hit"] == 1]
per_attempt = collections.Counter(attempt_of(r) for r in rows)
return dict(rows=len(rows), calls=len(rows) - len(free), free=len(free), total=total,
units=len(units), paid_units=len(paid), n1=len(n1), n1_money=n1_money,
n2=len(n2), n2_but_first=but_first, n2_but_last=but_last,
by_degraded=by_degraded, by_ok0=by_ok0, per_attempt=dict(per_attempt),
n1_keys=sorted(n1), n2_keys=sorted(n2))
def report(m, label):
def pct(x):
return f"{100 * x / m['total']:.1f}%" if m["total"] else "n/a"
print(f"=== {label} ===")
print(f" rows={m['rows']} calls={m['calls']} free checkpoint replays={m['free']} "
f"total=${m['total']:.6f} per attempt (from trace_id): {m['per_attempt']}")
print(f" UNITS total={m['units']} paid (sum cost>0)={m['paid_units']} "
f"{'⚠ the paid filter removed nothing — its behaviour is still unexercised' if m['units'] == m['paid_units'] else 'the paid filter DID remove units'}")
# ⛔ THE DIVISION IS GUARDED, AND THE REASON IS THE BEST ARGUMENT THIS FILE CARRIES. Two lines above,
# this same report prints "the paid filter removed nothing — its behaviour is still unexercised"
# whenever every unit was paid for. On run A and run B that was true, so the branch where NOTHING was
# paid for never ran — and the first time it did (the $0 smoke base, and any empty project), the
# instrument died with ZeroDivisionError instead of printing its own zero. An unexercised branch of a
# MEASURING tool is not a cosmetic gap: the one reading where the share is undefined is exactly the
# reading a $0 arm produces, which is where an instrument is supposed to be rehearsed before it meets
# money. Found by the acceptance, not by the author, on a base this file had already been run against.
share = f"{100 * m['n1'] / m['paid_units']:.1f}% of units" if m["paid_units"] else \
"share UNDEFINED — no unit was paid for at all (not the same statement as 0%)"
print(f" NUMERATOR 1 — a paid attempt that was not kept: {m['n1']} of {m['paid_units']} units = "
f"{share} (money ${m['n1_money']:.6f} = {pct(m['n1_money'])})")
for k in m["n1_keys"]:
print(f" {k}")
print(f" NUMERATOR 2 — a cell whose KEPT result was paid for more than once: {m['n2']} units")
print(f" all payments but the FIRST = ${m['n2_but_first']:.6f} = {pct(m['n2_but_first'])} <- the declared number (lower bound)")
print(f" all payments but the LAST = ${m['n2_but_last']:.6f} = {pct(m['n2_but_last'])} <- the other orientation, printed beside it")
for k in m["n2_keys"]:
print(f" {k}")
print(f" MONEY, path 1 (degraded <> '') = ${m['by_degraded']:.6f} = {pct(m['by_degraded'])}")
print(f" MONEY, path 2 (ok=0 AND cost>0) = ${m['by_ok0']:.6f} = {pct(m['by_ok0'])}")
print(f" {'the two paths AGREE — a weak sign: on run A they agree BY CONSTRUCTION' if abs(m['by_degraded'] - m['by_ok0']) < 1e-9 else '⛔ the two paths DISAGREE — a STRONG finding: the classifier and the acceptance mark have come apart'}")
SELFCHECK = {"rows": 33, "calls": 27, "free": 6, "units": 17, "paid_units": 17, "n1": 4, "n2": 6}
SELFCHECK_MONEY = {"total": 0.419423, "by_degraded": 0.106472, "by_ok0": 0.106472,
"n2_but_first": 0.028742, "n2_but_last": 0.035723}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--db", required=True)
ap.add_argument("--label", default="run")
ap.add_argument("--selfcheck", action="store_true",
help="the db is run A's: refuse unless every published figure of run A comes back")
ap.add_argument("--json")
a = ap.parse_args()
m = measure(load(a.db))
report(m, a.label)
if a.json:
with open(a.json, "w") as f:
json.dump({k: v for k, v in m.items() if k not in ("n1_keys", "n2_keys")}, f, indent=2)
if a.selfcheck:
bad = [f"{k}: {m[k]} != {v}" for k, v in SELFCHECK.items() if m[k] != v]
bad += [f"{k}: {m[k]:.6f} != {v:.6f}" for k, v in SELFCHECK_MONEY.items()
if abs(m[k] - v) > 5e-7]
if bad:
print("⛔ SELFCHECK FAILED — this instrument does not reproduce run A:")
for b in bad:
print(" " + b)
sys.exit(1)
print("SELFCHECK PASSED: every published figure of run A comes back from its own database.")
if __name__ == "__main__":
main()