Builders¶
The builders module provides fluent API classes for constructing configuration objects using the builder pattern.
Base Builders¶
Base classes and mixins for configuration builders.
Provides shared functionality used across all builder types.
- class mmm_framework.builders.base.BuilderProtocol(*args, **kwargs)[source]¶
Bases:
Protocol[T]Protocol for configuration builders.
All builders should implement a build() method that returns the final configuration object.
- __init__(*args, **kwargs)¶
- class mmm_framework.builders.base.VariableConfigBuilderMixin[source]¶
Bases:
objectMixin providing shared methods for variable configuration builders.
This mixin provides common functionality for builders that configure variables with display names, units, and dimensions. It should be used by MediaChannelConfigBuilder, ControlVariableConfigBuilder, and KPIConfigBuilder to eliminate code duplication.
Attributes¶
- _display_namestr | None
Human-readable display name for the variable.
- _unitstr | None
Unit of measurement (e.g., ‘USD’, ‘GRPs’).
- _dimensionslist[DimensionType]
Dimensions this variable is defined over.
- with_display_name(name)[source]¶
Set human-readable display name.
- Return type:
Self
Parameters¶
- namestr
Display name for the variable.
Returns¶
- Self
Builder instance for method chaining.
- with_unit(unit)[source]¶
Set unit of measurement.
- Return type:
Self
Parameters¶
- unitstr
Unit of measurement (e.g., ‘USD’, ‘GRPs’, ‘Index’).
Returns¶
- Self
Builder instance for method chaining.
- with_dimensions(*dims)[source]¶
Set dimensions this variable is defined over.
If PERIOD is not included in the provided dimensions, it will be automatically inserted at the beginning.
- Parameters:
*dims (
DimensionType) – Variable number of dimension types.- Return type:
Self- Returns:
Builder instance for method chaining.
- national()[source]¶
Set as national-level (Period only).
This is a convenience method equivalent to: with_dimensions(DimensionType.PERIOD)
- Return type:
Self
Returns¶
- Self
Builder instance for method chaining.
- by_geo()[source]¶
Set as geo-level (Period + Geography).
This is a convenience method equivalent to: with_dimensions(DimensionType.PERIOD, DimensionType.GEOGRAPHY)
- Return type:
Self
Returns¶
- Self
Builder instance for method chaining.
Prior Builders¶
Prior and transformation configuration builders.
Provides builders for PriorConfig, AdstockConfig, and SaturationConfig.
- class mmm_framework.builders.prior.PriorConfigBuilder[source]¶
Bases:
objectBuilder for PriorConfig objects.
Examples¶
>>> prior = (PriorConfigBuilder() ... .half_normal(sigma=2.0) ... .with_dims("channel") ... .build())
>>> prior = (PriorConfigBuilder() ... .gamma(alpha=2, beta=1) ... .build())
- truncated_normal(mu=0.0, sigma=1.0, lower=0.0, upper=None)[source]¶
Set TruncatedNormal distribution.
- Return type:
Self
- class mmm_framework.builders.prior.AdstockConfigBuilder[source]¶
Bases:
objectBuilder for AdstockConfig objects.
Examples¶
>>> adstock = (AdstockConfigBuilder() ... .geometric() ... .with_max_lag(8) ... .with_alpha_prior(PriorConfigBuilder().beta(1, 3).build()) ... .build())
- class mmm_framework.builders.prior.SaturationConfigBuilder[source]¶
Bases:
objectBuilder for SaturationConfig objects.
Examples¶
>>> saturation = (SaturationConfigBuilder() ... .hill() ... .with_kappa_prior(PriorConfigBuilder().beta(2, 2).build()) ... .with_slope_prior(PriorConfigBuilder().half_normal(1.5).build()) ... .with_kappa_bounds(0.1, 0.9) ... .build())
- with_kappa_bounds(lower, upper)[source]¶
Set percentile bounds for kappa prior (data-driven).
- Return type:
Self
Variable Builders¶
Variable configuration builders.
Provides builders for MediaChannelConfig, ControlVariableConfig, and KPIConfig.
- class mmm_framework.builders.variable.MediaChannelConfigBuilder(name)[source]¶
Bases:
VariableConfigBuilderMixinBuilder for MediaChannelConfig objects.
Examples¶
>>> tv_config = (MediaChannelConfigBuilder("TV") ... .with_dimensions(DimensionType.PERIOD) ... .with_adstock(AdstockConfigBuilder().geometric().with_max_lag(8).build()) ... .with_saturation(SaturationConfigBuilder().hill().build()) ... .with_positive_prior(sigma=2.0) ... .build())
>>> social_meta = (MediaChannelConfigBuilder("Meta") ... .with_dimensions(DimensionType.PERIOD) ... .with_parent_channel("Social") ... .with_split_dimensions(DimensionType.OUTLET) ... .build())
- with_geometric_adstock(l_max=8)[source]¶
Convenience: set geometric adstock with max lag.
- Return type:
Self
- with_logistic_saturation()[source]¶
Convenience: set logistic saturation with defaults.
- Return type:
Self
- with_michaelis_menten_saturation()[source]¶
Convenience: set Michaelis-Menten saturation with defaults.
- Return type:
Self
- with_root_saturation()[source]¶
Convenience: set root / power saturation (
x ** k) with defaults.- Return type:
Self
- with_positive_prior(sigma=2.0)[source]¶
Convenience: set HalfNormal prior for positive effects.
- Return type:
Self
- with_roi_prior(*, median=None, mu=None, sigma=None)[source]¶
State the channel’s prior directly on the ROI (decision) scale.
Sets the hyper-parameters of the ROI-parameterized prior
roi_<ch> ~ LogNormal(mu, sigma)— the channel then uses the ROI parameterization even undermedia_prior_mode="coefficient". Give the location asmedian(ROI units, e.g.1.2for a break-even+ belief;mu = log(median)) or asmu(log scale) — not both.sigmais the log-scale spread: 0.5 ⇒ 90% within ~[0.44×, 2.3×] of the median; the default 1.0 within ~[0.19×, 5.2×].- Return type:
Self
- with_parent_channel(parent)[source]¶
Set parent channel for hierarchical grouping.
- Return type:
Self
- with_split_dimensions(*dims)[source]¶
Set additional split dimensions (e.g., Outlet for platforms).
- Return type:
Self
- with_time_varying(enabled=True, innovation_sigma=0.15)[source]¶
Model this channel’s coefficient as time-varying (a random walk on
log(beta_t)) so effectiveness can drift.innovation_sigmais the per-step log-innovation scale (smaller ⇒ closer to constant beta).- Return type:
Self
- measured_in(unit)[source]¶
Declare how the modeled variable is measured.
"spend"(default) keeps normal ROI."impressions"/"clicks"/"other"mark the variable as a volume, so ROI is resolved from aspend_column/cpm/cpcif provided, else reported as efficiency per 1,000 impressions (or per click / unit). The response curve is always fit on the modeled variable regardless.- Return type:
Self
- with_spend_column(column)[source]¶
Use a SEPARATE MFF variable as the dollar spend for ROI (option a).
Implies a non-spend
measurement_unit; defaults it to impressions if stillspend.- Return type:
Self
- with_cpm(cpm)[source]¶
Derive ROI by costing the modeled impressions at
cpmper 1,000 (option b). Implies impressions ifmeasurement_unitis still spend.- Return type:
Self
- with_cpc(cpc)[source]¶
Derive ROI by costing the modeled clicks at
cpcper click (option b). Setsmeasurement_unitto clicks.- Return type:
Self
- class mmm_framework.builders.variable.ControlVariableConfigBuilder(name)[source]¶
Bases:
VariableConfigBuilderMixinBuilder for ControlVariableConfig objects.
Examples¶
>>> price = (ControlVariableConfigBuilder("Price") ... .with_dimensions(DimensionType.PERIOD) ... .allow_negative() ... .with_normal_prior(mu=0, sigma=1) ... .build())
>>> distribution = (ControlVariableConfigBuilder("Distribution") ... .national() ... .with_shrinkage() ... .build())
- with_causal_role(role, reason=None)[source]¶
Declare the control’s causal role (confounder vs precision control).
Accepts the enum or its string value (
"confounder"/"precision_control"; the spec-level shorthand"precision"is normalized). The role drives back-door reporting and the explicit-prior-on-confounder warning in the core model.- Return type:
Self
- class mmm_framework.builders.variable.KPIConfigBuilder(name)[source]¶
Bases:
VariableConfigBuilderMixinBuilder for KPIConfig objects.
Examples¶
>>> kpi = (KPIConfigBuilder("Sales") ... .with_dimensions(DimensionType.PERIOD, DimensionType.GEOGRAPHY) ... .multiplicative() ... .build())
Model Builders¶
Model-level configuration builders.
Provides builders for ModelConfig and related configuration objects.
- class mmm_framework.builders.model.HierarchicalConfigBuilder[source]¶
Bases:
objectBuilder for HierarchicalConfig objects.
Examples¶
>>> hierarchical = (HierarchicalConfigBuilder() ... .enabled() ... .pool_across_geo() ... .pool_across_product() ... .with_non_centered_threshold(20) ... .build())
- with_non_centered_threshold(threshold)[source]¶
Set minimum observations for centered parameterization.
- Return type:
Self
- class mmm_framework.builders.model.SeasonalityConfigBuilder[source]¶
Bases:
objectBuilder for SeasonalityConfig objects.
Examples¶
>>> seasonality = (SeasonalityConfigBuilder() ... .with_yearly(order=2) ... .with_weekly(order=3, prior_sigma=0.2) ... .with_prior_sigma(0.5) # shared amplitude prior ... .build())
- with_yearly(order=2, prior_sigma=None)[source]¶
Add yearly seasonality with given Fourier order (and optionally a component-specific amplitude prior sigma).
- Return type:
Self
- with_prior_sigma(sigma)[source]¶
Set the shared seasonal amplitude prior: Fourier coefficients get
Normal(0, sigma)on standardized y. Per-component sigmas (passed towith_yearly(..., prior_sigma=)etc.) override this.- Return type:
Self
- class mmm_framework.builders.model.ControlSelectionConfigBuilder[source]¶
Bases:
objectBuilder for ControlSelectionConfig objects.
Examples¶
>>> selection = (ControlSelectionConfigBuilder() ... .horseshoe(expected_nonzero=3) ... .build())
- class mmm_framework.builders.model.ModelConfigBuilder[source]¶
Bases:
objectBuilder for ModelConfig objects.
Examples¶
>>> model = (ModelConfigBuilder() ... .additive() ... .bayesian_numpyro() ... .with_chains(4) ... .with_draws(2000) ... .with_hierarchical(HierarchicalConfigBuilder().enabled().build()) ... .build())
- with_parametric_adstock(enabled=True)[source]¶
Estimate a continuous in-graph adstock kernel per channel (default).
Honors each
MediaChannelConfig.adstock(geometric, delayed, or Weibull). Passenabled=Falsefor the legacy fixed-alpha blend — the pre-2026-06 default, kept for reproducing older fits.- Return type:
Self
- with_legacy_blend_adstock()[source]¶
Use the legacy fixed-alpha-bank blend adstock (pre-change default).
- Return type:
Self
- bayesian_nutpie()[source]¶
Use the nutpie NUTS sampler (Rust implementation).
Samples the same graph as
bayesian_pymc()/bayesian_numpyro()— only the NUTS implementation differs, so all three target the identical posterior. Which one is fastest is model-dependent: measured on effective samples per second, the ranking inverts between a small national model and a geo panel. Benchmark on your own model rather than assuming (nbs/demos/nuts_backends.ipynbdoes exactly this).- Return type:
Self
- frequentist_ridge()[source]¶
Ridge estimation with bootstrap confidence intervals (epic #180).
A different paradigm, not a faster Bayesian one. Adstock and saturation are chosen by rolling-origin out-of-sample search rather than estimated under priors, the remaining coefficients are solved in closed form, and the intervals come from a moving-block residual bootstrap.
Not a synonym for
fit(method="map"): ridge is MAP under Gaussian coefficient priors, and this framework’s media prior isGamma(mu=1.5, sigma=1)orLogNormal(0, 1)on ROI — neither of which is Gaussian, so MAP here is a different penalized estimator. What this adds is transform selection by search, bootstrap confidence intervals, and (viafrequentist_cvxpy()) hard constraints.MMMResults.convergedisNonefor such a fit and every report surface labels its intervals as confidence intervals. Requires a model that is linear given fixed transforms; a GP trend, per-geo media coefficients or reach/frequency channels raise, naming the feature.- Return type:
Self
- frequentist_cvxpy()[source]¶
Constrained estimation via a convex program (epic #180).
Everything
frequentist_ridge()does, with hard linear constraints the solver must satisfy exactly — the one capability a prior cannot express. AHalfNormalprior makes a negative coefficient merely unlikely; a constraint makes it impossible, and a sum-of-contributions constraint (“match the number finance booked”) has no prior analogue at all.Selecting this with no explicit
constraints=applies non-negative media, the one restriction every MMM wants. Needs the optional extra:pip install 'mmm-framework[frequentist]'(cvxpy).A coefficient pinned by an active constraint has no meaningful two-sided interval; those columns are listed in
diagnostics["at_boundary"].- Return type:
Self
- with_inference_method(method)[source]¶
Set the inference method (paradigm) directly.
The generic form of
bayesian_pymc()/frequentist_ridge()/ …, for a caller holding the enum value rather than choosing at the call site — e.g. the agent spec layer mappinginference.method.Selecting a frequentist paradigm also clears
fit_method— enforced byModelConfig’s own validator rather than here, so it holds however the config was built.- Return type:
Self
- with_fit_method(method)[source]¶
Set the default fit method used by
BayesianMMM.fit()."nuts"(default) runs full MCMC;"smc"runs tempered Sequential Monte Carlo (also exact — robust to multimodality, estimates the log marginal likelihood, but slower than NUTS on well-behaved posteriors). The approximate methods —"map","laplace","advi","fullrank_advi","pathfinder"— fit in seconds for quick model checks but produce uncalibrated uncertainty.Noneis accepted and means “no Bayesian fit method applies” — the state a frequentist config is left in, sinceFitMethodhas no member for a penalized point estimate and a default of"nuts"would make an unfitted model’s prefit surfaces announce a full MCMC posterior.- Return type:
Self
- map_fit()[source]¶
Default to a maximum a posteriori (MAP) point estimate — fastest check.
- Return type:
Self
- laplace()[source]¶
Default to a Laplace (Gaussian around the MAP point) approximation.
Cheap curvature-based uncertainty — a step up from bare MAP for model checking, still approximate (via the declared
pymc_extrasdep).- Return type:
Self
- advi(full_rank=False)[source]¶
Default to variational inference (ADVI, or full-rank ADVI).
- Return type:
Self
- pathfinder()[source]¶
Default to Pathfinder VI (requires the optional
pymc_extraspackage).- Return type:
Self
- smc()[source]¶
Default to tempered Sequential Monte Carlo (
pm.sample_smc).An exact sampler (not an approximate check): use it to confirm suspected multimodality (R-hat across independent SMC runs) or to get a log marginal likelihood for model comparison. Slower than NUTS on well-behaved posteriors.
- Return type:
Self
- with_likelihood(likelihood)[source]¶
Set the observation (likelihood) family. Default is normal/identity.
- Return type:
Self
- with_intercept_prior(mu=0.0, sigma=0.5)[source]¶
Set the intercept prior: Normal(mu, sigma) on standardized y.
muis measured in KPI standard deviations from the mean, so values beyond roughly ±2 place the baseline outside the observed KPI range.- Return type:
Self
- with_media_prior_mode(mode='roi', *, roi_mu=None, roi_sigma=None)[source]¶
Choose the DEFAULT media-effect prior parameterization.
"roi"samples each channel’s prior ROI directly (roi_<ch> ~ LogNormal(roi_mu, roi_sigma), default median 1.0 = break-even) and derivesbeta_<ch>in-graph — the default prior lives on the decision scale and is comparable across channels."coefficient"keeps the historical standardized-coefficient Gamma. Only applies to channels without an experiment-calibratedroi_prioror an explicitcoefficient_prior.- Return type:
Self
- roi_based_media_priors(roi_mu=0.0, roi_sigma=1.0)[source]¶
Convenience: switch the default media priors to the ROI scale.
- Return type:
Self
- with_grouped_media_priors(enabled=True)[source]¶
Partial-pool the coefficients of channels sharing a
parent_channelgroup toward a shared mean (DF-2). Off by default; only pool genuinely exchangeable channels. Calibrated / explicitly-priored channels are excluded from the pool.- Return type:
Self
- with_events(events)[source]¶
Add holiday / event effects (#143) — an
EventsConfigof named country holidays and/or custom event windows, fit as an additiveevent_componentdistinct from the smooth Fourier seasonality.- Return type:
Self
- with_channel_interactions(*interactions)[source]¶
Add cross-channel synergy / interaction terms (#142) — one or more
ChannelInteraction, each abeta_ij * sat_i * sat_jterm with a sign-aware, shrink-to-zero prior. Off by default (strictly additive).- Return type:
Self
- with_price(price)[source]¶
Promote a control column to a price lever (#138) — a log-price elasticity (≤ 0) with an optional reference/discount-depth. Pass a
PriceConfig.- Return type:
Self
- with_promotions(*promotions)[source]¶
Promote control columns to promo levers (#138) — a lift with its own carryover. Pass one or more
PromoConfig.- Return type:
Self
- with_reach_frequency(*configs)[source]¶
Model channels as reach × a frequency-saturation curve (#141). Pass one or more
ReachFrequencyConfig; each names a channel (its column is reach) and a frequency column (from the control block). Off by default.- Return type:
Self
- with_control_selection_builder(builder)[source]¶
Set control selection from builder.
- Return type:
Self
- with_bootstrap_samples(n)[source]¶
Set number of bootstrap samples for uncertainty.
- Return type:
Self
- class mmm_framework.builders.model.TrendConfigBuilder[source]¶
Bases:
objectBuilder for TrendConfig objects with support for various trend types.
Supports: - None (intercept only) - Linear (simple linear trend) - Piecewise (Prophet-style changepoint detection) - Spline (B-spline flexible trend) - Gaussian Process (HSGP approximation for smooth trends)
Examples¶
>>> # Simple linear trend >>> trend = TrendConfigBuilder().linear().build()
>>> # Piecewise linear with changepoints >>> trend = (TrendConfigBuilder() ... .piecewise() ... .with_n_changepoints(10) ... .with_changepoint_range(0.8) ... .with_changepoint_prior_scale(0.05) ... .build())
>>> # B-spline trend >>> trend = (TrendConfigBuilder() ... .spline() ... .with_n_knots(15) ... .with_spline_degree(3) ... .with_spline_prior_sigma(1.0) ... .build())
>>> # Gaussian Process trend >>> trend = (TrendConfigBuilder() ... .gaussian_process() ... .with_gp_lengthscale(mu=0.3, sigma=0.2) ... .with_gp_amplitude(sigma=0.5) ... .with_gp_n_basis(25) ... .build())
- with_changepoint_range(range_pct)[source]¶
Set range (0-1) for placing changepoints.
- Return type:
Self
- class mmm_framework.builders.model.DimensionAlignmentConfigBuilder[source]¶
Bases:
objectBuilder for DimensionAlignmentConfig objects.
Examples¶
>>> alignment = (DimensionAlignmentConfigBuilder() ... .geo_by_population() ... .product_by_sales() ... .build())
- geo_by_custom(weight_variable)[source]¶
Allocate to geos using custom weight variable from MFF.
- Return type:
Self
- product_by_custom(weight_variable)[source]¶
Allocate to products using custom weight variable.
- Return type:
Self
- prefer_disaggregation(prefer=True)[source]¶
Prefer disaggregating national data vs aggregating detailed data.
- Return type:
Self
MFF Builders¶
MFF (Master Flat File) configuration builders.
Provides builders for MFFColumnConfig and MFFConfig.
- class mmm_framework.builders.mff.MFFColumnConfigBuilder[source]¶
Bases:
objectBuilder for MFFColumnConfig objects.
Examples¶
>>> columns = (MFFColumnConfigBuilder() ... .with_period_column("Week") ... .with_geography_column("Region") ... .build())
- class mmm_framework.builders.mff.MFFConfigBuilder[source]¶
Bases:
objectBuilder for MFFConfig objects - the main configuration builder.
Examples¶
>>> config = (MFFConfigBuilder() ... .with_kpi(KPIConfigBuilder("Sales").by_geo().build()) ... .add_media(MediaChannelConfigBuilder("TV").national().with_geometric_adstock(8).build()) ... .add_media(MediaChannelConfigBuilder("Digital").national().with_geometric_adstock(4).build()) ... .add_control(ControlVariableConfigBuilder("Price").allow_negative().build()) ... .with_alignment(DimensionAlignmentConfigBuilder().geo_by_sales().build()) ... .with_date_format("%Y-%m-%d") ... .weekly() ... .build())
- add_national_media(name, adstock_lmax=8)[source]¶
Convenience: add national media with defaults.
- Return type:
Self
- add_social_platforms(platforms, parent_name='Social', adstock_lmax=4)[source]¶
Convenience: add social platforms with hierarchy.
- Return type:
Self
- add_price_control(name='Price')[source]¶
Convenience: add price control (allows negative).
- Return type:
Self
- add_distribution_control(name='Distribution')[source]¶
Convenience: add distribution/ACV control.
- Return type:
Self
- with_fill_missing_media(value)[source]¶
Set fill value for missing media (default: 0).
- Return type:
Self