SPB Git

spb/firc0001_uqo

Public

Stage F B1-B2 N1-N14

Verification sign-off (Agent 11) corrections, no new analysis, no LLM call.

B1 construct.tex §5.1: financing-class amounts printed in trillions (\SPIDfinAmountT / \SPIDfinAmountDenomT; source matched_amount_gusd is in billions), consistent with the Table 5 note and §7.3.
B2 make_tables.py Table 16 note: key read from results/resolver/example_provenance_keys.csv ("san patricio county, texas"), cluster sizes (8 / 1 / 176) from spec_curve.csv at the release threshold; Table 9 note defines "Cheniere cluster size" as the size of the cluster containing the example mention.
N1 data/documented_inputs.json: the six pipeline constants (chunk words/overlap, max file MB, cooldown, SEC rate limit, gateway divisor) recorded with snapshot path + SHA-256; make_tables.py reads them there and cross-checks the archive when reachable; verified in a git-archive copy outside the review tree (844 macros, tables/figures/inline numbers identical, 22/22 tests, latexmk 0 errors).
N2 Table 8 Panel B: retryable lines 94,593 = 94,465 inside the window + 128 in the 20 s tail (no double count); §5.5 prose aligned (\SPIDllmRetryableInsideWindow).
N3 Table 17 note + §8.2: normalisation stated (case-fold, punctuation to spaces, alias list, whole-word search); results/grounding/partners_normalisation_check.json (read-only re-derivation on M3U96b: 99.3 % under the rule vs 96.1 % plain case-fold on a 20,000-mention sample).
N4 abstract: "every structured field verbatim ... for 88.2 % of mentions" (150 words).
N5 Table 6 headers shortened: overfull 20.07 pt removed (max overfull now 3.3 pt).
N6 §5.5 / Table 8 Panel C: log vs ledger bases labelled (5,198 / 4,769 rows; 34 / 30 mentions); the 504 outage-window rows verified productive (results/ledger/outage_window_rows_check.json, 0 zero-mention rows).
N7 Table 9 note prints the branch count 229 as in §6.1 and explains the 286 keys ending in "general".
N8 Appendix E: pooled shares 12.4 / 12.7 % vs per-issuer means 11.9 / 12.5 % (paired 0.6 pp, SE 1.3); stored vs ledger mention bases named once.
N9 cost basis: 775 USD stated as recorded against the October run, alternative per-call figure if both runs (0.0011 USD over 703,725 calls), author to confirm (documented_inputs.json source updated).
N12 reproducibility/validation_report.md: gate regex has 25 alternatives.
N10, N11, N13, N14: no change (author decisions / engine limitation), recorded in improvements/FIRC-0001/unresolved_issues.md.

Build: latexmk 0 errors, 0 undefined references, 0 overfull > 5 pt, 0 bibtex warnings, 84 pages; pytest 22/22.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Nov 5, 2026) parent 94fe869 Browse files

18 changed files +274 −60

modified code/make_tables.py +79 −23
@@ -15,7 +15,7 @@ Outputs:
15 15 No numeric literal is typed in this file; stats.json is never rewritten.
16 16 Usage: python3 -I code/make_tables.py [--out SCRATCH_DIR] (--out is used by tests/)
17 17 """
18 −import argparse, ast, csv, json, math, re, sys
18 +import argparse, ast, csv, hashlib, json, math, re, sys
19 19 from pathlib import Path
20 20 import matplotlib
21 21 matplotlib.use("Agg")
@@ -430,8 +430,10 @@ if oc_ and tp and rb1.exists():
430 430 tab += f"Ledger rows with a timestamp inside the largest gap ({str(tp.get('october_first_flush',''))[:0]}{str(tp['largest_gaps_seconds'][-1][1])[11:16]}--{str(tp['largest_gaps_seconds'][-1][2])[11:16]}, {tp['largest_gap_hours']:.2f}~h) & {n(oc_.get('ledger_rows_in_largest_gap', 0))} \\\\\n"
431 431 tab += f"Log batches returning zero mentions inside the outage window (batch ids {an_ids[0] if an_ids else ''}--{an_ids[-1] if an_ids else ''}) & {len(anomalous)} \\\\\n"
432 432 tab += "\\midrule\n\\multicolumn{2}{l}{\\itshape Panel B: failed-call lines in the October log (" + n(oc_.get('llm_call_failed_total', 0)) + " lines; the ledger records none of them)} \\\\\n"
433 − tab += f"Retryable HTTP 429/5xx inside the outage window {str(lw[0])[11:16]}--{str(lw[1])[11:16]} & {n(lw[2])} \\\\\n"
434 − tab += f"Retryable HTTP 429/5xx in the outage tail (first ledger window definition, 20 s after the log window) & {n(owr.get('retryable_http_429_5xx', 0))} \\\\\n"
433 + retry_all = int(rsn.get('retryable_http_429_5xx', 0)); retry_tail = int(owr.get('retryable_http_429_5xx', 0)); win_ = oc_.get("window", lw)
434 + tab += f"Retryable HTTP 429/5xx, all lines & {n(retry_all)} \\\\\n"
435 + tab += f"\\quad of which inside the outage window {str(win_[0])[11:16]}--{str(win_[1])[11:16]} & {n(retry_all - retry_tail)} \\\\\n"
436 + tab += f"\\quad of which in the twenty seconds after it (to {str(lw[1])[11:19]}, the end of the log window) & {n(retry_tail)} \\\\\n"
435 437 tab += f"Connection resets & {n(rsn.get('connection_error', 0))} \\\\\n"
436 438 tab += f"JSON-decode failures outside the outage (ledgered as ``no project'': silent empty results) & {n(oc_.get('silent_empty_results_outside_outage', 0))} \\\\\n"
437 439 tab += f"``Section failed'' lines & {n(oc_.get('section_failed', 0))} \\\\\n"
@@ -439,6 +441,7 @@ if oc_ and tp and rb1.exists():
439 441 tab += f"JSON-decode failures in the June log (of {n(bb.get('june_log_sections', 0))} sections) & {n(jn_.get('llm_call_failed_total', 0))} \\\\\n"
440 442 tab += "\\midrule\n\\multicolumn{2}{l}{\\itshape Panel C: first batch after the resume (" + str(rs.get('window', ['', ''])[0])[11:16] + "--" + str(rs.get('window', ['', ''])[1])[11:16] + ", " + n(len(rs.get('ciks', []))) + " issuers)} \\\\\n"
441 443 tab += f"Sections in the batch (log) / ledgered rows & {n(rs.get('log_sections', 0))} / {n(rb_rows)} \\\\\n"
444 + tab += f"Mentions returned (log) / ledgered & {n(rs.get('log_mentions', 0))} / {n(sum(int(r['mentions']) for r in rb))} \\\\\n"
442 445 tab += f"Gate-passing calls & {n(rb_calls)} \\\\\n"
443 446 tab += f"Productive sections observed & {n(rb_prod)} \\\\\n"
444 447 tab += f"Productive sections expected at the form $\\times$ item rates of the rest of the run & {rb_exp:.1f} \\\\\n"
@@ -814,16 +817,29 @@ if ex:
814 817 macro("provenanceProjectId", ex[0]["project_id"][:8], "data/example_provenance.csv", "project_id[:8]")
815 818 json.dump({k: ex[0][k] for k in ("project_id", "ticker", "project_name", "project_type", "status", "total_amount_usd", "n_mentions", "first_seen", "last_seen")},
816 819 open(DATA_OUT / "example_project.json", "w"), indent=2)
817 − sc_ex = jload("resolver/composition.json")
820 + # Stage F (B2): the key and the cluster sizes come from the resolver audit (example_provenance_keys.csv, spec_curve.csv), never typed
821 + epk = R / "resolver" / "example_provenance_keys.csv"; scf = R / "resolver" / "spec_curve.csv"
822 + ex_key = next((r for r in rows(epk.stem, epk.parent) if r["project_id"] == ex[0]["project_id"]), None) if epk.exists() else None
823 + key_tok = ex_key["gkey"].split("|")[-1] if ex_key else ""
824 + sc16 = rows(scf.stem, scf.parent) if scf.exists() else []
825 + thr16 = min(float(r["threshold"]) for r in sc16) if sc16 else None
826 + def cs16(key):
827 + return next((r["example_cluster_size_cheniere"] for r in sc16 if r["key"] == key and float(r["threshold"]) == thr16), None)
828 + if key_tok:
829 + macro("provenanceKeyToken", esc(key_tok), "results/resolver/example_provenance_keys.csv", "last field of gkey (location token) of the example project")
830 + if key_tok and cs16("k_current") is not None:
831 + key_note = (f"The key for this issuer and type is the location ``{esc(key_tok)}'' (\\texttt{{results/resolver/example\\_provenance\\_keys.csv}}), so distinct phases at that "
832 + f"site share one identifier; the cluster that contains the example's highest-confidence mention holds {n(cs16('k_current'))} mentions under this key, "
833 + f"{n(cs16('k_strict'))} under the strict name $\\times$ location key and {n(cs16('k_cik_type'))} under the issuer $\\times$ type key (Table~\\ref{{tab:spec_curve}}).")
834 + else:
835 + key_note = "The key for this issuer and type is a location token, so distinct phases share one identifier (cluster sizes under alternative keys in Table~\\ref{tab:spec_curve})."
818 836 float_table("tab_provenance.tex", "tab:provenance", f"Provenance of one resolved project: Cheniere Energy, Corpus Christi liquefaction (project \\texttt{{{ex[0]['project_id'][:8]}\\ldots}})",
819 837 tab, f"Snippets are the first {DI['snippet_chars']['value']} characters of the model-generated description stored in \\texttt{{project\\_timeline.snippet}}; "
820 838 "the description is a paraphrase, not a quotation, and the character span of the supporting passage is not stored; the full source section is retrievable "
821 839 f"through \\texttt{{project\\_mentions.section\\_id}}. All {len(ex[:8])} printed mentions are {' and '.join(forms)} filings. The representative name is that of the "
822 840 f"highest-confidence mention (filed {ex[:8][-1]['filing_date'][:4]}) applied retroactively to the whole timeline, and is one admissible choice among "
823 − "mentions tied at the maximum confidence. The key for this issuer and type is the location token ``corpus christi'', so distinct phases share one identifier: "
824 − "the same mentions form "
825 − + (f"{sc_ex['project_count_range_045'].get('k_strict', '')} projects under the strict key and {sc_ex['project_count_range_045'].get('k_cik_type', '')} under the issuer $\\times$ type key" if sc_ex.get("project_count_range_045") else "different numbers of projects under alternative keys")
826 − + " (cluster sizes in Table~\\ref{tab:spec_curve}).", "data/example\\_provenance.csv, documented\\_inputs.json:snippet\\_chars", size="\\scriptsize", colsep="3pt", fit=True)
841 + "mentions tied at the maximum confidence. " + key_note,
842 + "data/example\\_provenance.csv, documented\\_inputs.json:snippet\\_chars, results/resolver/example\\_provenance\\_keys.csv, spec\\_curve.csv", size="\\scriptsize", colsep="3pt", fit=True)
827 843
828 844 # ---- Table 12: data-model audit (Appendix C) as a longtable ---------------------------------------------------------
829 845 if (D / "datamodel_audit.csv").exists():
@@ -934,10 +950,21 @@ if gg.exists() and gas.exists():
934 950 macro("yearsNotInText", n(fbd["years_not_subset_of_text"]), "results/grounding/fallback_bounds.json", "years_not_subset_of_text")
935 951 macro("techDeployMentionedNoSignal", n(fbd["tech_deploy_mentioned_no_any_signal"]), "results/grounding/fallback_bounds.json", "tech_deploy_mentioned_no_any_signal")
936 952 macro("techDeployMentionedTotal", n(fbd["tech_deploy_total"][1]), "results/grounding/fallback_bounds.json", "tech_deploy_total[1]")
953 + pnc = jload("grounding/partners_normalisation_check.json"); pnc_note = ""
954 + if pnc:
955 + macro("groundPartnersCheckN", n(pnc["with_partners"]), "results/grounding/partners_normalisation_check.json", "with_partners (mentions with a partner in the sample)")
956 + macro("groundPartnersCheckAuditPct", pct(pnc["partners_grounded_pct_audit_rule"]), "results/grounding/partners_normalisation_check.json", "partners_grounded_pct_audit_rule")
957 + macro("groundPartnersPlainCasefoldPct", pct(pnc["partners_grounded_pct_naive_casefold"]), "results/grounding/partners_normalisation_check.json", "partners_grounded_pct_naive_casefold")
958 + macro("groundPartnersCheckSample", n(pnc["sample_mentions"]), "results/grounding/partners_normalisation_check.json", "sample_mentions (reservoir sample)")
959 + pnc_note = (f"The normalisation matters for partners: on a reservoir sample of {n(pnc['sample_mentions'])} mentions ({n(pnc['with_partners'])} with a partner), the rate is "
960 + f"{pct(pnc['partners_grounded_pct_audit_rule'])}\\% under this rule and {pct(pnc['partners_grounded_pct_naive_casefold'])}\\% under a plain case-folded substring search "
961 + "(\\texttt{results/grounding/partners\\_normalisation\\_check.json}). ")
937 962 float_table("tab_grounding.tex", "tab:grounding", "Mechanical grounding of the structured fields of all stored mentions in the stored section text", tab,
938 963 f"Population: all {n(M)} stored mentions, joined to the text of their section as stored (legacy June text for the S\\&P~500 10-Ks). A field is ``grounded'' "
939 − "when every element of the list (years, partners, locations, technologies, case-folded) or the amount (tested in units, thousands, millions and billions with "
964 + "when every element of the list (years, partners, locations, technologies) or the amount (tested in units, thousands, millions and billions with "
940 965 "one to three significant digits) occurs literally in that text; a currency token is \\$, USD, dollar, million or billion within 40 characters of the number. "
966 + "Before the search, element and text are normalised identically: case-folded, punctuation replaced by spaces, whitespace collapsed, and a short alias list applied "
967 + "to the element (U.S., USA $\\to$ united states; U.K. $\\to$ united kingdom; UAE; EU); the element must then occur as whole words. " + pnc_note +
941 968 "Confidence intervals are percentile bootstraps with 1{,}000 replicates clustered by section. Grounding is a necessary condition for correctness, not a "
942 969 "measure of it: a grounded year can belong to another initiative, and an ungrounded location can be a correct normalisation (``Texas'' for ``Austin''); "
943 970 "precision and recall require the human labels of gate (d). Panel C counts mentions with at least one structured field." + fb_note,
@@ -977,7 +1004,8 @@ if sc_path.exists():
977 1004 pb = dict((str(k), v) for k, v in cp["projects_by_key_branch"]); mb_ = dict((str(k), v) for k, v in cp.get("mentions_by_key_branch", []))
978 1005 tn = rows("top_name_tokens", R / "resolver") if (R / "resolver" / "top_name_tokens.csv").exists() else []
979 1006 comp_note = (f" Under the current key {n(pb.get('name_token', 0))} projects ({100*pb.get('name_token', 0)/P:.1f}\\%) are keyed on a name token because the mention has no "
980 − f"location, {n(pb.get('location', 0))} on a location and {n(cp.get('projects_keyed_general', 0))} on the fallback ``general''"
1007 + f"location, {n(pb.get('location', 0))} on a location and {n(pb.get('general', 0))} on the fallback ``general'' (as in the text; {n(cp.get('projects_keyed_general', 0))} keys end in "
1008 + f"the token ``general'' because a project name whose first token is the word ``general'' is not distinguished from the fallback)"
981 1009 + (f"; the single token ``{esc(tn[0]['name_tok'])}'' absorbs {n(tn[0]['mentions'])} mentions into {n(tn[0]['projects'])} projects (Table~\\ref{{tab:top_name_tokens}})" if tn else "")
982 1010 + ".")
983 1011 tie_note = (f" The stored table is not exactly reproducible: {n(ti['projects_with_tie_at_max_confidence'])} projects have several mentions at the maximum confidence "
@@ -985,8 +1013,8 @@ if sc_path.exists():
985 1013 "descriptions while reproducing every project identifier." if ti else "")
986 1014 float_table("tab_spec_curve.tex", "tab:spec_curve", "Specification curve of the project count over alternative resolution keys and confidence thresholds", tab,
987 1015 f"Every key is applied to the same {n(M)} stored mentions, rebuilt from \\texttt{{resolve.py}} in a temporary table on the master node; $\\dagger$ marks the key "
988 − "of the released \\texttt{projects} table, whose identifier set is reproduced exactly. ``Cheniere cluster size'' is the number of projects formed by the mentions "
989 − f"of the Table~\\ref{{tab:provenance}} example. Thresholds drop mentions below the stated confidence before resolution. The project count is therefore a "
1016 + "of the released \\texttt{projects} table, whose identifier set is reproduced exactly. ``Cheniere cluster size'' is the number of mentions in the cluster that contains the "
1017 + f"highest-confidence mention of the Table~\\ref{{tab:provenance}} example. Thresholds drop mentions below the stated confidence before resolution. The project count is therefore a "
990 1018 f"property of the key: {n(min(int(float(r['projects'])) for r in sc if float(r['threshold']) == t0_))}--{n(max(int(float(r['projects'])) for r in sc if float(r['threshold']) == t0_))} "
991 1019 f"at the release threshold, and the single-mention share and projects-per-issuer statistics move with it." + comp_note + tie_note,
992 1020 "results/resolver/spec\\_curve.csv, composition.json, ties\\_idempotence.json, top\\_name\\_tokens.csv", size="\\scriptsize", colsep="3pt", fit=True)
@@ -1059,8 +1087,8 @@ if jo.exists():
1059 1087 fbf = R / "focus" / "focus_coverage_by_form.csv"; sk = jload("focus/stats_keys.json")
1060 1088 if fcp.exists() and fbf.exists():
1061 1089 tab = ("\\begin{tabular}{llrrrrrrrr}\n\\toprule\nForm & Item & " + H("Ledgered\\\\rows") + " & " + H("Over 9{,}000\\\\chars (\\%)") + " & " + H("Median\\\\chars") + " & " +
1062 − H("Share shown\\\\P10") + " & " + H("Share shown\\\\median") + " & " + H("Share shown\\\\P90") + " & " + H("Rows shown\\\\$< 25$\\% (\\%)") + " & " +
1063 − H("Mentions from rows\\\\shown $< 25$\\% (\\%)") + " \\\\\n\\midrule\n")
1090 + H("Share shown\\\\P10") + " & " + H("Share shown\\\\median") + " & " + H("Share shown\\\\P90") + " & " + H("Rows shown\\\\$<25$\\% (\\%)") + " & " +
1091 + H("Mentions from\\\\such rows (\\%)") + " \\\\\n\\midrule\n")
1064 1092 def frow(lab1, lab2, r):
1065 1093 return (f"{lab1} & {lab2} & {n(r['n'])} & {pct(r['pct_over_9000'])} & {n(r['chars_p50'])} & {100*float(r['shown_p10']):.0f} & {100*float(r['shown_p50']):.0f} & "
1066 1094 f"{100*float(r['shown_p90']):.0f} & {pct(r['pct_shown_lt_25'])} & {pct(r['pct_mentions_from_lt25'])} \\\\\n")
@@ -1272,6 +1300,9 @@ jmacro("llmCallFailedInsideOutage", oc_, "llm_call_failed_inside_window", "resul
1272 1300 jmacro("llmCallFailedOutsideOutage", oc_, "silent_empty_results_outside_outage", "results/ledger/log_summary.json:october")
1273 1301 if isinstance(oc_.get("outside_window_by_reason"), dict):
1274 1302 jmacro("llmRetryableTailLines", oc_["outside_window_by_reason"], "retryable_http_429_5xx", "results/ledger/log_summary.json:october.outside_window_by_reason")
1303 + if isinstance(oc_.get("llm_call_failed_by_reason"), dict):
1304 + macro("llmRetryableInsideWindow", n(int(oc_["llm_call_failed_by_reason"].get("retryable_http_429_5xx", 0)) - int(oc_["outside_window_by_reason"].get("retryable_http_429_5xx", 0))),
1305 + "results/ledger/log_summary.json:october", "llm_call_failed_by_reason.retryable_http_429_5xx - outside_window_by_reason.retryable_http_429_5xx (lines inside the 01:10-06:22 window)")
1275 1306 if isinstance(oc_.get("llm_call_failed_by_reason"), dict):
1276 1307 for k, v in oc_["llm_call_failed_by_reason"].items(): macro("llmFailed" + "".join(p.capitalize() for p in k.split("_")), n(v), "results/ledger/log_summary.json:october.llm_call_failed_by_reason", k)
1277 1308 if isinstance(oc_.get("outage_window_from_log"), list) and len(oc_["outage_window_from_log"]) >= 2:
@@ -1350,6 +1381,8 @@ if oc_:
1350 1381 rs = oc_.get("resume_batch1", {})
1351 1382 if rs:
1352 1383 macro("resumeBatchMentions", n(rs.get("log_mentions", 0)), "results/ledger/log_summary.json:october.resume_batch1.log_mentions", "log")
1384 + macro("resumeBatchLedgerMentions", n(rs.get("ledger_mentions", 0)), "results/ledger/log_summary.json:october.resume_batch1.ledger_mentions", "ledger (sum of resume_batch1_vs_expected.csv:mentions)")
1385 + macro("resumeBatchLedgerRows", n(rs.get("ledger_rows", 0)), "results/ledger/log_summary.json:october.resume_batch1.ledger_rows", "ledger rows of the batch")
1353 1386 macro("resumeBatchWindow", f"{str(rs['window'][0])[11:16]}--{str(rs['window'][1])[11:16]}", "results/ledger/log_summary.json:october.resume_batch1.window", "hh:mm")
1354 1387 rec = oc_.get("outage_rows_reconciliation", {})
1355 1388 macro("logExtractionCompleteSum", n(rec.get("extraction_complete_sum_sections", 0)), "results/ledger/log_summary.json:october.outage_rows_reconciliation", "extraction_complete_sum_sections")
@@ -1363,6 +1396,10 @@ if oc_:
1363 1396 if bb.get("rows_in_outage_window"):
1364 1397 macro("outageSurvivingRows", n(bb["rows_in_outage_window"][0]), "results/ledger/backfill_block.json", "rows_in_outage_window[0]")
1365 1398 macro("outageSurvivingMentions", n(bb["rows_in_outage_window"][1]), "results/ledger/backfill_block.json", "rows_in_outage_window[1]")
1399 +owc = jload("ledger/outage_window_rows_check.json")
1400 +if owc:
1401 + macro("outageSurvivingProductive", n(owc["rows_with_mentions"]), "results/ledger/outage_window_rows_check.json", "rows_with_mentions (read-only re-derivation, Stage F)")
1402 + macro("outageSurvivingZeroRows", n(owc["rows_zero_mentions"]), "results/ledger/outage_window_rows_check.json", "rows_zero_mentions")
1366 1403 if bb.get("min_extracted_at"):
1367 1404 macro("backfillTimestamp", longdate(bb["min_extracted_at"]) + ", " + str(bb["min_extracted_at"])[11:16], "results/ledger/backfill_block.json:min_extracted_at", "date, hh:mm")
1368 1405 if rb1.exists():
@@ -1381,6 +1418,12 @@ if gr.exists():
1381 1418 if gt:
1382 1419 macro("gateRejectedPct", f"{100*gt['gate_rejected_zero']/gt['ledger_rows']:.1f}", "results/ledger/gate_pass_totals.json", "100*gate_rejected_zero/ledger_rows")
1383 1420 macro("usdPerOctoberRow", f"{gt['usd_per_october_row_paper_total_775']:.4f}", "results/ledger/gate_pass_totals.json", "usd_per_october_row_paper_total_775")
1421 + gpr_ = R / "ledger" / "gate_pass_by_run.csv"
1422 + if gpr_.exists() and di("total_cost_usd"):
1423 + calls_both = sum(int(r["calls"]) for r in rows(gpr_.stem, gpr_.parent))
1424 + macro("callsBothRuns", n(calls_both), "results/ledger/gate_pass_by_run.csv", "sum(calls) over the two runs")
1425 + macro("usdPerCallBothRuns", f"{float(str(di('total_cost_usd')).replace('{,}', '')) / calls_both:.4f}", "results/ledger/gate_pass_by_run.csv, data/documented_inputs.json:total_cost_usd",
1426 + "total_cost_usd / sum(calls) over both runs (alternative basis, author to confirm)")
1384 1427 if cvd:
1385 1428 macro("confExactDefault", n(cvd["confidence_exact_0600_total"]), "results/ledger/code_vs_data_checks.json", "confidence_exact_0600_total")
1386 1429 for run_, cnt in cvd.get("confidence_exact_0600_by_run", []):
@@ -1550,6 +1593,8 @@ if wst.exists():
1550 1593 wp = jload("runs/within_issuer_paired_10q.json")
1551 1594 if wp:
1552 1595 macro("pairedIssuers", n(wp["issuers_paired"]), "results/runs/within_issuer_paired_10q.json", "issuers_paired"); macro("pairedDeltaPp", f"{100*wp['mean_diff_productive_share_oct_minus_june']:.1f}", "results/runs/within_issuer_paired_10q.json", "100*mean_diff_productive_share_oct_minus_june"); macro("pairedSePp", f"{100*wp['se']:.1f}", "results/runs/within_issuer_paired_10q.json", "100*se")
1596 + macro("pairedProductiveJune", f"{100*wp['june_mean_productive_share']:.1f}", "results/runs/within_issuer_paired_10q.json", "100*june_mean_productive_share (mean of per-issuer shares)")
1597 + macro("pairedProductiveOctober", f"{100*wp['oct_mean_productive_share']:.1f}", "results/runs/within_issuer_paired_10q.json", "100*oct_mean_productive_share (mean of per-issuer shares)")
1553 1598 if t6.exists() and t6r:
1554 1599 crd = {r["cohort"]: r for r in rows(t6.stem, t6.parent)}
1555 1600 macro("cohortBothPerIssuerAll", pct(crd["both"]["per_issuer_all"]), "results/runs/table6_cohort.csv", "both per_issuer_all"); macro("cohortRussellOnlyPerIssuer", pct(crd["russell_only"]["per_issuer_all"]), "results/runs/table6_cohort.csv", "russell_only per_issuer_all"); macro("cohortBothPerIssuerOctober", pct(crd["both"]["per_issuer_october_only"]), "results/runs/table6_cohort.csv", "both per_issuer_october_only")
@@ -1622,7 +1667,7 @@ if rta.exists():
1622 1667 # -- financing class (results/fp_classes) --
1623 1668 if fps:
1624 1669 mm = fps["mentions_matched"]; macro("finMentions", n(mm[0]), "results/fp_classes/summary.json", "mentions_matched[0]"); macro("finMentionsPct", pct(mm[2]), "results/fp_classes/summary.json", "mentions_matched[2]")
1625 − macro("finAmountB", f"{fps['matched_amount_gusd'][0]/1e3:.1f}", "results/fp_classes/summary.json", "matched_amount_gusd[0]/1e3 (T USD)"); macro("finAmountDenomB", f"{fps['matched_amount_gusd'][1]/1e3:.1f}", "results/fp_classes/summary.json", "matched_amount_gusd[1]/1e3 (T USD)")
1670 + macro("finAmountT", f"{fps['matched_amount_gusd'][0]/1e3:.1f}", "results/fp_classes/summary.json", "matched_amount_gusd[0]/1e3 (T USD; the source field is in billions)"); macro("finAmountDenomT", f"{fps['matched_amount_gusd'][1]/1e3:.1f}", "results/fp_classes/summary.json", "matched_amount_gusd[1]/1e3 (T USD; the source field is in billions)")
1626 1671 macro("finAmountSharePct", f"{100*fps['matched_amount_gusd'][0]/fps['matched_amount_gusd'][1]:.1f}", "results/fp_classes/summary.json", "100*matched_amount_gusd[0]/[1]")
1627 1672 us_ = fps["unregistered_sales_matched"]; macro("finUnregPct", f"{100*us_[0]/us_[1]:.1f}", "results/fp_classes/summary.json", "100*unregistered_sales_matched[0]/[1]"); macro("finUnregN", n(us_[0]), "results/fp_classes/summary.json", "unregistered_sales_matched[0]"); macro("finUnregDenom", n(us_[1]), "results/fp_classes/summary.json", "unregistered_sales_matched[1]")
1628 1673 fs_ = fps["financial_statements_matched"]; macro("finFinStatementsPct", f"{100*fs_[0]/fs_[1]:.1f}", "results/fp_classes/summary.json", "100*financial_statements_matched[0]/[1]")
@@ -1740,8 +1785,8 @@ kge = {r["node_type"]: r["n"] for r in kg if r["k"] == "edges"}
1740 1785 for e_, nm in (("owns", "kgEdgesOwns"), ("located_in", "kgEdgesLocatedIn"), ("uses", "kgEdgesUses")):
1741 1786 if e_ in kge: macro(nm, n(kge[e_]), "data/kg.csv", f"edges {e_}")
1742 1787 macro("projectsPerIssuerMax", n(rows("projects_per_issuer")[0]["mx"]), "data/projects_per_issuer.csv", "mx")
1743 −# -- pipeline constants read from the frozen snapshot and the archived pipeline code (manuscripts/.../pipeline_code_2324b58) --
1744 −PIPE = ROOT.parent.parent / "manuscripts" / "FIRC-0001" / "pipeline_code_2324b58"
1788 +# -- pipeline constants read from the frozen snapshot (code/audit/spid_snapshot, in the repository) --
1789 +PIPE = ROOT.parent.parent / "manuscripts" / "FIRC-0001" / "pipeline_code_2324b58" # archived pipeline code, outside the repository; only used to cross-check documented inputs
1745 1790 llm_src = _snapshot("llm.py"); cfg_src = _snapshot("config.py"); pipe_src = _snapshot("pipeline.py")
1746 1791 v = rxs(llm_src, r"m\.start\(\) - (\d+)", int); macro("focusWindowBefore", n(v), rel(llm_src), "_focus: characters before a gate hit") if v else None
1747 1792 v = rxs(llm_src, r"m\.end\(\) \+ (\d+)", int); macro("focusWindowAfter", n(v), rel(llm_src), "_focus: characters after a gate hit") if v else None
@@ -1759,12 +1804,23 @@ v = rxs(llm_src, r"project_name',''\)\)\[:(\d+)\]", int); macro("idNameChars", n
1759 1804 v = rxs(llm_src, r"retries: int = (\d+)", int); macro("retries", word(v), rel(llm_src), "_call retries") if v else None
1760 1805 v = rxs(cfg_src, r"llm_timeout: float = ([0-9.]+)", float); macro("timeoutSeconds", n(v), rel(cfg_src), "llm_timeout") if v else None
1761 1806 v = rxs(pipe_src, r"min_words: int = (\d+)", int); macro("minWords", n(v), rel(pipe_src), "min_words") if v else None
1762 −v = rxs(PIPE / "src/parsing/chunker.py", r"chunk_size: int = (\d+)", int); macro("chunkWords", n(v), "src/parsing/chunker.py", "chunk_size") if v else None
1763 −v = rxs(PIPE / "src/parsing/chunker.py", r"overlap: int = (\d+)", int); macro("chunkOverlap", n(v), "src/parsing/chunker.py", "overlap") if v else None
1764 −v = rxs(PIPE / "src/parsing/html_parser.py", r"max_file_size_mb\", (\d+)", int); macro("maxFileMb", n(v), "src/parsing/html_parser.py", "max_file_size_mb default") if v else None
1765 −v = rxs(PIPE / "src/acquisition/bulk_downloader.py", r"COOLDOWN_SECONDS = ([0-9.]+)", float); macro("cooldownSeconds", n(v), "src/acquisition/bulk_downloader.py", "COOLDOWN_SECONDS") if v else None
1766 −v = rxs(PIPE / "src/acquisition/bulk_downloader.py", r"SEC allows (\d+) req/s", int); macro("secRateLimit", word(v), "src/acquisition/bulk_downloader.py (docstring) / secfairaccess", "requests per second per IP") if v else None
1767 −v = rxs(PIPE / "src/acquisition/bulk_downloader.py", r"rate: float = ([0-9.]+)", float); macro("acqGatewayDivisor", word(v), "src/acquisition/bulk_downloader.py", "default aggregate rate shared behind one IP") if v else None
1807 +# -- constants of the archived pipeline code (manuscripts/FIRC-0001/pipeline_code_2324b58, outside this repository): recorded in data/documented_inputs.json with the
1808 +# snapshot path and the SHA-256 of each source file, so that a bare clone regenerates every macro; when the archive is reachable the value is re-read and must agree (Stage F, N1)
1809 +def pipe_const(key, name, relpath, pattern, cast, fmt):
1810 + e = DI.get(key)
1811 + if e is None: return
1812 + p_ = PIPE / relpath
1813 + if p_.exists():
1814 + live = rxs(p_, pattern, cast)
1815 + if live is not None and cast(e["value"]) != live: raise SystemExit(f"documented input {key} = {e['value']} disagrees with {p_}: {live}")
1816 + if e.get("sha256") and hashlib.sha256(p_.read_bytes()).hexdigest() != e["sha256"]: raise SystemExit(f"documented input {key}: {p_} does not match the recorded SHA-256")
1817 + macro(name, fmt(cast(e["value"])), f"data/documented_inputs.json:{key}", e.get("source", ""))
1818 +pipe_const("pipeline_chunk_words", "chunkWords", "src/parsing/chunker.py", r"chunk_size: int = (\d+)", int, n)
1819 +pipe_const("pipeline_chunk_overlap", "chunkOverlap", "src/parsing/chunker.py", r"overlap: int = (\d+)", int, n)
1820 +pipe_const("pipeline_max_file_mb", "maxFileMb", "src/parsing/html_parser.py", r"max_file_size_mb\", (\d+)", int, n)
1821 +pipe_const("pipeline_cooldown_seconds", "cooldownSeconds", "src/acquisition/bulk_downloader.py", r"COOLDOWN_SECONDS = ([0-9.]+)", float, n)
1822 +pipe_const("pipeline_sec_rate_limit", "secRateLimit", "src/acquisition/bulk_downloader.py", r"SEC allows (\d+) req/s", int, word)
1823 +pipe_const("pipeline_acq_gateway_divisor", "acqGatewayDivisor", "src/acquisition/bulk_downloader.py", r"rate: float = ([0-9.]+)", float, word)
1768 1824 for key_, nm in (("acq_new_docs", "acqNewDocs"), ("acq_hours", "acqHours"), ("acq_req_per_sec", "acqReqPerSec"), ("acq_nodes", "acqNodes"), ("acq_node_rate", "acqNodeRate"), ("acq_gateway_rate", "acqGatewayRate"),
1769 1825 ("parse_filings_per_sec", "parseFilingsPerSec"), ("parse_minutes_range", "parseMinutesRange"), ("embed_minutes_range", "embedMinutesRange"), ("merge_minutes", "mergeMinutes"), ("total_cost_usd", "totalCostUsd"),
1770 1826 ("stability_sections", "stabilitySections"), ("stability_calls", "stabilityCalls"), ("embedding_dim", "embeddingDim"), ("factor_anchors", "factorAnchors"), ("tenk_sections_max", "tenKsectionsMax"), ("tenq_sections_n", "tenQsectionsN")):
modified data/documented_inputs.json +45 −3
@@ -77,8 +77,8 @@
77 77 },
78 78 "total_cost_usd": {
79 79 "value": "775",
80 − "source": "author-reported total of the October 2026 extraction (docs/research_log.md); the API invoice is not archived",
81 − "status": "author-reported"
80 + "source": "author-reported total recorded in docs/research_log.md against the 2026-10-27/28 entry 'Full LLM extraction of 968,134 new sections ... ≈ 775 USD total' (October run); the review's constraints note (peer_reviews/FIRC-0001/_constraints.md) attributes the same ≈ 775 USD to both runs (153,894 + 968,134 sections); the API invoice is not archived; basis to be confirmed by the authors (Stage F, N9)",
81 + "status": "author-reported; basis (October run only vs both runs) to be confirmed by the authors"
82 82 },
83 83 "stability_sections": {
84 84 "value": "500",
@@ -119,5 +119,47 @@
119 119 "value": "29.1",
120 120 "source": "N0 schema probe (results/n0_probe, commit 7680606): parquet corpus 1,342,283 sections / 29.1 GB on the master node",
121 121 "status": "node measurement"
122 + },
123 + "pipeline_chunk_words": {
124 + "value": 512,
125 + "source": "chunk_size default of the embedding chunker (chunk_size: int = 512); manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/parsing/chunker.py, SHA-256 716a4013fb839a68ec613aa5f0c91e8da4c8a8135051fb95fec5350d35fc3c79; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable",
126 + "snapshot_path": "manuscripts/FIRC-0001/pipeline_code_2324b58/src/parsing/chunker.py",
127 + "sha256": "716a4013fb839a68ec613aa5f0c91e8da4c8a8135051fb95fec5350d35fc3c79",
128 + "status": "code constant (archived pipeline snapshot)"
129 + },
130 + "pipeline_chunk_overlap": {
131 + "value": 64,
132 + "source": "overlap default of the embedding chunker (overlap: int = 64); manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/parsing/chunker.py, SHA-256 716a4013fb839a68ec613aa5f0c91e8da4c8a8135051fb95fec5350d35fc3c79; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable",
133 + "snapshot_path": "manuscripts/FIRC-0001/pipeline_code_2324b58/src/parsing/chunker.py",
134 + "sha256": "716a4013fb839a68ec613aa5f0c91e8da4c8a8135051fb95fec5350d35fc3c79",
135 + "status": "code constant (archived pipeline snapshot)"
136 + },
137 + "pipeline_max_file_mb": {
138 + "value": 50,
139 + "source": "max_file_size_mb default of the HTML parser (html_cfg.get(\"max_file_size_mb\", 50)); manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/parsing/html_parser.py, SHA-256 61aca675ff4652e3479f50a10ce29503f66f7f74ffd4f78df0b87ad517b1908e; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable",
140 + "snapshot_path": "manuscripts/FIRC-0001/pipeline_code_2324b58/src/parsing/html_parser.py",
141 + "sha256": "61aca675ff4652e3479f50a10ce29503f66f7f74ffd4f78df0b87ad517b1908e",
142 + "status": "code constant (archived pipeline snapshot)"
143 + },
144 + "pipeline_cooldown_seconds": {
145 + "value": 65.0,
146 + "source": "COOLDOWN_SECONDS = 65.0 after an SEC 403/429; manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/acquisition/bulk_downloader.py, SHA-256 862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable",
147 + "snapshot_path": "manuscripts/FIRC-0001/pipeline_code_2324b58/src/acquisition/bulk_downloader.py",
148 + "sha256": "862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159",
149 + "status": "code constant (archived pipeline snapshot)"
150 + },
151 + "pipeline_sec_rate_limit": {
152 + "value": 10,
153 + "source": "module docstring 'SEC allows 10 req/s per IP' (sec.gov fair-access policy); manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/acquisition/bulk_downloader.py, SHA-256 862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable",
154 + "snapshot_path": "manuscripts/FIRC-0001/pipeline_code_2324b58/src/acquisition/bulk_downloader.py",
155 + "sha256": "862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159",
156 + "status": "code constant (archived pipeline snapshot)"
157 + },
158 + "pipeline_acq_gateway_divisor": {
159 + "value": 9.0,
160 + "source": "BulkDownloader default rate: float = 9.0 requests per second shared behind one IP; manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/acquisition/bulk_downloader.py, SHA-256 862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable",
161 + "snapshot_path": "manuscripts/FIRC-0001/pipeline_code_2324b58/src/acquisition/bulk_downloader.py",
162 + "sha256": "862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159",
163 + "status": "code constant (archived pipeline snapshot)"
122 164 }
123 −}
\ No newline at end of file
165 +}
modified paper/appendix/appendix.tex +1 −1
@@ -81,7 +81,7 @@ Author order, affiliations and CRediT contributions & Pending confirmation by th
81 81
82 82 \lstinputlisting{../results/runs/git_diff_0304133_2324b58.patch}
83 83
84 −\paragraph{Mentions and projects by cohort, run and form.} \Cref{tab:june_october} reconciles the mentions of the S\&P~500 cohort (\texttt{in\_sp500} true) and of the Russell-only cohort by run and form; the June block is identified by the backfill timestamp. The S\&P~500 cohort's 10-Q mentions come from both runs for the same issuers, which permits a within-issuer comparison: on the \SPIDpairedIssuers\ issuers with 10-Qs in both runs, the productive share is \SPIDwithinProductiveJune\% in June and \SPIDwithinProductiveOctober\% in October (paired difference \SPIDpairedDeltaPp\ percentage points, standard error \SPIDpairedSePp), mentions per row \SPIDwithinMentionsPerRowJune\ and \SPIDwithinMentionsPerRowOctober, mean confidence \SPIDwithinConfJune\ and \SPIDwithinConfOctober, and the M\&A share \SPIDwithinMaShareJune\% and \SPIDwithinMaShareOctober\% (\texttt{results/runs/within\_sp500\_*.csv}). For 10-Qs, whose text went through the same parser in both runs, the two runs therefore behave alike; the cohort difference of \Cref{sec:time} comes from the volume of sections and from the 10-K segmenter, not from the extractor.
84 +\paragraph{Mentions and projects by cohort, run and form.} \Cref{tab:june_october} reconciles the mentions of the S\&P~500 cohort (\texttt{in\_sp500} true) and of the Russell-only cohort by run and form; the June block is identified by the backfill timestamp. The S\&P~500 cohort's 10-Q mentions come from both runs for the same issuers, which permits a within-issuer comparison. Pooled over the cohort's 10-Q ledger rows, the productive share is \SPIDwithinProductiveJune\% in June and \SPIDwithinProductiveOctober\% in October; on the \SPIDpairedIssuers\ issuers with 10-Qs in both runs, the per-issuer means are \SPIDpairedProductiveJune\% and \SPIDpairedProductiveOctober\% (paired difference \SPIDpairedDeltaPp\ percentage points, standard error \SPIDpairedSePp). The cohort is the same in both files (\texttt{in\_sp500} of the issuer), but \Cref{tab:june_october} counts stored mentions whereas the within-cohort files sum the ledger's mention counts, which exceed the stored ones by the identifier collisions of \Cref{sec:ledger}. Mentions per row are \SPIDwithinMentionsPerRowJune\ and \SPIDwithinMentionsPerRowOctober, mean confidence \SPIDwithinConfJune\ and \SPIDwithinConfOctober, and the M\&A share \SPIDwithinMaShareJune\% and \SPIDwithinMaShareOctober\% (\texttt{results/runs/within\_sp500\_*.csv}). For 10-Qs, whose text went through the same parser in both runs, the two runs therefore behave alike; the cohort difference of \Cref{sec:time} comes from the volume of sections and from the 10-K segmenter, not from the extractor.
85 85
86 86 \input{tables/tab_june_october}
87 87
modified paper/main.pdf +0 −0

Binary file not shown.

modified paper/main.tex +1 −1
@@ -117,7 +117,7 @@
117 117 \newcommand{\WPversion}{1.1 (revised after peer review; research database under validation)}
118 118 \newcommand{\WPfunding}{Research funded by the FIRC (Fonds institutionnel de recherche et de création), Université du Québec en Outaouais.}
119 119 \newcommand{\WPabstract}{%
120 − Can strategic projects be identified, classified, dated and linked across the SEC filings of a broad cross-section of U.S.-listed issuers? We document SPID, a project-level research database built for a Russell~3000 proxy (iShares IWV holdings, \SPIDrussellDistinctCiks\ of \SPIDrussellIndexConstituents\ index constituents) merged with the S\&P~500: \SPIDfilingsTotal\ Forms 10-K, 10-Q and 8-K of \SPIDuniverseN\ registrants filed since 2010, segmented into \SPIDsectionsTotal\ sections, of which \SPIDgatePassTotal\ reached a large-language-model extractor in JSON mode. The output is \SPIDmentions\ section-linked candidate mentions and \SPIDprojectCountCurrentKey\ candidate projects under the current resolution key (\SPIDprojectsKeyTypeDropped--\SPIDprojectsKeyStrict\ under alternative keys). Mechanical audits find \SPIDgroundAllPct\% of structured fields verbatim in the source section, a median \SPIDmdnaShownMedianPct\% of a 10-K MD\&A shown to the model, and \SPIDfinMentionsPct\% of mentions describing financing instruments. Precision and recall against human labels are not yet measured; the annotation frame is delivered. SPID is a research instrument under validation, not a census of projects.%
120 + Can strategic projects be identified, classified, dated and linked across the SEC filings of a broad cross-section of U.S.-listed issuers? We document SPID, a project-level research database built for a Russell~3000 proxy (iShares IWV holdings, \SPIDrussellDistinctCiks\ of \SPIDrussellIndexConstituents\ index constituents) merged with the S\&P~500: \SPIDfilingsTotal\ Forms 10-K, 10-Q and 8-K of \SPIDuniverseN\ registrants filed since 2010, segmented into \SPIDsectionsTotal\ sections, of which \SPIDgatePassTotal\ reached a large-language-model extractor in JSON mode. The output is \SPIDmentions\ section-linked candidate mentions and \SPIDprojectCountCurrentKey\ candidate projects under the current key (\SPIDprojectsKeyTypeDropped--\SPIDprojectsKeyStrict\ under alternative keys). Mechanical audits find every structured field verbatim in the source section for \SPIDgroundAllPct\% of mentions, a median \SPIDmdnaShownMedianPct\% of a 10-K MD\&A shown to the model, and \SPIDfinMentionsPct\% of mentions describing financing instruments. Precision and recall against human labels are not yet measured; the annotation frame is delivered. SPID is an instrument under validation, not a census of projects.%
121 121 }
122 122 \newcommand{\WPkeywords}{SEC filings, text as data, corporate investment, strategic projects, information extraction, large language models, entity resolution, Russell 3000, measurement validation}
123 123 \newcommand{\WPjel}{C55, C81, G31, G38, M41, O33}
modified paper/sections/conclusion.tex +1 −1
@@ -29,7 +29,7 @@ Beyond the identification limits above, the current build has concrete technical
29 29
30 30 \paragraph{Code.} The acquisition, parsing and extraction pipeline is revision \texttt{2324b58} of the private repository \texttt{uqo-research-db.git}; the \SPIDsnapshotFilesN\ files that define the extraction instrument and the resolver are archived with the paper (\texttt{code/audit/spid\_snapshot/}, with SHA-256 checksums). The exhibit code (\texttt{code/export\_stats.py}, \texttt{code/make\_tables.py}), the exports (\texttt{data/}), the audit scripts of this revision (\texttt{code/audit/}) and their outputs (\texttt{results/}) are released with the paper; \texttt{run\_all.sh} regenerates every table, figure and inline number from the exports, and \texttt{tests/} (\SPIDtestsN\ checks) verifies that the exports reconcile. The master-node environment is pinned (\texttt{requirements.lock}, \SPIDrequirementsLockPackages\ packages; Python \SPIDpythonVersion; DuckDB \SPIDduckdbVersion); no licence file has been committed pending the authors' decision (MIT for code and CC BY 4.0 for derived tables are proposed).
31 31
32 −\paragraph{Reproducibility.} The exhibits are reproducible from the released materials. The databases are regenerable by the authors from the repository, the frozen universe artefact, the download logs and the extraction ledger; the paid extraction step is resumable and ledgered but not deterministic (\Cref{sec:contract}), and the complete October run cost about \SPIDtotalCostUsd~USD at October 2026 prices. The repository contains no credentials and no private infrastructure details. The database does not contain personal data; the only names it stores are those of corporations, places and technologies as they appear in public filings.
32 +\paragraph{Reproducibility.} The exhibits are reproducible from the released materials. The databases are regenerable by the authors from the repository, the frozen universe artefact, the download logs and the extraction ledger; the paid extraction step is resumable and ledgered but not deterministic (\Cref{sec:contract}), and the extraction cost about \SPIDtotalCostUsd~USD at October 2026 prices (recorded against the October run; whether the total also covers the June block is to be confirmed by the authors). The repository contains no credentials and no private infrastructure details. The database does not contain personal data; the only names it stores are those of corporations, places and technologies as they appear in public filings.
33 33
34 34 \subsection{Conclusion}
35 35
modified paper/sections/construct.tex +3 −3
@@ -17,7 +17,7 @@ The unit of measurement is a \emph{distinct, identifiable, issuer-associated str
17 17 \item \texttt{uncertain\_candidate}: a plausible initiative without enough identity evidence (no name, location, counterparty or amount) to be counted.
18 18 \end{itemize}
19 19
20 −The current build implements the first two tiers directly. The third is enforced only through the prompt and the confidence threshold; the fourth, fifth and sixth exist as instructions and as a confidence score, not as stored labels. The financing class was added in this revision after a regular-expression pass over the stored mentions (\texttt{repurchase|buy-?back|dividend|credit facility|revolving|term loan|notes? (due|offering)|at-the-market|senior notes|commercial paper} on the project name and description) matched \SPIDfinMentions\ mentions, \SPIDfinMentionsPct\% of the total, carrying \SPIDfinAmountB~B of the \SPIDfinAmountDenomB~B USD of mention amounts; the match concentrates in Part~II Item~2 of the 10-Q (\SPIDfinUnregPct\% of its mentions, where issuer repurchases are tabled) and in 8-K bodies (\SPIDfinEightKPct\%), and by category in \texttt{capex\_program} (\SPIDfinCapexPct\% of its mentions), \texttt{cost\_reduction} (\SPIDfinCostPct\%) and \texttt{ma\_integration} (\SPIDfinMaPct\%). \Cref{sec:results} reports the category counts with and without this class, and \Cref{tab:fp_classes} gives the match by item, category and term. Promoting the labels to explicit columns of the mention table, filled by annotators and eventually by a classifier, is the first schema change proposed in \texttt{docs/project\_ontology.md}.
20 +The current build implements the first two tiers directly. The third is enforced only through the prompt and the confidence threshold; the fourth, fifth and sixth exist as instructions and as a confidence score, not as stored labels. The financing class was added in this revision after a regular-expression pass over the stored mentions (\texttt{repurchase|buy-?back|dividend|credit facility|revolving|term loan|notes? (due|offering)|at-the-market|senior notes|commercial paper} on the project name and description) matched \SPIDfinMentions\ mentions, \SPIDfinMentionsPct\% of the total, carrying \SPIDfinAmountT~T of the \SPIDfinAmountDenomT~T USD of mention amounts; the match concentrates in Part~II Item~2 of the 10-Q (\SPIDfinUnregPct\% of its mentions, where issuer repurchases are tabled) and in 8-K bodies (\SPIDfinEightKPct\%), and by category in \texttt{capex\_program} (\SPIDfinCapexPct\% of its mentions), \texttt{cost\_reduction} (\SPIDfinCostPct\%) and \texttt{ma\_integration} (\SPIDfinMaPct\%). \Cref{sec:results} reports the category counts with and without this class, and \Cref{tab:fp_classes} gives the match by item, category and term. Promoting the labels to explicit columns of the mention table, filled by annotators and eventually by a classifier, is the first schema change proposed in \texttt{docs/project\_ontology.md}.
21 21
22 22 \input{tables/tab_fp_classes}
23 23
@@ -57,10 +57,10 @@ The instrument is re-runnable and ledgered, not deterministic. The calls used te
57 57
58 58 \subsection{Cost, throughput and the extraction ledger}\label{sec:ledger}
59 59
60 −Every ledgered section is written to the \texttt{extracted\_sections} table (section, accession, backend, number of mentions, timestamp), which makes the step resumable and prevents double billing. Restated on calls rather than rows, the October 2026 run wrote \SPIDoctoberRows\ rows of which \SPIDoctoberCalls\ were calls, and the June block \SPIDbackfilledRows\ rows of which \SPIDjuneCalls\ were calls; the model returned at least one project for \SPIDproductiveShareCalls\% of calls (\Cref{tab:ledger_calls}) (\SPIDproductiveShareLedger\% of rows; \SPIDjuneProductiveShareCalls\% and \SPIDoctoberProductiveShareCalls\% by run), \SPIDmentionsPerCall\ mentions per call (\SPIDmentionsPerLedgerSection\ per row). At the authors' recorded total of about \SPIDtotalCostUsd~USD for the October run at the then-current \texttt{gpt-4o-mini} prices, the cost is about \SPIDusdPerCall~USD per call (\SPIDusdPerOctoberRow~USD per ledger row, the figure the earlier version reported ``per section''). The run was executed with \SPIDworkers\ concurrent workers; the ledger timestamps span \SPIDoctoberWallClockHours\ hours of wall-clock time (\SPIDoctoberFirstFlush\ to \SPIDoctoberLastFlush), of which \SPIDoutageHours\ hours are the largest gap, so the effective throughput over \SPIDoctoberEffectiveHours\ hours was \SPIDoctoberRowsPerSecondEffective\ rows or \SPIDoctoberCallsPerSecondEffective\ calls per second; the ``twelve hours'' and ``27--36 sections per second'' of the earlier version were mutually inconsistent and are withdrawn.
60 +Every ledgered section is written to the \texttt{extracted\_sections} table (section, accession, backend, number of mentions, timestamp), which makes the step resumable and prevents double billing. Restated on calls rather than rows, the October 2026 run wrote \SPIDoctoberRows\ rows of which \SPIDoctoberCalls\ were calls, and the June block \SPIDbackfilledRows\ rows of which \SPIDjuneCalls\ were calls; the model returned at least one project for \SPIDproductiveShareCalls\% of calls (\Cref{tab:ledger_calls}) (\SPIDproductiveShareLedger\% of rows; \SPIDjuneProductiveShareCalls\% and \SPIDoctoberProductiveShareCalls\% by run), \SPIDmentionsPerCall\ mentions per call (\SPIDmentionsPerLedgerSection\ per row). At the authors' recorded total of about \SPIDtotalCostUsd~USD at the then-current \texttt{gpt-4o-mini} prices, entered in the research log against the October run, the cost is about \SPIDusdPerCall~USD per call (\SPIDusdPerOctoberRow~USD per ledger row, the figure the earlier version reported ``per section''); the invoice is not archived, and if the recorded total covered the June block as well, as the review's constraints note states, the figure would be \SPIDusdPerCallBothRuns~USD per call over the \SPIDcallsBothRuns\ calls of both runs---the basis is to be confirmed by the authors. The run was executed with \SPIDworkers\ concurrent workers; the ledger timestamps span \SPIDoctoberWallClockHours\ hours of wall-clock time (\SPIDoctoberFirstFlush\ to \SPIDoctoberLastFlush), of which \SPIDoutageHours\ hours are the largest gap, so the effective throughput over \SPIDoctoberEffectiveHours\ hours was \SPIDoctoberRowsPerSecondEffective\ rows or \SPIDoctoberCallsPerSecondEffective\ calls per second; the ``twelve hours'' and ``27--36 sections per second'' of the earlier version were mutually inconsistent and are withdrawn.
61 61
62 62 \input{tables/tab_ledger_calls}
63 63
64 −The ledger also records a failure mode encountered during the run, which this revision has reconciled against the run log rather than reported from memory (\Cref{tab:outage}). When the API key's credit balance was exhausted (log window \SPIDoutageWindowStart\ to \SPIDoutageWindowEnd), the extractor's retry logic returned empty results that were recorded as ``extracted with zero mentions'': the log carries \SPIDllmFailedRetryableHttpFourTwoNineFivexx\ retryable HTTP errors and \SPIDllmFailedConnectionError\ connection resets in that window, batches \SPIDoutageZeroBatchRange\ (\SPIDoutageZeroBatchWindow) returned no mention at all, and \SPIDoutageRowsRemoved\ rows were removed from the ledger---a number that reconciles exactly with the log as the sum of sections reported complete (\SPIDlogExtractionCompleteSum) minus the sections pending when the run was restarted (\SPIDlogPendingSecondStart). The final ledger has no row inside the largest gap (\SPIDoutageLargestGapStart\ to \SPIDoutageLargestGapEnd); \SPIDoutageSurvivingRows\ productive rows (\SPIDoutageSurvivingMentions\ mentions) stamped in the first quarter of an hour of the window survived because their calls had succeeded. Two residues remain. First, the extractor also returns an empty list after \SPIDretries\ failed attempts on any other error, and the log shows \SPIDllmFailedJsonDecodeError\ JSON-decode failures outside the outage (\SPIDjsonFailBeforeOutage\ before, \SPIDjsonFailAfterOutage\ after; \SPIDjuneLlmCallFailed\ in the June run), each ledgered as ``no project''; the earlier version reported one. Second, the first batch after the restart (\SPIDresumeBatchWindow; \SPIDresumeBatchSections\ sections of \SPIDresumeBatchCiks\ issuers, \SPIDresumeBatchCalls\ calls) returned \SPIDresumeBatchMentions\ mentions with no logged failure: \SPIDresumeBatchProductive\ productive sections against about \SPIDresumeBatchExpectedProductive\ expected at the form-by-item rates of the rest of the run, with \SPIDresumeBatchZeroSharePct\% zero-mention calls in its last minute. The most likely reading is a block of empty answers returned with a success status while the quota was being restored; those sections are ledgered as ``no project'' and were not re-run. A re-submission of the \SPIDresumeBatchCalls\ gate-passing sections of that batch (about \SPIDresumeBatchCalls\ calls) was requested during the review and is not granted in this revision, so the batch stays flagged as suspect in \Cref{tab:evidence} and the issuer list is published with the audit outputs. The general point stands: a ledger that cannot distinguish ``no project'' from ``call failed'' or ``empty answer'' is not sufficient, and an explicit \texttt{call\_status} column, together with the raw response, is part of the proposed schema.
64 +The ledger also records a failure mode encountered during the run, which this revision has reconciled against the run log rather than reported from memory (\Cref{tab:outage}). When the API key's credit balance was exhausted (log window \SPIDoutageWindowStart\ to \SPIDoutageWindowEnd), the extractor's retry logic returned empty results that were recorded as ``extracted with zero mentions'': the log carries \SPIDllmRetryableInsideWindow\ retryable HTTP errors and \SPIDllmFailedConnectionError\ connection resets in that window (\SPIDllmFailedRetryableHttpFourTwoNineFivexx\ retryable lines in all, the remaining \SPIDllmRetryableTailLines\ in the twenty seconds after it), batches \SPIDoutageZeroBatchRange\ (\SPIDoutageZeroBatchWindow) returned no mention at all, and \SPIDoutageRowsRemoved\ rows were removed from the ledger---a number that reconciles exactly with the log as the sum of sections reported complete (\SPIDlogExtractionCompleteSum) minus the sections pending when the run was restarted (\SPIDlogPendingSecondStart). The final ledger has no row inside the largest gap (\SPIDoutageLargestGapStart\ to \SPIDoutageLargestGapEnd); the \SPIDoutageSurvivingRows\ rows stamped in the first quarter of an hour of the window survived because their calls had succeeded, and all of them are productive (\SPIDoutageSurvivingMentions\ mentions, \SPIDoutageSurvivingZeroRows\ rows with zero mentions). Two residues remain. First, the extractor also returns an empty list after \SPIDretries\ failed attempts on any other error, and the log shows \SPIDllmFailedJsonDecodeError\ JSON-decode failures outside the outage (\SPIDjsonFailBeforeOutage\ before, \SPIDjsonFailAfterOutage\ after; \SPIDjuneLlmCallFailed\ in the June run), each ledgered as ``no project''; the earlier version reported one. Second, the first batch after the restart (\SPIDresumeBatchWindow; \SPIDresumeBatchSections\ sections in the log, \SPIDresumeBatchLedgerRows\ ledgered rows of \SPIDresumeBatchCiks\ issuers, \SPIDresumeBatchCalls\ calls) returned \SPIDresumeBatchMentions\ mentions in the log (\SPIDresumeBatchLedgerMentions\ ledgered) with no logged failure: \SPIDresumeBatchProductive\ productive sections against about \SPIDresumeBatchExpectedProductive\ expected at the form-by-item rates of the rest of the run, with \SPIDresumeBatchZeroSharePct\% zero-mention calls in its last minute. The most likely reading is a block of empty answers returned with a success status while the quota was being restored; those sections are ledgered as ``no project'' and were not re-run. A re-submission of the \SPIDresumeBatchCalls\ gate-passing sections of that batch (about \SPIDresumeBatchCalls\ calls) was requested during the review and is not granted in this revision, so the batch stays flagged as suspect in \Cref{tab:evidence} and the issuer list is published with the audit outputs. The general point stands: a ledger that cannot distinguish ``no project'' from ``call failed'' or ``empty answer'' is not sufficient, and an explicit \texttt{call\_status} column, together with the raw response, is part of the proposed schema.
65 65
66 66 \input{tables/tab_outage}
modified paper/sections/validation.tex +1 −1
@@ -20,7 +20,7 @@ The aim of identifying ``all projects'' of an issuer is a \emph{coverage target}
20 20
21 21 \subsection{Mechanical accuracy audits (implemented in this revision)}\label{sec:val_mechanical}
22 22
23 −\paragraph{Field-level grounding.} The prompt requires that numbers and locations appear in the text, and the structured fields are literal tokens, so their containment in the stored section text can be tested for every mention. For all \SPIDmentions\ mentions joined to their section text (\texttt{results/grounding/}; \SPIDgroundBootstrapReps\ bootstrap replicates clustered by section), every listed year is found in \SPIDgroundYearsPct\% of mentions with years (\SPIDgroundYearsCiLow--\SPIDgroundYearsCiHigh), every listed partner in \SPIDgroundPartnersPct\%, the amount as a number (in any of the usual renderings) in \SPIDgroundAmountNumberPct\% and with a currency token within forty characters in \SPIDgroundAmountCurrencyPct\%, every listed location in \SPIDgroundLocationsPct\% (10-K \SPIDgroundLocationsTenK\%, 10-Q \SPIDgroundLocationsTenQ\%, 8-K \SPIDgroundLocationsEightK\%), and every technology tag in \SPIDgroundTechPct\%; at the level of individual elements the rates are \SPIDgroundElemYears\% for years, \SPIDgroundElemPartners\% for partners, \SPIDgroundElemLocations\% for locations and \SPIDgroundElemTech\% for technologies. All structured fields of a mention are found in \SPIDgroundAllPct\% of the \SPIDgroundAllN\ mentions that carry at least one (10-K \SPIDgroundAllTenK\%, 10-Q \SPIDgroundAllTenQ\%, 8-K \SPIDgroundAllEightK\%; June block \SPIDgroundAllJune\%, October \SPIDgroundAllOctober\%), with no difference between filings before and after the model's training cut-off beyond one percentage point. Three readings of these rates are needed. First, they are rates of \emph{transcription}, not of correctness: a year, partner or amount can be copied faithfully from a passage that describes a counterparty's initiative, a hypothetical, or a financing instrument, so grounding is necessary for a correct mention and not sufficient. Second, the lower rates for locations and technologies reflect normalization by the model (``Texas'' for ``Austin, TX''; ``cloud computing'' for ``AWS'') as much as invention; the element-level rate for locations is \SPIDgroundElemLocations\%. Third, \SPIDyearsAbsentFromText\ mentions carry a year absent from the section, and the technology and year lists equal the regex output on the model's own description for up to \SPIDtechRegexEqualPct\% and \SPIDyearsRegexEqualPct\% of mentions, so for those fields ``grounded'' may mean ``copied from the paraphrase'' (\Cref{sec:contract}). The breakdown by field and form is in \Cref{tab:grounding} and \Cref{fig:grounding}; the full breakdown by item is in \texttt{results/grounding/grounding\_by\_field\_form\_item.csv}.
23 +\paragraph{Field-level grounding.} The prompt requires that numbers and locations appear in the text, and the structured fields are literal tokens, so their containment in the stored section text can be tested for every mention (after case-folding, replacement of punctuation by spaces and a short alias list for country names, as stated in the note of \Cref{tab:grounding}; partners are the field most sensitive to this normalisation). For all \SPIDmentions\ mentions joined to their section text (\texttt{results/grounding/}; \SPIDgroundBootstrapReps\ bootstrap replicates clustered by section), every listed year is found in \SPIDgroundYearsPct\% of mentions with years (\SPIDgroundYearsCiLow--\SPIDgroundYearsCiHigh), every listed partner in \SPIDgroundPartnersPct\%, the amount as a number (in any of the usual renderings) in \SPIDgroundAmountNumberPct\% and with a currency token within forty characters in \SPIDgroundAmountCurrencyPct\%, every listed location in \SPIDgroundLocationsPct\% (10-K \SPIDgroundLocationsTenK\%, 10-Q \SPIDgroundLocationsTenQ\%, 8-K \SPIDgroundLocationsEightK\%), and every technology tag in \SPIDgroundTechPct\%; at the level of individual elements the rates are \SPIDgroundElemYears\% for years, \SPIDgroundElemPartners\% for partners, \SPIDgroundElemLocations\% for locations and \SPIDgroundElemTech\% for technologies. All structured fields of a mention are found in \SPIDgroundAllPct\% of the \SPIDgroundAllN\ mentions that carry at least one (10-K \SPIDgroundAllTenK\%, 10-Q \SPIDgroundAllTenQ\%, 8-K \SPIDgroundAllEightK\%; June block \SPIDgroundAllJune\%, October \SPIDgroundAllOctober\%), with no difference between filings before and after the model's training cut-off beyond one percentage point. Three readings of these rates are needed. First, they are rates of \emph{transcription}, not of correctness: a year, partner or amount can be copied faithfully from a passage that describes a counterparty's initiative, a hypothetical, or a financing instrument, so grounding is necessary for a correct mention and not sufficient. Second, the lower rates for locations and technologies reflect normalization by the model (``Texas'' for ``Austin, TX''; ``cloud computing'' for ``AWS'') as much as invention; the element-level rate for locations is \SPIDgroundElemLocations\%. Third, \SPIDyearsAbsentFromText\ mentions carry a year absent from the section, and the technology and year lists equal the regex output on the model's own description for up to \SPIDtechRegexEqualPct\% and \SPIDyearsRegexEqualPct\% of mentions, so for those fields ``grounded'' may mean ``copied from the paraphrase'' (\Cref{sec:contract}). The breakdown by field and form is in \Cref{tab:grounding} and \Cref{fig:grounding}; the full breakdown by item is in \texttt{results/grounding/grounding\_by\_field\_form\_item.csv}.
24 24
25 25 \input{tables/tab_grounding}
26 26
modified paper/tables/numbers.tex +16 −2
@@ -196,6 +196,7 @@
196 196 \newcommand{\SPIDprovenanceMaxAmountBusd}{11.5}
197 197 \newcommand{\SPIDprovenanceNameYear}{2022}
198 198 \newcommand{\SPIDprovenanceProjectId}{3e2705ba}
199 +\newcommand{\SPIDprovenanceKeyToken}{san patricio county, texas}
199 200 \newcommand{\SPIDgateRegexFlags}{re.IGNORECASE}
200 201 \newcommand{\SPIDgateRegexLength}{215}
201 202 \newcommand{\SPIDgateRegexAlternatives}{25}
@@ -205,6 +206,10 @@
205 206 \newcommand{\SPIDyearsNotInText}{604}
206 207 \newcommand{\SPIDtechDeployMentionedNoSignal}{90}
207 208 \newcommand{\SPIDtechDeployMentionedTotal}{124}
209 +\newcommand{\SPIDgroundPartnersCheckN}{5{,}287}
210 +\newcommand{\SPIDgroundPartnersCheckAuditPct}{99.3}
211 +\newcommand{\SPIDgroundPartnersPlainCasefoldPct}{96.1}
212 +\newcommand{\SPIDgroundPartnersCheckSample}{20{,}000}
208 213 \newcommand{\SPIDgroundedYearsPct}{99.8}
209 214 \newcommand{\SPIDgroundedYearsCI}{[99.7, 99.8]}
210 215 \newcommand{\SPIDgroundedYearsOneZeroKPct}{99.6}
@@ -376,6 +381,7 @@
376 381 \newcommand{\SPIDllmCallFailedInsideOutage}{94{,}467}
377 382 \newcommand{\SPIDllmCallFailedOutsideOutage}{14}
378 383 \newcommand{\SPIDllmRetryableTailLines}{128}
384 +\newcommand{\SPIDllmRetryableInsideWindow}{94{,}465}
379 385 \newcommand{\SPIDllmFailedJsonDecodeError}{14}
380 386 \newcommand{\SPIDllmFailedRetryableHttpFourTwoNineFivexx}{94{,}593}
381 387 \newcommand{\SPIDllmFailedConnectionError}{2}
@@ -415,6 +421,8 @@
415 421 \newcommand{\SPIDoutageZeroBatchRange}{47--60}
416 422 \newcommand{\SPIDoutageZeroBatchWindow}{01:44--06:05}
417 423 \newcommand{\SPIDresumeBatchMentions}{34}
424 +\newcommand{\SPIDresumeBatchLedgerMentions}{30}
425 +\newcommand{\SPIDresumeBatchLedgerRows}{4{,}769}
418 426 \newcommand{\SPIDresumeBatchWindow}{06:38--06:40}
419 427 \newcommand{\SPIDlogExtractionCompleteSum}{1{,}117{,}479}
420 428 \newcommand{\SPIDlogPendingSecondStart}{968{,}127}
@@ -423,6 +431,8 @@
423 431 \newcommand{\SPIDworkers}{48}
424 432 \newcommand{\SPIDoutageSurvivingRows}{504}
425 433 \newcommand{\SPIDoutageSurvivingMentions}{1{,}011}
434 +\newcommand{\SPIDoutageSurvivingProductive}{504}
435 +\newcommand{\SPIDoutageSurvivingZeroRows}{0}
426 436 \newcommand{\SPIDbackfillTimestamp}{October~27, 2026, 13:12}
427 437 \newcommand{\SPIDresumeBatchExpectedProductive}{536}
428 438 \newcommand{\SPIDresumeBatchZeroSharePct}{99.2}
@@ -431,6 +441,8 @@
431 441 \newcommand{\SPIDoctoberProductiveShareCalls}{23.6}
432 442 \newcommand{\SPIDgateRejectedPct}{37.3}
433 443 \newcommand{\SPIDusdPerOctoberRow}{0.0008}
444 +\newcommand{\SPIDcallsBothRuns}{703{,}725}
445 +\newcommand{\SPIDusdPerCallBothRuns}{0.0011}
434 446 \newcommand{\SPIDconfExactDefault}{3{,}278}
435 447 \newcommand{\SPIDconfExactDefaultOctober}{2{,}974}
436 448 \newcommand{\SPIDconfExactDefaultJune}{304}
@@ -570,6 +582,8 @@
570 582 \newcommand{\SPIDpairedIssuers}{81}
571 583 \newcommand{\SPIDpairedDeltaPp}{0.6}
572 584 \newcommand{\SPIDpairedSePp}{1.3}
585 +\newcommand{\SPIDpairedProductiveJune}{11.9}
586 +\newcommand{\SPIDpairedProductiveOctober}{12.5}
573 587 \newcommand{\SPIDcohortBothPerIssuerAll}{55.8}
574 588 \newcommand{\SPIDcohortRussellOnlyPerIssuer}{46.9}
575 589 \newcommand{\SPIDcohortBothPerIssuerOctober}{25.1}
@@ -648,8 +662,8 @@
648 662 \newcommand{\SPIDrulesTypeCost}{76.9}
649 663 \newcommand{\SPIDfinMentions}{22{,}412}
650 664 \newcommand{\SPIDfinMentionsPct}{7.3}
651 −\newcommand{\SPIDfinAmountB}{17.8}
652 −\newcommand{\SPIDfinAmountDenomB}{104.2}
665 +\newcommand{\SPIDfinAmountT}{17.8}
666 +\newcommand{\SPIDfinAmountDenomT}{104.2}
653 667 \newcommand{\SPIDfinAmountSharePct}{17.1}
654 668 \newcommand{\SPIDfinUnregPct}{54.5}
655 669 \newcommand{\SPIDfinUnregN}{5{,}179}
modified paper/tables/tab_focus.tex +1 −1
@@ -7,7 +7,7 @@
7 7 \begin{adjustbox}{max width=\textwidth}
8 8 \begin{tabular}{llrrrrrrrr}
9 9 \toprule
10 −Form & Item & \multicolumn{1}{c}{\makecell{Ledgered\\rows}} & \multicolumn{1}{c}{\makecell{Over 9{,}000\\chars (\%)}} & \multicolumn{1}{c}{\makecell{Median\\chars}} & \multicolumn{1}{c}{\makecell{Share shown\\P10}} & \multicolumn{1}{c}{\makecell{Share shown\\median}} & \multicolumn{1}{c}{\makecell{Share shown\\P90}} & \multicolumn{1}{c}{\makecell{Rows shown\\$< 25$\% (\%)}} & \multicolumn{1}{c}{\makecell{Mentions from rows\\shown $< 25$\% (\%)}} \\
10 +Form & Item & \multicolumn{1}{c}{\makecell{Ledgered\\rows}} & \multicolumn{1}{c}{\makecell{Over 9{,}000\\chars (\%)}} & \multicolumn{1}{c}{\makecell{Median\\chars}} & \multicolumn{1}{c}{\makecell{Share shown\\P10}} & \multicolumn{1}{c}{\makecell{Share shown\\median}} & \multicolumn{1}{c}{\makecell{Share shown\\P90}} & \multicolumn{1}{c}{\makecell{Rows shown\\$<25$\% (\%)}} & \multicolumn{1}{c}{\makecell{Mentions from\\such rows (\%)}} \\
11 11 \midrule
12 12 10-K & \texttt{business} & 29{,}285 & 81.1 & 37{,}168 & 5 & 16 & 100 & 66.6 & 86.1 \\
13 13 10-K & \texttt{full\_body} & 1{,}230 & 99.9 & 304{,}265 & 1 & 2 & 4 & 99.8 & 100.0 \\
modified paper/tables/tab_grounding.tex +1 −1
@@ -38,7 +38,7 @@ By run: June (backfilled) / October & 36{,}806 / 239{,}362 & 84.5 / 88.7 & & & \
38 38 \end{tabular}
39 39 \end{adjustbox}
40 40 \begin{tablenotes}\footnotesize
41 −\item Population: all 308{,}065 stored mentions, joined to the text of their section as stored (legacy June text for the S\&P~500 10-Ks). A field is ``grounded'' when every element of the list (years, partners, locations, technologies, case-folded) or the amount (tested in units, thousands, millions and billions with one to three significant digits) occurs literally in that text; a currency token is \$, USD, dollar, million or billion within 40 characters of the number. Confidence intervals are percentile bootstraps with 1{,}000 replicates clustered by section. Grounding is a necessary condition for correctness, not a measure of it: a grounded year can belong to another initiative, and an ungrounded location can be a correct normalisation (``Texas'' for ``Austin''); precision and recall require the human labels of gate (d). Panel C counts mentions with at least one structured field. Upper bounds on the regex back-fill from the model's own paraphrase (\texttt{llm.py}): the stored years equal the regex output on the description for 98{,}438 of 252{,}469 mentions with years (39\%) and the technology list for 16{,}949 of 36{,}740 (46\%); 604 mentions cite a year absent from the section text.
41 +\item Population: all 308{,}065 stored mentions, joined to the text of their section as stored (legacy June text for the S\&P~500 10-Ks). A field is ``grounded'' when every element of the list (years, partners, locations, technologies) or the amount (tested in units, thousands, millions and billions with one to three significant digits) occurs literally in that text; a currency token is \$, USD, dollar, million or billion within 40 characters of the number. Before the search, element and text are normalised identically: case-folded, punctuation replaced by spaces, whitespace collapsed, and a short alias list applied to the element (U.S., USA $\to$ united states; U.K. $\to$ united kingdom; UAE; EU); the element must then occur as whole words. The normalisation matters for partners: on a reservoir sample of 20{,}000 mentions (5{,}287 with a partner), the rate is 99.3\% under this rule and 96.1\% under a plain case-folded substring search (\texttt{results/grounding/partners\_normalisation\_check.json}). Confidence intervals are percentile bootstraps with 1{,}000 replicates clustered by section. Grounding is a necessary condition for correctness, not a measure of it: a grounded year can belong to another initiative, and an ungrounded location can be a correct normalisation (``Texas'' for ``Austin''); precision and recall require the human labels of gate (d). Panel C counts mentions with at least one structured field. Upper bounds on the regex back-fill from the model's own paraphrase (\texttt{llm.py}): the stored years equal the regex output on the description for 98{,}438 of 252{,}469 mentions with years (39\%) and the technology list for 16{,}949 of 36{,}740 (46\%); 604 mentions cite a year absent from the section text.
42 42 \end{tablenotes}
43 43 \end{threeparttable}
44 44 \end{table}
modified paper/tables/tab_outage.tex +4 −2
@@ -15,8 +15,9 @@ Ledger rows with a timestamp inside the largest gap (01:25--06:40, 5.25~h) & 0 \
15 15 Log batches returning zero mentions inside the outage window (batch ids 47--60) & 14 \\
16 16 \midrule
17 17 \multicolumn{2}{l}{\itshape Panel B: failed-call lines in the October log (94{,}609 lines; the ledger records none of them)} \\
18 −Retryable HTTP 429/5xx inside the outage window 01:10--06:22 & 94{,}593 \\
19 −Retryable HTTP 429/5xx in the outage tail (first ledger window definition, 20 s after the log window) & 128 \\
18 +Retryable HTTP 429/5xx, all lines & 94{,}593 \\
19 +\quad of which inside the outage window 01:10--06:22 & 94{,}465 \\
20 +\quad of which in the twenty seconds after it (to 06:22:21, the end of the log window) & 128 \\
20 21 Connection resets & 2 \\
21 22 JSON-decode failures outside the outage (ledgered as ``no project'': silent empty results) & 14 \\
22 23 ``Section failed'' lines & 0 \\
@@ -24,6 +25,7 @@ JSON-decode failures in the June log (of 153{,}894 sections) & 3 \\
24 25 \midrule
25 26 \multicolumn{2}{l}{\itshape Panel C: first batch after the resume (06:38--06:40, 19 issuers)} \\
26 27 Sections in the batch (log) / ledgered rows & 5{,}198 / 4{,}769 \\
28 +Mentions returned (log) / ledgered & 34 / 30 \\
27 29 Gate-passing calls & 2{,}686 \\
28 30 Productive sections observed & 23 \\
29 31 Productive sections expected at the form $\times$ item rates of the rest of the run & 536.2 \\
modified paper/tables/tab_provenance.tex +2 −2
@@ -1,4 +1,4 @@
1 −% Generated by code/make_tables.py from data/example\_provenance.csv, documented\_inputs.json:snippet\_chars. Do not edit by hand.
1 +% Generated by code/make_tables.py from data/example\_provenance.csv, documented\_inputs.json:snippet\_chars, results/resolver/example\_provenance\_keys.csv, spec\_curve.csv. Do not edit by hand.
2 2 \begin{table}[H]
3 3 \centering\scriptsize
4 4 \setlength{\tabcolsep}{3pt}
@@ -21,7 +21,7 @@ Accession & Filed & Form & Status / amount & Evidence snippet (model-generated d
21 21 \end{tabular}
22 22 \end{adjustbox}
23 23 \begin{tablenotes}\footnotesize
24 −\item Snippets are the first 170 characters of the model-generated description stored in \texttt{project\_timeline.snippet}; the description is a paraphrase, not a quotation, and the character span of the supporting passage is not stored; the full source section is retrievable through \texttt{project\_mentions.section\_id}. All 8 printed mentions are 8-K filings. The representative name is that of the highest-confidence mention (filed 2022) applied retroactively to the whole timeline, and is one admissible choice among mentions tied at the maximum confidence. The key for this issuer and type is the location token ``corpus christi'', so distinct phases share one identifier: the same mentions form 198488 projects under the strict key and 21027 under the issuer $\times$ type key (cluster sizes in Table~\ref{tab:spec_curve}).
24 +\item Snippets are the first 170 characters of the model-generated description stored in \texttt{project\_timeline.snippet}; the description is a paraphrase, not a quotation, and the character span of the supporting passage is not stored; the full source section is retrievable through \texttt{project\_mentions.section\_id}. All 8 printed mentions are 8-K filings. The representative name is that of the highest-confidence mention (filed 2022) applied retroactively to the whole timeline, and is one admissible choice among mentions tied at the maximum confidence. The key for this issuer and type is the location ``san patricio county, texas'' (\texttt{results/resolver/example\_provenance\_keys.csv}), so distinct phases at that site share one identifier; the cluster that contains the example's highest-confidence mention holds 8 mentions under this key, 1 under the strict name $\times$ location key and 176 under the issuer $\times$ type key (Table~\ref{tab:spec_curve}).
25 25 \end{tablenotes}
26 26 \end{threeparttable}
27 27 \end{table}
modified paper/tables/tab_spec_curve.tex +1 −1
@@ -22,7 +22,7 @@ No resolution (one project per mention) & 308{,}065 & 100.0 & 0.0 & 1.00 & 1 & 2
22 22 \end{tabular}
23 23 \end{adjustbox}
24 24 \begin{tablenotes}\footnotesize
25 −\item Every key is applied to the same 308{,}065 stored mentions, rebuilt from \texttt{resolve.py} in a temporary table on the master node; $\dagger$ marks the key of the released \texttt{projects} table, whose identifier set is reproduced exactly. ``Cheniere cluster size'' is the number of projects formed by the mentions of the Table~\ref{tab:provenance} example. Thresholds drop mentions below the stated confidence before resolution. The project count is therefore a property of the key: 21{,}027--308{,}065 at the release threshold, and the single-mention share and projects-per-issuer statistics move with it. Under the current key 66{,}405 projects (54.8\%) are keyed on a name token because the mention has no location, 54{,}658 on a location and 286 on the fallback ``general''; the single token ``acquisition'' absorbs 40{,}407 mentions into 2{,}256 projects (Table~\ref{tab:top_name_tokens}). The stored table is not exactly reproducible: 33{,}820 projects have several mentions at the maximum confidence (\texttt{arg\_max} ties), and re-running the authors' SQL changes 394 representative names and 600 descriptions while reproducing every project identifier.
25 +\item Every key is applied to the same 308{,}065 stored mentions, rebuilt from \texttt{resolve.py} in a temporary table on the master node; $\dagger$ marks the key of the released \texttt{projects} table, whose identifier set is reproduced exactly. ``Cheniere cluster size'' is the number of mentions in the cluster that contains the highest-confidence mention of the Table~\ref{tab:provenance} example. Thresholds drop mentions below the stated confidence before resolution. The project count is therefore a property of the key: 21{,}027--308{,}065 at the release threshold, and the single-mention share and projects-per-issuer statistics move with it. Under the current key 66{,}405 projects (54.8\%) are keyed on a name token because the mention has no location, 54{,}658 on a location and 229 on the fallback ``general'' (as in the text; 286 keys end in the token ``general'' because a project name whose first token is the word ``general'' is not distinguished from the fallback); the single token ``acquisition'' absorbs 40{,}407 mentions into 2{,}256 projects (Table~\ref{tab:top_name_tokens}). The stored table is not exactly reproducible: 33{,}820 projects have several mentions at the maximum confidence (\texttt{arg\_max} ties), and re-running the authors' SQL changes 394 representative names and 600 descriptions while reproducing every project identifier.
26 26 \end{tablenotes}
27 27 \end{threeparttable}
28 28 \end{table}
modified reproducibility/validation_report.md +1 −1
@@ -18,7 +18,7 @@ No LLM or embedding call was made; nothing was written on any node; `data/stats.
18 18 - **Export reconciliation (21 assertions, all pass):** the marginal CSV tables sum to the headline counts of `stats.json` — ledger rows 1,123,098; stored mentions 308,065 (by form, by year, by confidence); projects 121,099 (by type, status, span, mention bucket, first-seen year, sector, index membership); filings 527,548 (by form, by year × form, by year, by parse status); sections 1,342,283; universe 2,564 = 491 + 9 + 2,064 = 2,499 + 65 = Σ sectors; 10-K and 10-Q item tables sum to the per-form ledger rows and mentions; amount-bearing projects 56,500 in `amount_dist.csv`, `projects_by_type.csv` and `stats.json`.
19 19 - **Regeneration:** `make_tables.py --out <scratch>` reproduces every committed `paper/tables/*.tex` byte for byte and the committed `results/inline_numbers.json` (207 macros). Figures are regenerated as PDF (vector; `CreationDate` suppressed) — compared visually, not byte-wise.
20 20 - **Build:** `latexmk` exit 0, 0 LaTeX errors, 0 undefined citations/references, bibtex 0 warnings, 39 pages. Overfull boxes: 17 (baseline 14); the three new ones are the wider Tables 3 and 5 whose notes still sit inside the `adjustbox` (item LITE-10, Stage E/L3 moves the notes outside), and the 835 pt box is the hand-typed regex of `appendix.tex:19` that Stage E replaces by `\input{tables/gate_regex}`.
21 −- **Prompt and gate regex:** `paper/tables/prompt_verbatim.tex` and `gate_regex.tex` are now generated from the frozen snapshot (`code/audit/spid_snapshot/llm.py`, `taxonomy.py`, `rules.py`, SHA-256 in `SHA256SUMS`) by parsing the source with `ast`; the printed regex concatenates to the `_PROJECT_GATE` pattern of `rules.py` exactly (three source lines, 26 alternatives), and the prompt lists the 21 taxonomy labels in the order `TYPE_LABELS` sends them.
21 +- **Prompt and gate regex:** `paper/tables/prompt_verbatim.tex` and `gate_regex.tex` are now generated from the frozen snapshot (`code/audit/spid_snapshot/llm.py`, `taxonomy.py`, `rules.py`, SHA-256 in `SHA256SUMS`) by parsing the source with `ast`; the printed regex concatenates to the `_PROJECT_GATE` pattern of `rules.py` exactly (three source lines, 25 alternatives), and the prompt lists the 21 taxonomy labels in the order `TYPE_LABELS` sends them.
22 22 - **Literals removed from the generators:** `make_tables.py` — `2{,}587` (now `data/documented_inputs.json:iwv_equity_lines_reported`, labelled "count reported by the build log, source file not archived"), `(500 CIKs)` (now `stats.json:universe.n_sp500` / `sp500_ciks`), `2010-01-04`/`2026-10-27` (now `stats.json:filing_date_range` or min/max of `filings_by_form.csv`), `128.5` (now `stats.json:amount_total_median_musd` or `amount_dist.csv`), `2700` (y-limit now `universe.n × 1.05`), `300` (label offset now 0.7 % of the maximum), `170` (now `documented_inputs.json:snippet_chars`); `export_stats.py` — the `spid_runs` strings (`2026-06 (gpt-4o-mini, 16 workers, 153,894 sections)`, `… 48 workers, 968,134 sections`) are now read by regex from the two run logs and from the ledger (`None` when absent). `tests/test_tables.py::test_make_tables_has_no_data_literals` tokenises the generator and fails on any integer literal of three or more digits other than the scale `100` (`1e3/1e6/1e9` are exponent forms).
23 23
24 24 ## What did not match / is still open
added results/grounding/partners_normalisation_check.json +16 −0
@@ -0,0 +1,16 @@
1 +{
2 + "_about": "Stage F (N3) read-only re-derivation on M3U96b of the verifier's verify_partners.py (reservoir sample of 20,000 project_mentions, repeatable seed 20261105, joined to src_sections/spid_sections text; duckdb read_only, threads=4). Compares the audit rule of code/audit/a02_grounding.py:norm (case-fold, punctuation to spaces, alias list, whole-word search) with a plain case-folded substring search.",
3 + "date": "2026-11-05",
4 + "db": "project-db/data/db/spid.duckdb (3155701760 bytes, 2026-10-28 12:21)",
5 + "sample_mentions": 20000,
6 + "seed": 20261105,
7 + "with_partners": 5287,
8 + "partners_grounded_pct_audit_rule": 99.32,
9 + "partners_grounded_pct_naive_casefold": 96.1,
10 + "with_locations": 7638,
11 + "locations_grounded_pct_audit_rule": 78.16,
12 + "population_rates_table17": {
13 + "partners": 99.4,
14 + "locations": 78.3
15 + }
16 +}
modified results/inline_numbers.json +87 −17
@@ -979,6 +979,11 @@
979 979 "source": "data/example_provenance.csv",
980 980 "formula": "project_id[:8]"
981 981 },
982 + "provenanceKeyToken": {
983 + "value": "san patricio county, texas",
984 + "source": "results/resolver/example_provenance_keys.csv",
985 + "formula": "last field of gkey (location token) of the example project"
986 + },
982 987 "gateRegexFlags": {
983 988 "value": "re.IGNORECASE",
984 989 "source": "code/audit/spid_snapshot/rules.py",
@@ -1024,6 +1029,26 @@
1024 1029 "source": "results/grounding/fallback_bounds.json",
1025 1030 "formula": "tech_deploy_total[1]"
1026 1031 },
1032 + "groundPartnersCheckN": {
1033 + "value": "5{,}287",
1034 + "source": "results/grounding/partners_normalisation_check.json",
1035 + "formula": "with_partners (mentions with a partner in the sample)"
1036 + },
1037 + "groundPartnersCheckAuditPct": {
1038 + "value": "99.3",
1039 + "source": "results/grounding/partners_normalisation_check.json",
1040 + "formula": "partners_grounded_pct_audit_rule"
1041 + },
1042 + "groundPartnersPlainCasefoldPct": {
1043 + "value": "96.1",
1044 + "source": "results/grounding/partners_normalisation_check.json",
1045 + "formula": "partners_grounded_pct_naive_casefold"
1046 + },
1047 + "groundPartnersCheckSample": {
1048 + "value": "20{,}000",
1049 + "source": "results/grounding/partners_normalisation_check.json",
1050 + "formula": "sample_mentions (reservoir sample)"
1051 + },
1027 1052 "groundedYearsPct": {
1028 1053 "value": "99.8",
1029 1054 "source": "results/grounding/grounding_by_group.csv",
@@ -1879,6 +1904,11 @@
1879 1904 "source": "results/ledger/log_summary.json:october.outside_window_by_reason",
1880 1905 "formula": "retryable_http_429_5xx"
1881 1906 },
1907 + "llmRetryableInsideWindow": {
1908 + "value": "94{,}465",
1909 + "source": "results/ledger/log_summary.json:october",
1910 + "formula": "llm_call_failed_by_reason.retryable_http_429_5xx - outside_window_by_reason.retryable_http_429_5xx (lines inside the 01:10-06:22 window)"
1911 + },
1882 1912 "llmFailedJsonDecodeError": {
1883 1913 "value": "14",
1884 1914 "source": "results/ledger/log_summary.json:october.llm_call_failed_by_reason",
@@ -2074,6 +2104,16 @@
2074 2104 "source": "results/ledger/log_summary.json:october.resume_batch1.log_mentions",
2075 2105 "formula": "log"
2076 2106 },
2107 + "resumeBatchLedgerMentions": {
2108 + "value": "30",
2109 + "source": "results/ledger/log_summary.json:october.resume_batch1.ledger_mentions",
2110 + "formula": "ledger (sum of resume_batch1_vs_expected.csv:mentions)"
2111 + },
2112 + "resumeBatchLedgerRows": {
2113 + "value": "4{,}769",
2114 + "source": "results/ledger/log_summary.json:october.resume_batch1.ledger_rows",
2115 + "formula": "ledger rows of the batch"
2116 + },
2077 2117 "resumeBatchWindow": {
2078 2118 "value": "06:38--06:40",
2079 2119 "source": "results/ledger/log_summary.json:october.resume_batch1.window",
@@ -2114,6 +2154,16 @@
2114 2154 "source": "results/ledger/backfill_block.json",
2115 2155 "formula": "rows_in_outage_window[1]"
2116 2156 },
2157 + "outageSurvivingProductive": {
2158 + "value": "504",
2159 + "source": "results/ledger/outage_window_rows_check.json",
2160 + "formula": "rows_with_mentions (read-only re-derivation, Stage F)"
2161 + },
2162 + "outageSurvivingZeroRows": {
2163 + "value": "0",
2164 + "source": "results/ledger/outage_window_rows_check.json",
2165 + "formula": "rows_zero_mentions"
2166 + },
2117 2167 "backfillTimestamp": {
2118 2168 "value": "October~27, 2026, 13:12",
2119 2169 "source": "results/ledger/backfill_block.json:min_extracted_at",
@@ -2154,6 +2204,16 @@
2154 2204 "source": "results/ledger/gate_pass_totals.json",
2155 2205 "formula": "usd_per_october_row_paper_total_775"
2156 2206 },
2207 + "callsBothRuns": {
2208 + "value": "703{,}725",
2209 + "source": "results/ledger/gate_pass_by_run.csv",
2210 + "formula": "sum(calls) over the two runs"
2211 + },
2212 + "usdPerCallBothRuns": {
2213 + "value": "0.0011",
2214 + "source": "results/ledger/gate_pass_by_run.csv, data/documented_inputs.json:total_cost_usd",
2215 + "formula": "total_cost_usd / sum(calls) over both runs (alternative basis, author to confirm)"
2216 + },
2157 2217 "confExactDefault": {
2158 2218 "value": "3{,}278",
2159 2219 "source": "results/ledger/code_vs_data_checks.json",
@@ -2849,6 +2909,16 @@
2849 2909 "source": "results/runs/within_issuer_paired_10q.json",
2850 2910 "formula": "100*se"
2851 2911 },
2912 + "pairedProductiveJune": {
2913 + "value": "11.9",
2914 + "source": "results/runs/within_issuer_paired_10q.json",
2915 + "formula": "100*june_mean_productive_share (mean of per-issuer shares)"
2916 + },
2917 + "pairedProductiveOctober": {
2918 + "value": "12.5",
2919 + "source": "results/runs/within_issuer_paired_10q.json",
2920 + "formula": "100*oct_mean_productive_share (mean of per-issuer shares)"
2921 + },
2852 2922 "cohortBothPerIssuerAll": {
2853 2923 "value": "55.8",
2854 2924 "source": "results/runs/table6_cohort.csv",
@@ -3239,15 +3309,15 @@
3239 3309 "source": "results/fp_classes/summary.json",
3240 3310 "formula": "mentions_matched[2]"
3241 3311 },
3242 − "finAmountB": {
3312 + "finAmountT": {
3243 3313 "value": "17.8",
3244 3314 "source": "results/fp_classes/summary.json",
3245 − "formula": "matched_amount_gusd[0]/1e3 (T USD)"
3315 + "formula": "matched_amount_gusd[0]/1e3 (T USD; the source field is in billions)"
3246 3316 },
3247 − "finAmountDenomB": {
3317 + "finAmountDenomT": {
3248 3318 "value": "104.2",
3249 3319 "source": "results/fp_classes/summary.json",
3250 − "formula": "matched_amount_gusd[1]/1e3 (T USD)"
3320 + "formula": "matched_amount_gusd[1]/1e3 (T USD; the source field is in billions)"
3251 3321 },
3252 3322 "finAmountSharePct": {
3253 3323 "value": "17.1",
@@ -3871,33 +3941,33 @@
3871 3941 },
3872 3942 "chunkWords": {
3873 3943 "value": "512",
3874 − "source": "src/parsing/chunker.py",
3875 − "formula": "chunk_size"
3944 + "source": "data/documented_inputs.json:pipeline_chunk_words",
3945 + "formula": "chunk_size default of the embedding chunker (chunk_size: int = 512); manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/parsing/chunker.py, SHA-256 716a4013fb839a68ec613aa5f0c91e8da4c8a8135051fb95fec5350d35fc3c79; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable"
3876 3946 },
3877 3947 "chunkOverlap": {
3878 3948 "value": "64",
3879 − "source": "src/parsing/chunker.py",
3880 − "formula": "overlap"
3949 + "source": "data/documented_inputs.json:pipeline_chunk_overlap",
3950 + "formula": "overlap default of the embedding chunker (overlap: int = 64); manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/parsing/chunker.py, SHA-256 716a4013fb839a68ec613aa5f0c91e8da4c8a8135051fb95fec5350d35fc3c79; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable"
3881 3951 },
3882 3952 "maxFileMb": {
3883 3953 "value": "50",
3884 − "source": "src/parsing/html_parser.py",
3885 − "formula": "max_file_size_mb default"
3954 + "source": "data/documented_inputs.json:pipeline_max_file_mb",
3955 + "formula": "max_file_size_mb default of the HTML parser (html_cfg.get(\"max_file_size_mb\", 50)); manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/parsing/html_parser.py, SHA-256 61aca675ff4652e3479f50a10ce29503f66f7f74ffd4f78df0b87ad517b1908e; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable"
3886 3956 },
3887 3957 "cooldownSeconds": {
3888 3958 "value": "65",
3889 − "source": "src/acquisition/bulk_downloader.py",
3890 − "formula": "COOLDOWN_SECONDS"
3959 + "source": "data/documented_inputs.json:pipeline_cooldown_seconds",
3960 + "formula": "COOLDOWN_SECONDS = 65.0 after an SEC 403/429; manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/acquisition/bulk_downloader.py, SHA-256 862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable"
3891 3961 },
3892 3962 "secRateLimit": {
3893 3963 "value": "ten",
3894 − "source": "src/acquisition/bulk_downloader.py (docstring) / secfairaccess",
3895 − "formula": "requests per second per IP"
3964 + "source": "data/documented_inputs.json:pipeline_sec_rate_limit",
3965 + "formula": "module docstring 'SEC allows 10 req/s per IP' (sec.gov fair-access policy); manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/acquisition/bulk_downloader.py, SHA-256 862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable"
3896 3966 },
3897 3967 "acqGatewayDivisor": {
3898 3968 "value": "nine",
3899 − "source": "src/acquisition/bulk_downloader.py",
3900 − "formula": "default aggregate rate shared behind one IP"
3969 + "source": "data/documented_inputs.json:pipeline_acq_gateway_divisor",
3970 + "formula": "BulkDownloader default rate: float = 9.0 requests per second shared behind one IP; manuscripts/FIRC-0001/pipeline_code_2324b58 (archived pipeline code at revision 2324b58, outside this repository)/src/acquisition/bulk_downloader.py, SHA-256 862139f47d52cef9c33d6a64e77fae1ae3a31cc9b288762c6a2187579f5a9159; recorded here (Stage F, N1) so that a bare clone regenerates the macro, re-read and checked against the archive by code/make_tables.py when it is reachable"
3901 3971 },
3902 3972 "acqNewDocs": {
3903 3973 "value": "about 400{,}000",
@@ -3952,7 +4022,7 @@
3952 4022 "totalCostUsd": {
3953 4023 "value": "775",
3954 4024 "source": "data/documented_inputs.json:total_cost_usd",
3955 − "formula": "author-reported total of the October 2026 extraction (docs/research_log.md); the API invoice is not archived"
4025 + "formula": "author-reported total recorded in docs/research_log.md against the 2026-10-27/28 entry 'Full LLM extraction of 968,134 new sections ... ≈ 775 USD total' (October run); the review's constraints note (peer_reviews/FIRC-0001/_constraints.md) attributes the same ≈ 775 USD to both runs (153,894 + 968,134 sections); the API invoice is not archived; basis to be confirmed by the authors (Stage F, N9)"
3956 4026 },
3957 4027 "stabilitySections": {
3958 4028 "value": "500",
added results/ledger/outage_window_rows_check.json +14 −0
@@ -0,0 +1,14 @@
1 +{
2 + "_about": "Stage F (N6) read-only re-derivation on M3U96b: ledger rows whose extracted_at lies inside the outage window of results/ledger/backfill_block.json:outage_window, split by productivity (duckdb read_only, threads=4).",
3 + "date": "2026-11-05",
4 + "db": "project-db/data/db/spid.duckdb (3155701760 bytes, 2026-10-28 12:21)",
5 + "query": "select count(*), count(*) filter (where n_mentions>0), count(*) filter (where n_mentions=0), sum(n_mentions) from extracted_sections where extracted_at between TIMESTAMP '2026-10-28 01:10:00' and TIMESTAMP '2026-10-28 06:22:00'",
6 + "window": [
7 + "2026-10-28 01:10:00",
8 + "2026-10-28 06:22:00"
9 + ],
10 + "rows": 504,
11 + "rows_with_mentions": 504,
12 + "rows_zero_mentions": 0,
13 + "mentions": 1011
14 +}
15
Menu

Theme