Model Garden

Bespoke-model authoring: the CustomMMM contract, capability gates, and the compatibility suite behind the Atelier.

Model Garden — author, test, version, and share bespoke MMM models.

A garden model is a custom BayesianMMM subclass (see CustomMMM) that an expert authors, tests for compatibility, and registers under a name + version + documentation so the agent (“the oracle”) can load and re-fit it on any project’s data, and so it can be reused across projects and future sessions.

Light symbols (the contract + its checks) are importable directly. The heavier pieces — CustomMMM (pulls in the PyMC model stack), the source load_garden_class_from_path(), and the run_compatibility_check() suite — are resolved lazily via module __getattr__ so import mmm_framework.garden stays cheap and free of import cycles.

mmm_framework.garden.validate_class(cls)[source]

Static structural check (NO instantiation, no PyMC import).

Returns a list of human-readable problems — empty means the class is structurally compatible. This is the gate run before a candidate is written to the registry and the first tier of compat.run_compatibility_check().

Return type:

list[str]

mmm_framework.garden.validate_instance(mmm)[source]

Runtime check on a CONSTRUCTED model (post-__init__, pre/post fit).

Confirms the required attributes are present and sane. channel_names must be a non-empty ordered list and equal the panel’s channels (the serializer raises on a mismatch).

Return type:

list[str]

mmm_framework.garden.validate_fitted(mmm)[source]

Runtime check on a FITTED model: _trace populated, posterior coords align with the model, and the channel parameter-name conventions hold (so the extraction helpers return non-empty results).

Return type:

list[str]

mmm_framework.garden.find_garden_class(module)[source]

Locate THE garden model class inside an imported module.

Resolution order: an explicit module-level GARDEN_MODEL attribute, else the single class defined in the module that subclasses BayesianMMM, else the single class defined in the module that passes validate_class(). Raises ValueError when ambiguous or absent.

Return type:

type

mmm_framework.garden.is_bayesian_mmm_subclass(cls)[source]

True if cls derives from BayesianMMM / an extended-model base — checked by MRO name so this stays PyMC-free (no model import).

Return type:

bool

mmm_framework.garden.describe_contract()[source]

Markdown summary of the contract — surfaced to the authoring agent / docs.

Return type:

str

Contract

Model Garden contract — the interface a custom MMM must satisfy to be “oracle-compatible” (runnable by the agent + the reporting / analysis stack).

A “garden model” is a bespoke BayesianMMM subclass (the recommended path — see mmm_framework.garden.base.CustomMMM) or any class that duck-types the same surface. This module is the single source of truth for that surface; the compatibility suite (mmm_framework.garden.compat) is just the executable encoding of the checks below.

It is intentionally dependency-light — no PyMC / model imports at module load — so the static structural check validate_class() can run anywhere (host or kernel) without paying the heavy model-stack import. The runtime checks (validate_instance(), validate_fitted()) inspect a constructed / fitted instance and are meant to run kernel-side, where the model lives.

mmm_framework.garden.contract.GARDEN_CONTRACT_VERSION = '1.0'

Bumped when the required surface changes. Stored on every registered model so a consumer can detect a model authored against an older/newer contract.

mmm_framework.garden.contract.DEFAULT_MODEL_KIND = 'mmm'

Default model kind when a class/instance does not declare one — the historical MMM behaviour (channels, spend, beta_<channel> parameters, channel read-ops).

mmm_framework.garden.contract.REQUIRED_ATTRS: tuple[str, ...] = ('y_mean', 'y_std', 'panel', 'model_config', 'has_geo', 'has_product', 'channel_names', '_media_raw_max')

Union, kept for backward compatibility (importers + describe_contract).

mmm_framework.garden.contract.REQUIRED_METHODS: tuple[str, ...] = ('fit',)

Methods every model must define (fit is the one true requirement; everything else is inherited from BayesianMMM or duck-typed).

mmm_framework.garden.contract.RECOMMENDED_METHODS: tuple[str, ...] = ('predict', 'sample_channel_contributions', 'compute_component_decomposition')

Methods that each unlock specific ops; absent ones must degrade gracefully.

mmm_framework.garden.contract.PARAM_PREFIXES: tuple[str, ...] = ('beta_', 'adstock_alpha_', 'sat_half_', 'sat_slope_')

Posterior parameter-name conventions the extraction helpers key off (compute_adstock_weights / compute_saturation_curves / compute_roi_with_uncertainty look these prefixes up by channel). These mirror BayesianMMM’s graph: a channel c gets beta_c (coefficient), adstock_alpha_c (carryover), and sat_half_c / sat_slope_c (saturation half-saturation point + slope).

class mmm_framework.garden.contract.SupportsFit(*args, **kwargs)[source]

Bases: Protocol

fit(...) returning an MMMResults and setting self._trace.

fit(*args, **kwargs)[source]
Return type:

Any

__init__(*args, **kwargs)
class mmm_framework.garden.contract.HasScaling(*args, **kwargs)[source]

Bases: Protocol

Standardization params used to unstandardize every downstream metric.

y_mean: float
y_std: float
__init__(*args, **kwargs)
class mmm_framework.garden.contract.HasMediaMeta(*args, **kwargs)[source]

Bases: Protocol

Channel labels + raw-spend maxima (predict divides by the latter).

channel_names: list
__init__(*args, **kwargs)
mmm_framework.garden.contract.is_bayesian_mmm_subclass(cls)[source]

True if cls derives from BayesianMMM / an extended-model base — checked by MRO name so this stays PyMC-free (no model import).

Return type:

bool

mmm_framework.garden.contract.model_kind(obj)[source]

The Garden model_kind of a class or instance.

A model declares its kind with the class attribute __garden_model_kind__ (e.g. "cfa", "latent_class"). Absent that, anything in the BayesianMMM MRO is "mmm"; otherwise "unknown" so a structurally non-MMM class is not silently treated as an MMM.

Return type:

str

mmm_framework.garden.contract.is_mmm_model(obj)[source]

Whether the MMM-specific gates apply to obj (class or instance).

True unless the model explicitly declares a non-"mmm" __garden_model_kind__. A duck-typed / unknown model with no declaration is treated as MMM — the historical default, so its channel attributes, beta_<channel> posterior convention, and channel read-ops are still checked. Only a declared non-MMM family (e.g. a CFA) opts out.

Return type:

bool

mmm_framework.garden.contract.has_latent_structure(obj)[source]

Whether obj exposes a latent-structure summary the report can render.

Duck-typed (a class or instance): True if it defines a callable factor_loadings_summary or class_profile_summary. This is orthogonal to is_mmm_model() — a hybrid family (an MMM that ALSO estimates a latent factor, e.g. LatentFactorMMM) is both is_mmm_model and has_latent_structure, so its report shows the channel/ROI sections and a factor-loadings section. A pure CFA/LCA/CLV is non-MMM and has latent structure; a plain MMM has none of these methods, so the latent section stays off.

Return type:

bool

mmm_framework.garden.contract.validate_class(cls)[source]

Static structural check (NO instantiation, no PyMC import).

Returns a list of human-readable problems — empty means the class is structurally compatible. This is the gate run before a candidate is written to the registry and the first tier of compat.run_compatibility_check().

Return type:

list[str]

mmm_framework.garden.contract.validate_instance(mmm)[source]

Runtime check on a CONSTRUCTED model (post-__init__, pre/post fit).

Confirms the required attributes are present and sane. channel_names must be a non-empty ordered list and equal the panel’s channels (the serializer raises on a mismatch).

Return type:

list[str]

mmm_framework.garden.contract.validate_fitted(mmm)[source]

Runtime check on a FITTED model: _trace populated, posterior coords align with the model, and the channel parameter-name conventions hold (so the extraction helpers return non-empty results).

Return type:

list[str]

mmm_framework.garden.contract.find_garden_class(module)[source]

Locate THE garden model class inside an imported module.

Resolution order: an explicit module-level GARDEN_MODEL attribute, else the single class defined in the module that subclasses BayesianMMM, else the single class defined in the module that passes validate_class(). Raises ValueError when ambiguous or absent.

Return type:

type

mmm_framework.garden.contract.describe_contract()[source]

Markdown summary of the contract — surfaced to the authoring agent / docs.

Return type:

str

Compat

Compatibility test suite for Model Garden custom models.

run_compatibility_check(cls, ...) is the executable encoding of the contract in mmm_framework.garden.contract. It fits the candidate class on labeled synthetic worlds (mmm_framework.synth.generate_mff(), which ships a ground-truth answer key) using a fast approximate fit, then exercises the read surface the oracle relies on. Each check is a tier; some are BLOCKING (must pass before a model can move draft -> tested) and some are advisory.

Pure + kernel-importable (imports the model stack lazily inside the function), so it runs identically in-process and inside the sandboxed session kernel.

mmm_framework.garden.compat.run_compatibility_check(cls, *, scenarios=('clean', 'realistic'), fit_method='map', seed=7, n_weeks=104, check_carryover=True)[source]

Run the full compatibility suite on a candidate class.

Returns a JSON-safe report:

{contract_version, class_name, is_bayesian_mmm_subclass, scenario,
 tiers: [{name, passed, blocking, detail, skipped}], blocking_passed,
 score, summary}

The suite never raises: every tier is independently guarded so a crash surfaces as passed=False with the exception text.

Return type:

dict

mmm_framework.garden.compat.BLOCKING_TIERS: frozenset[str] = frozenset({'build', 'fit', 'instance', 'ops_smoke', 'scaling', 'static', 'trace'})

Tiers that gate the draft -> tested promotion. The rest are reported but never block (a model can be structurally sound yet recover ROI poorly on a hard world — that is information, not a failure).