Skip to content

feat(v4): item 9b of the 4.0 cut - retire the 5.0-window shims at 4.0 + placebo wrappers take the calendar-time contract (M-016, M-050..M-058, M-148, M-149, M-151, M-152 removed; M-156, M-157) - #885

Merged
igerber merged 1 commit into
mainfrom
feat/v4-pr9b-shim-retirement-placebo-time
Nov 1, 2026

Conversation

@igerber

@igerber igerber commented Nov 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Item 9b of the 4.0 cut - the 14 compatibility shims scheduled for a 5.0 removal are retired at 4.0 (user decision 2026-11-01: no 4.x debt). Rows [M-016] [M-050..M-058] [M-151] [M-152] flip to removed with deprecated_in: null: none of their FutureWarning shims had reached a release (they landed on main after the v3.13.0 tag), so the released 3.x names overall_att / overall_se / overall_t_stat / overall_p_value / overall_conf_int (nine results classes + DMLDiDResults; all nine ContinuousDiDResults spellings), EventStudyResults.period_effects and LWDiDResults.pvalue / .ci break at 4.0 without a warned release - stated plainly in the migration guide, the ledger notes and the spec amendments. Reading one is an AttributeError; the canonical quintet att / se / t_stat / p_value / conf_int is the only vocabulary; the __setstate__ legacy-key migration stays so 3.x pickles load. [M-148] [M-149] (SyntheticControl(inner_max_iter=, inner_min_decrease=)) flip to removed with deprecated_in: 3.13.0 - that DeprecationWarning did ship in 3.13.0 - so passing either is a TypeError and standardize / alpha / seed / v_cv_t0 move two positional slots left. diff_diff/_deprecation.py is reduced to the pickle-migration (LEGACY_OVERALL_QUINTET, migrate_renamed_state_keys, migrate_event_study_reference_sentinel) and required-arg (NOT_SUPPLIED, require_arg) helpers; deprecated_field_property, resolve_renamed_kwarg, warn_deprecated_kwarg, deprecated_kwarg_message are deleted with their last consumers. With the descriptors gone, assigning a legacy name to a results instance creates a shadow attribute instead of raising (non-slotted dataclasses) - recorded in the tests and the migration guide; the naming guard's consumer sweep is the in-repo defense.
  • Placebo wrappers take the calendar-time contract (TODO.md's 2(c)-ii redesign row closed; new behavior rows [M-156] [M-157]). run_placebo_test / run_all_placebo_tests take time= as the calendar period column only (spec section 8 rule 1) and require post_periods for every test_type; the 0/1 post dummy for the permutation and leave-one-out tests is derived as data[time].isin(post_periods) by the new private _resolve_placebo_post (the placebo_timing_test / placebo_group_test idiom), on a copy under the collision-guarded _placebo_post column. A missing time column, missing/empty post_periods, NaN in time, post_periods values absent from the column, or a pre-existing _placebo_post column raise ValueError - on the suite wrapper BEFORE its try/except blocks. This also fixes a live bug: through 3.x the wrappers forwarded time itself as the dummy, so on any multi-period calendar column DifferenceInDifferences.fit rejected it and run_all_placebo_tests silently reported the two tests as error dicts (tutorial 04's placebo cell printed ERROR lines; the key-only test assertions hid it). Calls that passed the 0/1 indicator as time= with post_periods=[1] return identical numbers; calls that omitted post_periods must add it. The practitioner snippet, llms-practitioner.txt and llms-full.txt stop advising time='post'; the two wrappers join the naming guard's rule-1 calendar inventory.
  • Bookkeeping: ledger header + 16 rows (ROW_COUNT_FLOOR 139, new id range (156, 157), docstrings); spec ladder, sections 5 / 8 / 11, the phase-5 roster and a new cut-sequence item 9b, all as dated amendments; migration guide (orientation rows, the Results-fields section, the M-016 / M-148 / M-149 / M-151 / M-152 appendix Fix cells, two new renames-post rows, the positional-shift paragraph; 115 rows in total); REGISTRY (field-naming paragraph, M-016 / LWDiD / SyntheticControl notes, a new PlaceboTests - **Note:** for the wrapper contract), docs/api/results.rst and diagnostics.rst, docs/doc-deps.yaml notes, both guides; the five unreleased changelog fragments that promised a 5.0 removal reworded; API autosummary stubs drop the retired properties; the naming guard's DMLDiDResults.overall_att surface entry and _deprecation.py placebo_effects consumer entry deleted and the retired token families' remaining in-repo hits allowlisted (body-local overall_att names, the kept __setstate__ key maps, methodology history); the capture / benchmark getattr fallbacks read canonical names; tutorial 17 re-executed on the Rust backend (only the stored practitioner-snippet cell moved - no numbers).

Methodology references (required if estimator / math changes)

  • Method name(s): N/A for the shim retirement (no numbers move; the canonical fields were already the native storage). Placebo wrappers: Bertrand, Duflo & Mullainathan (2004) placebo battery - the wrapper's post dummy is now the same time.isin(post_periods) rule the timing/group callees already apply; the callee arithmetic is untouched.
  • Paper / source link(s): docs/methodology/REGISTRY.md PlaceboTests section (BDM 2004, https://doi.org/10.1162/003355304772839588); docs/v4-design.md section 8 rule 1 (time = calendar column, post = 0/1 indicator), section 5 (results contract).
  • Any intentional deviations from the source (and why): The wrappers require post_periods (no midpoint default) while the direct placebo_timing_test / placebo_group_test callees keep their second-half-of-periods fallback - recorded as a REGISTRY - **Note:** (M-156 / M-157). The pre-existing permutation-test t_stat = original_att / se line (REGISTRY "permutation inference is not safe_inference" Note) is unchanged.

Validation

  • Tests added/updated: tests/test_diagnostics.py (new TestPlaceboWrapperCalendarContract: post_periods required for every test type and on the suite wrapper - raised, not an error dict; misspelled time column; absent post_periods values; NaN in time; pre-existing _placebo_post column; input not mutated; bitwise parity of the wrapper with the direct permutation_test / leave_one_out_test callees; run_all_placebo_tests returns a real PlaceboTestResults for every key on a six-period panel - the regression test for the bug); tests/test_v4_storage_flip.py, tests/test_result_aliases.py, tests/test_balance_interop_contract.py, tests/test_v4_merge_mpd.py, tests/test_lwdid.py, tests/test_methodology_synthetic_control.py (legacy names asserted ABSENT - getattr_static None, not a field, hasattr False; SyntheticControl rejects the removed params at __init__ and set_params, get_params no longer lists them); tests/test_v4_rename_mechanical.py (tests of the four deleted helpers removed); tests/test_v4_rename_semantic.py, tests/test_methodology_placebo.py (callers pass post_periods); tests/test_v4_matrix.py (row floor / id snapshot / docstrings); tests/test_naming_guard.py (allowlist moves). Green locally: the 4.0 gate suites (test_v4_matrix, test_naming_guard, test_tracking_files, test_changelog_fragments, test_guides, test_docs_ia, test_doc_deps_integrity, test_v4_diagnostic_family_docs, test_base_estimator, test_practitioner) plus every per-estimator suite whose results class changed (test_staggered, test_sun_abraham, test_imputation, test_two_stage, test_stacked_did, test_efficient_did, test_wooldridge, test_chaisemartin_dhaultfoeuille, test_continuous_did, test_dml_did, test_twfe_event_study_decoupling); a simulated __version__ = 4.0.0 bump keeps test_v4_matrix + test_naming_guard green (reverted). Sphinx -W build green with no autosummary drift beyond the deliberate stub edits. Python 3.9 compileall of the touched test files green.
  • Backtest / simulation / notebook evidence (if applicable): pytest --nbmake green on 04_parallel_trends (its placebo cell now yields five PlaceboTestResults, no ERROR lines - the notebook has no committed outputs, item 10 re-executes it) and 17_brand_awareness_survey; 17 re-executed on the Rust backend with the worktree kernel - the cell-level diff touches only the stored practitioner-snippet text (plus the ipykernel path prefix, normalized back); no number moved.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

Changelog

  • changelog.d/ fragment added (or N/A - no user-visible change): Yes (changelog.d/20261101-v4-pr9b-shim-retirement-placebo-time.md)

… and give the placebo wrappers the calendar-time contract (M-016, M-050..M-058, M-148, M-149, M-151, M-152 removed; M-156, M-157 done)

Shim retirement (user decision 2026-11-01: no 4.x debt). The 14 ledger rows
scheduled for a 5.0 removal flip to `removed` at 4.0. None of their
FutureWarning shims had reached a release (they landed on main after the
v3.13.0 tag), so the released 3.x names break at 4.0 without a warned
release - stated plainly in the migration guide, the ledger notes and the
spec amendments. The overall_* / overall_att_* FutureWarning properties are
deleted from the nine results classes and the DMLDiDResults re-declaration
(AttributeError now; the canonical att/se/t_stat/p_value/conf_int are the
only vocabulary; the __setstate__ legacy-key migration stays so 3.x pickles
load); EventStudyResults.period_effects, _PeriodEffectView and the
leaf-local stars helper are gone from results_base.py (to_dataframe() is the
route); SyntheticControl(inner_max_iter=, inner_min_decrease=) are removed
(TypeError; positional slots after them shift up two - the
DeprecationWarning DID ship in 3.13.0, so deprecated_in is corrected to
3.13.0); LWDiDResults.pvalue / .ci are gone. diff_diff/_deprecation.py is
reduced to the pickle-migration (LEGACY_OVERALL_QUINTET,
migrate_renamed_state_keys, migrate_event_study_reference_sentinel) and
required-arg (NOT_SUPPLIED, require_arg) helpers; deprecated_field_property,
resolve_renamed_kwarg, warn_deprecated_kwarg and deprecated_kwarg_message
are deleted with their last consumers. With the descriptors gone, assigning
a legacy name to a results instance creates a shadow attribute instead of
raising (non-slotted dataclasses) - the naming guard's consumer sweep is the
in-repo defense.

Placebo wrappers (TODO.md 2(c)-ii row closed; rows M-156 / M-157).
run_placebo_test / run_all_placebo_tests take time= as the calendar period
column only (spec section 8 rule 1) and require post_periods for every
test_type; the 0/1 post dummy the permutation and leave-one-out tests need
is derived as data[time].isin(post_periods) by the new
diagnostics._resolve_placebo_post (the placebo_timing_test /
placebo_group_test idiom), on a copy under the collision-guarded
_placebo_post column. A missing time column, missing/empty post_periods,
NaN in time, post_periods values absent from the column, or a pre-existing
_placebo_post column raise ValueError - on the suite wrapper before its
try/except blocks. Through 3.x the wrappers forwarded time itself as the
dummy, so on any multi-period calendar column DifferenceInDifferences.fit
rejected it and run_all_placebo_tests silently returned the two tests as
error dicts (tutorial 04's placebo cell printed ERROR lines; the key-only
test assertions hid it). The practitioner snippet, llms-practitioner.txt
and llms-full.txt stop advising time='post'; REGISTRY carries the wrapper
Note; tests/test_diagnostics.py gains the calendar-contract suite
(validation, no-mutation, bitwise parity with the direct callees, real
results from run_all on a six-period panel) as the rows' test_ref.

Bookkeeping: ledger header + 16 rows (M-156/M-157 added; ROW_COUNT_FLOOR
139, id range (156, 157)); spec ladder, sections 5/8/11, the phase-5 roster
and item 9b with dated amendments; migration guide (orientation rows, the
Results-fields section, M-016/M-148/M-149/M-151/M-152 appendix Fix cells,
two new renames-post rows, the positional-shift paragraph, 115 rows in
total); REGISTRY / REPORTING-adjacent prose, docs/api/results.rst and
diagnostics.rst, doc-deps notes, the two guides; the unreleased changelog
fragments that promised a 5.0 removal reworded; API autosummary stubs drop
the retired properties (Sphinx -W green, no further drift); the naming
guard's DMLDiDResults.overall_att and _deprecation placebo_effects entries
deleted, the two wrappers moved into the rule-1 calendar inventory, and the
retired token families' remaining in-repo hits allowlisted; the capture /
benchmark getattr fallbacks read the canonical names; tutorial 17
re-executed on the Rust backend (only the practitioner-snippet cell moved).
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

✅ Looks good

No unmitigated P0 or P1 findings identified in the supplied diff.

Executive summary

  • Placebo wrappers consistently derive the post indicator from calendar periods and post_periods.
  • Changed validation handles missing columns, empty period lists, missing calendar values, absent labels, and temporary-column collisions.
  • Shim retirement preserves canonical result storage and legacy pickle migration; no dangling library consumers were found.
  • The required-post_periods deviation is documented and informational.
  • Runtime tests could not run: this environment lacks pytest, NumPy, pandas, and SciPy.

Methodology

  • P3 — Informational: documented wrapper contract.
    Location: docs/methodology/REGISTRY.md:L6392-L6395; diff_diff/diagnostics.py:L195-L234.
    Impact: Wrappers require explicit post_periods, while direct timing/group functions retain their fallback. This difference is explicitly documented in a Registry Note. Cross-checking the Registry and repository’s BDM (2004) paper review found no new estimator, variance, or inference-formula mismatch. The existing permutation-inference exception remains documented and unchanged.
    Concrete fix: None required.

Code Quality

No findings. Shared validation precedes the suite’s exception handlers, and temporary post indicators are added to copies of the input.

Performance

No findings. The additional validation and data copy introduce no evident material regression relative to repeated estimator fits.

Maintainability

No findings. Deleted helpers have no remaining imports in the scanned library, tests, or benchmarks. SyntheticControl parameter introspection follows its updated constructor automatically.

Tech Debt

No findings. The removed TODO row corresponds to the implemented wrapper redesign. Required pickle-migration helpers remain intact.

Security

No findings in the changed code or notebook content.

Documentation/Tests

No findings. The PR includes the required changelog fragment, migration guidance, removal checks, and wrapper tests covering callee parity, datetime labels, validation, and input immutability (tests/test_diagnostics.py:L741-L969).

Static parsing passed for 366 Python files. Runtime test results reported in the PR were not independently verified because the required dependencies are unavailable.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Nov 1, 2026
@igerber
igerber merged commit ba74694 into main Nov 1, 2026
43 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-ci Triggers CI test workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant