Skip to content

feat(reports): DiagnosticReport consumes the post-fit aggregate('event_study') surface - #767

Merged
igerber merged 2 commits into
mainfrom
feat/dr-postfit-event-study
Aug 15, 2026
Merged

feat(reports): DiagnosticReport consumes the post-fit aggregate('event_study') surface#767
igerber merged 2 commits into
mainfrom
feat/dr-postfit-event-study

Conversation

@igerber

@igerber igerber commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Summary

  • DiagnosticReport now derives the unified EventStudyResults surface internally via the result's post-fit aggregate('event_study') when the raw event_study_effects field is absent — once, cached, fail-soft — so the event-study-gated checks run on modern plain fits without the deprecated fit-time aggregate= kwarg that 4.0 removes. Unlocked routes: parallel_trends/pretrends_power/sensitivity on CallawaySantAnna (the derived container is M-093-admitted into compute_pretrends_power/HonestDiD with raw-route parity pinned at 1e-12), parallel_trends (pretrends=True fits) + heterogeneity on ImputationDiD and TwoStageDiD, and heterogeneity on ContinuousDiD.
  • Raw-field precedence is absolute: a present event_study_effects — including the requested-but-empty {} sentinel, which encodes fit-time balance_e configuration — is authoritative and never re-derived. The M-093 consumer-admission set, dCDH's placebo_event_study branch, and the EventStudyResults INPUT rejection at DR/BR construction are all unchanged. Wooldridge's mutating aggregate() is never auto-called.
  • Derivation failures fail closed to explicit per-check skip reasons (bootstrapped fits' NotImplementedError, kit-less unpickles' ValueError, embedded verbatim and never masked by other availability legs), and the remediation taxonomy is estimator-accurate: Wooldridge points at its own in-place results.aggregate(type='event_study'), the staggered-DDD reason names the fit-time kwarg as that surface's canonical route (M-140), and a one-pre-period MultiPeriodDiD fit gets an accurate omitted-reference message instead of a wrong "no post-fit route" claim.
  • Derived-route sections carry an additive pre_period_source="aggregate_event_study" schema key — attached exactly to the checks whose gate consulted the derived surface, on success, error, and skip paths alike (user-opt-out skips carry nothing). Warnings re-emitted by a kit recompute are captured exception-safely and republished on the consuming sections and, deduplicated, on the top-level channel. BusinessReport lifts the provenance key through its _lift_pre_trends whitelist (its only source change).
  • Accessing applicable_checks may now trigger the one cached derivation; the documented laziness guarantee is amended across the docstring/module-docstring/API-page/llms-full surfaces, and the two report API pages migrate their examples to plain fits.
  • Retires the TODO.md "diagnostic_report ES-gated checks" row; records the pre-existing runner-level-skip bookkeeping gap (surfaced during review, present on main) as a new TODO row rather than bolting a cross-cutting schema change onto this PR.

Methodology references (required if estimator / math changes)

  • Method name(s): No estimator math changes — report-layer consumption of each estimator's existing post-fit aggregate('event_study') output (Callaway & Sant'Anna 2021; Borusyak, Jaravel & Spiess 2024; Gardner 2022; Callaway, Goodman-Bacon & Sant'Anna 2024; Roth 2022 pre-trends power; Rambachan & Roth 2023 HonestDiD).
  • Paper / source link(s): per-estimator citations unchanged in docs/methodology/REGISTRY.md; the new report-layer routing is documented in the M-020/M-021/M-022/M-025 post-fit aggregate() Notes (REGISTRY) and docs/methodology/REPORTING.md.
  • Any intentional deviations from the source (and why): None. Consumer admission stays exactly the M-093 set (CS-only on the derived route); non-admitted producers' containers feed only DR-internal coefficient summaries.

Validation

  • Tests added/updated: tests/test_diagnostic_report.py (new TestDerivedEventStudySurface, 30+ tests: raw-vs-derived parity for PT joint-Wald/Bonferroni, pretrends-power (1e-3, SciPy Genz jitter), HonestDiD grid CIs (1e-12), survey finite-df F-path parity, heterogeneity numeric parity for Imputation/TwoStage/Continuous, anticipation boundary, zero-count/reference-row adapter pins, requested-but-empty {} precedence with a zero-aggregate()-call assertion, bootstrap/missing-kit/replicate fail-closed reasons across all three consumers, warning capture/republish/dedup on success, error, skip, and wrong-type paths, provenance on opt-out vs consulted checks, caching (single aggregate() call per report), and non-mutation); tests/test_business_report.py (derived-route lift + raw-route None provenance).
  • Backtest / simulation / notebook evidence (if applicable): N/A — report-layer plumbing over pinned aggregation contracts; tests/test_event_study_consumers.py parity pins unchanged.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

…t_study') surface

On a modern plain fit (no fit-time aggregate=), DiagnosticReport now derives
the EventStudyResults container internally - once, cached, fail-soft - so the
event-study-gated checks run without the deprecated kwarg the 4.0 release
removes: parallel_trends / pretrends_power / sensitivity on CallawaySantAnna
(the container is M-093-admitted into the consumers with pinned raw-route
parity), parallel_trends (pretrends=True fits) and heterogeneity on
ImputationDiD / TwoStageDiD, and heterogeneity on ContinuousDiD.

Raw-field precedence is absolute (a present event_study_effects - the
requested-but-empty {} sentinel included - is never re-derived, preserving
fit-time balance_e semantics); the M-093 consumer-admission set, dCDH's
placebo branch, and the EventStudyResults input rejection are unchanged.
Derivation failures fail closed to explicit per-check skip reasons (never
masked by other availability legs), remediation strings are estimator-
accurate (Wooldridge's in-place aggregate; the staggered-DDD canonical
fit-time route; MPD's omitted-reference case), and derived-route sections
carry pre_period_source provenance - attached exactly to the checks whose
gate consulted the derived surface, on success, error, and skip paths alike.
Warnings emitted by a kit recompute are captured exception-safely and
republished on the consuming sections and (deduplicated) the top-level
channel. BusinessReport lifts the provenance key through its whitelist.

Docs: report API pages migrate to plain-fit examples; the applicable_checks
laziness guarantee is amended (one cached derivation may run there);
REPORTING.md documents the new data source and schema key; REGISTRY.md gains
the M-024-style plain-fit notes for CS/Imputation/TwoStage/Continuous; the
M-020/M-021/M-022/M-025 ledger rows gain notes+code_refs; migration-4.0.md
notes the checks survive the kwarg migration. Retires the TODO.md row; a
pre-existing runner-skip bookkeeping gap is recorded as a new TODO row.
@github-actions

Copy link
Copy Markdown

PR Review

Overall assessment

Looks good — no unmitigated P0/P1 findings.

Executive summary

  • Post-fit routing matches Registry notes M-020/M-021/M-022/M-025 and M-093 admission.
  • Empty, bootstrap, missing-kit, and undefined-inference paths fail closed.
  • One minor BusinessReport provenance propagation gap remains.
  • Two known limitations are explicitly documented/tracked.
  • Static parsing passed; runtime tests were unavailable because test dependencies are absent.

Methodology

  • Severity: P3 — Documented deviation. CS base_period="varying" still uses the linear power alternative intended for common-reference coefficients (diagnostic_report.py:L1964, REGISTRY.md:L5036).
    • Impact: Plain fits now reach this existing warned behavior.
    • Concrete fix: None required for approval; the eventual transformation/universal-base restriction is tracked in TODO.md:L41.

No other methodology mismatch or variance/SE issue found.

Code Quality

No findings.

Performance

No findings. Derivation is cached once per report.

Maintainability

No findings.

Tech Debt

  • Severity: P3 — Tracked. Runner-level skipped sections are not reconciled into public skip bookkeeping.
    • Impact: applicable_checks can include a check whose runner later skips.
    • Concrete fix: No PR action required; tracked in TODO.md:L31.

Security

No findings or apparent secrets.

Documentation/Tests

  • Severity: P2. _lift_pre_trends() drops pre_period_source whenever PT is skipped, despite DR attaching it to successful-but-empty derived surfaces (business_report.py:L946, diagnostic_report.py:L1284).
    • Impact: BusinessReport loses derived-surface provenance on skip paths.
    • Concrete fix: Include pre_period_source in the early return and add a BusinessReport test using default TwoStageDiD with no estimable pre-horizons.

…source provenance

The _lift_pre_trends early return (PT status != 'ran') dropped the
pre_period_source key DiagnosticReport attaches to gate-skipped sections
whose derivation succeeded but produced no pre-horizons; lift it on the
skip path too (None on raw routes, matching the computed path). Pinned
with a default-TwoStageDiD BusinessReport test.
@github-actions

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 624e299b72e6f3f871c2c967508a3dba714ca68f


PR Review

Overall assessment

Looks good — no unmitigated P0/P1 findings.

Executive summary

  • The prior BusinessReport provenance gap is fixed and regression-tested.
  • Routing matches Registry notes M-020/M-021/M-022/M-025 and M-093 admission.
  • Raw empty-surface precedence, undefined inference, and derivation failures fail closed.
  • Derivation is cached once per report.
  • Remaining limitations are documented or tracked.
  • Runtime tests were unavailable because pytest and NumPy are absent; static parsing passed.

Methodology

Affected methods: Callaway–Sant’Anna, ImputationDiD, TwoStageDiD, ContinuousDiD, Roth pre-trends power, and Rambachan–Roth HonestDiD. No estimator equations or variance estimators change; the report consumes existing aggregation surfaces.

  • Severity: P3 — Documented deviation.
    • Impact: CS base_period="varying" still uses a linear power alternative intended for common-reference coefficients.
    • Concrete fix: None required; explicitly documented and tracked in docs/methodology/REGISTRY.md:L5035-L5036 and TODO.md:L41.

No undocumented methodology, assumption-check, variance, or SE mismatch found.

Code Quality

No findings. Raw-field precedence and fail-soft derivation are clearly isolated in diff_diff/diagnostic_report.py:L742-L862.

Performance

No findings. The derived surface is cached and reused for all consumers.

Maintainability

No findings. M-093 consumer admission remains source-restricted in diff_diff/diagnostic_report.py:L264-L274.

Tech Debt

  • Severity: P3 — Tracked.
    • Impact: Runner-level skips can remain listed in applicable_checks.
    • Concrete fix: None required for this PR; tracked in TODO.md:L31.

Security

No findings or apparent secrets.

Documentation/Tests

The previous P2 is resolved: _lift_pre_trends() now preserves provenance on skipped PT sections (diff_diff/business_report.py:L946-L976), with a regression test at tests/test_business_report.py:L5084-L5098.

The added tests cover raw/derived parity, empty surfaces, NaN inference, bootstrap and missing-kit failures, warnings, caching, anticipation, and survey finite-df paths. Static parsing passed.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Aug 15, 2026
@igerber
igerber merged commit da5a578 into main Aug 15, 2026
39 of 40 checks passed
@igerber
igerber deleted the feat/dr-postfit-event-study branch August 15, 2026 23:41
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