BusinessReport#

BusinessReport wraps any fitted diff-diff result object and produces stakeholder-ready output:

  • summary() — a short paragraph block suitable for an email or Slack.

  • full_report() — a structured multi-section markdown report.

  • to_dict() — a stable AI-legible structured schema (single source of truth; prose renders from this dict).

By default, BusinessReport constructs an internal DiagnosticReport to surface pre-trends, sensitivity, and other validity checks as part of the narrative. Pass auto_diagnostics=False to skip this, or diagnostics=<DiagnosticReport> to supply an explicit one.

Pre-computed diagnostics can be forwarded directly to the auto- constructed DiagnosticReport via precomputed={'parallel_trends': ...}, precomputed={'sensitivity': ...}, precomputed={'pretrends_power': ...}, or precomputed={'bacon': ...} — same keys as DiagnosticReport(precomputed=...). DR validates keys and rejects estimator-incompatible entries.

Data-dependent checks (2x2 parallel trends on simple DiD, Goodman-Bacon decomposition on staggered estimators, the EfficientDiD Hausman PT-All vs PT-Post pretest) require the raw panel + column names. Pass data, outcome, treatment, unit, time, and/or first_treat to BusinessReport and they are forwarded to the auto-constructed DiagnosticReport. Without these kwargs, those specific checks are skipped with an explicit reason while the rest of the report still renders.

For survey-weighted fits (any result carrying survey_metadata) pass the original SurveyDesign via survey_design=<design>. It is threaded through to bacon_decompose for a fit-faithful Goodman-Bacon replay. When survey_metadata is set but survey_design is not supplied, Bacon is skipped with an explicit reason so the report never emits an unweighted decomposition for a design that differs from the estimate. The simple 2x2 parallel-trends helper has no survey-aware variant and is skipped unconditionally on a survey-backed DiDResults regardless of survey_design; supply precomputed={'parallel_trends': ...} with a survey-aware pretest to opt in.

Methodology deviations (no traffic-light gates, pre-trends verdict thresholds, power-aware phrasing, unit-translation policy, schema stability) are documented in docs/methodology/REPORTING.md.

The schema carries a top-level target_parameter block (experimental) naming what the headline scalar represents per estimator — simple ATT, event-study average, DID_M, DID_1, cost-benefit delta, dose-response aggregate, factor-model-adjusted ATT, etc. For the dCDH dynamic branch with trends_linear=True and L_max>=2, the scalar is intentionally NaN and aggregation is "no_scalar_headline" with headline_attribute set to None. Agents should dispatch on this case and inspect the headline reason field, which distinguishes the populated-surface subcase (per-horizon table available on linear_trends_effects) from the empty-surface subcase (no horizons survived estimation; re-fit with a larger L_max or with trends_linear=False). See the “Target parameter” section of docs/methodology/REPORTING.md for the full per-estimator dispatch table and schema shape.

Example#

from diff_diff import CallawaySantAnna, BusinessReport

cs = CallawaySantAnna(base_period="universal").fit(
    df, outcome="revenue", unit="store", time="period",
    first_treat="first_treat", aggregate="event_study",
)
report = BusinessReport(
    cs,
    outcome_label="Revenue per store",
    outcome_unit="$",
    business_question="Did the loyalty program lift revenue?",
    treatment_label="the loyalty program",
    # Optional: panel + column names so auto diagnostics can run the
    # data-dependent checks (2x2 PT, Goodman-Bacon, EfficientDiD
    # Hausman). Without these the auto path still runs and just
    # skips those checks.
    data=df,
    outcome="revenue",
    unit="store",
    time="period",
    first_treat="first_treat",
)
print(report.summary())

API#

class diff_diff.BusinessReport[source]

Bases: object

Produce a stakeholder-ready narrative from any diff-diff results object.

Parameters:
  • results (Any) – A fitted diff-diff results object. Any of the 16 result types is accepted. BaconDecompositionResults is not a valid input — Bacon is a diagnostic, not an estimator; use DiagnosticReport for that.

  • outcome_label (str, optional) – Stakeholder-friendly outcome name (e.g. "Revenue per user").

  • outcome_unit (str, optional) – Unit label: "$" / "%" / "pp" / "log_points" / "count" (recognized for formatting) or any free-form string (used verbatim without arithmetic translation).

  • outcome_direction (str, optional) – "higher_is_better" or "lower_is_better". Drives whether the effect is described as “lift” / “drag” rather than just “increase” / “decrease”.

  • business_question (str, optional) – Question the analysis answers (prepended to the summary).

  • treatment_label (str, optional) – Stakeholder-friendly treatment name (e.g. "the campaign").

  • alpha (float, optional) – Significance level. Defaults to results.alpha when not supplied. Single knob: drives both CI level and significance phrasing.

  • honest_did_results (HonestDiDResults or SensitivityResults, optional) – Pre-computed sensitivity result. When supplied, this is forwarded to the internal DiagnosticReport so sensitivity is not re-computed.

  • auto_diagnostics (bool, default True) – When True and diagnostics is None, auto-construct a DiagnosticReport. Set False to skip diagnostics entirely.

  • diagnostics (DiagnosticReport or DiagnosticReportResults, optional) – Explicit diagnostics object. Takes precedence over auto_diagnostics.

  • include_appendix (bool, default True) – Whether full_report() appends the estimator’s academic results.summary() output under a “Technical Appendix” section.

  • data (optional) – Raw panel + column names forwarded to the auto-constructed DiagnosticReport so data-dependent checks (2x2 PT on simple DiD, Bacon-from-scratch, EfficientDiD Hausman pretest) can run.

  • outcome (optional) – Raw panel + column names forwarded to the auto-constructed DiagnosticReport so data-dependent checks (2x2 PT on simple DiD, Bacon-from-scratch, EfficientDiD Hausman pretest) can run.

  • treatment (optional) – Raw panel + column names forwarded to the auto-constructed DiagnosticReport so data-dependent checks (2x2 PT on simple DiD, Bacon-from-scratch, EfficientDiD Hausman pretest) can run.

  • unit (optional) – Raw panel + column names forwarded to the auto-constructed DiagnosticReport so data-dependent checks (2x2 PT on simple DiD, Bacon-from-scratch, EfficientDiD Hausman pretest) can run.

  • time (optional) – Raw panel + column names forwarded to the auto-constructed DiagnosticReport so data-dependent checks (2x2 PT on simple DiD, Bacon-from-scratch, EfficientDiD Hausman pretest) can run.

  • first_treat (optional) – Raw panel + column names forwarded to the auto-constructed DiagnosticReport so data-dependent checks (2x2 PT on simple DiD, Bacon-from-scratch, EfficientDiD Hausman pretest) can run.

  • survey_design (SurveyDesign, optional) – The SurveyDesign object used to fit a survey-weighted estimator. Forwarded to the auto-constructed DiagnosticReport for fit-faithful Goodman-Bacon replay. When the fit carries survey_metadata but survey_design is not supplied, Bacon is skipped with an explicit reason rather than replaying an unweighted decomposition for a design that does not match the estimate. The simple 2x2 parallel-trends helper (utils.check_parallel_trends) has no survey-aware variant; on a survey-backed DiDResults it is skipped unconditionally regardless of survey_design. Supply precomputed={'parallel_trends': ...} with a survey-aware pretest to opt in. See docs/methodology/REPORTING.md.

  • precomputed (dict, optional) – Pre-computed diagnostic objects forwarded to the auto- constructed DiagnosticReport (same keys as DiagnosticReport(precomputed=...)): "parallel_trends", "sensitivity", "pretrends_power", "bacon". DR validates keys and rejects estimator-incompatible entries (e.g., HonestDiD bounds or generic PT on SDiD / TROP). honest_did_results remains a shorthand for sensitivity; an explicit precomputed['sensitivity'] wins on conflict.

__init__(results, *, outcome_label=None, outcome_unit=None, outcome_direction=None, business_question=None, treatment_label=None, alpha=None, honest_did_results=None, auto_diagnostics=True, diagnostics=None, include_appendix=True, data=None, outcome=None, treatment=None, unit=None, time=None, first_treat=None, survey_design=None, precomputed=None)[source]
Parameters:
to_dict()[source]

Return the AI-legible structured schema (single source of truth).

Return type:

Dict[str, Any]

to_json(*, indent=2)[source]

Return to_dict() serialized as JSON.

Parameters:

indent (int)

Return type:

str

summary()[source]

Return a short plain-English paragraph block (6-10 sentences).

Return type:

str

full_report()[source]

Return a structured multi-section markdown report.

Return type:

str

export_markdown()[source]

Alias for full_report() (discoverability).

Return type:

str

headline()[source]

Return just the headline sentence.

Return type:

str

caveats()[source]

Return the list of structured caveats (severity + topic + message).

Return type:

List[Dict[str, str]]

class diff_diff.BusinessContext[source]

Bases: object

Frozen bundle of business-framing metadata used when rendering prose.

Populated from BusinessReport constructor kwargs. Falls back to neutral labels when fields are not supplied.

outcome_label: str
outcome_unit: str | None
outcome_direction: str | None
business_question: str | None
treatment_label: str
alpha: float
__init__(outcome_label, outcome_unit, outcome_direction, business_question, treatment_label, alpha)
Parameters:
  • outcome_label (str)

  • outcome_unit (str | None)

  • outcome_direction (str | None)

  • business_question (str | None)

  • treatment_label (str)

  • alpha (float)

Return type:

None

diff_diff.BUSINESS_REPORT_SCHEMA_VERSION = '2.0'#

str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to ‘strict’.