synthesis-econ 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. synthesis_econ-0.2.0/PKG-INFO +237 -0
  2. synthesis_econ-0.2.0/README.md +208 -0
  3. synthesis_econ-0.2.0/setup.cfg +4 -0
  4. synthesis_econ-0.2.0/setup.py +34 -0
  5. synthesis_econ-0.2.0/synthesis/__init__.py +7 -0
  6. synthesis_econ-0.2.0/synthesis/alignment/__init__.py +4 -0
  7. synthesis_econ-0.2.0/synthesis/alignment/meta.py +184 -0
  8. synthesis_econ-0.2.0/synthesis/alignment/qqa.py +165 -0
  9. synthesis_econ-0.2.0/synthesis/cli.py +208 -0
  10. synthesis_econ-0.2.0/synthesis/data/__init__.py +4 -0
  11. synthesis_econ-0.2.0/synthesis/data/fred.py +134 -0
  12. synthesis_econ-0.2.0/synthesis/data/worldbank.py +114 -0
  13. synthesis_econ-0.2.0/synthesis/extraction/__init__.py +3 -0
  14. synthesis_econ-0.2.0/synthesis/extraction/claims.py +97 -0
  15. synthesis_econ-0.2.0/synthesis/report/__init__.py +4 -0
  16. synthesis_econ-0.2.0/synthesis/report/generate.py +121 -0
  17. synthesis_econ-0.2.0/synthesis/report/html.py +398 -0
  18. synthesis_econ-0.2.0/synthesis/retrieval/__init__.py +6 -0
  19. synthesis_econ-0.2.0/synthesis/retrieval/arxiv.py +55 -0
  20. synthesis_econ-0.2.0/synthesis/retrieval/dedup.py +45 -0
  21. synthesis_econ-0.2.0/synthesis/retrieval/nber.py +56 -0
  22. synthesis_econ-0.2.0/synthesis/retrieval/openalex.py +54 -0
  23. synthesis_econ-0.2.0/synthesis_econ.egg-info/PKG-INFO +237 -0
  24. synthesis_econ-0.2.0/synthesis_econ.egg-info/SOURCES.txt +26 -0
  25. synthesis_econ-0.2.0/synthesis_econ.egg-info/dependency_links.txt +1 -0
  26. synthesis_econ-0.2.0/synthesis_econ.egg-info/entry_points.txt +2 -0
  27. synthesis_econ-0.2.0/synthesis_econ.egg-info/requires.txt +7 -0
  28. synthesis_econ-0.2.0/synthesis_econ.egg-info/top_level.txt +1 -0
@@ -0,0 +1,237 @@
1
+ Metadata-Version: 2.4
2
+ Name: synthesis-econ
3
+ Version: 0.2.0
4
+ Summary: AI-powered economics research tool: QQA alignment of literature and empirical data.
5
+ Home-page: https://github.com/bsin-researcher/synthesis
6
+ Author: Blake Sinclair
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
11
+ Classifier: Intended Audience :: Science/Research
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: anthropic>=0.109.0
15
+ Requires-Dist: requests>=2.34.0
16
+ Requires-Dist: pandas>=2.0.0
17
+ Requires-Dist: plotly>=5.20.0
18
+ Requires-Dist: rich>=13.0.0
19
+ Requires-Dist: typer>=0.9.0
20
+ Requires-Dist: python-dotenv>=1.0.0
21
+ Dynamic: author
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: requires-dist
27
+ Dynamic: requires-python
28
+ Dynamic: summary
29
+
30
+ # Synthesis
31
+
32
+ **AI-powered economics research briefs combining qualitative literature and quantitative data.**
33
+
34
+ Synthesis retrieves papers from arXiv, OpenAlex, and NBER, extracts structured empirical claims using Claude, pulls FRED and World Bank time-series data, and scores theory against evidence using a novel **Quantitative-Qualitative Alignment (QQA)** method — all in a single command.
35
+
36
+ ```bash
37
+ synthesis "Does raising the minimum wage increase unemployment?"
38
+ ```
39
+
40
+ → Generates an 8-section PhD-level research brief with interactive charts and a gap matrix in under 2 minutes.
41
+
42
+ ---
43
+
44
+ ## What It Produces
45
+
46
+ - **Literature consensus** across 20+ papers with identified fault lines
47
+ - **Extracted claims** structured by variable, direction, methodology, geography, and confidence
48
+ - **Empirical data** from FRED (US macro) and World Bank (cross-country)
49
+ - **QQA alignment scores** — each theoretical claim scored against empirical data
50
+ - **Research gap matrix** — unstudied methodology × geography combinations
51
+ - **Suggested research directions** with identification strategies
52
+ - **Interactive HTML report** with Plotly charts (zoomable, hoverable)
53
+
54
+ ---
55
+
56
+ ## The Core Idea: QQA
57
+
58
+ Most research tools do one of two things: search literature (qualitative) or fetch data (quantitative). **Synthesis does both and aligns them.**
59
+
60
+ The Quantitative-Qualitative Alignment (QQA) method:
61
+ 1. Extracts structured claims from paper abstracts: `variable_a → variable_b | direction | methodology | geography | confidence`
62
+ 2. Fetches empirical data relevant to the question via Claude-identified FRED and World Bank series
63
+ 3. Scores each claim against the data: `strongly_supported / supported / neutral / contradicted / strongly_contradicted / insufficient_data`
64
+ 4. Identifies where the literature and data diverge — the most productive place for new research
65
+
66
+ ---
67
+
68
+ ## Quick Start
69
+
70
+ ```bash
71
+ pip install synthesis-econ
72
+ ```
73
+
74
+ Set your API keys (get them free):
75
+ - [Anthropic API key](https://console.anthropic.com/) — for Claude
76
+ - [FRED API key](https://fred.stlouisfed.org/docs/api/api_key.html) — for US macro data
77
+
78
+ ```bash
79
+ export ANTHROPIC_API_KEY='sk-ant-...'
80
+ export FRED_API_KEY='your-fred-key'
81
+ ```
82
+
83
+ Run a research brief:
84
+ ```bash
85
+ synthesis "Does immigration lower wages for native workers?"
86
+ synthesis "What is the effect of quantitative easing on inflation?"
87
+ synthesis "Do charter schools improve student outcomes?"
88
+ synthesis "Does foreign aid promote economic growth?"
89
+ ```
90
+
91
+ The HTML report opens automatically in your browser.
92
+
93
+ ---
94
+
95
+ ## Install from Source
96
+
97
+ ```bash
98
+ git clone https://github.com/blakesinclair/synthesis.git
99
+ cd synthesis
100
+ pip install -e .
101
+ ```
102
+
103
+ ---
104
+
105
+ ## How It Works
106
+
107
+ ```
108
+ Your question
109
+
110
+ ├── arXiv (economics: econ.GN, econ.EM, econ.LG, econ.TH, econ.HE, econ.IO)
111
+ ├── OpenAlex (250M+ peer-reviewed works)
112
+ └── NBER (64K+ working papers)
113
+
114
+
115
+ Claude extracts structured claims
116
+ (variable_a → variable_b, direction, methodology, geography, confidence)
117
+
118
+ ├── FRED (US macro time-series — series IDs chosen by Claude)
119
+ └── World Bank (cross-country indicators — chosen by Claude)
120
+
121
+
122
+ QQA Alignment Scoring
123
+ (theory vs. data, claim by claim)
124
+
125
+
126
+ Research Gap Matrix
127
+ (unstudied methodology × geography combinations)
128
+
129
+
130
+ 8-section research brief + interactive Plotly HTML report
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Example Output
136
+
137
+ **Question:** Does raising the minimum wage increase unemployment?
138
+
139
+ **Papers:** 24 (arXiv: 8, OpenAlex: 8, NBER: 8)
140
+ **Claims extracted:** 10
141
+ **FRED series:** Federal Minimum Wage, Unemployment Rate, Nonfarm Payrolls, Labor Force Participation
142
+ **World Bank series:** Unemployment, total (% of labor force)
143
+
144
+ > **Consensus:** No detectable disemployment effect from realistic minimum wage increases — a 138-change US DiD study and the 2022 German 22% hike both find employment essentially unchanged. Adjustment occurs on the *hours* margin, not headcount.
145
+ >
146
+ > **Key tension:** Structural search-friction models predict disemployment by construction; DiD studies find none. The reconciliation is a monopsony nonlinearity — disemployment appears only in competitive markets (German IV result), vanishes under labor-market concentration.
147
+ >
148
+ > **Top gap:** A US IV study interacting minimum wage changes with local labor-market concentration (HHI) — the German monopsony result has never been replicated on US data.
149
+
150
+ ---
151
+
152
+ ## Data Sources
153
+
154
+ | Source | Coverage | Key |
155
+ |--------|----------|-----|
156
+ | arXiv | Economics preprints (7 categories) | None required |
157
+ | OpenAlex | 250M+ peer-reviewed works | None required |
158
+ | NBER | 64K+ working papers | None required |
159
+ | FRED | 800K+ US macro time-series | Free API key |
160
+ | World Bank | Cross-country development indicators | None required |
161
+
162
+ ---
163
+
164
+ ## Options
165
+
166
+ ```
167
+ synthesis "question" [OPTIONS]
168
+
169
+ -o, --output TEXT Output directory [default: ./synthesis_output]
170
+ -p, --papers INT Max papers to retrieve [default: 24]
171
+ --no-fred Skip FRED data
172
+ --no-worldbank Skip World Bank data
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Requirements
178
+
179
+ - Python 3.10+
180
+ - Anthropic API key (uses Claude Opus 4.8 with adaptive thinking)
181
+ - FRED API key (free, optional but strongly recommended)
182
+
183
+ ```
184
+ anthropic>=0.109.0
185
+ requests>=2.34.0
186
+ pandas>=2.0.0
187
+ plotly>=5.20.0
188
+ rich>=13.0.0
189
+ typer>=0.9.0
190
+ python-dotenv>=1.0.0
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Roadmap
196
+
197
+ - [x] arXiv + OpenAlex + NBER retrieval
198
+ - [x] Claude-powered structured claim extraction (adaptive thinking)
199
+ - [x] FRED + World Bank empirical data (series chosen by Claude)
200
+ - [x] QQA alignment scoring
201
+ - [x] Research gap matrix
202
+ - [x] Interactive Plotly HTML report
203
+ - [ ] Semantic deduplication across sources (sentence-transformers)
204
+ - [ ] Numerical effect size extraction and meta-analytic pooling
205
+ - [ ] Formal QQA with confidence intervals
206
+ - [ ] Citation importance weighting (h-index, citation count)
207
+ - [ ] PDF and LaTeX export
208
+ - [ ] Benchmark vs Elicit, Consensus, Semantic Scholar
209
+
210
+ ---
211
+
212
+ ## Citation
213
+
214
+ If you use Synthesis in your research:
215
+
216
+ ```bibtex
217
+ @software{sinclair2026synthesis,
218
+ author = {Sinclair, Blake},
219
+ title = {Synthesis: AI-Powered Economics Research via Quantitative-Qualitative Alignment},
220
+ year = {2026},
221
+ url = {https://github.com/blakesinclair/synthesis},
222
+ }
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Contributing
228
+
229
+ Pull requests welcome. The highest-value open items are in the roadmap above.
230
+
231
+ To add a new data source, implement the pattern in `synthesis/data/` — a function `fetch_X(question: str, client: anthropic.Anthropic) -> list[DataSeries]` and wire it into `synthesis/cli.py`.
232
+
233
+ ---
234
+
235
+ ## License
236
+
237
+ MIT
@@ -0,0 +1,208 @@
1
+ # Synthesis
2
+
3
+ **AI-powered economics research briefs combining qualitative literature and quantitative data.**
4
+
5
+ Synthesis retrieves papers from arXiv, OpenAlex, and NBER, extracts structured empirical claims using Claude, pulls FRED and World Bank time-series data, and scores theory against evidence using a novel **Quantitative-Qualitative Alignment (QQA)** method — all in a single command.
6
+
7
+ ```bash
8
+ synthesis "Does raising the minimum wage increase unemployment?"
9
+ ```
10
+
11
+ → Generates an 8-section PhD-level research brief with interactive charts and a gap matrix in under 2 minutes.
12
+
13
+ ---
14
+
15
+ ## What It Produces
16
+
17
+ - **Literature consensus** across 20+ papers with identified fault lines
18
+ - **Extracted claims** structured by variable, direction, methodology, geography, and confidence
19
+ - **Empirical data** from FRED (US macro) and World Bank (cross-country)
20
+ - **QQA alignment scores** — each theoretical claim scored against empirical data
21
+ - **Research gap matrix** — unstudied methodology × geography combinations
22
+ - **Suggested research directions** with identification strategies
23
+ - **Interactive HTML report** with Plotly charts (zoomable, hoverable)
24
+
25
+ ---
26
+
27
+ ## The Core Idea: QQA
28
+
29
+ Most research tools do one of two things: search literature (qualitative) or fetch data (quantitative). **Synthesis does both and aligns them.**
30
+
31
+ The Quantitative-Qualitative Alignment (QQA) method:
32
+ 1. Extracts structured claims from paper abstracts: `variable_a → variable_b | direction | methodology | geography | confidence`
33
+ 2. Fetches empirical data relevant to the question via Claude-identified FRED and World Bank series
34
+ 3. Scores each claim against the data: `strongly_supported / supported / neutral / contradicted / strongly_contradicted / insufficient_data`
35
+ 4. Identifies where the literature and data diverge — the most productive place for new research
36
+
37
+ ---
38
+
39
+ ## Quick Start
40
+
41
+ ```bash
42
+ pip install synthesis-econ
43
+ ```
44
+
45
+ Set your API keys (get them free):
46
+ - [Anthropic API key](https://console.anthropic.com/) — for Claude
47
+ - [FRED API key](https://fred.stlouisfed.org/docs/api/api_key.html) — for US macro data
48
+
49
+ ```bash
50
+ export ANTHROPIC_API_KEY='sk-ant-...'
51
+ export FRED_API_KEY='your-fred-key'
52
+ ```
53
+
54
+ Run a research brief:
55
+ ```bash
56
+ synthesis "Does immigration lower wages for native workers?"
57
+ synthesis "What is the effect of quantitative easing on inflation?"
58
+ synthesis "Do charter schools improve student outcomes?"
59
+ synthesis "Does foreign aid promote economic growth?"
60
+ ```
61
+
62
+ The HTML report opens automatically in your browser.
63
+
64
+ ---
65
+
66
+ ## Install from Source
67
+
68
+ ```bash
69
+ git clone https://github.com/blakesinclair/synthesis.git
70
+ cd synthesis
71
+ pip install -e .
72
+ ```
73
+
74
+ ---
75
+
76
+ ## How It Works
77
+
78
+ ```
79
+ Your question
80
+
81
+ ├── arXiv (economics: econ.GN, econ.EM, econ.LG, econ.TH, econ.HE, econ.IO)
82
+ ├── OpenAlex (250M+ peer-reviewed works)
83
+ └── NBER (64K+ working papers)
84
+
85
+
86
+ Claude extracts structured claims
87
+ (variable_a → variable_b, direction, methodology, geography, confidence)
88
+
89
+ ├── FRED (US macro time-series — series IDs chosen by Claude)
90
+ └── World Bank (cross-country indicators — chosen by Claude)
91
+
92
+
93
+ QQA Alignment Scoring
94
+ (theory vs. data, claim by claim)
95
+
96
+
97
+ Research Gap Matrix
98
+ (unstudied methodology × geography combinations)
99
+
100
+
101
+ 8-section research brief + interactive Plotly HTML report
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Example Output
107
+
108
+ **Question:** Does raising the minimum wage increase unemployment?
109
+
110
+ **Papers:** 24 (arXiv: 8, OpenAlex: 8, NBER: 8)
111
+ **Claims extracted:** 10
112
+ **FRED series:** Federal Minimum Wage, Unemployment Rate, Nonfarm Payrolls, Labor Force Participation
113
+ **World Bank series:** Unemployment, total (% of labor force)
114
+
115
+ > **Consensus:** No detectable disemployment effect from realistic minimum wage increases — a 138-change US DiD study and the 2022 German 22% hike both find employment essentially unchanged. Adjustment occurs on the *hours* margin, not headcount.
116
+ >
117
+ > **Key tension:** Structural search-friction models predict disemployment by construction; DiD studies find none. The reconciliation is a monopsony nonlinearity — disemployment appears only in competitive markets (German IV result), vanishes under labor-market concentration.
118
+ >
119
+ > **Top gap:** A US IV study interacting minimum wage changes with local labor-market concentration (HHI) — the German monopsony result has never been replicated on US data.
120
+
121
+ ---
122
+
123
+ ## Data Sources
124
+
125
+ | Source | Coverage | Key |
126
+ |--------|----------|-----|
127
+ | arXiv | Economics preprints (7 categories) | None required |
128
+ | OpenAlex | 250M+ peer-reviewed works | None required |
129
+ | NBER | 64K+ working papers | None required |
130
+ | FRED | 800K+ US macro time-series | Free API key |
131
+ | World Bank | Cross-country development indicators | None required |
132
+
133
+ ---
134
+
135
+ ## Options
136
+
137
+ ```
138
+ synthesis "question" [OPTIONS]
139
+
140
+ -o, --output TEXT Output directory [default: ./synthesis_output]
141
+ -p, --papers INT Max papers to retrieve [default: 24]
142
+ --no-fred Skip FRED data
143
+ --no-worldbank Skip World Bank data
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Requirements
149
+
150
+ - Python 3.10+
151
+ - Anthropic API key (uses Claude Opus 4.8 with adaptive thinking)
152
+ - FRED API key (free, optional but strongly recommended)
153
+
154
+ ```
155
+ anthropic>=0.109.0
156
+ requests>=2.34.0
157
+ pandas>=2.0.0
158
+ plotly>=5.20.0
159
+ rich>=13.0.0
160
+ typer>=0.9.0
161
+ python-dotenv>=1.0.0
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Roadmap
167
+
168
+ - [x] arXiv + OpenAlex + NBER retrieval
169
+ - [x] Claude-powered structured claim extraction (adaptive thinking)
170
+ - [x] FRED + World Bank empirical data (series chosen by Claude)
171
+ - [x] QQA alignment scoring
172
+ - [x] Research gap matrix
173
+ - [x] Interactive Plotly HTML report
174
+ - [ ] Semantic deduplication across sources (sentence-transformers)
175
+ - [ ] Numerical effect size extraction and meta-analytic pooling
176
+ - [ ] Formal QQA with confidence intervals
177
+ - [ ] Citation importance weighting (h-index, citation count)
178
+ - [ ] PDF and LaTeX export
179
+ - [ ] Benchmark vs Elicit, Consensus, Semantic Scholar
180
+
181
+ ---
182
+
183
+ ## Citation
184
+
185
+ If you use Synthesis in your research:
186
+
187
+ ```bibtex
188
+ @software{sinclair2026synthesis,
189
+ author = {Sinclair, Blake},
190
+ title = {Synthesis: AI-Powered Economics Research via Quantitative-Qualitative Alignment},
191
+ year = {2026},
192
+ url = {https://github.com/blakesinclair/synthesis},
193
+ }
194
+ ```
195
+
196
+ ---
197
+
198
+ ## Contributing
199
+
200
+ Pull requests welcome. The highest-value open items are in the roadmap above.
201
+
202
+ To add a new data source, implement the pattern in `synthesis/data/` — a function `fetch_X(question: str, client: anthropic.Anthropic) -> list[DataSeries]` and wire it into `synthesis/cli.py`.
203
+
204
+ ---
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="synthesis-econ",
5
+ version="0.2.0",
6
+ author="Blake Sinclair",
7
+ description="AI-powered economics research tool: QQA alignment of literature and empirical data.",
8
+ long_description=open("README.md").read(),
9
+ long_description_content_type="text/markdown",
10
+ url="https://github.com/bsin-researcher/synthesis",
11
+ packages=find_packages(),
12
+ python_requires=">=3.10",
13
+ install_requires=[
14
+ "anthropic>=0.109.0",
15
+ "requests>=2.34.0",
16
+ "pandas>=2.0.0",
17
+ "plotly>=5.20.0",
18
+ "rich>=13.0.0",
19
+ "typer>=0.9.0",
20
+ "python-dotenv>=1.0.0",
21
+ ],
22
+ entry_points={
23
+ "console_scripts": [
24
+ "synthesis=synthesis.cli:main",
25
+ ],
26
+ },
27
+ classifiers=[
28
+ "Programming Language :: Python :: 3",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Operating System :: OS Independent",
31
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
32
+ "Intended Audience :: Science/Research",
33
+ ],
34
+ )
@@ -0,0 +1,7 @@
1
+ """
2
+ Synthesis — AI-powered economics research tool.
3
+ Combines qualitative literature analysis with quantitative empirical data.
4
+ """
5
+
6
+ __version__ = "0.2.0"
7
+ __author__ = "Blake Sinclair"
@@ -0,0 +1,4 @@
1
+ from .qqa import score_alignment, AlignmentResult
2
+ from .meta import pool_evidence, PooledEvidence
3
+
4
+ __all__ = ["score_alignment", "AlignmentResult", "pool_evidence", "PooledEvidence"]
@@ -0,0 +1,184 @@
1
+ """
2
+ Meta-analytic pooling for Synthesis.
3
+
4
+ Weights each extracted claim by methodology quality × confidence × log(citations+1),
5
+ then computes a pooled direction score and consensus statement.
6
+ """
7
+
8
+ import math
9
+ from dataclasses import dataclass
10
+ from synthesis.extraction.claims import Claim
11
+
12
+
13
+ # Evidence hierarchy — standard in meta-analysis
14
+ METHOD_WEIGHTS: dict[str, int] = {
15
+ "RCT": 5,
16
+ "IV": 4,
17
+ "DiD": 4,
18
+ "natural_experiment": 4,
19
+ "meta-analysis": 3,
20
+ "structural": 2,
21
+ "OLS": 2,
22
+ "survey": 1,
23
+ "theoretical": 1,
24
+ "other": 1,
25
+ }
26
+
27
+ CONFIDENCE_MULT: dict[str, float] = {
28
+ "high": 1.0,
29
+ "moderate": 0.6,
30
+ "low": 0.3,
31
+ "contested": 0.2,
32
+ }
33
+
34
+ DIRECTION_SCORE: dict[str, float] = {
35
+ "positive": 1.0,
36
+ "negative": -1.0,
37
+ "no_effect": 0.0,
38
+ "ambiguous": 0.0,
39
+ "nonlinear": 0.0,
40
+ }
41
+
42
+
43
+ @dataclass
44
+ class PooledEvidence:
45
+ # Core pooled estimate
46
+ pooled_direction: str # "positive" / "negative" / "no_effect" / "mixed"
47
+ weighted_score: float # -1.0 to +1.0
48
+ total_weight: float
49
+
50
+ # Direction breakdown
51
+ n_positive: int
52
+ n_negative: int
53
+ n_no_effect: int
54
+ n_ambiguous: int
55
+ n_claims: int
56
+
57
+ # Quality
58
+ high_confidence_count: int
59
+ top_methods: list[str] # top 3 methods by total weight
60
+
61
+ # Effect sizes (raw strings from extraction)
62
+ effect_sizes: list[str]
63
+
64
+ # Human-readable summary sentence
65
+ consensus_statement: str
66
+
67
+
68
+ def pool_evidence(claims: list[Claim]) -> PooledEvidence:
69
+ if not claims:
70
+ return PooledEvidence(
71
+ pooled_direction="insufficient_data", weighted_score=0.0,
72
+ total_weight=0.0, n_positive=0, n_negative=0, n_no_effect=0,
73
+ n_ambiguous=0, n_claims=0, high_confidence_count=0,
74
+ top_methods=[], effect_sizes=[], consensus_statement="No claims to pool.",
75
+ )
76
+
77
+ weighted_sum = 0.0
78
+ total_w = 0.0
79
+ direction_counts: dict[str, int] = {"positive": 0, "negative": 0,
80
+ "no_effect": 0, "ambiguous": 0, "nonlinear": 0}
81
+ method_weights: dict[str, float] = {}
82
+ high_conf = 0
83
+ effect_sizes: list[str] = []
84
+
85
+ for c in claims:
86
+ mw = METHOD_WEIGHTS.get(c.methodology, 1)
87
+ cw = CONFIDENCE_MULT.get(c.confidence, 0.3)
88
+ cite_w = math.log(getattr(c, "citations", 0) + 1) + 1 # +1 floor so uncited claims still count
89
+ weight = mw * cw * cite_w
90
+
91
+ d = c.direction if c.direction in DIRECTION_SCORE else "ambiguous"
92
+ direction_counts[d] = direction_counts.get(d, 0) + 1
93
+ weighted_sum += DIRECTION_SCORE[d] * weight
94
+ total_w += weight
95
+
96
+ method_weights[c.methodology] = method_weights.get(c.methodology, 0.0) + weight
97
+
98
+ if c.confidence == "high":
99
+ high_conf += 1
100
+
101
+ es = getattr(c, "effect_size", "")
102
+ if es and es not in ("unclear", "", "not reported", "N/A"):
103
+ effect_sizes.append(f"{c.paper_title[:40]}… — {es}")
104
+
105
+ norm_score = weighted_sum / total_w if total_w > 0 else 0.0
106
+
107
+ # Determine pooled direction
108
+ if abs(norm_score) < 0.15:
109
+ pooled_dir = "no_effect"
110
+ elif norm_score > 0:
111
+ pooled_dir = "positive"
112
+ else:
113
+ pooled_dir = "negative"
114
+
115
+ # Check for genuine disagreement
116
+ n_directional = direction_counts["positive"] + direction_counts["negative"]
117
+ if n_directional >= 2:
118
+ minority = min(direction_counts["positive"], direction_counts["negative"])
119
+ if minority / n_directional >= 0.35:
120
+ pooled_dir = "mixed"
121
+
122
+ top_methods = sorted(method_weights, key=method_weights.get, reverse=True)[:3]
123
+
124
+ consensus_statement = _build_statement(
125
+ pooled_dir, norm_score, direction_counts, high_conf, len(claims), top_methods
126
+ )
127
+
128
+ return PooledEvidence(
129
+ pooled_direction=pooled_dir,
130
+ weighted_score=round(norm_score, 3),
131
+ total_weight=round(total_w, 1),
132
+ n_positive=direction_counts["positive"],
133
+ n_negative=direction_counts["negative"],
134
+ n_no_effect=direction_counts["no_effect"],
135
+ n_ambiguous=direction_counts.get("ambiguous", 0) + direction_counts.get("nonlinear", 0),
136
+ n_claims=len(claims),
137
+ high_confidence_count=high_conf,
138
+ top_methods=top_methods,
139
+ effect_sizes=effect_sizes[:6],
140
+ consensus_statement=consensus_statement,
141
+ )
142
+
143
+
144
+ def _build_statement(
145
+ direction: str,
146
+ score: float,
147
+ counts: dict[str, int],
148
+ high_conf: int,
149
+ n: int,
150
+ methods: list[str],
151
+ ) -> str:
152
+ method_str = " + ".join(methods) if methods else "various methods"
153
+ conf_str = f"{high_conf} high-confidence" if high_conf else "no high-confidence"
154
+
155
+ if direction == "negative":
156
+ strength = "strongly" if score < -0.6 else "moderately"
157
+ return (
158
+ f"Pooled evidence ({conf_str} of {n} claims via {method_str}) "
159
+ f"{strength} supports a NEGATIVE effect (weighted score {score:+.2f}). "
160
+ f"{counts['positive']} claims find positive, {counts['negative']} negative, "
161
+ f"{counts['no_effect']} no effect."
162
+ )
163
+ elif direction == "positive":
164
+ strength = "strongly" if score > 0.6 else "moderately"
165
+ return (
166
+ f"Pooled evidence ({conf_str} of {n} claims via {method_str}) "
167
+ f"{strength} supports a POSITIVE effect (weighted score {score:+.2f}). "
168
+ f"{counts['positive']} claims find positive, {counts['negative']} negative, "
169
+ f"{counts['no_effect']} no effect."
170
+ )
171
+ elif direction == "no_effect":
172
+ return (
173
+ f"Pooled evidence ({conf_str} of {n} claims via {method_str}) "
174
+ f"finds NO CLEAR EFFECT (weighted score {score:+.2f}). "
175
+ f"{counts['no_effect']} claims find no effect, "
176
+ f"{counts['positive']} positive, {counts['negative']} negative."
177
+ )
178
+ else: # mixed
179
+ return (
180
+ f"Evidence is MIXED across {n} claims via {method_str} "
181
+ f"(weighted score {score:+.2f}, {conf_str} findings). "
182
+ f"{counts['positive']} positive vs {counts['negative']} negative — "
183
+ f"genuine disagreement in the literature."
184
+ )