feat: Meridian roi_calibration_period mask builder + to_code() array support - #768
Conversation
Overall assessment✅ Looks good — no unmitigated P0/P1 findings. Executive summary
MethodologyM1 — Documented Meridian behavior
Code QualityNo findings. PerformanceNo material findings. MaintainabilityNo findings. Tech DebtNo silent correctness debt introduced. Removing the completed mask-builder TODO is consistent with delivery. SecurityNo secrets, unsafe external operations, or new execution vulnerability found. The existing expression-string route remains explicitly user-generated code. Documentation/TestsD1 — Unrelated RDD tutorial and regression suite removed
The modified Python files parse successfully; runtime tests could not be executed because NumPy/Pandas are unavailable in the review environment. |
…support New public meridian_calibration_mask(media_times=, media_channels=, channel=, window=) builds the boolean (n_media_times, n_media_channels) mask Meridian's ModelSpec expects, from the MMM's own coordinates taken verbatim: the experiment channel's column(s) are True exactly on the window and every other channel's column is all-True (Meridian's documented convention - channels not named in an experiment use ALL periods; an all-False column would zero that channel's aggregated calibration spend in input_data._aggregate_spend). Window: a 2-tuple is (start, end) inclusive bounds (value-ordered, pd.to_datetime-coerced against datetime coordinates, tz mismatches fail closed both ways); any other sequence is explicit labels with fail-closed membership and named missing-label errors. Container/element gates reject strings, Mappings, scalars, MultiIndex (including tuple-label promotion), non-1-D arrays, and missing channel names. MeridianROIPrior.to_code() now accepts the array for roi_calibration_period, serialized into the snippet as an np.ones prelude plus per-column-group window assignments (.tolist()-coerced indices; round-trip-exact for any accepted mask). Acceptance is fail-closed: bool or 0/1-numeric only (cast to bool, matching Google's float np.zeros example), masked arrays rejected, all-False masks and ANY entirely-False column rejected. roi_m priors only, per Meridian 1.7.0's ModelSpec._validate_roi_calibration_period - and to_code() now fails closed for mroi_m with ANY non-None roi_calibration_period (array or expression), fixing the pre-existing expression route that emitted mroi snippets Meridian rejects at ModelSpec construction; the no-scope error is parameter-aware. Docs quote Meridian's own caution that the mask "is not generally recommended", pointing at full_model_window=True when evidence transfers. Container-mode scale= additionally rejects booleans (float(True) would silently scale by one). Docs: REGISTRY Meridian section (three routes + mask Notes + configure-model primary source), api/mmm.rst (executable mask example + autofunction section + references), api/index.rst autosummary + committed stub, both LLM guides, references.rst citation, CHANGELOG Added + Fixed entries, TODO mask-builder row removed (delivered). Tests: TestMeridianCalibrationMask (window resolution, coercion successes and fail-closed matrix) and TestToCodeArrayMask (serialization round-trips, per-column rejection, mroi rejection, Google float-parity), all message-anchored.
a14b1d4 to
eec7626
Compare
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall assessment✅ Looks good — no unmitigated P0/P1 findings. Executive summary
MethodologyM1 — Documented Meridian caveats
Code QualityNo findings. PerformanceNo material findings. MaintainabilityNo findings. Tech DebtNo untracked correctness debt. Removing the completed mask-builder TODO is appropriate. SecurityNo secrets or new unsafe execution paths found. Array serialization emits only validated integer indices and boolean assignments. Documentation/TestsNo findings. The prior RDD regression is restored, and the new tests cover empty selections, invalid values, serialization round-trips, channel-count checks, and Static compilation and |
Summary
meridian_calibration_mask(media_times=, media_channels=, channel=, window=)builds the boolean(n_media_times, n_media_channels)roi_calibration_periodmask from the MMM's own coordinates: the experiment channel's column(s) carry exactly the window, and every other channel's column is all-True — Meridian's documented convention (channels not named in an experiment use ALL periods; an all-False column would zero that channel's aggregated calibration spend ininput_data._aggregate_spend). Window is a 2-tuple of inclusive(start, end)bounds (value-ordered,pd.to_datetime-coerced against datetime coordinates, timezone mismatches fail closed both ways) or a sequence of explicit labels (fail-closed membership, named missing-label errors). Container and element gates reject strings/Mappings/scalars/MultiIndex (including tuple-label promotion)/non-1-D arrays/missing channel names.MeridianROIPrior.to_code()accepts the array forroi_calibration_period, serializing it into the snippet as annp.onesprelude plus per-column-group window assignments (round-trip-exact for any accepted mask). Fail-closed acceptance: bool or 0/1-numeric only (cast, matching Google's own floatnp.zerosexample), masked arrays rejected, all-False masks and ANY entirely-False column rejected.ModelSpec._validate_roi_calibration_periodaccepts a non-None mask only for'roi'prior types, soto_code()now fails closed forparameter="mroi_m"with any non-Noneroi_calibration_period— the pre-existing expression-string route emittedmroi_msnippets Meridian rejects atModelSpecconstruction. The no-scope error is parameter-aware. Docs also quote Meridian's caution that the mask "is not generally recommended", pointing atfull_model_window=Truewhen the evidence transfers. Container-modescale=additionally rejects booleans (float(True)would silently scale by one).api/mmm.rstmask example + autofunction section + references,api/index.rstautosummary + committed stub, both LLM guides,references.rstcitation, CHANGELOG Added + Fixed, TODO mask-builder row removed (delivered);TestMeridianCalibrationMask+TestToCodeArrayMaskwith message-anchored coverage of the window-resolution and rejection matrices, serialization round-trips, and Google float-parity.Methodology references (required if estimator / math changes)
model/spec.py_validate_roi_calibration_period,data/input_data.py_aggregate_spend). All cited in the REGISTRY "MMM Calibration Export (interop)" section anddocs/references.rst.**Note:**bullets.Validation
tests/test_mmm.py(TestMeridianCalibrationMask,TestToCodeArrayMask, extendedtest_scope_mutually_exclusive/test_public_exports, boolean-scale rejection),tests/test_doc_snippets.pyroster already executes the newapi/mmm.rstexample. 127 MMM tests, doc snippets, naming guard, docs IA, and agent-discoverability suites pass;make -C docs html SPHINXOPTS="-W"clean; mypy zero errors at the CI pins.Security / privacy