near-toll-audit / run.py · raw
#!/usr/bin/env python3"""One command rebuilds everything. python3 run.py --offline rebuild every number and chart from the committed snapshots, then verify them python3 run.py pull fresh keyless data first, then rebuild python3 run.py --bq-light also refresh the two rolling BigQuery queries, about 5 GB (skipped without credentials) python3 run.py --bq also refresh the larger BigQuery snapshots (needs GOOGLE_APPLICATION_CREDENTIALS, free sandbox works) python3 run.py --explorer also rebuild swap aggregates from local Explorer rowsOffline needs Python 3.9+ and nothing else. Node is used only by the model parity test, which is skipped if absent."""import argparse, gzip, hashlib, json, os, pathlib, shutil, subprocess, sys, time, zlibROOT = pathlib.Path(__file__).resolve().parentPY = sys.executabledef step(name, *args, ok_codes=(0,)): t0 = time.time() res = subprocess.run([PY, *map(str, args)], cwd=ROOT) print(f"[{name}] exit {res.returncode}, {time.time() - t0:.1f}s", flush=True) if res.returncode not in ok_codes: raise SystemExit(f"step failed: {name}")def content_hash(path): """sha256 of a snapshot's decompressed content, or None if the file is damaged.""" try: with gzip.open(path, "rb") as fh: return hashlib.sha256(fh.read()).hexdigest() except (OSError, EOFError, zlib.error): return Nonedef verify_snapshots(): """Every committed snapshot must hash to its newest manifest entry.""" manifest = ROOT / "raw" / "MANIFEST.jsonl" latest = {} for line in manifest.read_text().splitlines(): rec = json.loads(line) latest[rec["name"]] = rec checked = bad = 0 for name, rec in latest.items(): path = ROOT / "raw" / "latest" / f"{name}.json.gz" if not path.exists(): continue got = content_hash(path) checked += 1 if got != rec["sha256"]: bad += 1 print(f"HASH MISMATCH {name}") bq = ROOT / "raw" / "bigquery" / "MANIFEST.jsonl" latest_bq = {} for line in bq.read_text().splitlines(): rec = json.loads(line) latest_bq[rec["file"]] = rec for name, rec in latest_bq.items(): got = content_hash(ROOT / "raw" / "bigquery" / name) checked += 1 if got != rec["sha256_csv"]: bad += 1 print(f"HASH MISMATCH {name}") ex = ROOT / "raw" / "explorer" / "MANIFEST.json" if ex.exists(): for rec in json.loads(ex.read_text())["files"]: got = content_hash(ROOT / "raw" / "explorer" / rec["file"]) checked += 1 if got != rec["sha256_csv"]: bad += 1 print(f"HASH MISMATCH {rec['file']}") print(f"snapshots verified: {checked} ok, {bad} bad") if bad: raise SystemExit("raw snapshots do not match the manifest")def main(): ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--offline", action="store_true") ap.add_argument("--bq", action="store_true") ap.add_argument("--bq-light", action="store_true") ap.add_argument("--explorer", action="store_true") args = ap.parse_args() if not args.offline: step("public data", "pipeline/fetch_public.py", ok_codes=(0, 1)) step("chain data", "pipeline/fetch_chain.py", ok_codes=(0, 1)) if args.bq: step("bigquery", "pipeline/run_bq.py", "--only", "01,02,09,10,12,13", "--cap-gb", "120", ok_codes=(0, 1)) if args.bq_light: if os.environ.get("GOOGLE_APPLICATION_CREDENTIALS"): step("bigquery rolling", "pipeline/run_bq.py", "--only", "01b,09b", "--cap-gb", "12", ok_codes=(0, 1)) else: print("[bigquery rolling] skipped: no credentials, using committed snapshots") if args.explorer: step("explorer aggregates", "pipeline/analyze_explorer.py") verify_snapshots() step("analysis", "pipeline/analyze.py") step("model", "pipeline/model.py") step("kill criteria", "pipeline/grade.py", *(["--offline"] if args.offline else [])) step("site", "pipeline/build.py") step("claims", "pipeline/verify_claims.py") tests = ["tests/test_pipeline.py"] if shutil.which("node"): tests.append("tests/test_model.py") for t in tests: step(t, t) print("done: open docs/index.html through any static server, e.g. python3 -m http.server -d docs 8000")if __name__ == "__main__": main()