Skip to content

Add inference-field aliases on staggered result classes#406

Merged
igerber merged 3 commits intomainfrom
feature/result-class-inference-aliases
May 9, 2026
Merged

Add inference-field aliases on staggered result classes#406
igerber merged 3 commits intomainfrom
feature/result-class-inference-aliases

Conversation

@igerber
Copy link
Copy Markdown
Owner

@igerber igerber commented May 9, 2026

Summary

  • Adds read-only @property aliases (att / se / conf_int / p_value / t_stat) on every staggered result class that previously only carried prefixed canonical fields (overall_*, overall_att_*, avg_*).
  • Fixes a contract gap where getattr(res, "se", None) and getattr(res, "conf_int", None) returned None on every staggered result class — silently dropping se / conf_int from any external adapter that read them via flat getattr.
  • 11 result classes touched, 59 read-only properties added, no behavior change. Backwards compatible (the overall_* / overall_att_* / avg_* fields remain canonical).

Methodology references (required if estimator / math changes)

  • Method name(s): N/A — this is a result-class API extension, no methodology change. Aliases are pure read-throughs over canonical fields.
  • Paper / source link(s): N/A
  • Any intentional deviations from the source (and why): None. The safe_inference() joint-NaN consistency contract (per CLAUDE.md "Inference computation") is inherited automatically because aliases never recompute — NaN canonical → NaN alias is regression-locked.

Validation

  • Tests added/updated: tests/test_result_aliases.py (23 tests: 9 Pattern-B alias-mechanics + 9 Pattern-B NaN-propagation + 3 ContinuousDiD + 1 MultiPeriod + 1 balance-adapter regression)
  • Backtest / simulation / notebook evidence (if applicable): N/A. Verified empirically against a real CallawaySantAnnaResults fit pre-PR (returned se=None / conf_int=None) and post-PR (returns populated values).
  • Existing per-estimator suites (482 tests across test_chaisemartin_dhaultfoeuille.py / test_staggered.py / test_continuous_did.py) all green; full per-estimator sweep (1128 tests) green pre-rebase.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

Coverage map

Pattern Mapping Result classes
B overall_* → flat (5 props each) CallawaySantAnna, Stacked, EfficientDiD, dCDH, StaggeredTripleDiff, Wooldridge, SunAbraham, ImputationDiD, TwoStage
C overall_att_* → flat + overall_* (9 props) ContinuousDiD (ATT-side as headline; ACRT-side accessible unchanged via overall_acrt_*)
D avg_* → flat (5 props) MultiPeriodDiD

Per-result-class fields enumerated in docs/methodology/REGISTRY.md preamble.


Generated with Claude Code

Adds read-only @Property aliases (att / se / conf_int / p_value / t_stat)
on every result class that previously only carried prefixed canonical
fields, so external adapters that read getattr(res, "se") get populated
values transparently.

Coverage:
- Pattern B (overall_*): CallawaySantAnna, Stacked, EfficientDiD, dCDH,
  StaggeredTripleDiff, Wooldridge, SunAbraham, ImputationDiD, TwoStage
- Pattern C (overall_att_*, ATT-side headline): ContinuousDiD — adds both
  flat aliases and overall_* aliases for naming consistency
- Pattern D (avg_*): MultiPeriodDiD

Aliases are pure read-throughs over canonical fields — no recomputation,
no behavior change. NaN-canonical → NaN-alias inheritance is regression-
locked at tests/test_result_aliases.py::test_pattern_b_aliases_propagate_nan.
The native overall_* / overall_att_* / avg_* names remain canonical for
documentation and computation.

Motivated by an external adapter that reads getattr(res, "se", None)
without a fallback to overall_se / overall_att_se. Pre-alias every
staggered result class returned None on those keys; aliases fix the
adapter's diagnostic surface transparently with no consumer-side change.

23 alias-mechanic + adapter-pattern regression tests at
tests/test_result_aliases.py. Documented in CHANGELOG (Unreleased) and
REGISTRY.md preamble.

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

github-actions Bot commented May 9, 2026

Overall Assessment

Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • No estimator math, weighting, variance/SE, identification, or default behavior changes detected.
  • Alias properties are pure read-throughs to canonical fields, so they inherit existing inference and NaN behavior.
  • Sibling-surface audit found the intended scalar result classes covered.
  • Pattern-wide inference grep found no new inline inference computation in the alias changes.
  • Targeted tests could not be run in this review environment because pytest and numpy are unavailable.

Methodology

No P0/P1/P2 findings.

Severity: P3 informational
Impact: The PR is an API-surface extension over existing result fields. The affected result classes are the staggered/prefixed scalar result classes plus MultiPeriodDiDResults and ContinuousDiDResults; the aliases do not recompute ATT, SE, p-values, t-stats, or CIs. This matches the registry’s documented alias contract in docs/methodology/REGISTRY.md:L5.
Concrete fix: None required.

Code Quality

No findings. The aliases are simple read-only properties and do not introduce new inference logic.

Performance

No findings. Property access is constant-time and does not add material overhead.

Maintainability

No blocking findings. Sibling-surface mirror audit checked the parallel result naming surfaces: overall_*, overall_att_*, avg_*, and native flat fields.

Tech Debt

No findings. I did not see new untracked correctness debt, and no TODO mitigation is needed for this PR.

Security

No findings. Secret-pattern grep over the changed files found no obvious accidental credentials.

Documentation/Tests

Severity: P3 informational
Impact: The new registry preamble says “three native naming patterns” but enumerates four: flat att fields, overall_*, overall_att_*, and avg_*. This is documentation precision only, not a methodology or runtime defect. See docs/methodology/REGISTRY.md:L5.
Concrete fix: Change “three” to “four” and consider scoping “Every result class” to “Every scalar treatment-effect result class covered by this naming contract.”

Validation note: attempted pytest -q tests/test_result_aliases.py, but this environment lacks pytest; an import probe also showed numpy unavailable.

- Three -> four (the preamble enumerates four native naming patterns:
  flat, overall_*, overall_att_*, avg_*)
- Tighten "Every result class" -> "Every scalar treatment-effect result
  class covered by this naming contract" so the alias contract is
  explicitly scoped to scalar headline classes (excludes per-cell types
  like GroupTimeEffect / PeriodEffect that have flat fields natively)

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

igerber commented May 9, 2026

/ai-review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

🔁 AI review rerun (requested by @igerber)

Head SHA: d987f9e321e48e9259c8c2e4a3b736b78720a6c1


Overall Assessment

Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • This re-review found the prior registry wording issue resolved: REGISTRY.md now documents four naming patterns at docs/methodology/REGISTRY.md:L5.
  • No estimator math, weighting, variance/SE, identification assumptions, or defaults changed.
  • Alias properties are pure read-throughs to canonical fields, including ContinuousDiDResults ATT-side aliases at diff_diff/continuous_did_results.py:L146-L183.
  • Sibling-surface audit covered overall_*, overall_att_*, and avg_* result families; no missing targeted result class found.
  • Pattern-wide inference grep found no new inline inference or partial NaN guard in the changed files.
  • I could not run pytest because this environment lacks pytest and numpy; AST parsing of touched Python files passed.

Methodology

No P0/P1/P2 findings.

Severity: P3 informational
Impact: The affected methods are result API surfaces only: staggered overall_* result classes, ContinuousDiDResults, and MultiPeriodDiDResults. The aliases do not recompute effects, SEs, p-values, t-stats, or CIs, so they inherit canonical inference behavior. Examples: diff_diff/staggered_results.py:L141-L160, diff_diff/results.py:L450-L469.
Concrete fix: None required.

Code Quality

No findings. The implementation is simple property delegation and avoids new inference logic.

Performance

No findings. Property access is constant-time and adds no material runtime cost.

Maintainability

No findings. The registry contract and tests mirror the three changed prefixed surfaces: Pattern B overall_*, Pattern C overall_att_*, and Pattern D avg_*.

Tech Debt

No findings. I did not identify new untracked deferred correctness debt.

Security

No findings. Secret-pattern grep over the changed files did not find obvious credentials.

Documentation/Tests

No blocking findings.

Severity: P3 informational
Impact: Targeted validation could not be executed here because pytest and numpy are unavailable. The new tests do cover alias mechanics, NaN read-through for Pattern B, ContinuousDiD dual aliases, MultiPeriod aliases, and the balance-adapter regression at tests/test_result_aliases.py:L118-L301.
Concrete fix: None required for the PR; rely on CI to run tests/test_result_aliases.py.

Found via local pre-PR validation (wider polymorphic-consumer pytest
sweep + adversarial probe + local AI review).

1. Fix `mock_continuous_results` fixture in tests/test_practitioner.py.
   The fixture was setting `r.overall_se = 0.1` on a fresh
   `ContinuousDiDResults.__new__()` mock. Pre-PR this silently created a
   junk attribute (canonical SE on ContinuousDiDResults is
   `overall_att_se`); post-PR `overall_se` is a read-only @Property alias
   added by this PR, so the assignment raised `AttributeError: can't set
   attribute`. Changed to assign the canonical `overall_att_se` field.
   `_handle_continuous` in practitioner.py never reads the SE field
   anyway — only `_check_nan_att(results)` reads `att`/`overall_att`.

2. Lock read-only alias semantics with a regression test
   (`test_aliases_are_read_only` parametrized over Pattern B / C / D)
   so future contributors who write similar fixtures fail loudly via
   this test rather than via a surprise AttributeError in another suite.

3. One-sentence REGISTRY.md note that aliases are @Property descriptors
   and therefore do NOT appear in `dataclasses.fields()` or
   `dataclasses.asdict()` output, and that assignment raises
   AttributeError — so serializers and field-walkers continue to see
   only the canonical field set.

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

igerber commented May 9, 2026

/ai-review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

🔁 AI review rerun (requested by @igerber)

Head SHA: 5b58d5f5722f9738eec0727ec83ff0721b09d3ae


Overall Assessment

Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • No estimator math, weighting, variance/SE, identification assumptions, or defaults changed.
  • Registry now documents the scalar result-field naming contract and alias semantics at docs/methodology/REGISTRY.md:L5.
  • Alias implementations are pure read-throughs over canonical fields: Pattern B overall_*, ContinuousDiD overall_att_*, and MultiPeriod avg_*.
  • Sibling-surface audit found the alias pattern mirrored across all targeted result families.
  • Pattern-wide inference grep found no new inline inference computation or partial NaN guard in the changed result code.
  • Could not run pytest: this environment has no pytest; AST parse passed for the touched Python files.

Methodology

Severity: P3 informational
Impact: Affected methods are result API surfaces only: CallawaySantAnna/staggered-family results, ContinuousDiD, and MultiPeriodDiD. The new properties do not recompute ATT, SE, t-stat, p-value, or CI, so they inherit existing safe_inference() behavior. Examples: diff_diff/staggered_results.py:L141-L160, diff_diff/continuous_did_results.py:L146-L183, diff_diff/results.py:L450-L469.
Concrete fix: None required.

Code Quality

No findings.

Performance

No findings. Property lookup is constant-time and adds no estimator runtime cost.

Maintainability

No findings. The registry contract and tests cover the three sibling naming surfaces: Pattern B at tests/test_result_aliases.py:L118-L181, ContinuousDiD Pattern C at tests/test_result_aliases.py:L189-L236, and MultiPeriod Pattern D at tests/test_result_aliases.py:L244-L258.

Tech Debt

No findings. I did not identify new untracked deferred correctness debt.

Security

No findings. Secret-pattern grep over changed areas did not reveal credentials.

Documentation/Tests

Severity: P3 informational
Impact: Tests cover alias mechanics, NaN propagation, read-only semantics, and the balance-adapter regression at tests/test_result_aliases.py:L293-L349. tests/test_practitioner.py:L125-L133 was updated for ContinuousDiD’s read-only overall_se alias.
Concrete fix: None required; rely on CI to run pytest because local pytest is unavailable.

@igerber igerber added the ready-for-ci Triggers CI test workflows label May 9, 2026
@igerber igerber merged commit d0c81ab into main May 9, 2026
28 of 29 checks passed
@igerber igerber deleted the feature/result-class-inference-aliases branch May 9, 2026 22:40
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