Skip to content

feat(v4): ContinuousDiD post-fit aggregate() - mixed view/recompute adopter (2(b) PR-3c, M-025) - #750

Merged
igerber merged 1 commit into
mainfrom
feat/v4-continuous-aggregate-kit
Aug 5, 2026
Merged

feat(v4): ContinuousDiD post-fit aggregate() - mixed view/recompute adopter (2(b) PR-3c, M-025)#750
igerber merged 1 commit into
mainfrom
feat/v4-continuous-aggregate-kit

Conversation

@igerber

@igerber igerber commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • Row M-025 shimmed (v4 program 2(b) PR-3c): ContinuousDiD.fit(aggregate=) deprecates via the shared NOT_SUPPLIED sentinel - a plain fit() never warns, any supplied value (incl. None) warns once, the legacy routing runs unchanged until 4.0, and the PRE-EXISTING fit-time value validation survives (unknown strings still raise ValueError after the warning; the no-underscore "eventstudy" spelling dies with the param).
  • MIXED view/recompute aggregate() - unique among the adopters: the dose-response curves and the overall binarized ATT (ATT^{loc} under PT; equals ATT^{glob} under SPT) plus ACRT^{glob} are ALWAYS computed by fit() (aggregate="dose" was a fit-time no-op), so aggregate('simple') (2 rows, targets att/acrt; n = disjoint treated+control unit total, n_kind='units') and aggregate('dose') (2N target-discriminated rows over the dose grid) are pure VIEWS relaying stored inference verbatim - permitted on bootstrap fits (the dCDH per-level reasoning), including the stored FINITE overall t beside percentile p/CI. aggregate('event_study') RECOMPUTES the binarized event study from a pruned per-cell IF payload kit; bootstrap fits carry a scalars-only kit and fail closed; replicate-weight designs are supported post-fit (IF-based, no refit replay).
  • New leaf module diff_diff/continuous_did_aggregation.py: _aggregate_event_study moved verbatim + fit's inline analytical ES-SE block extracted as _compute_event_study_inference, shared by fit-time and the post-fit kit route (import DAG: the results module cannot import the estimator module).
  • First heterogeneous-target AggregationResult: summary() gains a target column + neutral estimate heading when targets are mixed; to_dataframe() orders heterogeneous-target rows by first-appearance target blocks (att before acrt) with labels ascending within block under the existing _sortable guard - uniform-target producers render byte-identically. Normative rule added to docs/v4-design.md section 6.
  • Consumer admission unchanged: compute_honest_did / compute_pretrends_power reject ContinuousDiD containers BY DESIGN (no joint event-study covariance; the binarized bins carry no reference-period normalization) - both terminal messages now say so (M-093 fifth amendment).
  • Ledger/guards: M-025 shimmed (register-style notes), M-092 scope qualifier extended (scalar df_survey only; per-row df tracked in the existing TODO row, now naming ContinuousDiD), REGISTRY ContinuousDiD Note 8, AggregationKit docstring pruned-payload variant, bootstrap-ES-replay TODO row.
  • Sweep: tests (19 no-op aggregate="dose" kwargs dropped, analytical ES sites migrated post-fit, the invalid-value pin now pins warn-then-raise, bootstrap-ES sites kept fit-time under module suppression), bench_dose_response.py (ES phase migrated post-fit so it keeps timing the event study), performance docs, practitioner HAD-handoff guidance strings, tutorial 14 (incl. the downstream plot-consumer cell's schema), llms guides, API rst; all continuous_did.py: line anchors re-derived symbol-by-symbol (two were already stale).

Methodology references (required if estimator / math changes)

  • Method name(s): ContinuousDiD (dose-response DiD) - binarized event-study aggregation; post-fit aggregation contract (spec section 6, row M-122).
  • Paper / source link(s): Callaway, Goodman-Bacon & Sant'Anna (2024), "Difference-in-Differences with a Continuous Treatment" (NBER WP 32117) - the binarized ATT^{es}_{loc}(e) construction; docs/methodology/REGISTRY.md § ContinuousDiD.
  • Any intentional deviations from the source (and why): none in the estimand or inference - the event-study machinery moved VERBATIM and post-fit-vs-fit-time inertness is pinned at 1e-14 across 13 design arms. Policy deviations documented in REGISTRY Note 8 / the M-025 ledger row: per-level bootstrap policy (views permitted, recompute fails closed - the dCDH deviation from CS's uniform fail-closed rule), the pruned-payload kit memory contract (panel-level survey ref retained on survey fits; replicate matrix on replicate designs), and the fit-faithful empty-post-period NaN-inference quirk.

Validation

  • Tests added/updated: tests/test_aggregate_contract.py (new 49-test ContinuousDiD block: shim pins, ES inertness at 1e-14 across plain/multi-cohort/anticipation/covariates/survey-TSL/zero-dose-drop/replicate(healthy,dropped,undefined-df)/discrete/lowest-dose/not-yet-treated/non-default-config arms, per-curve dose parity, bit-exact simple relay incl. the finite bootstrap t, heterogeneous-target rendering incl. an unsorted-dvals ordering arm and a mixed-type-label fallback arm, isolation incl. the res.n_bootstrap = 0 gate-bypass arm, scalars-only bootstrap kit, no-kit legacy views, vocabulary fail-closed, pickle round-trip, no-raw-identifier retention), tests/test_event_study_consumers.py (real-container rejection via both consumers), tests/test_practitioner.py + tests/test_guides.py (M-025 pins; _assert_shim_lines gained an optional bal_row), plus the migrated sites in tests/test_continuous_did.py, tests/test_methodology_continuous_did.py (benchmark 6 keeps exercising the ES route post-fit), tests/test_survey_phase3.py, tests/test_profile_panel.py.
  • Backtest / simulation / notebook evidence (if applicable): tutorial docs/tutorials/14_continuous_did.ipynb migrated to the post-fit route (analytical ES fit; downstream plot cell on the unified schema) and re-executed clean via nbmake. R-parity/benchmark numbers unchanged (the harness now exercises the ES route post-fit). Perf: fit timing bands overlap PRE/POST; pickle deltas are the documented kit retention (+79 KB plain / +171 KB TSL survey / +315 KB replicate on the 300x10 benchmark panel; scalars-only on bootstrap fits).

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

…dopter (2(b) PR-3c, M-025)

fit(aggregate=) deprecates via the NOT_SUPPLIED shim (sentinel-only
normalization; the pre-existing value validation survives, and the
"eventstudy" spelling dies with the param in 4.0). 'simple' (2 rows,
att+acrt) and 'dose' (2N target-discriminated rows) are pure views over
the always-computed curves/overalls - permitted on bootstrap fits,
relaying stored inference verbatim incl. the finite overall t.
'event_study' recomputes from a pruned per-cell IF payload kit
(scalars-only on bootstrap fits, which fail closed; replicate designs
supported IF-based, no refit replay; panel-level survey ref by decision).
The ES machinery moves verbatim to the new leaf module
continuous_did_aggregation.py, shared by fit and the post-fit route.

First heterogeneous-target AggregationResult: summary()/to_dataframe()
gain the target column + first-appearance target-block ordering
(uniform-target producers byte-stable; normative rule in v4-design §6).
honest/pretrends terminal messages name the by-design ContinuousDiD
rejection (no joint ES covariance; no reference normalization).

Ledger: M-025 shimmed; M-092 qualifier + M-093 fifth amendment; REGISTRY
ContinuousDiD Note 8; M-092/bootstrap-replay TODO rows. Contract tests:
49-test ContinuousDiD block (ES inertness 1e-14 across 13 design arms,
view parity, isolation, scalars-only kit pin, no-kit legacy views,
unsorted-dvals ordering, mixed-type-label fallback), consumer rejection
pins, practitioner/guides pins. Sweep: tests (dose no-ops dropped,
analytical ES sites migrated post-fit, invalid-value pin warns+raises),
bench_dose_response (ES phase migrated post-fit), performance docs,
practitioner HAD-handoff strings, tutorial 14 (incl. the downstream
plot-consumer cell), llms guides, api rst, re-derived line anchors.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Overall Assessment: ✅ Looks good

Executive Summary

  • No unmitigated P0/P1 findings.
  • ContinuousDiD estimands, weighting, and IF inference remain consistent with CGBS (2024) and the Methodology Registry.
  • Bootstrap replay and per-row survey df limitations are documented and tracked.
  • One minor documentation line-count mismatch remains.

Methodology

  • P3 — Documented implementation policy
    • Impact: None. Event-study weighting and analytical inference are preserved in diff_diff/continuous_did_aggregation.py:L52-L213; all inference uses safe_inference(). The mixed view/recompute policy, bootstrap fail-closed behavior, empty-post-period handling, and df provenance are documented in docs/methodology/REGISTRY.md:L1318.
    • Concrete fix: None required.

Code Quality

No findings.

Performance

  • P3 — Documented payload retention
    • Impact: Analytical results retain per-cell IF ingredients and, for replicate surveys, the replicate matrix. This increases result/pickle size but is explicitly documented in Registry Note 8.
    • Concrete fix: None required.

Maintainability

No findings.

Tech Debt

  • P3 — Tracked limitations
    • Impact: Bootstrap event-study replay, per-row survey df provenance, and post-fit DiagnosticReport consumption remain incomplete.
    • Concrete fix: Already tracked in TODO.md:L26-L28; no approval action required.

Security

No findings or apparent secrets. The retained kit uses positional indices and resolved numerical survey structures rather than source panel columns or raw unit identifiers.

Documentation / Tests

  • P3 — Stale module line count
    • Impact: docs/dev-status.md:L36 reports 2,459 lines for continuous_did.py, while the file currently has 2,455.
    • Concrete fix: Change the documented count to 2,455.

Test coverage is extensive across anticipation, controls, survey/replicate designs, bootstrap gating, empty results, and NaN inference. Static AST parsing passed; executable tests could not run because NumPy is unavailable in the review environment.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Aug 5, 2026
@igerber
igerber merged commit 0581e3f into main Aug 5, 2026
39 of 40 checks passed
@igerber
igerber deleted the feat/v4-continuous-aggregate-kit branch August 5, 2026 16:00
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