SPB Git

spb/wp44_uqo

Public
13.1 KB · 185 lines python
1#!/usr/bin/env python32"""WP44 — generate manuscript/tables/numbers.tex: every number quoted in the prose is a LaTeX macro produced here from3result files (never typed by hand). Macro names use letters only (no digits: `\\nCoreEligible`, not `\\n2026`).4Sources: data/metadata/prisma_counts.json, linkage_report.json, results/bibliometrics/*.csv, results/econometrics/*.json,5taxonomy/validation/metrics.json, results/emerging/metrics_h12.json (each optional: missing → macro undefined → compile error,6which is intended: the prose cannot cite a number that does not exist)."""7import json, os, sys, csv, re8R = sys.argv[1] if len(sys.argv) > 1 else os.path.dirname(os.path.dirname(os.path.abspath(__file__)))9out = []10WORDS = {"0": "Zero", "1": "One", "2": "Two", "3": "Three", "4": "Four", "5": "Five", "6": "Six", "7": "Seven", "8": "Eight", "9": "Nine"}11def name(s):12    s = re.sub(r"[^A-Za-z0-9]+", " ", s).title().replace(" ", "")13    return "".join(WORDS[ch] if ch.isdigit() else ch for ch in s)14def fmt(v):15    if isinstance(v, bool): return str(v)16    if isinstance(v, int): return f"{v:,}".replace(",", "{,}")17    if isinstance(v, float):18        return f"{v:,.3f}".replace(",", "{,}") if abs(v) < 100 else f"{v:,.1f}".replace(",", "{,}")19    return str(v).replace("%", "\\%").replace("_", "\\_")20def emit(k, v):21    out.append(f"\\newcommand{{\\{name(k)}}}{{{fmt(v)}}}")22def walk(prefix, obj):23    if isinstance(obj, dict):24        for k, v in obj.items(): walk(f"{prefix} {k}", v)25    elif isinstance(obj, (int, float, str, bool)) and obj is not None:26        emit(prefix, obj)27p = os.path.join(R, "data/metadata/prisma_counts.json")28if os.path.exists(p): walk("prisma", json.load(open(p)))29p = os.path.join(R, "data/metadata/linkage_report.json")30if os.path.exists(p): walk("link", json.load(open(p)))31for f in ["volume_yearly", "citation_windows_yearly", "international_collab_yearly", "industry_share_yearly", "venue_hhi_yearly"]:32    p = os.path.join(R, "results/bibliometrics", f + ".csv")33    if os.path.exists(p):34        for row in csv.DictReader(open(p)):35            y = row.get("year") or row.get("publication_year")36            for k, v in row.items():37                if k in ("year", "publication_year") or v in ("", None): continue38                try: v = int(v) if re.fullmatch(r"-?\d+", v) else float(v)39                except ValueError: pass40                emit(f"{f} {y} {k}", v)41for f, key in [("results/econometrics/its_results.json", "its"), ("taxonomy/validation/metrics.json", "val"), ("results/emerging/metrics_h12.json", "emerg"), ("results/emerging/metrics_h24.json", "emergTwoFour")]:42    p = os.path.join(R, f)43    if os.path.exists(p):44        d = json.load(open(p))45        walk(key, {k: v for k, v in d.items() if not isinstance(v, (list,))})46for f, key in [("data/metadata/dblp_parse_report.json", "dblp"), ("data/metadata/acl_parse_report.json", "acl"), ("data/metadata/api_increment_ledger.json", "increment"), ("data/metadata/increment_report.json", "incrementIngest")]:47    p = os.path.join(R, f)48    if os.path.exists(p):49        d = json.load(open(p))50        if key == "increment":51            sl = d.get("slices", {}); emit("increment api calls", d.get("calls", 0)); emit("increment records retrieved", sum(v.get("records", 0) for v in sl.values()))52            emit("increment truncated slices", sum(1 for v in sl.values() if v.get("truncated"))); emit("increment slices done", sum(1 for v in sl.values() if v.get("done"))); emit("increment cost usd", round(d.get("cost_usd_total", 0), 4))53        elif key == "incrementIngest":54            emit("increment new works", d.get("new_works", 0)); emit("increment already in snapshot", d.get("already_in_snapshot", 0))55        elif key == "dblp":56            emit("dblp parsed records", d.get("rows_written", 0)); [emit(f"dblp type {k}", v) for k, v in d.get("counts_by_type", {}).items()]57        elif key == "acl":58            emit("acl parsed entries", d.get("entries", 0))59for f, key in [("reports/classification_report.json", "classification"), ("literature_synthesis/landmarks/selection_report.json", "selection"), ("data/metadata/linkage_sample_summary.json", "linkagesample")]:60    p = os.path.join(R, f)61    if os.path.exists(p): walk(key, json.load(open(p)))62p = os.path.join(R, "results/emerging/known_set_lead_times.csv")63if os.path.exists(p):64    for row in csv.DictReader(open(p)):65        for k in ("total_works", "detection_month", "peak_growth_month", "lead_months"):66            v = row.get(k)67            if v in ("", None): continue68            try: v = int(float(v)) if re.fullmatch(r"-?\d+(\.0)?", v) else v69            except ValueError: pass70            emit(f"known {row['concept']} {k}", v)71    leads = [int(float(r["lead_months"])) for r in csv.DictReader(open(p)) if r.get("lead_months") not in ("", None)]72    if leads:73        import statistics; emit("known set median lead months", int(statistics.median(leads))); emit("known set n detected", len(leads)); emit("known set n positive lead", sum(1 for l in leads if l > 0))74# ---- per-category growth and yearly primary counts (appended by Agent F, 2026-11-03; used by Parts V–VII) ----75p = os.path.join(R, "results/bibliometrics/cagr_by_category.csv")76if os.path.exists(p):77    for row in csv.DictReader(open(p)):78        for k, v in row.items():79            if k == "category" or v in ("", None): continue80            try: v = int(v) if re.fullmatch(r"-?\d+", v) else float(v)81            except ValueError: pass82            emit(f"cagr {row['category']} {k}", v)83p = os.path.join(R, "results/graph/category_counts_yearly.csv")84if os.path.exists(p):85    agg = {}86    for row in csv.DictReader(open(p)):  # one row per (year, period, category): sum the periods87        k = (row["category_id"], row["year"]); agg[k] = agg.get(k, 0) + int(float(row["n_primary"]))88    for (c, y), v in sorted(agg.items()): emit(f"catcount {c} {y} primary", v)89    post = {}; base = {}90    for (c, y), v in agg.items():91        (post if int(y) >= 2023 else base).setdefault(c, 0); (post if int(y) >= 2023 else base)[c] += v92    for c in sorted(set(base) | set(post)): emit(f"catcount {c} baseline primary", base.get(c, 0)); emit(f"catcount {c} post primary", post.get(c, 0))93# de-duplicate macro names (last definition dropped) so that independent appends cannot break the compile94_seen = set(); _out = []95for line in out:96    n = line.split("}")[0]97    if n in _seen: continue98    _seen.add(n); _out.append(line)99out = _out100open(os.path.join(R, "manuscript/tables/numbers.tex"), "w").write("% machine-generated by tables/make_numbers.py — do not edit\n" + "\n".join(out) + "\n")101print(len(out), "macros")102103# ---- Part VII-b (security half of Part VII; Agent H, 2026-11-03): category-level macros for the five security_privacy ----104# categories, appended to numbers.tex. Sources: results/graph/category_counts_yearly.csv (eligible works, multi-label and105# primary counts), results/bibliometrics/cagr_by_category.csv, results/econometrics/breaks.csv,106# results/graph/category_flows_yearly.csv, results/graph/category_cooccurrence_yearly.csv,107# literature_synthesis/landmarks/security_privacy_landmarks.csv. Percentages are pre-formatted strings (1 or 2 decimals).108SEC = ["cybersecurity", "applied_cryptography", "blockchain", "privacy_preserving_computation", "federated_learning"]109extra = []110def emit2(k, v): extra.append(f"\\newcommand{{\\{name(k)}}}{{{fmt(v)}}}")111agg = {}; tot_primary = {}112p = os.path.join(R, "results/graph/category_counts_yearly.csv")113if os.path.exists(p):114    for row in csv.DictReader(open(p)):115        tot_primary[row["year"]] = tot_primary.get(row["year"], 0) + int(row["n_primary"])116        if row["category_id"] in SEC:117            a = agg.setdefault((row["category_id"], row["year"]), [0, 0]); a[0] += int(row["n_multilabel"]); a[1] += int(row["n_primary"])118    for (c, y), (m, pr) in sorted(agg.items()):119        emit2(f"sec count {c} {y} multilabel", m); emit2(f"sec count {c} {y} primary", pr)120        if tot_primary.get(y): emit2(f"sec share primary {c} {y} pct", f"{100 * pr / tot_primary[y]:.2f}")121    for c in SEC:122        for y0, y1, tag in [("2018", "2022", "baseline"), ("2023", "2025", "post"), ("2022", "2025", "span")]:123            m0, m1 = agg.get((c, y0), [0, 0])[0], agg.get((c, y1), [0, 0])[0]124            if m0 > 0:125                emit2(f"sec cagr multilabel {c} {tag} pct", f"{100 * ((m1 / m0) ** (1 / (int(y1) - int(y0))) - 1):.1f}"); emit2(f"sec ratio multilabel {c} {tag}", f"{m1 / m0:.2f}")126            p0, p1 = agg.get((c, y0), [0, 0])[1], agg.get((c, y1), [0, 0])[1]127            if p0 > 0: emit2(f"sec cagr primary {c} {tag} pct", f"{100 * ((p1 / p0) ** (1 / (int(y1) - int(y0))) - 1):.1f}")128        s = sum(v[0] for (cc, y), v in agg.items() if cc == c); emit2(f"sec count {c} all multilabel", s)129        s = sum(v[1] for (cc, y), v in agg.items() if cc == c); emit2(f"sec count {c} all primary", s)130p = os.path.join(R, "results/bibliometrics/cagr_by_category.csv")131if os.path.exists(p):132    for row in csv.DictReader(open(p)):133        if row["category"] in SEC:134            c = row["category"]135            for k in ("n_2018", "n_2022", "n_2023", "n_2025"): emit2(f"sec cagr {c} {k}", int(row[k]))136            for k in ("cagr_baseline", "cagr_post"): emit2(f"sec cagr {c} {k} pct", f"{100 * float(row[k]):.1f}")137            for k in ("share_2018", "share_2022", "share_2025"): emit2(f"sec cagr {c} {k} pct", f"{100 * float(row[k]):.2f}")138p = os.path.join(R, "results/econometrics/breaks.csv")139if os.path.exists(p):140    for row in csv.DictReader(open(p)):141        c = row["series"].replace("cat_", "")142        if c in SEC:143            for k in ("bp_breaks_bic", "pelt", "bocpd_resets"): emit2(f"sec break {c} {k}", (row[k] or "none").replace(";", ", "))144            for k in ("chatgpt_level", "gpt3_level", "dalle2_level", "gpt4_level", "o1_level"): emit2(f"sec break {c} {k}", float(row[k])); emit2(f"sec break {c} {k} p", float(row[k + "_p"]))145p = os.path.join(R, "results/graph/category_flows_yearly.csv")146if os.path.exists(p):147    want = SEC + ["computer_networks", "computer_vision", "ml_theory", "quantum_computing", "llm", "ai_safety", "ai_agents", "generative_ai", "distributed_computing", "edge_computing", "software_engineering", "database_systems", "hci"]148    YRS = ("2019", "2022", "2025", "2026"); rows = list(csv.DictReader(open(p))); tot = {}149    for row in rows:150        if row["citing_cat"] in SEC: tot[(row["citing_cat"], row["citing_year"])] = tot.get((row["citing_cat"], row["citing_year"]), 0) + int(row["n"])151    for row in rows:152        if row["citing_year"] not in YRS: continue153        if (row["citing_cat"] in SEC and row["cited_cat"] in want) or (row["cited_cat"] in SEC and row["citing_cat"] in ("llm", "ai_agents", "ai_safety", "generative_ai", "computer_networks", "distributed_computing", "edge_computing")):154            emit2(f"sec flow {row['citing_cat']} to {row['cited_cat']} {row['citing_year']} pct", f"{100 * float(row['row_share']):.1f}")155    for (c, y), n in sorted(tot.items()):156        if y in YRS: emit2(f"sec flow {c} total {y}", n)157p = os.path.join(R, "results/graph/category_cooccurrence_yearly.csv")158if os.path.exists(p):159    partners = ["llm", "ai_agents", "ai_safety", "generative_ai", "foundation_models"]160    for row in csv.DictReader(open(p)):161        a, b, y = row["cat_a"], row["cat_b"], row["year"]162        if a in SEC and b in partners: c, q = a, b163        elif b in SEC and a in partners: c, q = b, a164        elif a in SEC and b in SEC: c, q = a, b165        else: continue166        n = int(row["n"]); emit2(f"sec cooc {c} {q} {y}", n)167        m = agg.get((c, y), [0, 0])[0]168        if m: emit2(f"sec cooc {c} {q} {y} pct", f"{100 * n / m:.1f}")169p = os.path.join(R, "literature_synthesis/landmarks/security_privacy_landmarks.csv")170if os.path.exists(p):171    from collections import Counter172    L = list(csv.DictReader(open(p))); n = len(L); emit2("sec land n", n)173    wa = sum(r["has_abstract"] == "True" for r in L); emit2("sec land with abstract", wa); emit2("sec land with abstract pct", f"{100 * wa / n:.0f}")174    emit2("sec land oa pct", f"{100 * sum(r['is_oa'] == 'True' for r in L) / n:.0f}")175    emit2("sec land post", sum(r["period"] == "post" for r in L)); emit2("sec land baseline", sum(r["period"] == "baseline" for r in L)); emit2("sec land year 2026", sum(r["year"] == "2026" for r in L))176    emit2("sec land no doi", sum(not r["doi"] for r in L))177    for t, k in Counter(r["type"] for r in L).items(): emit2(f"sec land type {t}", k)178    for c in SEC:179        g = [r for r in L if r["primary_category"] == c]; emit2(f"sec land {c} n", len(g)); emit2(f"sec land {c} post", sum(r["period"] == "post" for r in g))180        emit2(f"sec land {c} with abstract", sum(r["has_abstract"] == "True" for r in g)); emit2(f"sec land {c} year 2026", sum(r["year"] == "2026" for r in g))181        emit2(f"sec land {c} preprints", sum(r["type"] == "preprint" for r in g)); emit2(f"sec land {c} articles", sum(r["type"] in ("article", "review") for r in g))182        emit2(f"sec land {c} conference", sum(r["type"] == "conference-paper" for r in g)); emit2(f"sec land {c} grey", sum(r["type"] in ("dissertation", "report", "book-chapter", "book") for r in g))183open(os.path.join(R, "manuscript/tables/numbers.tex"), "a").write("% ---- Part VII-b security macros (Agent H) ----\n" + "\n".join(extra) + "\n")184print(len(extra), "Part VII-b macros appended")185
Files · cc7bdc1d7e9c9d644aad447a0ce2c782cdb76947
Menu

Theme