SPB Git

spb/wp17_uqo

Public

FIRC0002 Stage E (Agent 10): main.tex v2.1 metadata, abstract rewritten to the three weakened pillars (EDIT-03, ECON-06, ADVE-01, ADVE-05), cleveref, threeparttable scaling hook; macros_stageE.tex generator (N1 construct-validity, survivorship bounds, SPID provenance, committed-baseline BEFORE values); references.bib repairs (LITE-02 Eisfeldt JF 2026 names, LITE-08 Wu–Lam authors, Wang et al. journal entity)

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

4 changed files +800 −36

modified paper/main.tex +44 −30
@@ -40,7 +40,7 @@
40 40
41 % --- Figures --- 41 % --- Figures ---
42 \usepackage{graphicx} 42 \usepackage{graphicx}
43−\graphicspath{{figures_r3k/}} 43+\graphicspath{{figures_r3k/}{../figures/r3k_v2/}}
44 \usepackage{adjustbox} 44 \usepackage{adjustbox}
45 \usepackage{subcaption} 45 \usepackage{subcaption}
46 \usepackage{float} 46 \usepackage{float}
@@ -56,8 +56,11 @@
56 ]{hyperref} 56 ]{hyperref}
57 \hypersetup{ 57 \hypersetup{
58 pdftitle = {Does the Language of Artificial Intelligence Move Markets? A Semantic Analysis of Russell 3000 SEC Filings and the Cross-Section of Stock Returns, 2010-2026}, 58 pdftitle = {Does the Language of Artificial Intelligence Move Markets? A Semantic Analysis of Russell 3000 SEC Filings and the Cross-Section of Stock Returns, 2010-2026},
59− pdfauthor = {Manel Kammoun, Djerrycharli Tandjambianda, Simon-Pierre Boucher} 59+ pdfauthor = {Manel Kammoun, Djerrycharli Tandjambianda, Simon-Pierre Boucher},
60+ pdfsubject = {UQO/FIRC Working Paper No. 0002, version 2.1 (revised after peer review, November 2026)},
61+ pdfkeywords = {textual analysis, sentence embeddings, artificial intelligence, disclosure, cross-section of returns, Russell 3000, firm size, event study, generative AI, G12, G14, G17, M41, O33}
60 } 62 }
63+\usepackage[capitalise,noabbrev]{cleveref}
61 64
62 % --- Bibliography --- 65 % --- Bibliography ---
63 \usepackage[authoryear,round,semicolon]{natbib} 66 \usepackage[authoryear,round,semicolon]{natbib}
@@ -92,6 +95,20 @@
92 \sisetup{group-minimum-digits=4, group-separator={,}} 95 \sisetup{group-minimum-digits=4, group-separator={,}}
93 \newcommand{\tstat}[1]{{\footnotesize(\textit{t}=#1)}} 96 \newcommand{\tstat}[1]{{\footnotesize(\textit{t}=#1)}}
94 \newcommand{\tablelegend}[1]{\par\vspace{0.25em}\begin{minipage}{\linewidth}\footnotesize\textit{Notes:} #1\end{minipage}} 97 \newcommand{\tablelegend}[1]{\par\vspace{0.25em}\begin{minipage}{\linewidth}\footnotesize\textit{Notes:} #1\end{minipage}}
98+% Generated table fragments carry their own \footnotesize notes paragraph after the tabular.
99+% Scaling the whole fragment with \adjustbox would put the notes on one scaled line, so every
100+% tabular of the document is wrapped, by an etoolbox hook, in adjustbox{max width=\textwidth}:
101+% a tabular wider than the text block is shrunk, a narrower one is untouched, and the notes
102+% paragraph is typeset at \textwidth. \widetable is kept as a marker for the fragments that
103+% needed scaling before the hook existed.
104+% The generated fragments wrap each tabular in a threeparttable (which redefines \tabular, so a
105+% hook on tabular would be bypassed); the hook is therefore attached to threeparttable. A fragment
106+% whose natural width exceeds \textwidth is shrunk with its notes (the exhibits agent is asked to
107+% narrow such fragments at generation; the hook keeps the build free of overfull boxes meanwhile).
108+\usepackage{etoolbox}
109+\BeforeBeginEnvironment{threeparttable}{\begin{adjustbox}{max width=\textwidth}}
110+\AfterEndEnvironment{threeparttable}{\end{adjustbox}}
111+\newcommand{\widetable}[1]{#1}
95 112
96 % --- Misc --- 113 % --- Misc ---
97 \setlength{\emergencystretch}{3em} 114 \setlength{\emergencystretch}{3em}
@@ -103,38 +120,28 @@
103 % ============================================================================ 120 % ============================================================================
104 \newcommand{\WPnumber}{0002} 121 \newcommand{\WPnumber}{0002}
105 \newcommand{\WPtitle}{Does the Language of Artificial Intelligence Move Markets?} 122 \newcommand{\WPtitle}{Does the Language of Artificial Intelligence Move Markets?}
123+% Stage E note (Agent 10, 2026-11-05): the Chief Editor and two referees (ADVE-13, EDIT-16) read the
124+% title verb "Move" as causal while every design in the paper is associational. A title change is an
125+% author decision (authorization_requests.md, "Not Rule 6"); a descriptive alternative that keeps the
126+% subtitle would be "Is the Language of Artificial Intelligence Priced?". Not imposed here.
106 \newcommand{\WPsubtitle}{A Semantic Analysis of Russell~3000 SEC Filings and the Cross-Section of Stock Returns, 2010--2026} 127 \newcommand{\WPsubtitle}{A Semantic Analysis of Russell~3000 SEC Filings and the Cross-Section of Stock Returns, 2010--2026}
107−\newcommand{\WPdate}{October 28, 2026} 128+\newcommand{\WPdate}{November 2026}
108−\newcommand{\WPversion}{2.0 (Russell 3000 extension)} 129+\newcommand{\WPversion}{2.1 (Russell 3000 extension, revised after peer review)}
109 \newcommand{\WPfunding}{Research funded by the FIRC (Fonds institutionnel de recherche et de création), Université du Québec en Outaouais.} 130 \newcommand{\WPfunding}{Research funded by the FIRC (Fonds institutionnel de recherche et de création), Université du Québec en Outaouais.}
110 \newcommand{\WPabstract}{% 131 \newcommand{\WPabstract}{%
111 \begingroup\singlespacing 132 \begingroup\singlespacing
112−We measure artificial-intelligence (AI) engagement in the SEC filings of some 2,500 U.S.\ listed 133+We measure artificial-intelligence (AI) language in the Form~10-K, 10-Q and 8-K filings of
113−firms---the current S\&P~500 constituents and the holdings of the iShares Russell~3000 ETF---by 134+\mnFirms{} U.S.\ firms (current S\&P~500 members and iShares Russell~3000 holdings, 2010--2026)
114−embedding every passage of their Form~10-K, 10-Q and 8-K filings of 2010--2026 with a sentence 135+by sentence-transformer similarity to anchor passages, and relate the filing-year signal to
115−transformer and scoring it against curated AI anchor concepts (internal adoption, infrastructure 136+look-ahead-free forward returns over fifteen formation cohorts (\mnObs{} firm-years). On
116−exposure, product-disruption risk). The signal of filing year $Y$ is linked to the forward 137+winsorized returns the equal-weighted top-minus-bottom quintile spread is \mspreadFullW{}
117−twelve-month return from July of $Y{+}1$, so that it predates the holding period by at least six 138+percentage points per year (95\% CI \mspreadFullWCI) and the Fama--MacBeth slope \mfmComposW{}
118−months. The panel of \mnObs{} firm-years (\mnFirms{} firms) extends our earlier S\&P~500 study, 139+points per standard deviation (CI \mfmComposWCI): a modest premium, robust in regression form,
119−which rested on current reports only, to annual and quarterly reports and to the mid- and 140+fragile in sort form, and conditional on survival to October 2026. A post-ChatGPT increase
120−small-capitalization firms that make up most of the Russell~3000, and it adds a second, 141+appears only among the largest thousand firms and in the 2024 cohort; the large-versus-small
121−independent measure of AI engagement: the AI, data-center, cloud and automation projects that a 142+contrast is insignificant (\mcontrastLarge{} points, $t=\mcontrastLargeT$) and the event-study
122−large-language-model reader extracts from the same filings. Three results stand out. First, in the 143+gradient depends on the benchmark. Disclosed AI projects extracted by a language model show no
123−broad universe AI language carries a modest but persistent cross-sectional premium over the whole 144+detectable premium (minimum detectable effect \mspidAnyFirmFullMDE{} points).
124−sample: the equal-weighted top-minus-bottom quintile spread is \mspreadFull{} percentage points per
125−year ($t=\mspreadFullT$) and the Fama--MacBeth slope \mrkFMcompos{} points per standard deviation
126−($t=\mrkFMcomposT$), with a significant within-sector component. Second, the sharp post-ChatGPT
127−increase documented for the S\&P~500---which we replicate within the new panel (\mspSpreadPre{} to
128−\mspSpreadPost{} points)---does not generalize: in the Russell~3000 the post-launch spread is
129−\mspreadPost{} points against \mspreadPre{} before, the 2022 cohort is negative, a placebo over ten
130−candidate break years ranks 2022 \mplaceboRank{}th, and the monthly long--short earns no
131−post-launch alpha. The increase is confined to the largest thousand firms (\mlargePre{} to
132−\mlargePost{} points), whereas the smaller firms earned a premium of about \msmallPre{} points
133−before the launch and \msmallPost{} after; the ChatGPT event study shows the same size gradient
134−(\mevLarge{} versus \mevSmall{} points of one-year abnormal return). Third, disclosed AI projects
135−carry no return premium, while the language of annual reports carries the strongest one. The
136−evidence is consistent with a persistent, partly within-industry premium on AI exposure in the
137−broad market and with a large-cap re-rating after 2022, not with a market-wide regime change.
138 \par\endgroup 145 \par\endgroup
139 } 146 }
140 \newcommand{\WPkeywords}{textual analysis, sentence embeddings, artificial intelligence, disclosure, cross-section of returns, Russell 3000, firm size, event study, generative AI} 147 \newcommand{\WPkeywords}{textual analysis, sentence embeddings, artificial intelligence, disclosure, cross-section of returns, Russell 3000, firm size, event study, generative AI}
@@ -160,7 +167,12 @@ broad market and with a large-cap re-rating after 2022, not with a market-wide r
160 % ============================================================================ 167 % ============================================================================
161 % DOCUMENT 168 % DOCUMENT
162 % ============================================================================ 169 % ============================================================================
170+% Number macros: tables/macros.tex (scripts/r3k_20_tables_v2.py, 442 macros from results/r3k_v2),
171+% sections/macros_stageE.tex (scripts/stageE_writing_macros.py: the N1 corpus counts of
172+% ../results/n1/corpus_macros.tex re-emitted with letter-only names, N1 construct-validity numbers,
173+% survivorship bounds, SPID provenance, and the committed-baseline BEFORE values \mvone...).
163 \input{tables/macros} 174 \input{tables/macros}
175+\input{sections/macros_stageE}
164 \begin{document} 176 \begin{document}
165 177
166 % --- Title Page --- 178 % --- Title Page ---
@@ -182,5 +194,7 @@ broad market and with a large-cap re-rating after 2022, not with a market-wide r
182 194
183 % --- Appendix --- 195 % --- Appendix ---
184 \input{appendix/appendix} 196 \input{appendix/appendix}
197+% Stage E (Agent 9, exhibits): appendix-bound and not-yet-placed exhibits, each behind an \ExhibitOnce guard (generated by scripts/r3k_20_tables_v2.py).
198+\input{tables/appendix_exhibits}
185 199
186 \end{document} 200 \end{document}
modified paper/references.bib +7 −6
@@ -645,21 +645,22 @@
645 } 645 }
646 646
647 @techreport{wulam2026genai, 647 @techreport{wulam2026genai,
648− author = {Institute for Monetary and Financial Research, Hong Kong}, 648+ author = {Wu, Gabriel Shui Tang and Lam, Stephen Lok Hang},
649 title = {{Do Firms' Stock Prices Respond to AI-related Discussions? Evidence from Earnings Calls}}, 649 title = {{Do Firms' Stock Prices Respond to AI-related Discussions? Evidence from Earnings Calls}},
650 year = {2026}, 650 year = {2026},
651 doi = {10.2139/ssrn.7482259}, 651 doi = {10.2139/ssrn.7482259},
652− institution = {Elsevier BV}, 652+ institution = {Hong Kong Institute for Monetary and Financial Research (HKIMR) / SSRN},
653 type = {Working paper}, 653 type = {Working paper},
654− note = {Elsevier BV, DOI 10.2139/ssrn.7482259} 654+ note = {SSRN 7482259; the Crossref record lists the HKIMR as author and names the two authors (Hong Kong Monetary Authority) in its abstract; re-resolved 2026-11-05}
655 } 655 }
656 656
657 @article{eisfeldt2026generative, 657 @article{eisfeldt2026generative,
658− author = {EISFELDT, ANDREA L. and SCHUBERT, GREGOR and ZHANG, MIAO BEN and TASKA, BLEDI}, 658+ author = {Eisfeldt, Andrea L. and Schubert, Gregor and Zhang, Miao Ben and Taska, Bledi},
659 title = {{Generative AI and Firm Values}}, 659 title = {{Generative AI and Firm Values}},
660 year = {2026}, 660 year = {2026},
661 doi = {10.1111/jofi.70089}, 661 doi = {10.1111/jofi.70089},
662− journal = {The Journal of Finance} 662+ journal = {The Journal of Finance},
663+ note = {Published online 8 October 2026, article jofi.70089; Crossref record re-resolved 2026-11-05 (names upper-cased in the record)}
663 } 664 }
664 665
665 @techreport{song2026productexposure, 666 @techreport{song2026productexposure,
@@ -687,7 +688,7 @@
687 title = {{Talk is cheap, green is hard: The effect of AI-washing on corporate energy transition}}, 688 title = {{Talk is cheap, green is hard: The effect of AI-washing on corporate energy transition}},
688 year = {2026}, 689 year = {2026},
689 doi = {10.1016/j.iref.2026.105756}, 690 doi = {10.1016/j.iref.2026.105756},
690− journal = {International Review of Economics &amp; Finance}, 691+ journal = {International Review of Economics \& Finance},
691 volume = {111}, 692 volume = {111},
692 pages = {105756} 693 pages = {105756}
693 } 694 }
added paper/sections/macros_stageE.tex +313 −0
@@ -0,0 +1,313 @@
1+% Generated by scripts/stageE_writing_macros.py (Stage E, Agent 10) — do not edit by hand.
2+% Sources: results/n1/*, results/r3k_v2/*, results/r3k/* (committed baseline 3cf7560 for the mvone* BEFORE values).
3+\newcommand{\mMaxSeq}{256}
4+\newcommand{\mTokenizerMax}{512}
5+\newcommand{\mTruncShare}{94.8}
6+\newcommand{\mTruncShareFull}{100.0}
7+\newcommand{\mTruncTokensMedian}{661}
8+\newcommand{\mTruncWindowWords}{195}
9+\newcommand{\mTruncMassDropped}{62}
10+\newcommand{\mTruncHitsBeyond}{61}
11+\newcommand{\mTruncHitChunksAllBeyond}{51}
12+\newcommand{\mTruncScoreBeyond}{0.18}
13+\newcommand{\mTruncScoreInside}{0.34}
14+\newcommand{\mTruncScoreBoth}{0.43}
15+\newcommand{\mTruncSampleN}{10{,}000}
16+\newcommand{\mKwFirmSpearman}{0.24}
17+\newcommand{\mKwShareZeroFirmYears}{84}
18+\newcommand{\mKwQfiveInKwQfive}{28}
19+\newcommand{\mKwUtilSpearman}{0.00}
20+\newcommand{\mKwUtilShareZero}{94}
21+\newcommand{\mKwUtilMeanZ}{0.52}
22+\newcommand{\mKwITMeanZ}{0.87}
23+\newcommand{\mKwITSpearman}{0.17}
24+\newcommand{\mUtilSectorRank}{2}
25+\newcommand{\mKwTopDecileShare}{4.8}
26+\newcommand{\mKwTopDecileShareExt}{9.8}
27+\newcommand{\mKwBottomDecileShare}{0.08}
28+\newcommand{\mKwCorrEarlyMin}{0.02}
29+\newcommand{\mKwCorrEarlyMax}{0.07}
30+\newcommand{\mKwCorrLateMin}{0.24}
31+\newcommand{\mKwCorrLateMax}{0.29}
32+\newcommand{\mKwShareZeroTwentyTen}{98}
33+\newcommand{\mKwShareZeroTwentyTwentyFour}{45}
34+\newcommand{\mStripRho}{0.98}
35+\newcommand{\mCoreRho}{0.80}
36+\newcommand{\mStripQfiveStays}{88}
37+\newcommand{\mProbeAffirm}{0.578}
38+\newcommand{\mProbeNegated}{0.396}
39+\newcommand{\mProbeDiff}{0.18}
40+\newcommand{\mProbePairsShare}{89}
41+\newcommand{\mProbeNegatedPct}{99.8}
42+\newcommand{\mProbeAffirmPct}{99.99}
43+\newcommand{\mProbeUtilityPct}{98.1}
44+\newcommand{\mProbeParaphrasePct}{99.8}
45+\newcommand{\mProbeCyberPct}{94.0}
46+\newcommand{\mProbeUnrelatedPct}{43}
47+\newcommand{\mProbeUtilInfra}{0.375}
48+\newcommand{\mProbeUtilAdopt}{0.268}
49+\newcommand{\mProbeParaphraseAdopt}{0.395}
50+\newcommand{\mProbeAutomationAdopt}{0.378}
51+\newcommand{\mProbeUnrelatedAdopt}{0.064}
52+\newcommand{\mAdoptPninetynine}{0.30}
53+\newcommand{\mAdoptPninetyninenine}{0.44}
54+\newcommand{\mIdaRank}{4}
55+\newcommand{\mIdaRankStrip}{4}
56+\newcommand{\mIdaRankCore}{7}
57+\newcommand{\mHeRank}{11}
58+\newcommand{\mHeRankStrip}{21}
59+\newcommand{\mHeRankCore}{63}
60+\newcommand{\mTopThirtyUtilOriginal}{3}
61+\newcommand{\mTopThirtyUtilCore}{2}
62+\newcommand{\mTopThirtyOverlapStrip}{22}
63+\newcommand{\mSectorZUtil}{0.52}
64+\newcommand{\mSectorZIT}{0.87}
65+\newcommand{\mKwRho}{0.13}
66+\newcommand{\mKwFullW}{+4.7}
67+\newcommand{\mKwFullWT}{2.30}
68+\newcommand{\mKwFullWTplain}{1.82}
69+\newcommand{\mKwFMW}{+0.61}
70+\newcommand{\mKwFMWT}{1.25}
71+\newcommand{\mKwPreW}{+3.1}
72+\newcommand{\mKwPostW}{+11.1}
73+\newcommand{\mKwLogRho}{0.14}
74+\newcommand{\mKwLogFullW}{+4.7}
75+\newcommand{\mKwLogFullWT}{2.30}
76+\newcommand{\mKwLogFullWTplain}{1.82}
77+\newcommand{\mKwLogFMW}{+0.81}
78+\newcommand{\mKwLogFMWT}{1.95}
79+\newcommand{\mKwLogPreW}{+3.1}
80+\newcommand{\mKwLogPostW}{+11.1}
81+\newcommand{\mStripFullW}{+4.1}
82+\newcommand{\mStripFullWT}{2.33}
83+\newcommand{\mStripFullWTplain}{2.13}
84+\newcommand{\mStripFMW}{+1.79}
85+\newcommand{\mStripFMWT}{2.70}
86+\newcommand{\mStripPreW}{+4.1}
87+\newcommand{\mStripPostW}{+4.0}
88+\newcommand{\mCoreFullW}{+4.8}
89+\newcommand{\mCoreFullWT}{2.69}
90+\newcommand{\mCoreFullWTplain}{1.99}
91+\newcommand{\mCoreFMW}{+1.74}
92+\newcommand{\mCoreFMWT}{3.02}
93+\newcommand{\mCorePreW}{+4.7}
94+\newcommand{\mCorePostW}{+5.2}
95+\newcommand{\mCyberStripRho}{0.35}
96+\newcommand{\mCyberStripFullW}{+1.9}
97+\newcommand{\mCyberStripFullWT}{0.97}
98+\newcommand{\mCyberStripFullWTplain}{1.12}
99+\newcommand{\mCyberStripFMW}{+0.92}
100+\newcommand{\mCyberStripFMWT}{1.49}
101+\newcommand{\mCyberStripPreW}{+0.1}
102+\newcommand{\mCyberStripPostW}{+8.8}
103+\newcommand{\mKwShareZero}{83.9}
104+\newcommand{\mHrComposVsKw}{+1.85}
105+\newcommand{\mHrComposVsKwT}{2.37}
106+\newcommand{\mHrKw}{+0.36}
107+\newcommand{\mHrKwT}{0.68}
108+\newcommand{\mHrComposVsStrip}{+0.15}
109+\newcommand{\mHrComposVsStripT}{0.06}
110+\newcommand{\mHrStrip}{+1.66}
111+\newcommand{\mHrStripT}{0.79}
112+\newcommand{\mExUtilFullW}{+4.6}
113+\newcommand{\mExUtilFullWT}{2.16}
114+\newcommand{\mExUtilN}{25{,}420}
115+\newcommand{\mUtilShareQfive}{6.5}
116+\newcommand{\mUtilShareAll}{3.2}
117+\newcommand{\mITShareQfive}{32}
118+\newcommand{\mBoundExitThirty}{8.1}
119+\newcommand{\mBoundExitHalf}{5.7}
120+\newcommand{\mBoundExitFull}{3.3}
121+\newcommand{\mExitSmallMin}{11.0}
122+\newcommand{\mExitSmallMax}{16.1}
123+\newcommand{\mExitLargeMin}{1.9}
124+\newcommand{\mExitLargeMax}{2.8}
125+\newcommand{\mExitRatioMin}{5.0}
126+\newcommand{\mExitRatioMax}{8.1}
127+\newcommand{\mnIWVsnapshotsSinceNineteen}{16}
128+\newcommand{\mContigAboveRaw}{3}
129+\newcommand{\mContigN}{12}
130+\newcommand{\mContigAboveW}{2}
131+\newcommand{\mContigBestYear}{2017}
132+\newcommand{\mContigBestDiff}{+10.7}
133+\newcommand{\mPermSubsets}{455}
134+\newcommand{\mSupWaldCVfive}{10.8}
135+\newcommand{\mRevWithWithoutTwelve}{-1.39}
136+\newcommand{\mRevWithWithoutTwelveT}{-0.91}
137+\newcommand{\mRevWithWithoutTwentyFour}{+1.35}
138+\newcommand{\mRevWithWithoutTwentyFourT}{0.62}
139+\newcommand{\mRevNhighWithProjects}{1{,}288}
140+\newcommand{\mRevNhigh}{5{,}001}
141+\newcommand{\mRevComposThirteenTwentyFourW}{+5.4}
142+\newcommand{\mRevComposThirteenTwentyFourWT}{3.13}
143+\newcommand{\mEvtRefSlope}{+5.9}
144+\newcommand{\mEvtRefT}{5.95}
145+\newcommand{\mEvtDiffTwentyTwoT}{-4.49}
146+\newcommand{\mEvtDiffTwentyT}{-5.65}
147+\newcommand{\mEvtDiffTwentyEighteen}{+1.8}
148+\newcommand{\mEvtDiffTwenty}{-6.8}
149+\newcommand{\mEvtN}{21{,}111}
150+\newcommand{\mAaplCapVone}{9.3}
151+\newcommand{\mAaplRankVone}{166}
152+\newcommand{\mAaplCapVtwo}{310}
153+\newcommand{\mAaplRankVtwo}{2}
154+\newcommand{\mLogCapMaxVone}{37.4}
155+\newcommand{\mAepCapVoneExp}{16}
156+\newcommand{\mAepCapVoneMant}{1.0}
157+\newcommand{\mAepCapVtwo}{18.1}
158+\newcommand{\mnCapsGtThreeTrillionVone}{29}
159+\newcommand{\mnCapsGtThreeTrillionVtwo}{7}
160+\newcommand{\mShareNoCapVone}{7.7}
161+\newcommand{\mShareNoCapVtwo}{1.1}
162+\newcommand{\mnSizeGroupChange}{1{,}915}
163+\newcommand{\mvoneNobs}{27{,}468}
164+\newcommand{\mvoneNfirms}{2{,}379}
165+\newcommand{\mvoneSpreadFull}{+5.5}
166+\newcommand{\mvoneSpreadFullT}{2.41}
167+\newcommand{\mvoneSpreadFullVW}{+13.9}
168+\newcommand{\mvoneSpreadFullVWT}{2.59}
169+\newcommand{\mvoneVWQone}{+0.4}
170+\newcommand{\mvoneVWQfive}{+14.3}
171+\newcommand{\mvoneFMcompos}{+3.07}
172+\newcommand{\mvoneFMcomposT}{2.80}
173+\newcommand{\mvoneFMsector}{+1.46}
174+\newcommand{\mvoneFMsectorT}{4.38}
175+\newcommand{\mvoneSectorNeutralFull}{-0.3}
176+\newcommand{\mvoneSectorNeutralFullT}{-0.11}
177+\newcommand{\mvoneSpreadPre}{+4.9}
178+\newcommand{\mvoneSpreadPost}{+7.9}
179+\newcommand{\mvonePooledPost}{+8.2}
180+\newcommand{\mvonePooledPostT}{1.80}
181+\newcommand{\mvonePooledPostW}{+3.0}
182+\newcommand{\mvonePooledPostExTwentyFour}{+1.8}
183+\newcommand{\mvonePooledPostExTwentyFourT}{0.33}
184+\newcommand{\mvoneCohortTwentyTwo}{-8.4}
185+\newcommand{\mvoneEntrantPrem}{+11.0}
186+\newcommand{\mvoneLargePre}{+2.9}
187+\newcommand{\mvoneLargePost}{+12.8}
188+\newcommand{\mvoneSmallPre}{+9.6}
189+\newcommand{\mvoneSmallPost}{+8.4}
190+\newcommand{\mvoneEvFullHL}{+14.0}
191+\newcommand{\mvoneEvFullWelch}{3.94}
192+\newcommand{\mvoneEvLargeHL}{+19.5}
193+\newcommand{\mvoneEvLargeWelch}{5.09}
194+\newcommand{\mvoneEvSmallHL}{+9.9}
195+\newcommand{\mvoneEvSmallWelch}{1.55}
196+\newcommand{\mvoneEvSixtyHL}{+0.9}
197+\newcommand{\mvoneEvN}{2{,}226}
198+\newcommand{\mvoneSpidT}{0.00}
199+\newcommand{\mvoneSpidPostCoefExp}{12}
200+\newcommand{\mvoneDidCont}{+1.11}
201+\newcommand{\mvoneDidContT}{0.69}
202+\newcommand{\mvoneDidBin}{+2.54}
203+\newcommand{\mvoneDidBinT}{0.98}
204+\newcommand{\mvoneEvtTwentyOne}{+7.0}
205+\newcommand{\mvoneEvtTwentyOneSE}{1.1}
206+\newcommand{\mvoneEvtTwentyTwo}{+0.0}
207+\newcommand{\mvoneEvtTwentyFour}{+5.0}
208+\newcommand{\mvoneSpreadPostTnaive}{1.34}
209+\newcommand{\mvoneSpidAnyPost}{+6.6}
210+\newcommand{\mvoneSpidAnyPostT}{1.68}
211+\newcommand{\mvoneSpidPre}{-1.9}
212+\newcommand{\mvoneSpidPreT}{-2.64}
213+\newcommand{\mvoneSpidLSmean}{-5.28}
214+\newcommand{\mvonePlaceboRank}{8}
215+\newcommand{\mSpidModel}{gpt-4o-mini}
216+\newcommand{\mSpidSampleN}{200}
217+\newcommand{\mSpidUniverseMentions}{203{,}944}
218+\newcommand{\mSpidEvidenceShare}{59.5}
219+\newcommand{\mSpidConfMedian}{0.9}
220+\newcommand{\mSpidDbDate}{2026-10-28}
221+\newcommand{\mSpidStrata}{63}
222+\newcommand{\mHygieneConfirmedMonthly}{37}
223+\newcommand{\mHygieneConfirmedFY}{101}
224+\newcommand{\mriskFullW}{+7.3}
225+\newcommand{\mriskFullWT}{2.20}
226+\newcommand{\mriskFullWTplain}{1.88}
227+\newcommand{\mriskFull}{+12.3}
228+\newcommand{\mriskFullT}{2.99}
229+\newcommand{\mriskTopOnePct}{+8.5}
230+\newcommand{\mtenkFullW}{+6.2}
231+\newcommand{\mtenkFullWT}{1.91}
232+\newcommand{\mformwFullWsort}{+5.9}
233+\newcommand{\mformwFullWsortT}{1.93}
234+\newcommand{\mcyberFullW}{+1.9}
235+\newcommand{\mcyberFullWT}{1.02}
236+\newcommand{\mcomposTopOnePctW}{+0.9}
237+\newcommand{\mtenkPreWsp}{+4.9}
238+\newcommand{\mtenkPreWspT}{1.36}
239+\newcommand{\mtenkPostWsp}{+11.6}
240+\newcommand{\mtenkPostWspT}{2.06}
241+\newcommand{\mriskPreWsp}{+5.0}
242+\newcommand{\mriskPreWspT}{2.01}
243+\newcommand{\mriskPostWsp}{+16.2}
244+\newcommand{\mriskPostWspT}{1.74}
245+\newcommand{\mcyberPreWsp}{+0.2}
246+\newcommand{\mcyberPreWspT}{0.10}
247+\newcommand{\mcyberPostWsp}{+8.9}
248+\newcommand{\mcyberPostWspT}{5.11}
249+\newcommand{\mformwPreWsp}{+4.3}
250+\newcommand{\mformwPreWspT}{1.40}
251+\newcommand{\mformwPostWsp}{+12.2}
252+\newcommand{\mformwPostWspT}{1.85}
253+\newcommand{\mpcaPreWsp}{+3.8}
254+\newcommand{\mpcaPreWspT}{1.61}
255+\newcommand{\mpcaPostWsp}{+8.6}
256+\newcommand{\mpcaPostWspT}{1.24}
257+\newcommand{\mlargeFortyTwoN}{11{,}356}
258+\newcommand{\mlargeFortyTwoFirms}{1{,}280}
259+\newcommand{\mlargeFortyTwoPreW}{+4.2}
260+\newcommand{\mlargeFortyTwoPreWT}{1.63}
261+\newcommand{\mlargeFortyTwoPostW}{+12.8}
262+\newcommand{\mlargeFortyTwoPostWT}{2.26}
263+\newcommand{\mlargeFortyTwoPostExTwentyFourW}{+3.6}
264+\newcommand{\msmallFiftyEightN}{15{,}690}
265+\newcommand{\msmallFiftyEightFirms}{1{,}766}
266+\newcommand{\msmallFiftyEightPreW}{+4.3}
267+\newcommand{\msmallFiftyEightPreWT}{1.28}
268+\newcommand{\msmallFiftyEightPostW}{+3.6}
269+\newcommand{\msmallFiftyEightPostWT}{0.61}
270+\newcommand{\msmallFiftyEightPostExTwentyFourW}{-3.3}
271+\newcommand{\mhrTenKW}{+2.78}
272+\newcommand{\mhrTenKWT}{2.23}
273+\newcommand{\mhrTenQW}{-0.32}
274+\newcommand{\mhrTenQWT}{-0.87}
275+\newcommand{\mhrEightKW}{+0.55}
276+\newcommand{\mhrEightKWT}{1.29}
277+\newcommand{\mseasonedPreW}{+3.2}
278+\newcommand{\mseasonedPostW}{+8.3}
279+\newcommand{\mentrantPremW}{+5.9}
280+\newcommand{\mentrantPremRaw}{+9.0}
281+\newcommand{\mnObsFirstCohort}{1{,}342}
282+\newcommand{\mnObsLastCohort}{2{,}374}
283+\newcommand{\mattritionSmallNineteen}{61}
284+\newcommand{\mattritionLargeNineteen}{16}
285+\newcommand{\mattritionSmallTwentyFour}{12}
286+\newcommand{\mattritionLargeTwentyFour}{2}
287+\newcommand{\mcohortTwentyOneW}{+12.1}
288+\newcommand{\mbreakTwentyOnePreW}{+2.6}
289+\newcommand{\mbreakTwentyOnePreWT}{1.13}
290+\newcommand{\mbreakTwentyOnePostW}{+7.5}
291+\newcommand{\mbreakTwentyOnePostWT}{2.40}
292+\newcommand{\mbreakTwentyOneDiffW}{+4.8}
293+\newcommand{\mbreakTwentyOneDiffWT}{1.91}
294+\newcommand{\mbreakTwentyOneDiffWTtw}{0.88}
295+\newcommand{\mContigBestBlock}{2017--2019}
296+\newcommand{\mnChunksCorpus}{10{,}720{,}890}
297+\newcommand{\mnSectionsCorpus}{1{,}342{,}283}
298+\newcommand{\mnFilingsCorpusN}{527{,}548}
299+\newcommand{\mnIssuersCorpusN}{2{,}499}
300+\newcommand{\mnEmbeddingsCorpus}{10{,}721{,}045}
301+\newcommand{\mnChunkScoresCorpus}{10{,}720{,}890}
302+\newcommand{\mChunkWordsMedian}{512}
303+\newcommand{\mChunkWordsMean}{480}
304+\newcommand{\mChunkShareFull}{84.2}
305+\newcommand{\mnFilingsTenK}{31{,}775}
306+\newcommand{\mnParsedTenK}{31{,}772}
307+\newcommand{\mnChunksTenK}{4{,}380{,}094}
308+\newcommand{\mnFilingsTenQ}{95{,}167}
309+\newcommand{\mnParsedTenQ}{95{,}155}
310+\newcommand{\mnChunksTenQ}{5{,}492{,}676}
311+\newcommand{\mnFilingsEightK}{400{,}606}
312+\newcommand{\mnParsedEightK}{400{,}605}
313+\newcommand{\mnChunksEightK}{848{,}120}
added scripts/stageE_writing_macros.py +436 −0
@@ -0,0 +1,436 @@
1+#!/usr/bin/env python3
2+"""Stage E (Agent 10, writing) — supplementary number macros for the manuscript prose.
3+
4+Every macro below is read from a result file produced by a Stage D / N1 script; nothing is typed.
5+Sources: results/n1/tokenizer_audit.json, keyword_baseline.json, keyword_chunk_deciles.csv,
6+keyword_corr_by_year.csv, alt_anchor_results.json; results/r3k_v2/results_construct.json,
7+results_surv.json, results2.json, mktcap_diag.json, validation/spid_sample_meta.json;
8+the committed baseline results/r3k/*.json (HEAD 3cf7560) for the BEFORE values.
9+Output: paper/sections/macros_stageE.tex (\\input from paper/main.tex after tables/macros.tex).
10+Usage: python3 -I scripts/stageE_writing_macros.py (run from the repository root)
11+"""
12+from __future__ import annotations
13+
14+import csv
15+import json
16+import math
17+from pathlib import Path
18+
19+ROOT = Path(__file__).resolve().parents[1]
20+OUT = ROOT / "paper" / "sections" / "macros_stageE.tex"
21+
22+
23+def load(p: str):
24+ with open(ROOT / p) as f:
25+ return json.load(f)
26+
27+
28+def pct(x: float, d: int = 1) -> str:
29+ return f"{100 * x:.{d}f}"
30+
31+
32+def signed(x: float, d: int = 1) -> str:
33+ return f"{x:+.{d}f}"
34+
35+
36+def num(x: float, d: int = 2) -> str:
37+ return f"{x:.{d}f}"
38+
39+
40+def thousands(n: int) -> str:
41+ return f"{int(n):,}".replace(",", "{,}")
42+
43+
44+M: dict[str, str] = {}
45+
46+# ---------------------------------------------------------------- tokenizer / truncation (N1)
47+tok = load("results/n1/tokenizer_audit.json")
48+R, K = tok["frames"]["R"], tok["frames"]["K"]
49+M["mMaxSeq"] = str(tok["max_seq_length"])
50+M["mTokenizerMax"] = str(tok["model_max_length_tokenizer"])
51+M["mTruncShare"] = pct(R["share_truncated"])
52+M["mTruncShareFull"] = pct(R["share_truncated_full_512w"])
53+M["mTruncTokensMedian"] = str(int(R["tokens_median"]))
54+M["mTruncWindowWords"] = str(int(R["words_in_window_median"]))
55+M["mTruncMassDropped"] = pct(R["share_of_token_mass_dropped"], 0)
56+M["mTruncHitsBeyond"] = pct(K["share_hits_beyond_window"], 0)
57+M["mTruncHitChunksAllBeyond"] = pct(K["share_hit_chunks_all_hits_beyond"], 0)
58+M["mTruncScoreBeyond"] = num(K["mean_score_adopt_by_hit_class"]["beyond_only"], 2)
59+M["mTruncScoreInside"] = num(K["mean_score_adopt_by_hit_class"]["inside_only"], 2)
60+M["mTruncScoreBoth"] = num(K["mean_score_adopt_by_hit_class"]["both"], 2)
61+M["mTruncSampleN"] = thousands(R["n"])
62+
63+# ---------------------------------------------------------------- keyword baseline (N1)
64+kw = load("results/n1/keyword_baseline.json")
65+M["mKwFirmSpearman"] = num(kw["firm_year"]["adopt_vs_kw_core_wmean"]["spearman"], 2)
66+M["mKwShareZeroFirmYears"] = pct(kw["firm_year"]["adopt_vs_kw_core_wmean"]["share_zero_b"], 0)
67+M["mKwQfiveInKwQfive"] = pct(kw["firm_year"]["adopt_vs_kw_core_wmean"]["share_a_Q5_in_b_Q5"], 0)
68+sec = {r["sector"]: r for r in kw["by_sector"]}
69+M["mKwUtilSpearman"] = num(abs(sec["Utilities"]["spearman"]), 2)
70+M["mKwUtilShareZero"] = pct(sec["Utilities"]["share_zero_kw"], 0)
71+M["mKwUtilMeanZ"] = num(sec["Utilities"]["mean_z_compos"], 2)
72+M["mKwITMeanZ"] = num(sec["Information Technology"]["mean_z_compos"], 2)
73+M["mKwITSpearman"] = num(sec["Information Technology"]["spearman"], 2)
74+rank_by_z = sorted(kw["by_sector"], key=lambda r: -r["mean_z_compos"])
75+M["mUtilSectorRank"] = str([r["sector"] for r in rank_by_z].index("Utilities") + 1)
76+with open(ROOT / "results/n1/keyword_chunk_deciles.csv") as f:
77+ dec = {int(r["decile"]): r for r in csv.DictReader(f)}
78+M["mKwTopDecileShare"] = pct(float(dec[10]["share_core_hit"]))
79+M["mKwTopDecileShareExt"] = pct(float(dec[10]["share_ext_hit"]))
80+M["mKwBottomDecileShare"] = pct(float(dec[1]["share_core_hit"]), 2)
81+with open(ROOT / "results/n1/keyword_corr_by_year.csv") as f:
82+ rows = list(csv.DictReader(f))
83+early = [float(r["spearman_compos_kw"]) for r in rows if int(r["fiscal_year"]) <= 2016]
84+late = [float(r["spearman_compos_kw"]) for r in rows if int(r["fiscal_year"]) >= 2022]
85+M["mKwCorrEarlyMin"] = num(min(early), 2)
86+M["mKwCorrEarlyMax"] = num(max(early), 2)
87+M["mKwCorrLateMin"] = num(min(late), 2)
88+M["mKwCorrLateMax"] = num(max(late), 2)
89+M["mKwShareZeroTwentyTen"] = pct(float(rows[0]["share_zero_kw"]), 0)
90+M["mKwShareZeroTwentyTwentyFour"] = pct(float(rows[-1]["share_zero_kw"]), 0)
91+
92+# ---------------------------------------------------------------- alternative anchors, negation probe (N1, AR-2)
93+alt = load("results/n1/alt_anchor_results.json")
94+M["mStripRho"] = num(alt["firm_level_spearman_compos_vs_strip"], 3)
95+M["mCoreRho"] = num(alt["firm_level_spearman_compos_vs_core_strip"], 2)
96+M["mStripQfiveStays"] = pct(alt["firm_year"]["adopt_vs_adopt_strip"]["share_Q5_stays_Q5"], 0)
97+pn = alt["probe_negation"]["s_ai_adoption"]
98+M["mProbeAffirm"] = num(pn["mean_affirmative"], 3)
99+M["mProbeNegated"] = num(pn["mean_negated"], 3)
100+M["mProbeDiff"] = num(pn["diff"], 2)
101+M["mProbePairsShare"] = pct(pn["share_pairs_aff_gt_neg"], 0)
102+M["mProbeNegatedPct"] = pct(alt["probe_pct_rank_in_corpus"]["negated_ai"])
103+M["mProbeAffirmPct"] = pct(alt["probe_pct_rank_in_corpus"]["affirmative_ai"], 2)
104+M["mProbeUtilityPct"] = pct(alt["probe_pct_rank_in_corpus"]["utility_data_center_load"])
105+M["mProbeParaphrasePct"] = pct(alt["probe_pct_rank_in_corpus"]["paraphrase_no_keyword"])
106+M["mProbeCyberPct"] = pct(alt["probe_pct_rank_in_corpus"]["cyber_no_ai"])
107+M["mProbeUnrelatedPct"] = pct(alt["probe_pct_rank_in_corpus"]["unrelated"], 0)
108+gm = alt["probe_group_means"]
109+M["mProbeUtilInfra"] = num(gm["utility_data_center_load"]["s_ai_infrastructure"], 3)
110+M["mProbeUtilAdopt"] = num(gm["utility_data_center_load"]["s_ai_adoption"], 3)
111+M["mProbeParaphraseAdopt"] = num(gm["paraphrase_no_keyword"]["s_ai_adoption"], 3)
112+M["mProbeAutomationAdopt"] = num(gm["automation_no_ai"]["s_ai_adoption"], 3)
113+M["mProbeUnrelatedAdopt"] = num(gm["unrelated"]["s_ai_adoption"], 3)
114+q = alt["corpus_adopt_quantiles_50_90_95_99_999"]
115+M["mAdoptPninetynine"] = num(q[3], 2)
116+M["mAdoptPninetyninenine"] = num(q[4], 2)
117+ranks = {r["ticker"]: r for r in alt["ida_he_ranks"]}
118+M["mIdaRank"] = str(int(ranks["IDA"]["r"]))
119+M["mIdaRankStrip"] = str(int(ranks["IDA"]["rs"]))
120+M["mIdaRankCore"] = str(int(ranks["IDA"]["rc"]))
121+M["mHeRank"] = str(int(ranks["HE"]["r"]))
122+M["mHeRankStrip"] = str(int(ranks["HE"]["rs"]))
123+M["mHeRankCore"] = str(int(ranks["HE"]["rc"]))
124+M["mTopThirtyUtilOriginal"] = str(alt["top30_utilities_original"])
125+M["mTopThirtyUtilCore"] = str(alt["top30_utilities_core_strip"])
126+M["mTopThirtyOverlapStrip"] = str(alt["top30_overlap_strip"])
127+M["mSectorZUtil"] = num(alt["sector_mean_z"]["Utilities"]["z_compos"], 2)
128+M["mSectorZIT"] = num(alt["sector_mean_z"]["Information Technology"]["z_compos"], 2)
129+
130+# ---------------------------------------------------------------- construct validity rows (r3k_23)
131+con = load("results/r3k_v2/results_construct.json")
132+S = con["signals"]
133+
134+
135+def sig(key: str, prefix: str) -> None:
136+ s = S[key]
137+ if s.get("spearman_with_composite") is not None:
138+ M[prefix + "Rho"] = num(s["spearman_with_composite"], 2)
139+ M[prefix + "FullW"] = signed(100 * s["full_w"]["mean"])
140+ M[prefix + "FullWT"] = num(s["full_w"]["t"])
141+ M[prefix + "FullWTplain"] = num(s["full_w"]["t_plain"])
142+ M[prefix + "FMW"] = signed(s["fm_w"]["slope_pct"], 2)
143+ M[prefix + "FMWT"] = num(s["fm_w"]["t"])
144+ M[prefix + "PreW"] = signed(100 * s["pre_w"]["mean"])
145+ M[prefix + "PostW"] = signed(100 * s["post_w"]["mean"])
146+
147+
148+sig("z_KW", "mKw")
149+sig("z_KWlog", "mKwLog")
150+sig("z_ALT", "mStrip")
151+sig("z_ALTcore", "mCore")
152+sig("z_CYBERstrip", "mCyberStrip")
153+M["mKwShareZero"] = pct(S["z_KW"]["share_zero"])
154+hr = con["horse_race_w"]
155+M["mHrComposVsKw"] = signed(hr["composite_vs_keyword"]["z_AI_COMPOS"]["slope_pct"], 2)
156+M["mHrComposVsKwT"] = num(hr["composite_vs_keyword"]["z_AI_COMPOS"]["t"])
157+M["mHrKw"] = signed(hr["composite_vs_keyword"]["z_KW"]["slope_pct"], 2)
158+M["mHrKwT"] = num(hr["composite_vs_keyword"]["z_KW"]["t"])
159+M["mHrComposVsStrip"] = signed(hr["composite_vs_stripped"]["z_AI_COMPOS"]["slope_pct"], 2)
160+M["mHrComposVsStripT"] = num(hr["composite_vs_stripped"]["z_AI_COMPOS"]["t"])
161+M["mHrStrip"] = signed(hr["composite_vs_stripped"]["z_ALT"]["slope_pct"], 2)
162+M["mHrStripT"] = num(hr["composite_vs_stripped"]["z_ALT"]["t"])
163+exu = con["ex_utilities_energy"]
164+M["mExUtilFullW"] = signed(100 * exu["full_w"]["mean"])
165+M["mExUtilFullWT"] = num(exu["full_w"]["t"])
166+M["mExUtilN"] = thousands(exu["n"])
167+M["mUtilShareQfive"] = pct(con["utilities_share_q5_vs_all"][0])
168+M["mUtilShareAll"] = pct(con["utilities_share_q5_vs_all"][1])
169+M["mITShareQfive"] = pct(con["q5_sector_shares"]["Information Technology"], 0)
170+
171+# ---------------------------------------------------------------- survivorship bounding (r3k_18)
172+sv = load("results/r3k_v2/results_surv.json")
173+b = sv["bounding"]
174+req = b["required_exit_rate_difference_e1_minus_e5"]
175+M["mBoundExitThirty"] = pct(req["r_x=-0.3"])
176+M["mBoundExitHalf"] = pct(req["r_x=-0.5"])
177+M["mBoundExitFull"] = pct(req["r_x=-1.0"])
178+small = {k: v for k, v in b["measured_annualised_exit_small_tercile"].items() if k >= "2019"}
179+large = {k: v for k, v in b["measured_annualised_exit_large_tercile"].items() if k >= "2019"}
180+M["mExitSmallMin"] = pct(min(small.values()))
181+M["mExitSmallMax"] = pct(max(small.values()))
182+M["mExitLargeMin"] = pct(min(large.values()))
183+M["mExitLargeMax"] = pct(max(large.values()))
184+ratios = [small[k] / large[k] for k in small if k in large]
185+M["mExitRatioMin"] = num(min(ratios), 1)
186+M["mExitRatioMax"] = num(max(ratios), 1)
187+M["mnIWVsnapshotsSinceNineteen"] = str(len([k for k in sv["snapshots"] if str(k)[:4] >= "2019"]) if isinstance(sv["snapshots"], (list, dict)) else "")
188+tm = sv["then_members_present"]
189+if isinstance(tm, dict):
190+ shares = [v.get("share_members") for v in tm.values() if isinstance(v, dict) and v.get("share_members") is not None]
191+elif isinstance(tm, list):
192+ shares = [v.get("share_members") for v in tm if isinstance(v, dict) and v.get("share_members") is not None]
193+else:
194+ shares = []
195+if shares:
196+ M["mThenMembersMin"] = pct(min(shares), 0)
197+ M["mThenMembersMax"] = pct(max(shares), 0)
198+es = sv.get("entry_side", {})
199+if isinstance(es, dict) and "entrant_premium_w_pct" in es:
200+ M["mEntrantPremW"] = signed(es["entrant_premium_w_pct"])
201+
202+# ---------------------------------------------------------------- break placebos (r3k_16)
203+r2 = load("results/r3k_v2/results2.json")
204+pb = r2["placebo_breaks"]
205+cb = pb["permutation"]["contiguous_blocks"]
206+M["mContigAboveRaw"] = str(sum(1 for k, v in cb.items() if k != "2022" and v > cb["2022"]))
207+M["mContigN"] = str(len(cb) - 1)
208+cbw = pb["winsorized"]["permutation"]["contiguous_blocks"]
209+M["mContigAboveW"] = str(sum(1 for k, v in cbw.items() if k != "2022" and v > cbw["2022"]))
210+M["mContigBestYear"] = max((k for k in cb if k != "2022"), key=lambda k: cb[k])
211+M["mContigBestDiff"] = signed(100 * cb[M["mContigBestYear"]])
212+M["mPermSubsets"] = str(pb["permutation"]["n_subsets"])
213+M["mSupWaldCVfive"] = num(pb["sup_wald"]["cv05_simulated"], 1)
214+ck = r2["cluster_k"]["reversal"]
215+M["mRevWithWithoutTwelve"] = signed(ck["high_language_with_vs_without_projects"]["month_1_12_w"]["mean_pct"], 2)
216+M["mRevWithWithoutTwelveT"] = num(ck["high_language_with_vs_without_projects"]["month_1_12_w"]["t"])
217+M["mRevWithWithoutTwentyFour"] = signed(ck["high_language_with_vs_without_projects"]["month_13_24_w"]["mean_pct"], 2)
218+M["mRevWithWithoutTwentyFourT"] = num(ck["high_language_with_vs_without_projects"]["month_13_24_w"]["t"])
219+M["mRevNhighWithProjects"] = thousands(ck["high_language_with_vs_without_projects"]["n_high_language_with_projects"])
220+M["mRevNhigh"] = thousands(ck["high_language_with_vs_without_projects"]["n_high_language"])
221+M["mRevComposThirteenTwentyFourW"] = signed(100 * ck["Q5_minus_Q1_month_13_24"]["w"]["mean"])
222+M["mRevComposThirteenTwentyFourWT"] = num(ck["Q5_minus_Q1_month_13_24"]["w"]["t"])
223+ft = r2["filing_type"]
224+if "wald_equal_slopes_w" in ft:
225+ M["mFormWaldChi"] = num(ft["wald_equal_slopes_w"].get("stat", float("nan")), 2)
226+dt = r2["did_eventtime"]
227+M["mEvtRefSlope"] = signed(100 * dt["base_slope_ref_year"])
228+M["mEvtRefT"] = num(dt["base_t"])
229+rows_dt = {r["year"]: r for r in dt["rows"]}
230+M["mEvtDiffTwentyTwoT"] = num(rows_dt[2022]["diff_from_ref"] / rows_dt[2022]["diff_se"])
231+M["mEvtDiffTwentyT"] = num(rows_dt[2020]["diff_from_ref"] / rows_dt[2020]["diff_se"])
232+M["mEvtDiffTwentyEighteen"] = signed(100 * rows_dt[2018]["diff_from_ref"])
233+M["mEvtDiffTwenty"] = signed(100 * rows_dt[2020]["diff_from_ref"])
234+M["mEvtN"] = thousands(rows_dt[2022]["n"])
235+
236+# ---------------------------------------------------------------- market-cap diagnostics (r3k_14)
237+mc = load("results/r3k_v2/mktcap_diag.json")
238+M["mAaplCapVone"] = num(mc["AAPL_cohort2010_cap_v1_usd"] / 1e9, 1)
239+M["mAaplRankVone"] = str(int(mc["AAPL_cohort2010_rank_v1"]))
240+M["mAaplCapVtwo"] = str(int(round(mc["AAPL_cohort2010_cap_v2_usd"] / 1e9)))
241+M["mAaplRankVtwo"] = str(int(mc["AAPL_cohort2010_rank_v2"]))
242+M["mLogCapMaxVone"] = num(mc["log_cap_min_max_v1"][1], 1)
243+M["mAepCapVoneExp"] = str(int(math.floor(math.log10(mc["AEP_2010"]["cap_v1"]))))
244+M["mAepCapVoneMant"] = num(mc["AEP_2010"]["cap_v1"] / 10 ** math.floor(math.log10(mc["AEP_2010"]["cap_v1"])), 1)
245+M["mAepCapVtwo"] = num(mc["AEP_2010"]["cap_v2"] / 1e9, 1)
246+M["mnCapsGtThreeTrillionVone"] = str(mc["n_caps_gt_3e12_v1"])
247+M["mnCapsGtThreeTrillionVtwo"] = str(mc["n_caps_gt_3e12"])
248+M["mShareNoCapVone"] = pct(mc["share_est_without_cap_v1"])
249+M["mShareNoCapVtwo"] = pct(mc["share_est_without_cap_v2"])
250+M["mnSizeGroupChange"] = thousands(mc["n_firm_years_changing_size_group"])
251+
252+# ---------------------------------------------------------------- BEFORE values from the committed baseline (HEAD 3cf7560)
253+r1 = load("results/r3k/results.json")
254+r2v1 = load("results/r3k/results2.json")
255+ev1 = load("results/r3k/results_event.json")
256+M["mvoneNobs"] = thousands(r1["n_obs"])
257+M["mvoneNfirms"] = thousands(r1["n_firms"])
258+M["mvoneSpreadFull"] = signed(100 * r1["sorts"]["AI_COMPOS_EW"]["Q5_minus_Q1"])
259+M["mvoneSpreadFullT"] = num(r1["sorts"]["AI_COMPOS_EW"]["t_stat"])
260+M["mvoneSpreadFullVW"] = signed(100 * r1["sorts"]["AI_COMPOS_VW"]["Q5_minus_Q1"])
261+M["mvoneSpreadFullVWT"] = num(r1["sorts"]["AI_COMPOS_VW"]["t_stat"])
262+M["mvoneVWQone"] = signed(100 * r1["sorts"]["AI_COMPOS_VW"]["Q1"])
263+M["mvoneVWQfive"] = signed(100 * r1["sorts"]["AI_COMPOS_VW"]["Q5"])
264+M["mvoneFMcompos"] = signed(100 * r1["fama_macbeth"]["compos"]["z_AI_COMPOS"]["mean_coef"], 2)
265+M["mvoneFMcomposT"] = num(r1["fama_macbeth"]["compos"]["z_AI_COMPOS"]["t"])
266+sc = r2v1["sector_controls"]["fm_full"]
267+M["mvoneFMsector"] = signed(sc["slope_sector_pct"], 2)
268+M["mvoneFMsectorT"] = num(sc["t_sector"])
269+M["mvoneSectorNeutralFull"] = signed(100 * r2v1["sector_neutral"]["full"]["mean"])
270+M["mvoneSectorNeutralFullT"] = num(r2v1["sector_neutral"]["full"]["t"])
271+M["mvoneSpreadPre"] = signed(r2v1["cohorts"]["pre_mean_pct"])
272+M["mvoneSpreadPost"] = signed(r2v1["cohorts"]["post_mean_pct"])
273+M["mvonePooledPost"] = signed(r2v1["cohorts"]["pooled_post"]["coef_pct"])
274+M["mvonePooledPostT"] = num(r2v1["cohorts"]["pooled_post"]["t"])
275+M["mvonePooledPostW"] = signed(r2v1["cohorts"]["pooled_post_wins"]["coef_pct"])
276+M["mvonePooledPostExTwentyFour"] = signed(r2v1["cohorts"]["pooled_post_excl_2024"]["coef_pct"])
277+M["mvonePooledPostExTwentyFourT"] = num(r2v1["cohorts"]["pooled_post_excl_2024"]["t"])
278+M["mvoneCohortTwentyTwo"] = signed(r2v1["cohorts"]["cohort_spreads_pct"]["2022"])
279+M["mvoneEntrantPrem"] = signed(r2v1["entrants"]["entrant_minus_seasoned_fwd_pct"])
280+g = r1["groups"]
281+M["mvoneLargePre"] = signed(100 * g["large_top1000"]["pre"]["mean"]) if "pre" in g["large_top1000"] else ""
282+M["mvoneLargePost"] = signed(100 * g["large_top1000"]["post"]["mean"]) if "post" in g["large_top1000"] else ""
283+M["mvoneSmallPre"] = signed(100 * g["small_rest"]["pre"]["mean"]) if "pre" in g["small_rest"] else ""
284+M["mvoneSmallPost"] = signed(100 * g["small_rest"]["post"]["mean"]) if "post" in g["small_rest"] else ""
285+M["mvoneEvFullHL"] = signed(ev1["car_1y"]["H_minus_L"])
286+M["mvoneEvFullWelch"] = num(ev1["car_1y"]["t_welch"])
287+M["mvoneEvLargeHL"] = signed(ev1["by_size"]["large"]["H_minus_L_1y"])
288+M["mvoneEvLargeWelch"] = num(ev1["by_size"]["large"]["t_welch"])
289+M["mvoneEvSmallHL"] = signed(ev1["by_size"]["small"]["H_minus_L_1y"])
290+M["mvoneEvSmallWelch"] = num(ev1["by_size"]["small"]["t_welch"])
291+M["mvoneEvSixtyHL"] = signed(ev1["car_60d"]["H_minus_L"])
292+M["mvoneEvN"] = thousands(ev1["n_firms"])
293+sp = r2v1["spid"]["z_SPID_TECH"]
294+M["mvoneSpidT"] = num(abs(sp["z_SPID_TECH:post"]["t"]))
295+M["mvoneSpidPostCoefExp"] = str(int(math.floor(math.log10(abs(sp["post"]["coef_pct"])))))
296+M["mvoneDidCont"] = signed(100 * r2v1["did"]["continuous"]["coef"], 2)
297+M["mvoneDidContT"] = num(r2v1["did"]["continuous"]["t"])
298+M["mvoneDidBin"] = signed(100 * r2v1["did"]["binary"]["coef"], 2)
299+M["mvoneDidBinT"] = num(r2v1["did"]["binary"]["t"])
300+evt1 = {r["year"]: r for r in r2v1["did_eventtime"]}
301+M["mvoneEvtTwentyOne"] = signed(100 * evt1[2021]["coef"])
302+M["mvoneEvtTwentyOneSE"] = num(100 * evt1[2021]["se"], 1)
303+M["mvoneEvtTwentyTwo"] = signed(100 * evt1[2022]["coef"])
304+M["mvoneEvtTwentyFour"] = signed(100 * evt1[2024]["coef"])
305+M["mvoneSpreadPostTnaive"] = num(r1["subperiod_spread"]["AI_COMPOS"]["post"]["t"]) if "post" in r1["subperiod_spread"]["AI_COMPOS"] else ""
306+M["mvoneSpidAnyPost"] = signed(r2v1["spid"]["spread_ai_any_post"]["post"]["mean_pct"])
307+M["mvoneSpidAnyPostT"] = num(r2v1["spid"]["spread_ai_any_post"]["post"]["t"])
308+M["mvoneSpidPre"] = signed(100 * r1["subperiod_spread"]["SPID_TECH"]["pre"]["mean"])
309+M["mvoneSpidPreT"] = num(r1["subperiod_spread"]["SPID_TECH"]["pre"]["t"])
310+M["mvoneSpidLSmean"] = signed(r1["ls_alpha"]["SPID_TECH"]["full"]["mean_ls_ann_pct"], 2)
311+M["mvonePlaceboRank"] = str(r2v1["placebo_breaks"]["rank_of_2022"]) if "placebo_breaks" in r2v1 else ""
312+
313+# ---------------------------------------------------------------- SPID extractor provenance (L8)
314+meta = load("results/r3k_v2/validation/spid_sample_meta.json")
315+M["mSpidModel"] = ", ".join(meta["extractor_provenance_from_code_snapshot"]["model"]).replace("_", "\\_")
316+M["mSpidSampleN"] = str(meta["n_sample"])
317+M["mSpidUniverseMentions"] = thousands(meta["n_universe_mentions"])
318+M["mSpidEvidenceShare"] = pct(meta["share_evidence_found"])
319+M["mSpidConfMedian"] = num(meta["confidence_quantiles"]["0.5"], 1)
320+M["mSpidDbDate"] = meta["db_mtime"][:10]
321+M["mSpidStrata"] = str(len(meta["strata_allocation"]))
322+
323+# ---------------------------------------------------------------- hygiene summary (r3k_13)
324+hy = load("results/r3k_v2/hygiene_summary.json")
325+M["mHygieneConfirmedMonthly"] = str(hy["verdicts_monthly"]["confirmed"])
326+M["mHygieneConfirmedFY"] = str(hy["verdicts_firm_year"]["confirmed"])
327+
328+
329+# ---------------------------------------------------------------- winsorized rows of results.json v2 (r3k_15)
330+rv = load("results/r3k_v2/results.json")
331+so = rv["sorts"]
332+M["mriskFullW"] = signed(100 * so["AI_RISK_EW_w"]["Q5_minus_Q1"])
333+M["mriskFullWT"] = num(so["AI_RISK_EW_w"]["t_stat"])
334+M["mriskFullWTplain"] = num(so["AI_RISK_EW_w"]["t_plain"])
335+M["mriskFull"] = signed(100 * so["AI_RISK_EW"]["Q5_minus_Q1"])
336+M["mriskFullT"] = num(so["AI_RISK_EW"]["t_stat"])
337+M["mriskTopOnePct"] = signed(100 * so["AI_RISK_EW"]["top1pct_contribution"])
338+M["mtenkFullW"] = signed(100 * so["AI_10k_EW_w"]["Q5_minus_Q1"])
339+M["mtenkFullWT"] = num(so["AI_10k_EW_w"]["t_stat"])
340+M["mformwFullWsort"] = signed(100 * so["AI_formw_EW_w"]["Q5_minus_Q1"])
341+M["mformwFullWsortT"] = num(so["AI_formw_EW_w"]["t_stat"])
342+M["mcyberFullW"] = signed(100 * so["CYBER_EW_w"]["Q5_minus_Q1"])
343+M["mcyberFullWT"] = num(so["CYBER_EW_w"]["t_stat"])
344+M["mcomposTopOnePctW"] = signed(100 * so["AI_COMPOS_EW_w"].get("top1pct_contribution", float("nan")))
345+sp = rv["subperiod_spread"]
346+for key, name in (("AI_10k", "tenk"), ("AI_RISK", "risk"), ("CYBER", "cyber"), ("AI_formw", "formw"), ("AI_PC1", "pca")):
347+ blk = sp[key]
348+ for per in ("pre", "post"):
349+ w = blk.get(per + "_w")
350+ if w:
351+ M[f"m{name}{per.capitalize()}Wsp"] = signed(100 * w["mean"])
352+ M[f"m{name}{per.capitalize()}WspT"] = num(w["t"])
353+g2 = rv["groups"]
354+for key, name in (("large_pct42", "largeFortyTwo"), ("small_pct42", "smallFiftyEight")):
355+ blk = g2[key]
356+ M[f"m{name}N"] = thousands(blk["n"])
357+ M[f"m{name}Firms"] = thousands(blk["firms"])
358+ for per in ("pre", "post", "full"):
359+ w = blk.get(per + "_w")
360+ if w:
361+ M[f"m{name}{per.capitalize()}W"] = signed(100 * w["mean"])
362+ M[f"m{name}{per.capitalize()}WT"] = num(w["t"])
363+ if "post_loo_w" in blk:
364+ M[f"m{name}PostExTwentyFourW"] = signed(100 * blk["post_loo_w"]["2024"])
365+ff = r2["filing_type"]["fm_forms_w"]["horse_race"]
366+for key, name in (("z_AI_10k", "TenK"), ("z_AI_10q", "TenQ"), ("z_AI_8k", "EightK")):
367+ if key in ff:
368+ M[f"mhr{name}W"] = signed(ff[key]["slope_pct"], 2)
369+ M[f"mhr{name}WT"] = num(ff[key]["t"])
370+en = r2["entrants"]
371+if "pre_w" in en["seasoned"]:
372+ M["mseasonedPreW"] = signed(100 * en["seasoned"]["pre_w"]["mean"])
373+ M["mseasonedPostW"] = signed(100 * en["seasoned"]["post_w"]["mean"]) if "post_w" in en["seasoned"] else ""
374+if "entrant_minus_seasoned_fwd_w_pct" in en:
375+ M["mentrantPremW"] = signed(en["entrant_minus_seasoned_fwd_w_pct"])
376+for k in ("entrant_minus_seasoned_fwd_pct",):
377+ if k in en: M["mentrantPremRaw"] = signed(en[k])
378+
379+# ---------------------------------------------------------------- cohort sizes, attrition 2019, 2021 split, contiguous block
380+nby = rv["n_by_year"]
381+M["mnObsFirstCohort"] = thousands(nby[min(nby)])
382+M["mnObsLastCohort"] = thousands(nby[max(nby)])
383+piv = sv["attrition_pivot_absent_share"]
384+M["mattritionSmallNineteen"] = pct(piv["20190531"]["small"], 0)
385+M["mattritionLargeNineteen"] = pct(piv["20190531"]["large"], 0)
386+M["mattritionSmallTwentyFour"] = pct(piv["20241206"]["small"], 0)
387+M["mattritionLargeTwentyFour"] = pct(piv["20241206"]["large"], 0)
388+cw = r2["cohorts_w"]
389+cs = cw.get("cohort_spreads_pct", cw)
390+M["mcohortTwentyOneW"] = signed(cs["2021"]) if isinstance(cs.get("2021"), (int, float)) else ""
391+br = r2["breaks"]
392+alt = br.get("split_2021")
393+if alt:
394+ M["mbreakTwentyOnePreW"] = signed(100 * alt["pre_w"]["mean"])
395+ M["mbreakTwentyOnePreWT"] = num(alt["pre_w"]["t"])
396+ M["mbreakTwentyOnePostW"] = signed(100 * alt["post_w"]["mean"])
397+ M["mbreakTwentyOnePostWT"] = num(alt["post_w"]["t"])
398+ pdw = alt["pooled_diff_w"]
399+ M["mbreakTwentyOneDiffW"] = signed(pdw["coef_pct"])
400+ M["mbreakTwentyOneDiffWT"] = num(pdw["t_firm"])
401+ M["mbreakTwentyOneDiffWTtw"] = num(pdw["t_twoway"])
402+by = M["mContigBestYear"]
403+M["mContigBestBlock"] = f"{by}--{int(by) + 2}"
404+
405+# ---------------------------------------------------------------- N1 corpus macros re-emitted with letter-only names
406+# results/n1/corpus_macros.tex (code/n1/n1_01_corpus_counts.py) uses digits in macro names (\mnFilings10K),
407+# which LaTeX cannot parse; the same values are re-emitted here as \mnFilingsTenK etc.
408+import re as _re
409+_digit_map = {"10K": "TenK", "10Q": "TenQ", "8K": "EightK"}
410+for line in (ROOT / "results/n1/corpus_macros.tex").read_text().splitlines():
411+ m = _re.match(r"\\newcommand\{\\(\w+)\}\{(.*)\}$", line.strip())
412+ if not m:
413+ continue
414+ name, val = m.group(1), m.group(2)
415+ for d, w in _digit_map.items():
416+ name = name.replace(d, w)
417+ if name not in M and name not in ("mnFilingsCorpus", "mnIssuersCorpus"):
418+ M[name] = val
419+
420+# ---------------------------------------------------------------- write
421+M = {k: v for k, v in M.items() if v not in ("", None)}
422+bad = [k for k in M if not k.isalpha()]
423+assert not bad, f"macro names must be letters only: {bad}"
424+# never redefine a macro that tables/macros.tex (Agent 9 / r3k_20) already provides: theirs wins
425+import re as _re2
426+_theirs = set(_re2.findall(r"\\newcommand\{\\(\w+)\}", (ROOT / "paper/tables/macros.tex").read_text()))
427+_dup = sorted(k for k in M if k in _theirs)
428+if _dup:
429+ print("skipping names already defined in tables/macros.tex:", _dup)
430+M = {k: v for k, v in M.items() if k not in _theirs}
431+lines = ["% Generated by scripts/stageE_writing_macros.py (Stage E, Agent 10) — do not edit by hand.",
432+ "% Sources: results/n1/*, results/r3k_v2/*, results/r3k/* (committed baseline 3cf7560 for the mvone* BEFORE values)."]
433+for k, v in M.items():
434+ lines.append(f"\\newcommand{{\\{k}}}{{{v}}}")
435+OUT.write_text("\n".join(lines) + "\n")
436+print(f"wrote {OUT.relative_to(ROOT)} with {len(M)} macros")
437
Menu

Theme