Skip to content

chore(types): mypy triage to enforceable zero + blocking CI job (3/3)#683

Merged
igerber merged 1 commit into
mainfrom
chore/mypy-triage
Jul 13, 2026
Merged

chore(types): mypy triage to enforceable zero + blocking CI job (3/3)#683
igerber merged 1 commit into
mainfrom
chore/mypy-triage

Conversation

@igerber

@igerber igerber commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Summary

Final part of the lint/type-hygiene program (#677/#679 normalization, #681 Lint workflow). mypy diff_diff goes from 184 errors to an enforced zero, and the Lint workflow gains a blocking Mypy job.

Error-class disposition (184 baseline, mid-triage peak 193 from annotation exposure):

Class Count Disposition
[index] 79 ~55 real fixes (float-keyed dict annotations, Optional narrowing, list-vs-array rebinds masked by the disabled assignment code) 14 -> prep_dgp per-module override (seeded-RNG-stream-load-bearing Optional arrays); rest fixed
[attr-defined] 51 Root cause: 19 survey_design/resolved_survey: object params across 13 files -> properly typed via TYPE_CHECKING imports; one attribute inferred as type None from unannotated = None init; one missing mixin stub all fixed
[union-attr] 21 (+conversions) assert ... is not None narrowings at flag-guarded blocks, each documenting a real invariant (incl. a 5-estimator copy-pasted twin block) all fixed
[misc] 11 Stale return-arity annotations + flag-dependent returns -> Literal overloads (_compute_aggregated_se_with_wif, _compute_overall_att); a variable reused for two types renamed; mypy's union-explosion give-up -> 2 reasoned inline ignores fixed / 2 ignores
[call-overload] 6-8 solve_ols overload chain unbroken (helper moved out from between @overload block and implementation); sentinel str key in int-keyed dCDH dicts -> scoped documented ignores fixed / 4 ignores
[no-redef] 4, [operator] 8, rest Duplicate conflicting annotations dropped; Optional comparisons narrowed all fixed

Net: ~170 of 184 resolved by real fixes; suppressions are one module override + matplotlib/plotly stub-parity skips + a handful of reasoned inline ignores. warn_unused_ignores = true added — it immediately surfaced 23 stale pre-existing ignores, now removed.

CI: lint.yml gains the Mypy job (pinned mypy==2.1.0 + pinned numpy/pandas/scipy so stubs cannot drift CI-vs-local; the package is NOT installed — no maturin/Rust). Lint Gate needs extended — no branch-protection change needed (the gate name is unchanged by design). TestLintWorkflowPinSync now covers the mypy pin, and a new test_mypy_job_stub_deps_exact_pinned locks the stub pins.

Docs: TODO.md "Type Annotations" note rewritten to the honest enforced-zero-with-listed-suppressions state; "Large Module Files" table regenerated from live counts (dated 2026-07-13); suppression-tightening filed as an Actionable Backlog row. CONTRIBUTING/CLAUDE.md updated to the all-three-tools-mirrored state.

Local review: 3 codex rounds (force-fresh), converged to zero actionable findings; round findings (stub-pin guard test, workflow header comment) folded in.

Methodology references (required if estimator / math changes)

  • Method name(s): N/A - no estimator equations, weights, control groups, inference families, or defaults changed (typing, narrowing asserts on documented invariants, and CI only). Local codex review cross-checked the touched estimator files against the registry.
  • Paper / source link(s): N/A
  • Any intentional deviations from the source (and why): None

Validation

  • Tests added/updated: 2 new workflow-guard tests (mypy pin sync, stub-dep pins); tests/test_openai_review.py -m '' green (254). Targeted estimator batteries across every touched file: 3,434 tests passed, zero narrowing-assert fires (survey, linalg, estimators, staggered, sun_abraham, imputation, two_stage, cdh, efficient_did, spillover, diagnostics, prep, bootstrap_chunking, survey_phase6, had_pretests, wooldridge, triple_diff, staggered_triple_diff, continuous_did, pretrends, bootstrap_utils, methodology_synthetic_control).
  • Backtest / simulation / notebook evidence (if applicable): N/A - mypy error count was verified byte-stable (184) across the pure-formatting stages, and mypy diff_diff now reports zero.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

Generated with Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01Lbd6nqWmg4snvvBmegwqiw

Tier 1 (real fixes):
- 19 survey params typed `object` across 13 estimator files -> proper
  Optional[SurveyDesign]/Optional[ResolvedSurveyDesign] via TYPE_CHECKING
  imports; linalg's param typed as the SurveyDesign|ResolvedSurveyDesign
  union its isinstance branches actually handle
- solve_ols overload chain unbroken (_validate_weights moved out from
  between the @overload block and the implementation)
- stale return-arity annotations fixed: _aggregate_simple (2->3-tuple),
  _compute_cluster_psi_sums (2->3-tuple), plus Literal overloads for the
  flag-dependent returns of _compute_aggregated_se_with_wif and
  SunAbraham._compute_overall_att
- cdh: float-keyed diagnostics dict annotated str-keyed; duplicate inner
  annotations dropped; a variable reused for two types in the bootstrap
  renamed; sentinel "_a11_warnings" str key in int-keyed dicts documented
  with scoped ignores
- missing mixin attribute stub (TwoStageDiDBootstrapMixin.pretrends);
  efficient_did._unit_resolved_survey no longer inferred as type None
- ~50 assert-based narrowings at flag-guarded blocks, each documenting a
  real invariant (all verified: 3,434 targeted tests pass, no assert fires)

Tier 2/3 (documented suppressions):
- prep_dgp per-module [index] override (seeded-DGP Optional covariate
  arrays; None-vs-array shape is RNG-stream-load-bearing)
- matplotlib/plotly follow_imports=skip for local/CI stub parity
- warn_unused_ignores=true; 23 already-stale ignores removed
- reasoned inline ignores for mypy's union-explosion limitation

CI: lint.yml gains a blocking Mypy job (pinned mypy + numpy/pandas/scipy,
no package build); Lint Gate needs extended; TestLintWorkflowPinSync now
covers the mypy pin. TODO.md Type Annotations note rewritten to the honest
enforced-zero state; Large Module Files table regenerated (2026-07-13);
override-tightening tracked as an Actionable row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbd6nqWmg4snvvBmegwqiw
@github-actions

Copy link
Copy Markdown

Overall Assessment

Looks good — no unmitigated P0 or P1 findings.

Executive Summary

  • No estimator equations, weighting formulas, variance/SE formulas, identification defaults, or public defaults appear to change in the provided diff.
  • Methodology-adjacent edits are type annotations, narrowing asserts, overloads, and documented invariant guards.
  • No new inline inference anti-patterns or partial NaN guards were found in the changed estimator files.
  • The remaining mypy suppressions are explicitly documented and tracked in TODO.md, so they are P3 informational.
  • The new Mypy job is wired into Lint Gate, and guard tests cover tool/stub pins.

Methodology

  • Severity: P3 informational
    Impact: Affected methods include survey-aware and inference-adjacent paths across dCDH, Callaway-Sant’Anna, EfficientDiD, SunAbraham, ImputationDiD, TwoStageDiD, SpilloverDiD, TWFE/DiD, and shared survey/linalg helpers. The changed lines do not alter the documented formulas; relevant survey bootstrap, replicate-weight, dCDH allocator, and Spillover Conley+survey choices are already documented in docs/methodology/REGISTRY.md:L998-L1040, docs/methodology/REGISTRY.md:L4912-L5068, and TODO.md:L77.
    Concrete fix: None required.

Code Quality

  • Severity: None
    Impact: The new assert ... is not None statements are invariant narrowings around already-gated branches, not replacement validation for user inputs. No new inline t_stat = effect / se pattern was introduced outside the existing safe_inference() implementation.
    Concrete fix: None.

Performance

  • Severity: None
    Impact: The PR adds a CI typecheck job but does not introduce material runtime changes in estimator code.
    Concrete fix: None.

Maintainability

  • Severity: None
    Impact: The overloads and TYPE_CHECKING imports improve static contracts without changing runtime APIs. The workflow pin-sync tests reduce future CI/local drift risk.
    Concrete fix: None.

Tech Debt

  • Severity: P3 informational
    Impact: Enforced-zero mypy still relies on global disabled codes and a prep_dgp per-module [index] override in pyproject.toml:L152-L182. This is explicitly tracked in TODO.md:L51 and summarized in TODO.md:L244-L256, so it is not a blocker under the deferred-work policy.
    Concrete fix: No action required for this PR; burn down the TODO row later.

Security

  • Severity: None
    Impact: No new secrets or credential material were found in the changed diff. Existing secret-like strings are test fixtures in tests/test_openai_review.py, not live credentials.
    Concrete fix: None.

Documentation/Tests

  • Severity: P3 informational
    Impact: The new Mypy workflow is included in the stable gate at .github/workflows/lint.yml:L48-L82, and tests cover tool pin sync plus exact stub dependency pins at tests/test_openai_review.py:L4682-L4724. I also verified the pinned package versions are resolvable on PyPI for mypy, numpy, pandas, scipy, black, and ruff. (pypi.org)
    Concrete fix: None.

I did not run the full test suite in this review environment; this was a static diff and methodology review.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Jul 13, 2026
@igerber igerber merged commit 8dc3421 into main Jul 13, 2026
44 of 45 checks passed
@igerber igerber deleted the chore/mypy-triage branch July 13, 2026 13:52
igerber added a commit that referenced this pull request Jul 13, 2026
…job)

The type: ignore[assignment] comments on the beta_p_* dataclass fields are
unused under the pinned CI mypy config, which now fails on unused ignores
(warn_unused_ignores = true).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4
igerber added a commit that referenced this pull request Jul 13, 2026
…0 parity (RDD PR-2) (#684)

* feat: RegressionDiscontinuity - sharp RD estimator with rdrobust parity

Estimation-path port + public estimator + docs sweep (PR-2 of 2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4

* fix: run mass-point detection before the bandwidth branch + reject bools on integer knobs

rdrobust.R:365-380 runs the mass-point check in rdrobust() itself, ahead of
the manual-vs-data-driven bandwidth split, so the warning must fire on
manual-h fits too (verified on installed 4.0.0). fit() now performs the
detection and the port's rdbwselect-level warning is suppressed on the
estimator path to mirror R's single warning per fit. Integer knobs
(p/q/nnmatch/bwcheck) now reject bool (int subclass) inputs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4

* chore: drop stale mypy ignores after rebase onto #683 (blocking mypy job)

The type: ignore[assignment] comments on the beta_p_* dataclass fields are
unused under the pinned CI mypy config, which now fails on unused ignores
(warn_unused_ignores = true).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGca52n6H8oDDXALjjrsp4

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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