Skip to content

marketing: MMM calibration launch carousel (Signal palette) + claims-sync guard - #781

Merged
igerber merged 2 commits into
mainfrom
feature/mmm-carousel
Aug 20, 2026
Merged

marketing: MMM calibration launch carousel (Signal palette) + claims-sync guard#781
igerber merged 2 commits into
mainfrom
feature/mmm-carousel

Conversation

@igerber

@igerber igerber commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • New LinkedIn launch carousel for the diff_diff.mmm interop: carousel/generate_mmm_carousel.py renders carousel/diff-diff-mmm-carousel.pdf (10 slides, 4:5), following the generate_cic_carousel.py architecture with a new "Signal" palette (violet = calibrated, coral = uncalibrated, amber reserved for ground truth in every chart).
  • Ease-first narrative with Tutorial 30 (Meridian geo launch) as the spine and Tutorial 29 (PyMC-Marketing spend boost) as the secondary beat: three-line handshake, the verbatim to_code() snippet, the default-vs-calibrated payoff interval chart (3.29 [3.02, 3.57] vs truth 2.5 -> 2.54 [2.47, 2.62]), and a capabilities slide.
  • tests/test_mmm_carousel_claims.py (18 tests, normal suite): every deck number is located on the committed executed notebook surfaces; slide-4 snippet lines must be byte-verbatim from the committed to_code() output; the confidently-wrong interval structure and 6x-narrower ratio are recomputed from the deck's own constants; slide-8 call arguments re-derive from tutorial 29's DGP; the four named aggregate('total') adopters are pinned against each results class's _AGGREGATE_SUPPORTED; the "Zero new dependencies" strip is backed by an mmm.py import scan plus a pyproject.toml requirement scan; the version strip derives from the tutorials' own requirement pins; competitive claims and unqualified guardrail absolutes are ban-listed.
  • Tracking: TODO.md gains a deck-agnostic PDF-vs-generator drift-guard row; DEFERRED.md's decision record documents the marketing-surface claim posture (scoped guardrail copy; slide-8 linearity scoping lives in tutorial 29, which the CTA points to).

Methodology references (required if estimator / math changes)

  • Method name(s): N/A - no methodology changes. Deck copy about to_meridian_roi_prior / meridian_calibration_mask / to_pymc_marketing_lift_test / aggregate('total') states committed-tutorial facts only.
  • Paper / source link(s): N/A
  • Any intentional deviations from the source (and why): None. The marketing-surface claim posture (scoped, never absolute) is recorded in DEFERRED.md's decision record.

Validation

  • Tests added/updated: tests/test_mmm_carousel_claims.py (new, 18 tests; skips cleanly when carousel/ or docs/ is absent, matching test_cic_carousel_claims.py).
  • Backtest / simulation / notebook evidence (if applicable): every number on the deck is a committed, seed-locked value from the executed tutorials 29/30; the committed PDF is regenerated from the final generator source in this commit.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

…sync guard

New LinkedIn launch deck for the diff_diff.mmm interop, following the
generate_cic_carousel.py architecture (magazine sidebar, split logo, one
dark slide, footer wordmark, phone type floors) with a new "Signal"
palette: violet = calibrated, coral = uncalibrated, amber reserved for
ground truth in every chart.

Ease-first narrative (Tutorial 30 / Meridian is the spine, Tutorial 29 /
PyMC-Marketing the secondary beat): cover hook with the two-posterior
motif ("the true ROI (simulated)" + illustrative tag) -> the
hand-derivation pain -> the three-line handshake (dark code slide) ->
the generated to_code() snippet shown verbatim -> the payoff interval
chart (default 3.29 [3.02, 3.57] vs truth 2.5; calibrated 2.54
[2.47, 2.62]) -> the aggregation schematic -> estimand-rigor cards ->
the PyMC lift-test beat (3.52 -> 2.21 vs truth 2.0, executable
keyword-only call) -> a capabilities slide -> CTA carrying the single
tutorial mention and the full simulation-provenance statement.

Claim posture (documented in the module docstring + DEFERRED.md decision
record): capabilities over validation; guardrail copy scoped, never
absolute ("the easy mistakes fail loudly", "you own the design, it owns
the math") with the retired absolutes ban-listed; truth provenance
concentrated into the cover/chart "(simulated)" labels and the CTA line;
slide 8 carries no on-slide linearity qualifier - the staggered-boost
compression's linear-channel scoping is tutorial 29's job.

tests/test_mmm_carousel_claims.py (18 tests, normal suite, skips when
carousel/ or docs/ absent) syncs the deck to the committed executed
notebooks: every constant is located on the tutorial output surface, the
slide-4 snippet lines must be byte-verbatim from the committed to_code()
output, the confidently-wrong interval structure and 6x-narrower ratio
are recomputed from constants, slide-8 call arguments are re-derived
from tutorial 29's DGP, the four named aggregate('total') adopters are
pinned against each results class's _AGGREGATE_SUPPORTED, the "Zero new
dependencies" strip is backed by an mmm.py import scan plus a
pyproject.toml requirement scan, the version strip is derived from the
tutorials' own requirement pins, and competitive claims plus unqualified
guardrail absolutes are ban-listed.

Tracking: TODO.md gains the deck-agnostic PDF-vs-generator drift-guard
row (fpdf date metadata makes byte-compare nontrivial); DEFERRED.md's
decision record documents the marketing-surface claim posture.

The committed PDF is the generator's build artifact, regenerated with
the final source per carousel/README.md.
@github-actions

Copy link
Copy Markdown

Overall assessment

⚠️ Needs changes — one unmitigated P1 API/methodology-contract mismatch.

Executive summary

  • No estimator, weighting, variance, or inference implementation changes.
  • Tutorial-derived numerical claims and MMM formulas are consistent with the registry.
  • All 18 claim-sync assertions passed via a lightweight runner.
  • The “Paste and Run” slide shows an invocation that necessarily raises ValueError.
  • PDF drift is already tracked in TODO.md.

Methodology

P1 — [Newly identified] Required Meridian scope arguments are omitted

  • Location: carousel/generate_mmm_carousel.py:L831-L845
  • Impact: The slide presents prior.to_code() under “Paste and Run,” but channel and time scope are mandatory. Calling it exactly as shown raises ValueError; the registry and exporter explicitly require channel/media_channels plus roi_calibration_period or full_model_window=True (docs/methodology/REGISTRY.md:L6577-L6580, diff_diff/mmm.py:L815-L903).
  • Concrete fix: Display the tutorial’s actual invocation:
prior.to_code(
    channel="search",
    media_channels=["search", "tv"],
    roi_calibration_period=mask,
)

The tracked omission of slide-8’s linearity qualifier is P3-informational under DEFERRED.md:L137; the shown tutorial calculation is valid for its linear, homogeneous DGP.

Code Quality

No additional findings.

Performance

No findings.

Maintainability

No findings.

Tech Debt

P3 — PDF/source drift guard is tracked

  • Location: TODO.md:L88
  • Impact: Future generator edits could leave the committed PDF stale.
  • Concrete fix: None required for this PR; the work is properly tracked.

Security

No findings. No secrets or suspicious PDF metadata were identified.

Documentation/Tests

P2 — Claim guard does not validate the displayed invocation

  • Location: tests/test_mmm_carousel_claims.py:L156-L160
  • Impact: The suite validates generated output lines but allows the invalid prior.to_code() call to ship.
  • Concrete fix: Pin the complete displayed invocation to the tutorial source or validate it against MeridianROIPrior.to_code’s required scoping contract.

Validation: both Python files compiled successfully; all 18 claim assertions passed; the committed PDF reports 10 pages. Full pytest was unavailable in the review environment.

Path to Approval

  1. Replace the bare prior.to_code() display with the scoped tutorial invocation.
  2. Add a regression assertion that the displayed call includes channel and time scope.

to_code() deliberately fails closed without channel + time scope, so the
'Paste and Run' slide now shows the tutorial's actual call
(channel/media_channels/roi_calibration_period) instead of a bare
prior.to_code() that would raise ValueError. New regression assertion
pins the scoping kwargs on the displayed call, bans the bare form, and
confirms the same kwargs in tutorial 30's to_code() cell. PDF
regenerated.
@github-actions

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: c2660fc875891f2d61d3b1610a9d106bd24c8752


Overall assessment

Looks good — the prior P1 is resolved; no unmitigated P0/P1 findings remain.

Executive summary

  • Slide 4 now shows the required channel and time scope, matching the tutorial and exporter contract.
  • The committed PDF contains the corrected invocation.
  • Regression coverage rejects a bare prior.to_code() call.
  • No estimator, weighting, variance, inference, or default behavior changed.
  • All 19 claim assertions passed via a lightweight runner; both Python files compile and the PDF has 10 pages.

Methodology

The prior P1 is fixed at carousel/generate_mmm_carousel.py:L835-L845 and guarded at tests/test_mmm_carousel_claims.py:L162-L179, consistent with docs/methodology/REGISTRY.md:L6577-L6580.

P3 — Documented marketing-surface scoping

  • Impact: Slide 8 omits an on-slide linearity qualifier, but this posture is explicitly recorded at DEFERRED.md:L137 and the CTA points to the scoped tutorial.
  • Concrete fix: None required.

Code Quality

No findings.

Performance

No findings.

Maintainability

No findings.

Tech Debt

P3 — PDF/source drift guard is tracked

  • Impact: Future generator edits could leave committed PDFs stale.
  • Concrete fix: No PR action required; the deterministic PDF/manifest guard is tracked at TODO.md:L88.

Security

No findings. No secrets or unsafe externally controlled operations identified.

Documentation/Tests

No findings. The new regression directly covers the previously invalid displayed invocation. Full pytest was unavailable in the review environment; the claim tests were executed through a lightweight direct runner.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Aug 20, 2026
@igerber
igerber merged commit d3ebdce into main Aug 20, 2026
29 of 30 checks passed
@igerber
igerber deleted the feature/mmm-carousel branch August 20, 2026 12:45
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