Skip to content

Commit d8def40

Browse files
igerberclaude
andcommitted
docs: CI review fixes - recognized Note label on the HAD intercept-SE bullet + waived-status wording
- REGISTRY HAD mass-point intercept-SE bullet relabeled with the reviewer-recognized form (**Note (deviation from R — classical intercept SE):**) so the DEFERRED.md decision-record pointer resolves to a labeled Note. - The two HAD Phase-4 checklist tails now say "Recorded as waived in the DEFERRED.md decision record" instead of claiming an active Low-priority follow-up (the row moved to the Decision record in this PR). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H8PDhdrdQH2DM8yAeEnib7
1 parent dba8d2c commit d8def40

1 file changed

Lines changed: 3 additions & 3 deletions

File tree

docs/methodology/REGISTRY.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3377,7 +3377,7 @@ Under `survey_design=SurveyDesign(weights, strata, psu, fpc)`, the variance comp
33773377
- **CR1 pweight-cluster meat**: for each cluster c, `s_c = Z'_c·(w·u)_c`; `Ω_CR1 = (G/(G-1))·((n-1)/(n-k))·Σ_c s_c s_c'` (`w¹` inside cluster score). Bit-exact with `estimatr::iv_robust(..., weights=, clusters=, se_type="stata")` at `atol=1e-10`.
33783378
- **Classical**: sandwich form `Ω_cl = σ²·Z'·diag(w²)·Z` with `σ² = Σw²u²/(Σw-k)`. Deviates from `estimatr` classical (projection-form + `n-k` DOF) by `O(1/n)` at non-uniform weights; unweighted path is bit-exact by equivalence. Skipped in cross-language parity tests.
33793379
- **Per-unit IF on β̂-scale** (for Binder-TSL survey composition): `psi_g = [(Z'WX)^{-1} · z_g · w_g · u_g][1] · sqrt((n-1)/(n-k))`. The scaling factor absorbs DOF / small-sample differences so `compute_survey_if_variance(psi, trivial_resolved) ≈ V_HC1[1,1]` at `atol=1e-10` (mirrors PR #359 convention; asserted by `TestIFScaleInvariant` and bit-exact against estimatr HC1 on 4 DGPs). Fixture: `benchmarks/R/generate_estimatr_iv_robust_golden.R` → `benchmarks/data/estimatr_iv_robust_golden.json`.
3380-
- **Intercept SE (`return_intercept_se=True`)**: the 2×2 sandwich `V` already carries the intercept variance `V[0,0]`; the opt-in hook surfaces `sqrt(V[0,0])` (default off — the production 3-tuple return is byte-unchanged, so no public-API change). HC1 and CR1 intercept SEs are bit-exact with `estimatr` `se_intercept` at `atol=1e-10` (`tests/test_estimatr_iv_robust_parity.py`); the **classical** intercept carries the same `O(1/n)` projection/DOF deviation as the slope (bullet above) and is likewise excluded from the parity lock.
3380+
- **Note (deviation from R — classical intercept SE):** `return_intercept_se=True`: the 2×2 sandwich `V` already carries the intercept variance `V[0,0]`; the opt-in hook surfaces `sqrt(V[0,0])` (default off — the production 3-tuple return is byte-unchanged, so no public-API change). HC1 and CR1 intercept SEs are bit-exact with `estimatr` `se_intercept` at `atol=1e-10` (`tests/test_estimatr_iv_robust_parity.py`); the **classical** intercept carries the same `O(1/n)` projection/DOF deviation as the slope (bullet above) and is likewise excluded from the parity lock.
33813381

33823382
*Event-study survey composition (Phase 4.5 B):* The per-horizon loop in `_fit_event_study` threads `weights_unit_full` + `resolved_survey_unit_full` through to both `_fit_continuous` and `_fit_mass_point_2sls` (the latter with `return_influence=True` under weighted fits). The returned IF matrix `Psi ∈ R^{G × H}` has a shared construction contract across paths — each column on the β̂-scale, such that `compute_survey_if_variance(Psi[:, e], resolved) ≈ V_β[e]`. Per-horizon analytical variance uses Binder-TSL via `compute_survey_if_variance` (the `survey_design=` path). `survey_metadata`, `variance_formula` (`"survey_binder_tsl"` / `"survey_binder_tsl_2sls"`), and `effective_dose_mean` populate identically to the static path. Pre-PR numerical output is preserved bit-exactly on the unweighted path when `cband=False` (stability invariant; Phase 2b convention unchanged for unweighted fits).
33833383

@@ -3601,8 +3601,8 @@ Shipped in `diff_diff/had_pretests.py` as `stute_joint_pretest()` (residuals-in
36013601
- [x] Phase 3: `stute_test()` Cramér-von Mises with Mammen wild bootstrap. Statistic `S = (1/G^2) Σ (cumsum_g)^2` (algebraically equivalent to paper's `Σ(g/G)^2 · ((1/g) Σ eps_{(h)})^2`). Bootstrap follows paper Appendix D Algorithm literal (per-iteration OLS refit). `n_bootstrap=999` default, `n_bootstrap >= 99` validated. `G < 10` returns NaN; `G > 100_000` emits a `UserWarning` pointing to `yatchew_hr_test`. The ~2× performance follow-up landed 2026-07-07 (loop-invariant hoisting, bit-identical — see the Phase 3 Stute bootstrap Note above).
36023602
- [x] Phase 3: `yatchew_hr_test()` heteroskedasticity-robust specification test. Test statistic `T_hr = sqrt(G) · (σ̂²_lin - σ̂²_diff) / σ̂²_W` from paper Equation 29. Normalizer `σ̂²_diff` divides by `2G` (paper-literal Theorem 7), NOT `2(G-1)`; hand-computed tight parity asserted at `atol=1e-12`. One-sided standard-normal critical value. `G < 3` returns NaN. Phase 3 shipped only the linearity null (paper Theorem 7); the `null="mean_independence"` R-parity extension shipped post-PR #392 (see the algorithm-variant block above for the contract).
36033603
- [x] Phase 3: `did_had_pretest_workflow()` composite helper. `data`-only entry point with `aggregate` dispatch: `aggregate="overall"` (default) requires a balanced two-period panel — multi-period panels are rejected at the front door by `_validate_had_panel` with a pointer to `aggregate="event_study"` — and runs steps 1 (QUG) + 3 (Stute + Yatchew-HR) only; `aggregate="event_study"` takes a multi-period panel (>=3 periods) and additionally runs step 2 (joint Stute pre-trends over pre-period horizons) + joint Stute homogeneity over post-period horizons, populating `pretrends_joint` / `homogeneity_joint`. `seed` forwards to all bootstrap-based tests (QUG and Yatchew are deterministic). Returns `HADPretestReport` with priority-ordered verdict string. On `aggregate="overall"` a fail-to-reject verdict explicitly flags the Assumption 7 gap rather than claiming unconditional TWFE safety: `"QUG and linearity diagnostics fail-to-reject; Assumption 7 pre-trends test NOT run (paper step 2 deferred)"`; on `aggregate="event_study"` a fail-to-reject across all three covered diagnostics reads `"TWFE admissible under Section 4 assumptions"` without the Assumption 7 caveat. Verdict priority follows the paper's one-way rule (TWFE admissible only if NO test rejects): **conclusive rejections are the primary verdict and are NEVER hidden by inconclusive status** — any unresolved-step note is appended via `"; additional steps unresolved: ..."` rather than replacing the rejection. The pure `"inconclusive - QUG NaN"` / `"inconclusive - both Stute and Yatchew linearity tests NaN"` forms only fire when NO conclusive test rejects AND a required step is unresolved. The partial-workflow fail-to-reject verdict may carry a `"(Yatchew NaN - skipped)"` (or Stute) suffix when one linearity test is NaN but the other is conclusive (step 3 resolved via the paper's "Stute OR Yatchew" wording). Bundled rejection-reason strings name each failed assumption in the conclusive-rejection case. `all_pass` is `True` iff QUG is conclusive AND at least one of Stute/Yatchew is conclusive AND no conclusive test rejects. **Non-negative-dose contract**: all three raw linearity helpers (`qug_test`, `stute_test`, `yatchew_hr_test`) raise a front-door `ValueError` on any `d < 0`, mirroring the `_validate_had_panel` guard (paper Section 2 HAD support restriction). On the `aggregate="overall"` path, the panel must already be exactly two periods (`_validate_had_panel` raises with a pointer to `aggregate="event_study"` otherwise); the first-difference helper computes `(t_post, t_pre)` per unit and feeds each raw helper directly. On the `aggregate="event_study"` path, joint Stute is dispatched across pre-period and post-period horizons directly (the joint Equation-18 form, no per-horizon pre-slicing).
3604-
- [x] Phase 4: Pierce-Schott (2016) replication harness reproduces Figure 2 values. **Waived 2026-05-20:** see Deviations block above; the paper itself self-acknowledges that NP estimators are too noisy to be informative on the LBD-restricted PNTR panel (Section 5.2), and R parity at `atol=1e-8` via `tests/test_did_had_parity.py` is a strictly stronger correctness anchor than Figure-2 reproduction on a proxy panel. Tracked as Low-priority follow-up in `DEFERRED.md`.
3605-
- [x] Phase 4: Full DGP 1/2/3 coverage-rate reproduction from Table 1. **Waived 2026-05-20:** see Deviations block above; R parity at `atol=1e-8` on the same 3 DGPs reproduces the exact point estimate and SE algorithm (Python ≡ R ≡ paper) at sample-mean level — stronger than coverage-rate MC, which re-verifies asymptotic-coverage already pinned by R parity. Tracked as Low-priority follow-up in `DEFERRED.md`.
3604+
- [x] Phase 4: Pierce-Schott (2016) replication harness reproduces Figure 2 values. **Waived 2026-05-20:** see Deviations block above; the paper itself self-acknowledges that NP estimators are too noisy to be informative on the LBD-restricted PNTR panel (Section 5.2), and R parity at `atol=1e-8` via `tests/test_did_had_parity.py` is a strictly stronger correctness anchor than Figure-2 reproduction on a proxy panel. Recorded as waived in the `DEFERRED.md` decision record.
3605+
- [x] Phase 4: Full DGP 1/2/3 coverage-rate reproduction from Table 1. **Waived 2026-05-20:** see Deviations block above; R parity at `atol=1e-8` on the same 3 DGPs reproduces the exact point estimate and SE algorithm (Python ≡ R ≡ paper) at sample-mean level — stronger than coverage-rate MC, which re-verifies asymptotic-coverage already pinned by R parity. Recorded as waived in the `DEFERRED.md` decision record.
36063606
- [x] Phase 5 (wave 1, PR #402): `practitioner_next_steps()` integration for HAD results - `_handle_had` and `_handle_had_event_study` route both result classes through HAD-specific Baker et al. (2025) step guidance with bidirectional HAD ↔ ContinuousDiD Step-4 routing closure. The `_check_nan_att` helper extends to ndarray `att` (HAD event-study) via `np.all(np.isnan(arr))` semantics; scalar path bit-exact preserved. The `llms-full.txt` HAD section's documented constructor and `fit()` parameter lists are regression-locked against `inspect.signature(HeterogeneousAdoptionDiD.__init__)` and `HeterogeneousAdoptionDiD.fit` for parameter-name presence (parameter defaults and the non-return parameter type annotations remain unpinned by the current `inspect.signature` test). The `fit()` return annotation is widened to `Union[HeterogeneousAdoptionDiDResults, HeterogeneousAdoptionDiDEventStudyResults]` at the source-code level to match the runtime polymorphism, AND that union is now pinned at the test level by `tests/test_had.py::TestFitReturnAnnotation::test_fit_return_annotation_is_union_of_result_classes` via `typing.get_type_hints` so the contract cannot drift silently.
36073607
- [x] Phase 5 (wave 1, PR #402): `llms-full.txt` HeterogeneousAdoptionDiD section + result-class blocks + `## HAD Pretests` index + Choosing-an-Estimator row landed; constructor / fit() parameter names are regression-locked against `inspect.signature(HeterogeneousAdoptionDiD.__init__)` and `HeterogeneousAdoptionDiD.fit` for parameter-name presence (parameter defaults and the non-return parameter type annotations remain unpinned; the `fit()` return-type union is locked BOTH at the source-code level AND at the test level by `TestFitReturnAnnotation`); result-class field tables enumerate every public dataclass field (regression-tested via `dataclasses.fields()`); `llms-practitioner.txt` Step 4 decision tree distinguishes ContinuousDiD (per-dose ATT(d), needs never-treated) from HeterogeneousAdoptionDiD (WAS, universal-rollout-compatible).
36083608
- [x] Phase 5 (partial): README catalog one-liner, bundled `llms.txt` `## Estimators` entry, `docs/api/had.rst` (autoclass for the three classes), and `docs/references.rst` citation landed in PR #372 docs refresh.

0 commit comments

Comments
 (0)