1#!/usr/bin/env python32"""Emit paper/tables/numbers.tex: LaTeX macros for every number quoted in the paper, read from recorded artefacts only."""3import json, sys, re4from pathlib import Path5import pandas as pd, yaml6sys.path.insert(0, "src")7from jof250 import config8R = config.ROOT; out = []9def mac(name, val, fmt=None):10 if isinstance(val, float) and fmt: val = format(val, fmt)11 if isinstance(val, (int,)) and not isinstance(val, bool): val = f"{val:,}".replace(",", "{,}")12 out.append(f"\\newcommand{{\\{name}}}{{{val}}}")13man = pd.read_parquet(R / "data/manifests/corpus_manifest.parquet"); inc = man[man.decision == "include"]14mac("nFiles", len(man)); mac("nPDF", int((man.ext == ".pdf").sum())); mac("nIncluded", len(inc)); mac("nExcluded", int((man.decision == "exclude").sum()))15for y, n in inc.bib_year.value_counts().sort_index().items(): mac(f"nYear{int(y)}", int(n))16for t, n in inc.article_type.value_counts().items(): mac("nType" + "".join(w.capitalize() for w in t.split("_")), int(n))17mac("nCrossrefOk", int(inc.crossref_ok.sum())); mac("pagesTotal", int(inc.pages.sum())); mac("pagesMedian", float(inc.pages.median()), ".0f"); mac("sizeMB", float(inc.size_bytes.sum() / 1e6), ".0f")18ps = pd.read_parquet(R / "data/derived/parse_summary.parquet"); mac("parseMeanConf", float(ps.confidence.mean()), ".3f"); mac("parseLowConf", int((ps.confidence < 0.8).sum()))19for k in ["n_words_body", "n_tables", "n_figures", "n_equations", "n_references", "n_footnotes", "abstract_words", "n_sections"]: mac("parseMedian" + "".join(w.capitalize() for w in k.split("_")), float(ps[k].median()), ".0f")20qa = json.loads((R / "data/derived/parser_qa.json").read_text())["metrics"]21for k, v in qa.items(): mac("qa" + "".join(w.capitalize() for w in k.replace(".", "").split("_")), (f"{100*v:.0f}" if isinstance(v, float) and v <= 1 else v))22ann_p = R / "data/annotations/annotations_table.parquet"23if ann_p.exists():24 a = pd.read_parquet(ann_p); mac("nAnnotated", len(a)); mac("nAnnValid", int(a.schema_valid.sum())); v = a[a.schema_valid == True]25 for g, n in v.genre.value_counts().items(): mac("nGenre" + "".join(w.capitalize() for w in g.split("_")), int(n))26 for g, n in v.subfield.value_counts().items(): mac("nSub" + "".join(w.capitalize() for w in g.split("_")), int(n))27 mac("roleAgreementMedian", float(v.role_agreement.median()), ".2f"); mac("roadmapAgree", float((v.roadmap_llm == v.roadmap_regex).mean() * 100), ".0f"); mac("contribAgree", float(((v.contribution_para_idx >= 0) == v.contribution_regex_intro).mean() * 100), ".0f")28 mac("annCostUSD", float(v.cost_usd.sum()), ".2f"); mac("annCostPerDoc", float(v.cost_usd.mean()), ".3f")29for name in ["corpus_all", "empirical_all", "empirical_asset_pricing", "corporate_finance", "theoretical_finance", "financial_econometrics", "financial_intermediation_banking", "household_behavioral_finance"]:30 p = R / "profiles" / f"{name}.yaml"31 if not p.exists(): continue32 prof = yaml.safe_load(p.read_text()); key = "".join(w.capitalize() for w in name.split("_")); mac(f"prof{key}N", prof["n_documents"])33 st = prof["structure"]34 for k in ["n_words_body", "intro_words", "intro_paragraphs", "intro_share", "n_tables", "n_equations", "n_references", "conclusion_share"]:35 if st.get(k, {}).get("n"): mac(f"prof{key}" + "".join(w.capitalize() for w in k.split("_")) + "Med", st[k]["median"], ".2f" if "share" in k else ".0f"); mac(f"prof{key}" + "".join(w.capitalize() for w in k.split("_")) + "Qa", st[k]["q25"], ".2f" if "share" in k else ".0f"); mac(f"prof{key}" + "".join(w.capitalize() for w in k.split("_")) + "Qb", st[k]["q75"], ".2f" if "share" in k else ".0f")36 rh = prof.get("rhetoric")37 if rh:38 for m, d in rh["abstract_moves_present"].items(): mac(f"prof{key}Abs" + m.capitalize(), float(100 * d["share"]), ".0f")39 intro = rh["introduction"]; mac(f"prof{key}Roadmap", float(100 * intro["roadmap_present_llm"]["share"]), ".0f"); mac(f"prof{key}RoadmapRegex", float(100 * intro["roadmap_present_regex"]["share"]), ".0f")40 if intro["contribution_relative_position"].get("n"): mac(f"prof{key}ContribPos", intro["contribution_relative_position"]["median"], ".2f"); mac(f"prof{key}ContribPosQa", intro["contribution_relative_position"]["q25"], ".2f"); mac(f"prof{key}ContribPosQb", intro["contribution_relative_position"]["q75"], ".2f")41 mac(f"prof{key}Causal", rh["identification"]["causal_language_strength"]["median"], ".1f"); mac(f"prof{key}EconSig", float(100 * rh["results"]["economic_significance_discussed"]["share"]), ".0f")42 mac(f"prof{key}ConclBounded", float(100 * rh["conclusion"]["bounded_claims"]["share"]), ".0f"); mac(f"prof{key}ConclOpen", float(100 * rh["conclusion"]["open_questions"]["share"]), ".0f")43 fm = intro["first_move"]; top = max((k for k in fm if k != "n"), key=lambda k: fm[k]) if len(fm) > 1 else "n/a"; mac(f"prof{key}FirstMove", top.replace("_", " ")); mac(f"prof{key}FirstMoveShare", float(100 * fm.get(top, 0)), ".0f")44 rl = rh["robustness"]["location"]; topr = max((k for k in rl if k != "n"), key=lambda k: rl[k]) if len(rl) > 1 else "n/a"; mac(f"prof{key}RobustLoc", topr.replace("_", " ")); mac(f"prof{key}RobustLocShare", float(100 * rl.get(topr, 0)), ".0f")45 se = rh["tables"]["standard_errors"]; tops = max((k for k in se if k != "n"), key=lambda k: se[k]) if len(se) > 1 else "n/a"; mac(f"prof{key}SE", tops.replace("_", " ")); mac(f"prof{key}SEShare", float(100 * se.get(tops, 0)), ".0f")46im = R / "data/derived/index/index_meta.json"47if im.exists():48 m = json.loads(im.read_text()); mac("idxUnits", m["n_units"]); mac("idxDocs", m["n_docs"]); mac("idxDocsAnn", m["n_docs_with_annotation"]); mac("idxDim", m["dim"])49 for k, v in m["units_by_type"].items(): mac("idxUnit" + "".join(w.capitalize() for w in k.split("_")), v)50sp = json.loads((R / "data/splits/heldout_v1.json").read_text()); mac("nTrain", sp["n_train"]); mac("nHeldout", sp["n_heldout"]); mac("nHeldoutAuthorOverlap", sp["heldout_with_author_surname_overlap_in_train"])51led = R / "data/annotations/api_ledger.jsonl"52if led.exists():53 L = [json.loads(l) for l in led.read_text().splitlines() if l.strip()]; mac("apiCalls", len(L)); mac("apiSpendUSD", float(sum(x.get("cost_usd", 0) for x in L)), ".2f")54 byp = {}55 for x in L: byp[x.get("purpose", "?").split(":")[0]] = byp.get(x.get("purpose", "?").split(":")[0], 0) + x.get("cost_usd", 0)56 for k, v in byp.items(): mac("apiSpend" + re.sub(r"[^A-Za-z]", "", k.capitalize()), float(v), ".2f")57res_p = R / "experiments/analysis/results_heldout_v1.json"58if res_p.exists():59 res = json.loads(res_p.read_text()); mac("bmRuns", res["n_runs"]); mac("bmRunsOk", res["n_runs_ok"]); mac("bmJudgments", res["n_judgments"])60 for c, d in res.get("judge_means", {}).items():61 key = c.split("_")[0]62 for dim, v in d.items():63 if v is not None: mac(f"bm{key}" + "".join(w.capitalize() for w in dim.split("_")), float(v), ".2f" if dim != "n" else ".0f")64 for h, tests in res.get("hypotheses", {}).items():65 for o, t in tests.items():66 if "mean_diff" in t:67 k = re.sub(r"[^A-Za-z]", "", h) + "".join(w.capitalize() for w in o.split("_")); mac(f"bm{k}Diff", t["mean_diff"], "+.2f"); mac(f"bm{k}Lo", t["ci95"][0], ".2f"); mac(f"bm{k}Hi", t["ci95"][1], ".2f"); mac(f"bm{k}P", t.get("holm_p", "n/a"), ".3f" if isinstance(t.get("holm_p"), float) else None); mac(f"bm{k}N", t["n"])68 for c, d in res.get("secondary_means", {}).items():69 key = c.split("_")[0] if c in ("A_baseline", "B_strong_prompt", "C_conventional_rag", "D_profiles_retrieval", "E_full_engine") else "".join(w.capitalize() for w in c.split("_"))70 for dim, v in d.items():71 if v is not None: mac(f"bmSec{key}" + "".join(w.capitalize() for w in dim.split("_")), float(v), ".3f" if dim in ("preserved_rate", "max_overlap_share", "cost_usd") else ".2f")72 if "H5_overlap" in res: mac("bmOverlapMax", res["H5_overlap"]["max_overlap_share_overall"], ".4f"); mac("bmOverlapBelow", float(100 * res["H5_overlap"]["share_outputs_below_0.02"]), ".0f")73 if "judge_consistency" in res:74 jc = res["judge_consistency"]; mac("bmJudgePairs", jc["n_pairs"]); mac("bmJudgeMAD", float(sum(jc[d]["mean_abs_diff"] for d in jc if d != "n_pairs") / 6), ".2f")75(R / "paper/tables").mkdir(exist_ok=True); (R / "paper/tables/numbers.tex").write_text("% generated by scripts/07_make_numbers.py — do not edit\n" + "\n".join(out) + "\n"); print(len(out), "macros")76