You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
| **The 4.0 migration guide's code blocks are not snippet-executed.** `tests/test_doc_snippets.py` discovers a hardcoded list of `.rst` files and only `.. code-block:: python` / RST `::` bodies, so `docs/migration-4.0.md` gets no coverage. Deliberate: the guide is a MIXED document - most "after" examples (the renames, `results.att`) run on the current release, but the `field-flip` and `df-convention-flip` examples describe 4.0 behaviour that cannot run until 4.0, so a blanket execution lane would fail by construction. Closing the gap fully means a markdown-fence extractor plus a skip-marker convention for the future-API blocks - a harness change, out of scope for a docs PR. What IS gated: the appendix's ledger parity (`test_migration_guide_*`), which pins the row set and every mechanically checkable cell; and, since the first local review found all three merge examples carrying invalid keywords, `test_migration_guide_examples_bind_to_real_signatures`, which ast-parses the guide's python blocks and asserts every constructor/`fit()` keyword exists on the target signature. That is signature binding, NOT execution - it deliberately skips calls whose owner it cannot resolve (e.g. `results.aggregate(...)`), and it cannot catch a wrong VALUE or a wrong sequence of calls. The hand-written `Fix` prose remains unverifiable by any available means. | `docs/migration-4.0.md`, `tests/test_doc_snippets.py` | Phase 4 / 2026-08-09 |
130
131
|**MultiPeriodDiD deprecation shim loses static constructor-arg checking (3.9 window).** The M-010 shim is `__init__(*args, **kwargs)` + an import-time `__signature__` mirror of DiD's constructor: runtime introspection (get_params/set_params, `inspect.signature`) and eager validation are fully preserved, but static type checkers / IDEs cannot check constructor arguments for the deprecated class until its 4.0 removal. Accepted: the alternative (hand-mirroring ~20 parameters) is a drift magnet on a class with one minor version of remaining life. |`diff_diff/estimators.py`| 3(a) / 2026-08-07 |
131
132
|**DCDH `sklearn.base.clone` param-identity failure won't-fix.**`ChaisemartinDHaultfoeuille._validate_paths_of_interest` unconditionally canonicalizes `paths_of_interest` into a fresh `List[Tuple[int, ...]]`, so sklearn `clone()`'s post-construction `param1 is param2` identity check fails for configured instances - a pre-existing normalization the BaseEstimator mixin PR documented rather than changed (get_params/set_params signatures are clone-compatible; the dependency-free `cls(**est.get_params())` config-equality contract is the enforced one, `tests/test_base_estimator.py`). Fixing would mean returning the caller's raw object from a validator whose job is canonicalization. |`chaisemartin_dhaultfoeuille.py`| mixin PR / 2026-08-01 |
132
133
|**scikit-learn stays out of dev deps; clone-identity tests remain importorskip-only.** The sklearn-`clone()` round-trip tests (`test_base_estimator.py`, had/rdd/cic suites) run only where scikit-learn happens to be installed - deliberate, matching the numpy/pandas/scipy-only dependency posture; the always-running contract is the dependency-free re-instantiation config-equality test. |`tests/test_base_estimator.py`| mixin PR / 2026-08-01 |
| Committed `fixest::feols` event-study golden for TWFE `event_study=True` (within + pooled specs, unbalanced + covariate panels, matched CR1 cluster convention, per-period effects + vcov block) - the in-suite gates are shared-core cross-checks (TWFE-within == MPD-absorb, pooled == MPD bit-exact), so a defect common to the shared core would pass; the live-R harness (`benchmarks/R/benchmark_multiperiod.R`, `feols(y ~ treated * time_f \| unit)`) validated the within design in `docs/benchmarks.rst` but is not a committed regression test - follow the `fixest_did_twfe_golden.json` committed-golden pattern (pytest.skip when absent) |`tests/test_fixest_did_twfe_parity.py`, `benchmarks/R/`| 3(a) R2 | Mid | Medium |
80
80
| Type-blind `n_bootstrap` acceptance in already-validated estimators - HAD bool (`isinstance(..., int)` passes `True`, runs as 1 replicate), dCDH bool+float (its bare `< 0` check passes both `True` and `2.5`), TROP float (`2.5` passes the `>= 2` floor), SyntheticDiD float under all three variance methods + bool/negative under jackknife (its floor check is skipped there) - align these local checks with the `utils.validate_n_bootstrap` type guard (M-081 kept them out of the sweep: it scoped to previously-UNvalidated estimators only) |`diff_diff/had.py`, `diff_diff/chaisemartin_dhaultfoeuille.py`, `diff_diff/trop.py`, `diff_diff/synthetic_did.py`| 2(d) PR-B | Quick | Low |
81
-
| M-020-era CS fit-time `aggregate=` teachings persist in troubleshooting.rst (:215/:241/:244) and choosing_estimator.rst (:243) - CS examples still fit with the deprecated kwarg; migrate to post-fit `results.aggregate('event_study')` (the two HAD examples in the same file were migrated with M-027) |`docs/troubleshooting.rst`| 2(b) PR-4 | Quick | Low |
81
+
| Fit-time `aggregate=` teachings persist across the docs and tutorials (M-020 family, removed at 4.0); migrate to post-fit `results.aggregate(...)`. Re-scoped 2026-08-09 while shipping the migration guide - this is NOT a quick sweep. Narrative docs: `troubleshooting.rst:216`/`:245` both sit on the `n_bootstrap=999` fit at `:213`, and post-fit event-study aggregation **raises `NotImplementedError` on a bootstrapped fit** (`staggered_results.py:323`), so those two need a decision about what to teach before any edit; `choosing_estimator.rst:252` and `python_comparison.rst:416` use the analytical default and can migrate freely; `r_comparison.rst:119-127` needs its `results.event_study_effects`/`.group_effects` reads rebound to the `aggregate()` return values in the same edit (the fields stay `None` after post-fit aggregation). API pages: `docs/api/triple_diff.rst:56`, plus `business_report.rst:77` and `diagnostic_report.rst:59`, which **cannot** migrate today because both report consumers read the raw `event_study_effects` field; `had.rst:164` and `continuous_did.rst:137` are prose references only. Tutorials: **28 executable code-cell sites across 9 notebooks** (`02_staggered_did` 7, `09_real_world_examples` 6, `16_survey_did` 6, `26_composition_drift_calibration` 3, `21_had_pretest_workflow` 2, and one each in `08_triple_diff`, `16_wooldridge_etwfe`, `17_brand_awareness_survey`, `24_staggered_vs_collapsed_power`) - all nbmake-executed; `14_continuous_did` and `15_efficient_did` match only in markdown prose | `docs/troubleshooting.rst`, `docs/choosing_estimator.rst`, `docs/python_comparison.rst`, `docs/r_comparison.rst`, `docs/api/*.rst`, `docs/tutorials/*.ipynb` | 2(b) PR-4 | Heavy | Medium |
82
82
| Evaluate adding the `BaseEstimator` param surface (get_params/set_params) to the exported classes that never had it - `PowerAnalysis`, `LinearRegression`, `BusinessReport`, `DiagnosticReport`, `TWFEWeightsResult` (a NEW public surface, deliberately out of the 2(c)-i pure-refactor scope; `LinearRegression` is the one `fit`-bearing class excluded from the contract suite's roster-completeness test). |`diff_diff/linalg.py`, `diff_diff/power.py`| mixin PR | Mid | Low |
83
83
| Tighten the mypy suppressions that back the enforced-zero posture: burn down `prep_dgp`'s per-module `[index]` override (needs a None-vs-array restructure that preserves the seeded RNG stream), and evaluate re-enabling the globally disabled codes (`arg-type`, `return-value`, `var-annotated`, `assignment`) one at a time — `assignment` alone hid several real annotation drifts found during the 2026-07 triage. |`pyproject.toml``[tool.mypy]`, `diff_diff/prep_dgp.py`| lint-CI | Mid | Low |
84
84
| MMM interop follow-up: Meridian `roi_calibration_period` mask builder - accept the MMM's time index + channel order and emit the boolean `(n_media_times, n_media_channels)` mask so `.to_code()` scopes the prior to the experiment window automatically (today the caller passes a mask expression / `full_model_window=True`). |`diff_diff/mmm.py`| mmm-interop | Quick | Low |
0 commit comments