Skip to content

spillover-conley: dedup serial Bartlett kernel + PSD guard helpers#489

Merged
igerber merged 3 commits into
mainfrom
spillover-conley-bartlett-dedup
May 25, 2026
Merged

spillover-conley: dedup serial Bartlett kernel + PSD guard helpers#489
igerber merged 3 commits into
mainfrom
spillover-conley-bartlett-dedup

Conversation

@igerber
Copy link
Copy Markdown
Owner

@igerber igerber commented May 24, 2026

Summary

  • Extract _serial_bartlett_kernel_matrix(t_codes, L) and _validate_meat_psd(M, *, error_msg, warning_template, stacklevel=3) to diff_diff/conley.py, collapsing 3 inline kernel constructions (conley.py panel-block + 2 two_stage.py survey branches) and 2 inline finite/eigvalsh PSD guards into shared helpers. No behavior change — the methodology lock (1 - |t-s|/(L+1) Bartlett weights, panel-wide dense time codes, < -1e-12 PSD threshold) now lives behind named helpers; survey and no-survey paths cannot drift on kernel weights or diagnostics.
  • Add 12 new tests: 5 kernel-matrix (hand-computed L=2/L=1/L=0/single-element + int-vs-float bit-equality contract), 4 PSD-guard (non-finite raises, negative-eigenvalue warns with {eigval:.2e} substitution, PSD silent, threshold boundary at -5e-13 silent), 3 stacklevel-attribution (unit test on the helper mechanic, end-to-end via _compute_conley_vcov proving warnings bubble to user code, static source check pinning stacklevel=3 on the survey orchestrator call site).
  • REGISTRY one-liners at both the no-survey Conley section and the SpilloverDiD Wave E.2 follow-up panel-block section; drop the Bartlett-dedup row from TODO.md (L150 compute_survey_metadata extraction is a separate open item — left untouched).

Methodology references (required if estimator / math changes)

  • Method name(s): ConleySpatialHAC (no-survey panel-block path) + SpilloverDiD Wave E.2 follow-up survey-side stratified-Conley + serial Bartlett path.
  • Paper / source link(s): Conley, T.G. (1999), JoE 92(1), pp. 1-45; Newey, W.K. and West, K.D. (1987), Econometrica 55(3), pp. 703-708; R conleyreg (Düsterhöft 2021, time_dist.cpp) for the 1-D radial Bartlett deviation. All as already documented in docs/methodology/REGISTRY.md.
  • Any intentional deviations from the source (and why): None new. Helpers preserve the existing documented practitioner specialization (1-D radial Bartlett, not Conley 1999 Eq 3.14's 2-D separable product window) and the existing < -1e-12 PSD threshold; the REGISTRY one-liners explicitly cross-reference the no-survey and survey paths through the shared helpers.

Validation

  • Tests added/updated: tests/test_conley_vcov.py extended with 12 new tests (5 in TestConleyKernels, 7 in new TestValidateMeatPsd class). Existing PSD-warning monkey-patch tests at tests/test_conley_vcov.py::TestConleyDirectHelper::{test_uniform_kernel_negative_eigenvalue_warns, test_indefinite_meat_warning_fires_for_bartlett} still pass byte-identically (substring \"bartlett\"/\"uniform\"/\"negative eigenvalue\" preserved in warning messages). The methodology anchor at tests/test_spillover.py::TestSpilloverDiDWaveE2FollowupConleySurveyLagCutoff (21 hand-computed Bartlett HAC tests including the L=1 anchor at L7054-7062) remains bit-equal pre/post refactor — pure refactor confirmed via 575-test green slice across tests/test_conley_vcov.py + tests/test_two_stage.py + tests/test_spillover.py.
  • Backtest / simulation / notebook evidence (if applicable): N/A — internal helper extraction with no public API surface change.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes.

Generated with Claude Code

igerber and others added 2 commits May 24, 2026 11:45
Extract _serial_bartlett_kernel_matrix(t_codes, L) and
_validate_meat_psd(M, *, error_msg, warning_template, stacklevel=3) to
diff_diff/conley.py. Collapses three inline kernel constructions
(conley.py panel-block, two_stage.py survey singleton-adjust,
two_stage.py survey multi-PSU) and two inline finite+eigvalsh guards
(conley.py _compute_conley_meat, two_stage.py survey orchestrator)
into shared helpers, so no-survey and survey paths cannot drift on
kernel weights, finite check, or the -1e-12 PSD threshold.

No behavior change. Methodology anchor at
tests/test_spillover.py::TestSpilloverDiDWaveE2FollowupConleySurveyLagCutoff
(21 tests) and the existing PSD-warning monkey-patch tests at
tests/test_conley_vcov.py:{654,709} still pass byte-identically
(substring "bartlett"/"uniform"/"negative eigenvalue" preserved).

Adds 9 new unit tests: 5 kernel-matrix (hand-computed L=2/L=1/L=0/
single-element + int-vs-float bit-equality contract) and 4 PSD-guard
(non-finite raises with caller's error_msg, negative eigval warns with
{eigval:.2e} substitution, PSD silent, threshold boundary at -5e-13
silent). REGISTRY one-liner at both the no-survey Conley section and
the Wave E.2 follow-up panel-block section. Drops the Bartlett-dedup
row from TODO.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds three defensive tests in TestValidateMeatPsd that lock the
stacklevel contract on both helper-call surfaces:

- test_stacklevel_attributes_to_caller_frame: unit test on a
  2-deep wrapper proving stacklevel=3 attributes the warning to
  the caller's caller (outer wrapper), not to the helper frame.
- test_no_survey_path_attributes_warning_to_user_code: end-to-end
  warning-capture via _compute_conley_vcov with a monkey-patched
  indefinite-bartlett kernel; asserts the warning bubbles through
  the three internal frames (_validate_meat_psd → _compute_conley_meat
  → _compute_conley_vcov) to land at user code. Locks stacklevel=4
  at the no-survey call site.
- test_survey_call_site_passes_stacklevel_3: static source check on
  _compute_stratified_conley_meat (two_stage.py) confirming it passes
  stacklevel=3 to _validate_meat_psd. The +1 frame shift from the
  helper extraction (pre-extraction was stacklevel=2 inline) means
  regressing this kwarg would silently re-attribute warnings to
  inside the helper.

No source changes; tests only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Overall Assessment

✅ Looks good

Executive Summary

  • Affected methods: the no-survey Conley panel-block meat path and the SpilloverDiD Wave E.2 follow-up survey path.
  • No P0/P1 findings. The refactor preserves the documented serial Bartlett weights, dense-time-code convention, and < -1e-12 PSD warning threshold.
  • I did not find an undocumented methodology deviation, incorrect variance/SE change, NaN/inference regression, or incomplete adoption of the shared helpers in the changed production paths.
  • One P3 informational item: the new survey warning-attribution check is a static source inspection, not a behavioral test.
  • I could not execute the tests locally because this review environment does not have numpy or pytest.

Methodology

  • No findings. The extracted helpers in diff_diff/conley.py:L368-L420 are used by the no-survey Conley panel-block path at diff_diff/conley.py:L1004-L1041 and the survey serial/PSD paths at diff_diff/two_stage.py:L908-L937 and diff_diff/two_stage.py:L1165-L1198. That matches the documented specialization and threshold in docs/methodology/REGISTRY.md:L3427-L3429 and docs/methodology/REGISTRY.md:L3587-L3589; I did not find a source-material mismatch or new undocumented deviation.

Code Quality

  • No findings. No new inline inference or partial-NaN anti-patterns were introduced, and the shared guard still fail-closes on non-finite meat before eigvalsh at diff_diff/conley.py:L386-L420.

Performance

  • No findings. This is a mechanical refactor of existing kernel/PSD work, not a new asymptotic cost.

Maintainability

  • No findings. Centralizing the serial Bartlett kernel and the finite/PSD guard materially reduces drift risk between survey and no-survey implementations.

Tech Debt

  • No findings. Removing the matching TODO row is consistent with the code change; I did not see new untracked deferrable work in the changed logic.

Security

  • No findings.

Documentation/Tests

  • Severity P3 | Impact: tests/test_conley_vcov.py:L840-L864 verifies the survey-path stacklevel=3 contract by inspect.getsource() substring matching against diff_diff/two_stage.py:L919-L936, not by forcing a real survey-path warning. That can false-pass if the literal survives while the runtime call stack changes, so a future warning-attribution regression could slip through undetected. Concrete fix: replace or supplement it with a runtime warning-capture test that drives _compute_stratified_conley_meat or the smallest caller above it through an indefinite combined meat and asserts the warning is attributed to the intended outer frame.

@igerber
Copy link
Copy Markdown
Owner Author

igerber commented May 24, 2026

/ai-review

@github-actions
Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 000d44faef3d7c53b30fddd9906c6bdf343e468c


Overall Assessment

✅ Looks good

Executive Summary

  • Affected methods: ConleySpatialHAC no-survey panel-block meat and SpilloverDiD Wave E.2 follow-up survey stratified-Conley + serial Bartlett meat.
  • I cross-checked the extracted _serial_bartlett_kernel_matrix() and _validate_meat_psd() against the prior inline logic, the in-code methodology notes, and docs/methodology/REGISTRY.md; I did not find an undocumented methodology deviation or variance/SE change.
  • The production refactor is complete across the changed paths: the three serial Bartlett constructions and both finite/PSD guards now share one implementation, which reduces drift risk without changing the documented < -1e-12 PSD threshold.
  • No new inline inference or partial-NaN anti-patterns were introduced in the changed code.
  • [Previously identified] One P3 remains: the survey warning-attribution check is still a static source inspection, while the new runtime attribution test only covers the no-survey path.
  • Verification was source-only; this environment does not have numpy, pandas, scipy, or pytest.

Methodology

Affected methods: ConleySpatialHAC and SpilloverDiD Wave E.2 follow-up. No findings. _serial_bartlett_kernel_matrix() preserves the documented off-diagonal Newey-West Bartlett weights on dense time codes, and _validate_meat_psd() preserves the documented non-finite error plus the < -1e-12 indefiniteness warning threshold at diff_diff/conley.py:L368-L420, diff_diff/conley.py:L1004-L1041, diff_diff/two_stage.py:L919-L937, and diff_diff/two_stage.py:L1165-L1204. That matches the documented Conley/Newey-West specialization in docs/methodology/REGISTRY.md:L3427-L3429 and docs/methodology/REGISTRY.md:L3587-L3589.

Code Quality

No findings. The refactor removes duplicated kernel/PSD logic without introducing new inline inference or NaN-handling anti-patterns.

Performance

No findings. This is a mechanical extraction of existing kernel and eigvalsh work; asymptotic cost is unchanged.

Maintainability

No findings. Centralizing the serial Bartlett kernel and PSD guard materially reduces drift risk between the survey and no-survey implementations.

Tech Debt

No findings. Removing the closed Bartlett-dedup item from TODO.md is consistent with the actual helper extraction, and I did not find new untracked deferrable debt in the changed production code.

Security

No findings.

Documentation/Tests

  • Severity P3 | Impact: [Previously identified] tests/test_conley_vcov.py:L840-L865 still validates the survey-path warning attribution by inspect.getsource() substring matching, not by executing the survey path. That can false-pass if runtime attribution drifts while the literal stacklevel=3 remains. The new runtime attribution guard at tests/test_conley_vcov.py:L772-L838 only covers the no-survey path. Concrete fix: add a runtime warning-capture test that forces an indefinite combined survey meat through _compute_stratified_conley_meat or the smallest caller above it and asserts the warning is attributed to the intended outer frame.

…ution

Adds test_survey_path_attributes_warning_to_user_code in
TestValidateMeatPsd. Mirrors the existing no-survey runtime
attribution pattern (monkey-patch the kernel helper to force an
indefinite combined meat) but exercises the SURVEY orchestrator
_compute_stratified_conley_meat with conley_lag_cutoff=1. Captures
the resulting PSD UserWarning and asserts attribution lands at user
code (this test file), proving stacklevel=3 correctly bubbles the
warning through both the helper (conley.py) and the orchestrator
(two_stage.py).

CI codex flagged the prior survey-path coverage as too weak: the
existing test_survey_call_site_passes_stacklevel_3 is a static
inspect.getsource() substring check on `stacklevel=3`, which can
false-pass if runtime attribution drifts while the literal text
remains. This commit adds the missing runtime check; the static
check is retained as a complementary fast-fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 502ea2f55de4d6b6f342b435b6557dc3f1031d42


Overall Assessment

✅ Looks good

Executive Summary

  • Affected methods: ConleySpatialHAC no-survey panel-block meat and SpilloverDiD Wave E.2 follow-up stratified-Conley + serial Bartlett meat.
  • I cross-checked the extracted helpers against the Methodology Registry and the in-code methodology notes; I did not find an undocumented methodology deviation or variance/SE change.
  • The touched production paths appear fully deduplicated: the three serial Bartlett constructions and both finite/PSD guards now share one implementation in diff_diff/conley.py.
  • The previous re-review item is resolved: survey-path warning attribution now has a runtime test in tests/test_conley_vcov.py:L869-L962, not only a source-string check.
  • No new inline inference or partial-NaN anti-patterns were introduced in the changed code.
  • Verification was source-only; I could not run the test suite here because importing the package fails without numpy in this environment.

Methodology

No findings. _serial_bartlett_kernel_matrix() preserves the documented Newey-West weights on dense time codes, and _validate_meat_psd() preserves the existing non-finite error plus < -1e-12 PSD-warning contract across both the no-survey and survey panel-block paths in diff_diff/conley.py:L368-L420, diff_diff/conley.py:L1004-L1041, diff_diff/two_stage.py:L908-L937, and diff_diff/two_stage.py:L1165-L1204. That matches the documented specialization and shared-helper notes in docs/methodology/REGISTRY.md:L3427-L3429 and docs/methodology/REGISTRY.md:L3587-L3589.

Code Quality

No findings. The refactor is complete within the touched production code: I did not find leftover inline serial Bartlett matrix construction or duplicated finite-plus-eigvalsh PSD guards in diff_diff/conley.py or diff_diff/two_stage.py.

Performance

No findings. This is a mechanical extraction of existing dense kernel and eigenvalue work; asymptotic cost and memory behavior are unchanged.

Maintainability

No findings. Moving the serial Bartlett kernel and PSD guard into shared helpers materially reduces drift risk between the Conley no-survey path and the Spillover/TwoStage survey path.

Tech Debt

No findings. Removing the closed Bartlett-dedup row from TODO.md is consistent with the actual extraction, and the remaining nearby follow-ups stay tracked in TODO.md:L154-L162.

Security

No findings.

Documentation/Tests

No findings. The new helper coverage is adequate for the refactor surface: kernel/PSD helper tests were added at tests/test_conley_vcov.py:L112-L163 and tests/test_conley_vcov.py:L667-L838, and the previously identified survey-path attribution gap is now closed by the runtime warning test at tests/test_conley_vcov.py:L869-L962. Residual risk: this was a source-only re-review because the local environment cannot import the package without numpy.

@igerber igerber added the ready-for-ci Triggers CI test workflows label May 25, 2026
@igerber igerber merged commit 85f1fc2 into main May 25, 2026
33 of 34 checks passed
@igerber igerber deleted the spillover-conley-bartlett-dedup branch May 25, 2026 13:27
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