Skip to content
Merged
57 changes: 57 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
committed `DoubleMLDIDCSBinary` characterization spike (no parity oracle
exists — DoubleML's RCS score differs and omits the λ term).

### Changed
- **`ContinuousDiD` rejects `pscore_trim=0`** (ledger row M-145): the bound
tightens from `[0, 0.5)` to `(0, 0.5)` — `trim=0` disabled the
`np.clip(pscore, trim, 1 - trim)` overlap guard that keeps the `1/(1-p)`
IPW/DR weights finite (the TripleDifference M-142 rationale). Validation now
runs through the shared `utils.validate_pscore_trim`, so the error message
wording changed, non-real-scalar inputs (`None`, strings, `Decimal`/
`Fraction`, 1-element arrays) raise `ValueError` instead of `TypeError` (or
silent acceptance, for the 1-element array), and the stored value is coerced
to built-in `float`.
- **`pscore_trim` validation unified on `utils.validate_pscore_trim`**
(M-145): `CallawaySantAnna` gains the same type guard (previously a bare
range check: `None`/str raised `TypeError`, a 1-element array and
`Decimal`/`Fraction` were accepted) at construction and at the fit-path
mutation re-check; `TripleDifference`, `CallawaySantAnna`, and
`ContinuousDiD` now store the value coerced to built-in `float`; `LWDiD`'s
message wording changed (guard behavior unchanged). The deprecated
`StaggeredTripleDifference` deliberately keeps its permissive construction
shape (M-013/M-144 posture).
- **Staggered-family `summary(alpha=...)`/`print_summary(alpha=...)` reject a
non-fit alpha** (M-146): `CallawaySantAnnaResults` and its siblings
(StaggeredTripleDiff, ChaisemartinDHaultfoeuille, Imputation, EfficientDiD,
TwoStage, Stacked, SunAbraham results) previously relabeled the
confidence-interval header at the requested alpha while printing the
fit-time stored intervals — silent coverage mislabeling (bootstrap
percentile intervals cannot be reconstructed from the SE). A value different
from the fit-time `alpha` now raises `ValueError` via the shared
`results_base._require_fit_alpha` guard (the DMLDiD/EventStudyResults
precedent); `alpha=0.0`, previously swallowed by a falsy-`or` default,
raises too. Re-fit at the desired alpha instead.

### Fixed
- **`plot_event_study` zero-SE pointwise gate** (the `plot_group_effects`
twin): the `effect ± z·SE` reconstruction NaN-gates zero/negative-SE rows —
their stored inference is all-NaN, and a finite zero-width interval
presented defined inference for them. Auto-inferred reference rows (effect
0, se 0) on the raw `event_study_effects` route retain their degenerate
constraint bar per the REGISTRY reference-retention contract; the
`EventStudyResults` container route after an explicit `reference_period=`
(which discards stored-interval overrides) no longer draws spurious
zero-width bars either.
- **`plot_honest_event_study` raw (non-container) routes** now mirror the
container's retained-row semantics: zero/non-finite-SE periods are excluded
up front instead of drawing a zero-width original CI beside honest
inference that was never computed for them (explicitly requesting one
raises); the reference period is auto-inferred (a `reference_period`
attribute or HonestDiD's own constraint-row signature — never a bare `-1`
fallback), enabling the existing reference tolerance on these routes; an
all-undefined surface raises instead of rendering a blank figure.

### Internal
- `DMLDiD`'s repeated-cross-section cell loop computes the Case 2 λ-slope
`Ĝ₂λ` once per cell via `_dr_scores._chang_rcs_score_augmented_with_slope`
(previously twice: inside `chang_rcs_score_augmented` and again for the
`g2_lambda` diagnostic, each with its own input-validation pass). Public
score functions and all numerics unchanged (bitwise-pinned).

## [3.10.0] - 2026-08-22

### Added
Expand Down
1 change: 1 addition & 0 deletions DEFERRED.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ decisions (refactor waivers, perf trade-offs, test-infrastructure calls) are rec

| Decision | Location | Verified |
|----------|----------|----------|
| **`utils.validate_pscore_trim` ships without the `allow_zero` flag the retired TODO row proposed.** The row (retired in the M-145 PR) suggested `validate_pscore_trim(value, *, allow_zero)`; after ContinuousDiD's alignment to `0 < x < 0.5`, no consumer needs `allow_zero=True` — every migrated copy (TripleDifference, DMLDiD, ContinuousDiD, LWDiD, CallawaySantAnna) wants the strict interval, and the deliberately-unmigrated `StaggeredTripleDifference` keeps its own bare check rather than a flagged call. A dead-on-arrival parameter on a shared validator is a drift magnet; re-add the flag only when a real caller needs a `[0, 0.5)` domain, with a distinct range message | `diff_diff/utils.py` | M-145 / 2026-08-27 |
| **Fixed-B Stata SE-golden comparisons in the LWDiD suite run unmarked in default CI** (no `ci_params.bootstrap()` scaling, no `slow` marker): the SE tolerance derives from BOTH fixed rep counts (ours B=999, Stata's R from the golden meta) so scaling either side would invalidate the committed-golden comparison — the ci_params convention governs convergence-style tests, not fixed-B golden comparisons — and these tests are the PR #588 acceptance bar, which must run in default CI; module-scoped fit memoization bounds the cost (~1-2 s per fit locally) | `tests/test_methodology_lwdid.py` | 2026-08-16 |
| **The 4.0 migration guide's code blocks are not snippet-executed.** `tests/test_doc_snippets.py` discovers a hardcoded list of `.rst` files and only `.. code-block:: python` / RST `::` bodies, so `docs/migration-4.0.md` gets no coverage. Deliberate: the guide is a MIXED document - most "after" examples (the renames, `results.att`) run on the current release, but the `field-flip` and `df-convention-flip` examples describe 4.0 behaviour that cannot run until 4.0, so a blanket execution lane would fail by construction. Closing the gap fully means a markdown-fence extractor plus a skip-marker convention for the future-API blocks - a harness change, out of scope for a docs PR. What IS gated: the appendix's ledger parity (`test_migration_guide_*`), which pins the row set and every mechanically checkable cell; and, since the first local review found all three merge examples carrying invalid keywords, `test_migration_guide_examples_bind_to_real_signatures`, which ast-parses the guide's python blocks and asserts every constructor/`fit()` keyword exists on the target signature. That is signature binding, NOT execution - it deliberately skips calls whose owner it cannot resolve (e.g. `results.aggregate(...)`), and it cannot catch a wrong VALUE or a wrong sequence of calls. The hand-written `Fix` prose remains unverifiable by any available means. | `docs/migration-4.0.md`, `tests/test_doc_snippets.py` | Phase 4 / 2026-08-09 |
| **MMM launch carousel carries scoped marketing claims, not exporter-contract documentation.** The deck (a LinkedIn marketing artifact, not a docs surface) states capabilities in scoped-but-punchy form: guardrail copy says "the easy mistakes fail loudly" / "you own the design, it owns the math" (the caller-owned estimand/population/window/outcome-scale alignment lives in REGISTRY.md's MMM section and the exporter docstrings, not on slides), and slide 8 shows tutorial 29's worked staggered-boost lift row WITHOUT an on-slide linearity qualifier - the compression's linear-channel scoping is the tutorial's job, which the CTA points to. Absolutes ("no silent mis-calibration", "any estimate exports", "anything with an estimate + SE") are ban-listed by `tests/test_mmm_carousel_claims.py`; local review R1/R2 pressed for compliance-style scoping language on the slides and the marketing-appropriate scoped copy was chosen instead. | `carousel/generate_mmm_carousel.py`, `tests/test_mmm_carousel_claims.py` | mmm-carousel / 2026-08-20 |
Expand Down
Loading
Loading