@@ -4,6 +4,140 @@ All notable changes to StatsPAI will be documented in this file.
44
55## [ Unreleased]
66
7+ ## [ 1.15.2] — 2026-05-17
8+
9+ ### Headline
10+
11+ Patch release on top of v1.15.1. ** No estimator numerical paths change.**
12+ Three independent hardening tracks land together:
13+
14+ 1 . ** Agent-native infrastructure** — ` sp.agent.mcp_server ` is now strict-
15+ JSON-clean over the wire (no ` NaN ` / ` Infinity ` literals reaching
16+ Claude Desktop / RFC 8259 parsers), agent schema metadata extraction
17+ is more complete, and a new ` text ` extra makes
18+ ` sentence-transformers ` an explicit opt-in for the v1.6 ` causal_text `
19+ surface instead of a soft import surprise.
20+ 2 . ** ` sp.replicate ` dual-track** — Card (1995), Abadie-Diamond-
21+ Hainmueller (2010), Lalonde (1986) / DW (1999), and Lee (2008)
22+ replications are promoted from single-track stubs to full
23+ ** classic + modern** recipes that ship with the original public-
24+ domain CSVs and pinned golden numbers.
25+ 3 . ** Release packaging** — wheel smoke tests now ** fail loudly** on
26+ import error, ` py.typed ` ships in the wheel for downstream
27+ ` mypy --strict ` consumers, the result ` _repr_html_ ` path escapes
28+ user-controlled cell content (notebook XSS-safety), and the
29+ ` formulaic ` dependency that ` sp.spatial ` parses formulas with is
30+ declared explicitly instead of relying on ` linearmodels ` 's
31+ transitive resolution.
32+
33+ ### Added — ` text ` optional extra
34+
35+ - New ` [project.optional-dependencies] text = ["sentence-transformers>=2.2.0"] `
36+ in [ ` pyproject.toml ` ] ( pyproject.toml ) . The v1.6 ` sp.causal_text ` MVP
37+ used to lazy-import ` sentence_transformers ` and raise an opaque
38+ ` ImportError ` on first call. ` pip install statspai[text] ` now wires
39+ the dependency explicitly; the lazy import still triggers a clear
40+ pointer to the extra when missing.
41+
42+ ### Added — ` sp.replicate ` dual-track guides for four canonical papers
43+
44+ - ** Card (1995)** — proximity-to-college IV for returns to schooling.
45+ - ** Abadie, Diamond & Hainmueller (2010)** — California Proposition 99
46+ synthetic-control.
47+ - ** Lalonde (1986) / Dehejia-Wahba (1999)** — NSW + PSID-1 (MatchIt
48+ subset, ` n=614 ` ) propensity-score matching.
49+ - ** Lee (2008)** — US Senate RD (` n=1390 ` ) bandwidth-selected jump.
50+
51+ Each entry now ships a ** classic track** (the estimator the paper
52+ used: 2SLS for Card, weighted synthetic-control for Abadie, naive
53+ OLS / adjusted OLS / 1:1 NN PSM for Lalonde, local-linear CCT RD
54+ for Lee) ** and a modern track** (DML PLR + entropy balancing,
55+ bias-corrected robust RD, multi-method synth-compare). Real CSVs
56+ land under [ ` src/statspai/datasets/data/ ` ] ( src/statspai/datasets/data/ )
57+ (` card_1995.csv ` , ` california_prop99.csv ` , ` lalonde_matchit.csv ` ,
58+ ` lee_2008_senate.csv ` ) and ` sp.datasets.nsw_lalonde ` /
59+ ` sp.datasets.lee_2008_senate ` gain a ` simulated=False ` real-data
60+ branch with published-paper benchmarks exposed via ` df.attrs ` . The
61+ Lalonde classic track now reproduces DW (1999) Table 3-4 within a
62+ $5 drift tolerance; the Lee CCT track returns Conv ` 7.414 ` and
63+ bias-corrected robust ` 7.507 ` (SE ` 1.741 ` , ` h=17.754 ` ), matching the
64+ R ` rdrobust ` reference. All 13 BibTeX keys cited across the four
65+ entries are verified in [ ` paper.bib ` ] ( paper.bib ) .
66+
67+ ### Fixed — MCP wire format is strict-JSON-clean
68+
69+ - [ ` sp.agent.mcp_server ` ] ( src/statspai/agent/mcp_server.py ) used to
70+ serialise responses with ` json.dumps(..., default=_json_default) ` ,
71+ which ** does not** intercept native Python ` float('nan') ` /
72+ ` float('inf') ` — those become the non-standard literals ` NaN ` /
73+ ` Infinity ` in the JSON output, which RFC 8259 parsers (including
74+ Claude Desktop's ` JSON.parse ` ) reject with errors like
75+ ` "No number after minus sign" ` . Responses now pass through
76+ ` _clean_floats ` (recursively replaces ` NaN ` / ` ±Infinity ` with
77+ ` null ` across ` dict ` / ` list ` / ` tuple ` containers) and serialise
78+ with ` allow_nan=False ` , so the server can never emit a JSON token a
79+ strict parser refuses. Covered by 273-line regression suite
80+ [ ` tests/test_mcp_nan_inf.py ` ] ( tests/test_mcp_nan_inf.py ) .
81+ - Agent schema metadata extraction (` sp.function_schema ` ,
82+ ` sp.describe_function ` ) now surfaces more signature detail for
83+ registry entries built from auto-introspection.
84+ - Stability-tier audit (` scripts/stability_audit.py ` ) accounts for
85+ evidence files more precisely; new
86+ [ ` tests/test_agent_schema.py ` ] ( tests/test_agent_schema.py ) locks
87+ the schema metadata fields agents rely on.
88+
89+ ### Fixed — result HTML escaping (notebook XSS-safety)
90+
91+ - [ ` CausalResult._repr_html_ ` ] ( src/statspai/core/results.py ) (and the
92+ surrounding rich-display helpers) now route every user-derived cell
93+ through ` html.escape ` . Previously, any string column whose contents
94+ contained ` < ` / ` > ` / ` & ` / ` " ` would interpolate raw into the
95+ rendered HTML, opening a path for notebook XSS when a result was
96+ displayed in Jupyter / VS Code / nbviewer. New regression test:
97+ [ ` tests/test_results_html_escape.py ` ] ( tests/test_results_html_escape.py ) .
98+
99+ ### Fixed — release packaging hygiene
100+
101+ - ` pyproject.toml ` bumps the build requirement to
102+ ` setuptools>=77.0.0 ` and migrates ` license = {text = "MIT"} ` to the
103+ modern PEP 639 ` license = "MIT" ` + ` license-files = ["LICENSE"] `
104+ pair. Drops the deprecated ` License :: OSI Approved :: ... `
105+ classifier path implicitly.
106+ - [ ` MANIFEST.in ` ] ( MANIFEST.in ) now includes ` src/statspai/py.typed `
107+ and the sdist test fixtures so ` pip install --no-binary :all: ` and
108+ ` mypy --strict ` both behave correctly on the published artifacts.
109+ - [ ` .github/workflows/build-wheels.yml ` ] ( .github/workflows/build-wheels.yml )
110+ and [ ` .github/workflows/ci-cd.yml ` ] ( .github/workflows/ci-cd.yml ) :
111+ wheel smoke tests now ** fail the job** on ` ImportError ` instead of
112+ swallowing it as a warning. Releases that silently ship a broken
113+ wheel are no longer possible from a green CI run.
114+ - New explicit dependency ` formulaic>=0.6.0 ` in ` dependencies `
115+ (` sp.spatial.* ` parses Wilkinson formulas through it; relying on
116+ ` linearmodels ` 's transitive resolution broke when downstream users
117+ pinned older ` linearmodels ` ).
118+
119+ ### Docs
120+
121+ - JOSS submission [ ` paper.md ` ] ( paper.md ) is rewritten for the Scott
122+ Rozelle review pass — tighter scope statement, cleaner schema
123+ description, explicit AI-use disclosure, 12 May 2026 submission
124+ date. Cited bibliography entries in [ ` paper.bib ` ] ( paper.bib ) are
125+ refreshed to match.
126+ - README / README\_ CN add the hero banner image
127+ ([ ` docs/logo/readme-1.png ` ] ( docs/logo/readme-1.png ) ).
128+ - Track-C performance comparison table
129+ ([ ` tests/perf/results/perf_table.tex ` ] ( tests/perf/results/perf_table.tex ) )
130+ switches to ` \scriptsize ` with package-name macros and a
131+ direction-aware "Winner" column; log-log figure regenerated to match.
132+
133+ ### Internal
134+
135+ - Perf-benchmark harness factored — new
136+ [ ` tests/perf/_common.py ` ] ( tests/perf/_common.py ) shared utilities;
137+ ` tests/perf/05_feols_jax_bootstrap_bench.py ` rewritten on top.
138+ - Full-suite validation snapshot refreshed in
139+ [ ` test_results_full_suite.md ` ] ( test_results_full_suite.md ) .
140+
7141## [ 1.15.1] — 2026-05-07
8142
9143### Headline
0 commit comments