Skip to content

feat(v4): EventStudyResults consumers + dCDH aggregate shim (2(b) PR-1, M-026) - #744

Merged
igerber merged 5 commits into
mainfrom
feat/v4-aggregate-shims
Aug 3, 2026
Merged

feat(v4): EventStudyResults consumers + dCDH aggregate shim (2(b) PR-1, M-026)#744
igerber merged 5 commits into
mainfrom
feat/v4-aggregate-shims

Conversation

@igerber

@igerber igerber commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Container consumers (v4 §9 item 5, rows M-092/M-093 pre-cut half): compute_honest_did, compute_pretrends_power and plot_event_study accept the unified EventStudyResults container from CallawaySantAnnaResults.aggregate('event_study'); plot_honest_event_study renders HonestDiD results computed from a container. Route parity is the pinned gate (HonestDiD bounds equal across routes at 1e-14; pretrends extraction bit-exact). Admission in HonestDiD/PreTrendsPower is source-scoped to CS-produced containers - dCDH l1 containers rejected by design; widening is each estimator's own aggregate() migration. Plotters take no source guard.
  • Four provenance fields on EventStudyResults (declared last, positional compat preserved): base_period, anticipation, df_survey (scalar inference df incl. the replicate-undefined 0.0 sentinel the per-row df column cannot encode), and reference_event_times (distinct per-cohort positional-base event times; fit-level under universal base, recomputed over retained cohorts per aggregation under balance_e). Threaded by all builders; to_dict() JSON-safe.
  • Common-reference guard (both routes): universal-base fits whose cohorts carry more than one positional-base event time - gapped grids, including the overlap layout where a cohort's base coincides with another cohort's estimated horizon and no reference-only row marks it - fail closed in HonestDiD and PreTrendsPower. Provenance-less universal inputs derive the bases from materialized reference cells (pre-3.9 pickles) or warn fail-safe (hand-built containers). Multi-reference containers also fail closed in HonestDiD.
  • Roth-anchored linear violations: pretrends relative times are measured from the omitted reference (|t - t_ref|) on the CS-universal, SunAbraham and container routes, matching the MPD branch's existing anchor. Universal-base CS/SA linear power/MDV numbers change to the Roth-correct values (weights e.g. [3, 2][2, 1] for pre [-3, -2] around t_ref = -1); the REGISTRY linear-convention note records the correction. Varying-base input keeps raw labels under a new linear-scoped interpretation warning (transformation tracked in TODO.md).
  • Anticipation boundary unified: HonestDiD's no-reference split, plot pre-shading, and pretrends' cutoff all treat [e=-k, -1] as anticipated treatment effects.
  • Inference hygiene: zero-SE rows drop on every HonestDiD adapter; MPD extraction fails closed when a dropped horizon breaks the positional grid and returns the estimated period labels beta_hat was built from; empty explicit pre_periods subsets rejected on all pretrends paths.
  • Plotting fidelity: multi-reference containers render every anchor hollow via row-aligned reference marks (explicit renormalization around a non-anchor fails closed); interval bars draw endpoint-based so stored percentile/bootstrap intervals excluding the point estimate render exactly; container stored-interval levels are checked against the requested alpha (warning on mismatch); the honest plotter restricts to HonestDiD-retained rows with the reference kept as a hollow normalization anchor.
  • dCDH shim (row M-026, phase 5): fit(aggregate=) deprecated - supplying it warns, any non-None value raises pointing at results.aggregate() (it never computed anything). ChaisemartinDHaultfoeuilleResults.aggregate() ships as a pure view: 'event_study' returns the unified container (Phase-1 fits the 2-row l=1 view); 'simple' a one-row AggregationResult relaying the overall estimand bit-exactly with estimand-aware target/n/n_kind and df resolved from the actual inference path (the L_max>=2 delta keeps analytical survey df under bootstrap per the REGISTRY delta-SE note). Bootstrap fits permitted (nothing recomputed - documented contrast with CS's fail-closed kit recompute). AggregationMixin hooks are ClassVar (dynamic regression enrolls every dataclass adopter).
  • Ledger/docs: M-026 → shimmed; M-092 provenance amendments; M-093 records the pre-cut consumer delivery; v4-design §5, REGISTRY notes (dCDH view, common-reference guard, plotting edge cases, linear-convention correction), autosummary rosters, guides (llms/practitioner/full), CHANGELOG; TODO consumability row delivered and removed, varying-base transformation row added.

Methodology references (required if estimator / math changes)

  • Method name(s): Callaway & Sant'Anna event-study aggregation; Rambachan & Roth (2023) HonestDiD; Roth (2022) pre-trends power; de Chaisemartin & D'Haultfoeuille DID_M / DID_l.
  • Paper / source link(s): see docs/methodology/REGISTRY.md sections for CallawaySantAnna, HonestDiD, PreTrendsPower, and de Chaisemartin-D'Haultfoeuille (citations therein).
  • Any intentional deviations from the source (and why): recorded as REGISTRY notes in this diff - the dCDH post-fit view's bootstrap relay (nothing recomputed, so CS's fail-closed rationale does not apply); the common-reference fail-closed guard on gapped universal grids; the multi-reference container rejection (deliberate deviation from the fit-time first-marker split); the varying-base pretrends warning (warned, not rejected; matches the existing HonestDiD deviation-from-R note); the Event Study Plotting multi-reference edge case.

Validation

  • Tests added/updated: tests/test_event_study_consumers.py (new, 80+ tests: end-to-end gates, route parity at 1e-14, source-scoped admission, common-reference guard incl. the overlap layout, provenance threading, anchoring regressions with hand-calculated weights, plotting fidelity on both backends), tests/test_aggregate_contract.py (dCDH shim + view classes incl. the estimand ladder and df provenance), tests/test_event_study_surface.py, tests/test_honest_did.py, tests/test_pretrends.py, tests/test_visualization.py, tests/test_chaisemartin_dhaultfoeuille.py (shim pins).
  • Backtest / simulation / notebook evidence (if applicable): N/A - no numerical changes outside the documented Roth-anchoring correction; parity asserted against stored fit values, not re-derived.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

…1, M-026; M-092/M-093 amended)

v4 program section 9 item 5, PR-1 of the 2(b) wave: the unified
event-study container becomes CONSUMABLE (the TODO consumability row's
acceptance gates), and dCDH - the one row whose fit-time aggregate=
never computed anything - ships its post-fit aggregate() view.

Container consumers (M-092/M-093 pre-cut half):
- compute_honest_did, compute_pretrends_power and plot_event_study
  accept EventStudyResults from
  CallawaySantAnnaResults.aggregate('event_study');
  plot_honest_event_study renders HonestDiDResults computed from a
  container. Route parity is the pinned gate: HonestDiD bounds equal
  across routes at 1e-14; pretrends extraction bit-exact (its MVN-CDF
  power carries scipy's internal jitter either way).
- Admission in HonestDiD/PreTrendsPower is SOURCE-SCOPED to CS-produced
  containers; dCDH l1 containers rejected BY DESIGN (placebo semantics
  need the native branch); plotters take no source guard.
- EventStudyResults gains four provenance fields declared last:
  base_period, anticipation, df_survey (scalar inference df with the
  replicate-undefined 0.0 sentinel the per-row df column cannot
  encode), and reference_event_times (distinct per-cohort
  positional-base event times, computed at fit under universal base and
  recomputed per-aggregation over retained cohorts under balance_e).
  Threaded by all builders incl. the requested-but-empty path;
  to_dict() JSON-safe.

Correctness hardening that landed with the wave (both routes each):
- Common-reference guard: universal-base fits whose cohorts carry more
  than one positional-base event time (gapped grids - including the
  overlap layout where a cohort's base coincides with another cohort's
  estimated horizon and NO reference-only row marks it) fail closed in
  HonestDiD and PreTrendsPower; provenance-less universal inputs derive
  the bases from materialized reference cells (pre-3.9 pickles) or warn
  fail-safe (hand-built containers). Multi-reference containers also
  fail closed in HonestDiD (deliberate deviation from the fit-time
  first-marker split).
- Roth-anchored linear violations: pretrends relative times are
  measured from the omitted reference (|t - t_ref|) on the
  CS-universal, SunAbraham and container routes, matching the MPD
  branch's existing anchor; universal-base CS/SA linear power/MDV
  numbers change to the Roth-correct values (REGISTRY linear-convention
  note records the correction). Varying-base input keeps raw labels
  under a new interpretation warning scoped to the linear benchmark.
- Anticipation boundary unified: HonestDiD's no-reference split, the
  plotters' pre-shading, and pretrends' cutoff all treat [e=-k, -1] as
  anticipated treatment effects (e < -k is the clean pre set).
- Zero-SE rows drop on every HonestDiD adapter (MPD/dCDH joined CS/
  container); MPD extraction fails closed when a dropped horizon breaks
  the positional grid and returns the estimated period labels beta_hat
  was built from; explicit empty pre_periods subsets are rejected.
- Plotting: multi-reference containers render every anchor hollow via
  row-aligned reference marks (explicit renormalization around a
  non-anchor fails closed; REGISTRY Event Study Plotting edge case);
  interval bars draw endpoint-based so stored percentile/bootstrap
  intervals that exclude the point estimate render exactly; container
  stored-interval levels are honesty-checked against the requested
  alpha; the honest plotter restricts to HonestDiD-retained rows with
  the reference kept as a hollow normalization anchor.

dCDH shim (M-026, phase 5):
- fit(aggregate=) is deprecated: supplying it warns; any non-None value
  raises pointing at results.aggregate() (it never computed anything -
  "reserved for Phase 3").
- ChaisemartinDHaultfoeuilleResults.aggregate() ships as a pure VIEW:
  'event_study' returns the unified container (Phase-1 L_max=None fits
  the 2-row l=1 view); 'simple' a one-row AggregationResult relaying
  the overall estimand bit-exactly with estimand-aware target/n/n_kind
  (DID_M/switcher_cells, DID_1/groups, delta/no-scalar-count, the
  trends-linear all-NaN first-difference row) and df resolved from the
  ACTUAL inference path (the L_max>=2 delta keeps analytical survey df
  under bootstrap per the REGISTRY delta-SE note). Because nothing is
  recomputed, bootstrap fits are permitted - unlike CallawaySantAnna's
  kit-based aggregate(), which stays fail-closed.
- AggregationMixin routing hooks are ClassVar (the annotated override
  had leaked into the dataclass __init__/repr/eq surface); dynamic
  regression enrolls every dataclass mixin adopter.

Ledger/docs: M-026 flips to shimmed; M-092 gains its provenance-field
amendments; M-093 records the pre-cut consumer delivery with
source-scoped admission widening per-estimator; v4-design section 5 and
REGISTRY notes updated (dCDH view Note, common-reference guard Note,
plotting edge cases, pretrends linear-convention correction);
autosummary rosters extended; TODO consumability row delivered and
removed, two follow-up methodology rows added (varying-base violation
transformation; the pre-existing native-route items resolved in-tree).
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Overall assessment

⚠️ Needs changes — two unmitigated P1 methodology-contract issues exist in the new container consumers.

Executive summary

  • HonestDiD can silently misclassify interleaved container coefficients because it does not enforce [β_pre; β_post] ordering.
  • HonestDiD and PreTrendsPower accept malformed or SE-inconsistent covariance matrices from publicly constructible containers.
  • The reference anchoring, varying-base warning, diagonal-covariance fallback, common-reference guard, and dCDH view behavior are documented deviations and are not defects.
  • No security, performance, or untracked technical-debt issues were identified.
  • Tests could not run because NumPy, SciPy, pandas, matplotlib, and pytest are unavailable; static parsing succeeded.

Methodology

M1 — Container rows are not reordered into pre/post blocks

  • Severity: P1
  • Location: diff_diff/results_base.py:L160-L165, diff_diff/results_base.py:L327-L354, diff_diff/honest_did.py:L678-L763, diff_diff/honest_did.py:L2824-L2844
  • Impact: EventStudyResults documents sorted event times but neither validates nor enforces that invariant. HonestDiD derives pre_times and post_times by filtering row order, while beta_hat remains in the original full-row order. An interleaved surface such as [-3, 0, -2, -1, 1] passes the within-block consecutiveness checks but produces beta_hat=[β_-3, β_0, β_-2, β_1]. HonestDiD.fit() then treats the first two entries as β_pre, silently violating Rambachan–Roth’s [β_pre; β_post] contract and misaligning the covariance.
  • Concrete fix: Sort retained pre/post labels, form ordered_times = pre_times + post_times, and subset att, se, and vcov using that exact order. Alternatively, reject non-sorted/duplicate event-time containers in EventStudyResults.__post_init__. Add a permuted-row regression test asserting identical bounds and covariance to the sorted container.

M2 — Covariance integrity is not validated at the new consumer boundary

  • Severity: P1
  • Location: diff_diff/results_base.py:L433-L452, diff_diff/honest_did.py:L749-L763, diff_diff/pretrends.py:L242-L268, diff_diff/pretrends.py:L1544-L1564
  • Impact: Validation covers only shape and index membership. A hand-built container can claim source="CallawaySantAnnaResults" while supplying non-finite, asymmetric, indefinite, duplicate-indexed, or se²-inconsistent covariance. HonestDiD requires a consistent covariance estimator, while PreTrendsPower explicitly requires valid Σ₂₂ (docs/methodology/REGISTRY.md:L4607-L4609, L4674-L4695). Such inputs can cause invalid finite inference or downstream numerical failures.
  • Concrete fix: Before consumption, require a one-dimensional unique index, finite covariance entries, symmetry within tolerance, and a diagonal matching retained se**2. Reject materially indefinite matrices; additionally enforce HonestDiD’s positive-definiteness/eigenvalue assumption while preserving PreTrendsPower’s documented singular-covariance handling. Add malformed-container tests for each condition.

M3 — Documented deviations

  • Severity: P3 — informational
  • Impact: The dCDH pure-view aggregation, varying-base warnings, common-reference rejection, diagonal-covariance fallback, and reference-anchored Roth weights are explicitly documented in the Registry or tracked in TODO.md.
  • Concrete fix: None required.

Code Quality

No additional findings.

Performance

No findings.

Maintainability

No additional findings beyond centralizing the validation described in M1–M2.

Tech Debt

No untracked findings. The varying-base linear-violation limitation is tracked in TODO.md:L24.

Security

No findings or accidental secrets observed.

Documentation/Tests

The added coverage is extensive, but it lacks the permuted-row and malformed-covariance cases described above. Runtime validation was unavailable because the test dependencies are not installed.

Path to Approval

  1. Order HonestDiD container coefficients and covariance explicitly as sorted pre periods followed by sorted post periods; add an interleaved-row parity test.
  2. Validate container covariance finiteness, symmetry, index uniqueness, diagonal/SE consistency, and applicable PSD/PD assumptions; add rejection tests for malformed matrices.

…riance validation (PR #744 review M1/M2)

- HonestDiD subsets beta_hat/sigma in EXPLICIT [sorted pre; sorted
  post] label order instead of row order: an interleaved hand-built
  container passed the within-block grid checks while beta_hat stayed
  row-ordered, silently violating the Rambachan-Roth [beta_pre;
  beta_post] contract (reproduced: permuted rows moved bounds from
  (1.9625, 2.0375) to (-0.35, 2.5)). Duplicate event_time labels are
  rejected on both consumers; permuted-row parity is regression-pinned
  for HonestDiD and PreTrendsPower (whose elementwise label alignment
  was already order-consistent).
- Consumer-boundary covariance validation (shared
  results_base._validate_vcov_subblock, called by the HonestDiD
  container branch and the pretrends container subblock helper):
  finite entries, symmetry, diagonal == stored se**2 (the container
  contract clears vcov rather than ship an inconsistent matrix), no
  material indefiniteness (singular passes - consumers document their
  own singular handling), and unique vcov_index labels. Parameterized
  malformed-container rejections + a valid-matrix acceptance pin.
  CS-produced containers are unaffected (sorted unique rows and
  consistent vcov by construction - all parity suites unchanged).
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: e48b577552c85d04afb596a85056f90cda2d43ac


Overall assessment

⚠️ Needs changes — the previous covariance-integrity finding is only partially resolved.

Executive summary

  • Prior coefficient-ordering P1 is resolved.
  • Covariance checks now cover duplicates, finiteness, symmetry, diagonal consistency, and obvious indefiniteness.
  • Validation tolerances are not scale-aware, allowing materially invalid low-variance covariance matrices.
  • HonestDiD still accepts singular covariance despite Rambachan–Roth Assumption 3.
  • Documented deviations and tracked varying-base work remain non-blocking.
  • Tests could not run because pytest/NumPy are unavailable; changed Python files parse successfully.

Methodology

M1 — Covariance validation remains incomplete

  • Severity: P1
  • Status: Prior finding partially resolved
  • Location: diff_diff/results_base.py:L626-L666, diff_diff/honest_did.py:L785-L786
  • Impact: The PSD check uses max(eigmax, 1.0), making its tolerance effectively absolute. For example, a matrix with diagonal 1e-10 and off-diagonal 2e-10 has eigenvalues [-1e-10, 3e-10]—materially indefinite relative to its scale—but passes the -1e-8 threshold. Fixed 1e-12 tolerances similarly permit large relative symmetry or se² inconsistencies for small SEs. Such matrices can produce invalid finite bounds or power. Additionally, singular covariance is admitted to HonestDiD, contrary to Rambachan–Roth’s eigenvalues-bounded-away-from-zero assumption (docs/methodology/papers/rambachan-roth-2023-review.md:L20-L25). PreTrendsPower may retain singular support as explicitly documented (docs/methodology/REGISTRY.md:L4688-L4695).
  • Concrete fix: Make symmetry, diagonal, and eigenvalue checks relative to covariance scale without a 1.0 floor. Add a consumer-specific allow_singular option: reject singular/near-singular covariance for HonestDiD while permitting it for PreTrendsPower. Add low-scale indefinite/asymmetric/diagonal-mismatch tests and a singular matrix test asserting HonestDiD rejects while PreTrendsPower accepts.

M2 — Prior ordering issue resolved

  • Severity: P3 — informational
  • Impact: HonestDiD now explicitly constructs [sorted pre; sorted post] and aligns coefficients and covariance by label.
  • Concrete fix: None.

M3 — Documented deviations

  • Severity: P3 — informational
  • Impact: dCDH view aggregation, common-reference rejection, varying-base warnings, and numerical implementation choices are documented in the Registry or tracked in TODO.md.
  • Concrete fix: None.

Code Quality

No additional findings.

Performance

No findings.

Maintainability

No additional findings; the shared covariance validator is the appropriate centralization point.

Tech Debt

  • Severity: P3 — informational
  • Impact: The varying-base linear-violation limitation is tracked in TODO.md.
  • Concrete fix: None required for this PR.

Security

No findings or accidental secrets observed.

Documentation/Tests

No additional findings. Runtime tests were unavailable because the environment lacks pytest and NumPy; static AST parsing passed for all changed Python files.

Path to Approval

  1. Replace the fixed-scale covariance tolerances with scale-relative checks and add low-scale corruption regressions.
  2. Enforce HonestDiD’s positive-definite/eigenvalue assumption while preserving PreTrendsPower’s documented singular-covariance support.

… sub-blocks (PR #744 review round 2)

- Every validator tolerance is now RELATIVE to the covariance scale:
  the previous max(eigmax, 1.0) floor made the indefiniteness check
  effectively absolute, waving through a uniformly tiny indefinite
  matrix (diag 1e-10, eigenvalues [-1e-10, 3e-10]); the symmetry atol
  scales with max|sigma| and the diagonal-vs-se**2 check is pure
  relative (retained rows have se > 0, so the target is strictly
  positive). Low-scale indefinite regression added on both consumers.
- HonestDiD's container boundary passes allow_singular=False:
  Rambachan-Roth inference assumes covariance eigenvalues bounded away
  from zero, so singular/near-singular sub-blocks (eigmin < 1e-10 *
  eigmax) are rejected with an actionable message. PreTrendsPower
  keeps its documented singular-covariance handling (default True) -
  regression pins the split (perfectly-correlated pre-rows: honest
  rejects, pretrends computes). Real-fit parity suites unchanged.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 2c2036a0734841be1d96420c69c8c574b072f9ae


Overall assessment

⚠️ Needs changes — one newly identified P1 can silently mis-map a PreTrendsPower violation to the wrong horizon.

Executive summary

  • The previous covariance-integrity P1 is resolved.
  • [Newly identified] Reordered containers break violation_type="last_period" semantics.
  • Documented deviations and tracked varying-base work remain non-blocking.
  • Bootstrap guidance contains one P2 contradiction.
  • Runtime tests were unavailable because pytest/NumPy are not installed; targeted AST parsing succeeded.

Methodology

M1 — Container pre-periods are not chronologically ordered

  • Severity: P1 — [Newly identified]
  • Location: diff_diff/pretrends.py:L1535-L1563, diff_diff/pretrends.py:L1012-L1021
  • Impact: Extraction preserves container row order despite promising a sorted pre-period set. For rows ordered [-2, -3, -1, ...], last_period assigns weights[-1] to -3, although Roth’s last pre-period is -2. This silently changes power and MDV; explicit custom weights can be similarly misaligned.
  • Concrete fix: Sort eligible indices by event_time before extracting effects, SEs, labels, and the VCV sub-block. Add a regression with reversed pre-period rows using violation_type="last_period".

M2 — Previous covariance finding resolved

  • Severity: P3 — informational
  • Location: diff_diff/results_base.py:L626-L690, diff_diff/honest_did.py:L766-L804
  • Impact: Validation is now scale-relative, and HonestDiD rejects singular/near-singular supplied covariance while PreTrendsPower retains its documented singular support.
  • Concrete fix: None.

M3 — Documented deviations

  • Severity: P3 — informational
  • Impact: The dCDH view behavior and common-reference guard are documented in the Registry. Varying-base linear violations are tracked in TODO.md.
  • Concrete fix: None.

Code Quality

No additional findings.

Performance

No findings.

Maintainability

No additional findings.

Tech Debt

  • Severity: P3 — informational
  • Impact: The varying-base PreTrendsPower limitation is properly tracked in TODO.md.
  • Concrete fix: None required here.

Security

No findings or accidental secrets observed.

Documentation/Tests

D1 — Bootstrap guidance contradicts runtime behavior

  • Severity: P2
  • Location: diff_diff/staggered.py:L1863-L1867, diff_diff/staggered_results.py:L312-L321
  • Impact: The fit docstring says no consumer requires fit-time aggregation, but post-fit aggregation raises for bootstrapped CS fits.
  • Concrete fix: Add the bootstrap exception already documented in the guides.

Runtime tests could not run because pytest and NumPy are unavailable.

Path to Approval

  1. Chronologically sort container pre-periods before mapping violation weights and covariance, with a reversed-row last_period regression test.

…; bootstrap exception in the fit docstring (PR #744 review round 3)

- The pretrends container branch sorts retained pre-periods
  CHRONOLOGICALLY before extraction: row order is harmless for the
  elementwise linear weights but silently mis-maps POSITIONAL
  violation patterns on a hand-built unsorted container -
  last_period assigned weights[-1] to whichever row came last
  (reproduced: power 0.999992 sorted vs 0.998876 reversed), and
  user-supplied custom weights are positional too. Reversed-row
  last_period regression pins power parity and extraction-level
  label equality. Native CS/SA branches already sorted.
- The CS fit(aggregate=) docstring now carries the bootstrap
  exception the guides already document: post-fit aggregate() raises
  on bootstrapped fits (percentile inference is not reproducible from
  retained analytical state), so fit-time aggregation remains the
  supported route there - the blanket 'no consumer requires the
  fit-time surface' claim contradicted runtime behavior.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 421b5611e4095c13e065239e5f1bee1c916b7e5e


Overall assessment

Looks good — no unmitigated P0 or P1 findings.

Executive summary

  • The previous chronological-order P1 is resolved.
  • Reversed-row last_period regression coverage was added.
  • Bootstrap guidance now matches runtime behavior.
  • Methodology deviations are documented or tracked.
  • Static parsing succeeded; runtime tests could not run because dependencies are unavailable.

Methodology

Previous P1 resolved

  • Severity: P3 — informational
  • Impact: Pre-period effects, SEs, labels, and covariance are now chronologically aligned before positional violation weights are applied.
  • Location: diff_diff/pretrends.py:L1535-L1568, tests/test_event_study_consumers.py:L595-L610
  • Concrete fix: None.

Documented deviations

  • Severity: P3 — informational
  • Impact: The dCDH view behavior, common-reference guard, and varying-base warning conform to documented Registry decisions.
  • Location: docs/methodology/REGISTRY.md:L4651, docs/methodology/REGISTRY.md:L4717-L4718
  • Concrete fix: None.

Code Quality

No findings.

Performance

No findings.

Maintainability

No findings.

Tech Debt

  • Severity: P3 — informational
  • Impact: Varying-base linear-violation transformation remains explicitly tracked.
  • Location: TODO.md:L24
  • Concrete fix: None required for this PR.

Security

No findings or accidental secrets observed.

Documentation/Tests

Previous documentation issue resolved

  • Severity: P3 — informational
  • Impact: The CS fit documentation now clearly identifies bootstrap fits as requiring fit-time aggregation.
  • Location: diff_diff/staggered.py:L1863-L1871
  • Concrete fix: None.

Runtime tests were unavailable because pytest, NumPy, pandas, and SciPy are not installed; repository-wide AST parsing succeeded.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Aug 3, 2026
…ear violation

The cs_fit fixture pin predated the Roth-anchoring correction: the
linear violation is measured from the omitted reference, so the
universal-base pre-periods [-4, -3, -2] around the e=-1 reference
carry offsets [-3, -2, -1] and the deterministic fixture value moves
from mdv*max|t| = 0.0937*4 = 0.375 to mdv*max|t - t_ref| =
0.1337*3 = 0.401. The ratio (0.225) stays under the 0.25
well_powered threshold, so the no-downgrade contract the test pins
is unchanged - only the level-scale constant moved.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 7864aedd65a2063493ee39794674747a9b582b77


Overall assessment

Looks good — no unmitigated P0 or P1 findings.

Executive summary

  • The previous chronological-order P1 remains resolved and regression-tested.
  • Roth linear violations are correctly anchored to the omitted reference period.
  • dCDH aggregation and common-reference deviations are documented in the Registry.
  • No new P1+ issues were identified.
  • Runtime tests were unavailable because pytest is not installed; AST parsing succeeded for 263 Python files.

Methodology

Previous P1 resolved

  • Severity: P3 — informational
  • Impact: Effects, SEs, labels, and covariance are chronologically aligned before positional violation weights are applied.
  • Location: diff_diff/pretrends.py:L1535-L1568, tests/test_event_study_consumers.py:L595-L610
  • Concrete fix: None.

Documented methodology decisions

  • Severity: P3 — informational
  • Impact: Reference anchoring, gapped-grid rejection, varying-base warnings, and the dCDH view behavior agree with the cited sources or explicit Registry notes.
  • Location: docs/methodology/REGISTRY.md:L1062-L1064, docs/methodology/REGISTRY.md:L4651, docs/methodology/REGISTRY.md:L4717-L4718
  • Concrete fix: None.

Code Quality

No findings.

Performance

No findings.

Maintainability

No findings.

Tech Debt

  • Severity: P3 — informational
  • Impact: The unresolved varying-base linear-violation transformation is explicitly tracked.
  • Location: TODO.md:L24
  • Concrete fix: None required for this PR.

Security

No findings or accidental secrets observed.

Documentation/Tests

  • Severity: P3 — informational
  • Impact: Bootstrap guidance and the updated business-report numerical pin match runtime semantics and reference-anchored weights.
  • Location: diff_diff/staggered.py:L1863-L1871, tests/test_business_report.py:L2607-L2636
  • Concrete fix: None.

@igerber
igerber merged commit 6fd82bd into main Aug 3, 2026
31 checks passed
@igerber
igerber deleted the feat/v4-aggregate-shims branch August 3, 2026 10:52
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