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 41 % --- Figures ---
42 42 \usepackage{graphicx}
43 −\graphicspath{{figures_r3k/}}
43 +\graphicspath{{figures_r3k/}{../figures/r3k_v2/}}
44 44 \usepackage{adjustbox}
45 45 \usepackage{subcaption}
46 46 \usepackage{float}
@@ -56,8 +56,11 @@
56 56 ]{hyperref}
57 57 \hypersetup{
58 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 65 % --- Bibliography ---
63 66 \usepackage[authoryear,round,semicolon]{natbib}
@@ -92,6 +95,20 @@
92 95 \sisetup{group-minimum-digits=4, group-separator={,}}
93 96 \newcommand{\tstat}[1]{{\footnotesize(\textit{t}=#1)}}
94 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 113 % --- Misc ---
97 114 \setlength{\emergencystretch}{3em}
@@ -103,38 +120,28 @@
103 120 % ============================================================================
104 121 \newcommand{\WPnumber}{0002}
105 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 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}
108 −\newcommand{\WPversion}{2.0 (Russell 3000 extension)}
128 +\newcommand{\WPdate}{November 2026}
129 +\newcommand{\WPversion}{2.1 (Russell 3000 extension, revised after peer review)}
109 130 \newcommand{\WPfunding}{Research funded by the FIRC (Fonds institutionnel de recherche et de création), Université du Québec en Outaouais.}
110 131 \newcommand{\WPabstract}{%
111 132 \begingroup\singlespacing
112 −We measure artificial-intelligence (AI) engagement in the SEC filings of some 2,500 U.S.\ listed
113 −firms---the current S\&P~500 constituents and the holdings of the iShares Russell~3000 ETF---by
114 −embedding every passage of their Form~10-K, 10-Q and 8-K filings of 2010--2026 with a sentence
115 −transformer and scoring it against curated AI anchor concepts (internal adoption, infrastructure
116 −exposure, product-disruption risk). The signal of filing year $Y$ is linked to the forward
117 −twelve-month return from July of $Y{+}1$, so that it predates the holding period by at least six
118 −months. The panel of \mnObs{} firm-years (\mnFirms{} firms) extends our earlier S\&P~500 study,
119 −which rested on current reports only, to annual and quarterly reports and to the mid- and
120 −small-capitalization firms that make up most of the Russell~3000, and it adds a second,
121 −independent measure of AI engagement: the AI, data-center, cloud and automation projects that a
122 −large-language-model reader extracts from the same filings. Three results stand out. First, in the
123 −broad universe AI language carries a modest but persistent cross-sectional premium over the whole
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.
133 +We measure artificial-intelligence (AI) language in the Form~10-K, 10-Q and 8-K filings of
134 +\mnFirms{} U.S.\ firms (current S\&P~500 members and iShares Russell~3000 holdings, 2010--2026)
135 +by sentence-transformer similarity to anchor passages, and relate the filing-year signal to
136 +look-ahead-free forward returns over fifteen formation cohorts (\mnObs{} firm-years). On
137 +winsorized returns the equal-weighted top-minus-bottom quintile spread is \mspreadFullW{}
138 +percentage points per year (95\% CI \mspreadFullWCI) and the Fama--MacBeth slope \mfmComposW{}
139 +points per standard deviation (CI \mfmComposWCI): a modest premium, robust in regression form,
140 +fragile in sort form, and conditional on survival to October 2026. A post-ChatGPT increase
141 +appears only among the largest thousand firms and in the 2024 cohort; the large-versus-small
142 +contrast is insignificant (\mcontrastLarge{} points, $t=\mcontrastLargeT$) and the event-study
143 +gradient depends on the benchmark. Disclosed AI projects extracted by a language model show no
144 +detectable premium (minimum detectable effect \mspidAnyFirmFullMDE{} points).
138 145 \par\endgroup
139 146 }
140 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 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 174 \input{tables/macros}
175 +\input{sections/macros_stageE}
164 176 \begin{document}
165 177
166 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 195 % --- Appendix ---
184 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 200 \end{document}
modified paper/references.bib +7 −6
@@ -645,21 +645,22 @@
645 645 }
646 646
647 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 649 title = {{Do Firms' Stock Prices Respond to AI-related Discussions? Evidence from Earnings Calls}},
650 650 year = {2026},
651 651 doi = {10.2139/ssrn.7482259},
652 − institution = {Elsevier BV},
652 + institution = {Hong Kong Institute for Monetary and Financial Research (HKIMR) / SSRN},
653 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 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 659 title = {{Generative AI and Firm Values}},
660 660 year = {2026},
661 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 666 @techreport{song2026productexposure,
@@ -687,7 +688,7 @@
687 688 title = {{Talk is cheap, green is hard: The effect of AI-washing on corporate energy transition}},
688 689 year = {2026},
689 690 doi = {10.1016/j.iref.2026.105756},
690 − journal = {International Review of Economics &amp; Finance},
691 + journal = {International Review of Economics \& Finance},
691 692 volume = {111},
692 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