@@ -100,6 +100,23 @@ class WooldridgeDiDResults:
100100 # or wait for the deferred bootstrap-cohort-share follow-up.
101101 _bootstrap_used : bool = field (default = False , repr = False )
102102
103+ # Model-surface metadata for self-describing reporting.
104+ # ``cohort_trends`` records whether the fit was produced under the
105+ # Section 8 / Eq. 8.1 heterogeneous-cohort-trends design (paper
106+ # W2025 ``dg_i · t`` interactions on the OLS path). False on the
107+ # default ``cohort_trends=False`` fit and on logit/Poisson paths
108+ # (which reject ``cohort_trends=True`` at the constructor).
109+ # ``active_aggregation_weights`` records the most recent
110+ # ``aggregate(weights=...)`` scheme that wrote to the ``overall_*``
111+ # / ``event_study_effects`` / ``group_effects`` / ``calendar_effects``
112+ # fields ("cell" by default; flips to "cohort_share" after an opt-in
113+ # cohort-share aggregation). Surfaced in ``summary()`` so downstream
114+ # consumers can tell which estimand the printed rows represent
115+ # without inspecting ``cohort_trend_coefs`` (which is legitimately
116+ # empty on all-treated panels per the last-cohort drop rule).
117+ cohort_trends : bool = field (default = False , repr = False )
118+ active_aggregation_weights : str = field (default = "cell" , repr = False )
119+
103120 # ------------------------------------------------------------------ #
104121 # Internal — used by aggregate() for delta-method SEs #
105122 # ------------------------------------------------------------------ #
@@ -195,6 +212,14 @@ def aggregate(self, type: str, weights: str = "cell") -> "WooldridgeDiDResults":
195212 f"jwdid_estat-style cell-count weighting."
196213 )
197214
215+ # Record the active aggregation weighting scheme on the Results
216+ # object so downstream reporting (``summary()`` / ``to_dataframe()``
217+ # / ``__repr__``) can label the persisted ``overall_*`` /
218+ # ``event_study_effects`` / ``group_effects`` / ``calendar_effects``
219+ # fields with the right estimand. Stamped AFTER the raise-paths
220+ # above so invalid combinations don't pollute the metadata.
221+ self .active_aggregation_weights = weights
222+
198223 gt = self .group_time_effects
199224 cell_weights = self ._gt_weights
200225 n_g_per_cohort = self ._n_g_per_cohort
@@ -556,6 +581,8 @@ def summary(self, aggregation: str = "simple") -> str:
556581 f"Observations: { self .n_obs } " ,
557582 f"Treated units: { self .n_treated_units } " ,
558583 f"Control units: { self .n_control_units } " ,
584+ f"Cohort trends: { self .cohort_trends } " ,
585+ f"Aggregation w: { self .active_aggregation_weights } " ,
559586 "-" * 70 ,
560587 ]
561588
@@ -714,7 +741,10 @@ def plot_event_study(self, weights: str = "cell", **kwargs) -> None:
714741 paper W2025 Eq. 7.6 cohort-share-by-exposure weights
715742 (post-treatment ``k >= 0`` only); inference fields are
716743 fail-closed to NaN per the Section 7.5 conditional-on-shares
717- contract documented in REGISTRY.
744+ contract documented in REGISTRY, and the plot **suppresses
745+ error bars / CI bands** to honor the fail-closed contract
746+ (the conditional-on-shares SE would build a misleading
747+ normal-theory CI in the plotter).
718748 **kwargs
719749 Forwarded to ``diff_diff.visualization.plot_event_study``.
720750
@@ -738,7 +768,17 @@ def plot_event_study(self, weights: str = "cell", **kwargs) -> None:
738768 from diff_diff .visualization import plot_event_study # type: ignore
739769
740770 effects = {k : v ["att" ] for k , v in (self .event_study_effects or {}).items ()}
741- se = {k : v ["se" ] for k , v in (self .event_study_effects or {}).items ()}
771+ if weights == "cohort_share" :
772+ # Honor the fail-closed inference contract per paper Section
773+ # 7.5: the conditional-on-shares SE understates unconditional
774+ # uncertainty, so passing the finite SE into the plotter
775+ # would let it render a normal-theory CI that contradicts
776+ # the NaN inference fields the aggregate() helper produces.
777+ # Pass NaN SEs so the plotter suppresses error bars / CI
778+ # bands. Locked by ``test_plot_event_study_cohort_share_suppresses_error_bars``.
779+ se = {k : float ("nan" ) for k in (self .event_study_effects or {})}
780+ else :
781+ se = {k : v ["se" ] for k , v in (self .event_study_effects or {}).items ()}
742782 plot_event_study (effects = effects , se = se , alpha = self .alpha , ** kwargs )
743783
744784 # --- Inference-field aliases (balance/external-adapter compatibility) ---
0 commit comments