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().
- 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_namesmust be a non-empty ordered list and equal the panel’s channels (the serializer raises on a mismatch).
- mmm_framework.garden.validate_fitted(mmm)[source]
Runtime check on a FITTED model:
_tracepopulated, posterior coords align with the model, and the channel parameter-name conventions hold (so the extraction helpers return non-empty results).
- mmm_framework.garden.find_garden_class(module)[source]
Locate THE garden model class inside an imported module.
Resolution order: an explicit module-level
GARDEN_MODELattribute, else the single class defined in the module that subclasses BayesianMMM, else the single class defined in the module that passesvalidate_class(). RaisesValueErrorwhen ambiguous or absent.- Return type:
- mmm_framework.garden.is_bayesian_mmm_subclass(cls)[source]
True if
clsderives fromBayesianMMM/ an extended-model base — checked by MRO name so this stays PyMC-free (no model import).- Return type:
- mmm_framework.garden.describe_contract()[source]
Markdown summary of the contract — surfaced to the authoring agent / docs.
- Return type:
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_uncertaintylook these prefixes up by channel). These mirror BayesianMMM’s graph: a channelcgetsbeta_c(coefficient),adstock_alpha_c(carryover), andsat_half_c/sat_slope_c(saturation half-saturation point + slope).
- class mmm_framework.garden.contract.SupportsFit(*args, **kwargs)[source]¶
Bases:
Protocolfit(...)returning anMMMResultsand settingself._trace.- __init__(*args, **kwargs)¶
- class mmm_framework.garden.contract.HasScaling(*args, **kwargs)[source]¶
Bases:
ProtocolStandardization params used to unstandardize every downstream metric.
- __init__(*args, **kwargs)¶
- class mmm_framework.garden.contract.HasMediaMeta(*args, **kwargs)[source]¶
Bases:
ProtocolChannel labels + raw-spend maxima (predict divides by the latter).
- __init__(*args, **kwargs)¶
- mmm_framework.garden.contract.is_bayesian_mmm_subclass(cls)[source]¶
True if
clsderives fromBayesianMMM/ an extended-model base — checked by MRO name so this stays PyMC-free (no model import).- Return type:
- mmm_framework.garden.contract.model_kind(obj)[source]¶
The Garden
model_kindof 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 theBayesianMMMMRO is"mmm"; otherwise"unknown"so a structurally non-MMM class is not silently treated as an MMM.- Return type:
- 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:
- mmm_framework.garden.contract.has_latent_structure(obj)[source]¶
Whether
objexposes a latent-structure summary the report can render.Duck-typed (a class or instance): True if it defines a callable
factor_loadings_summaryorclass_profile_summary. This is orthogonal tois_mmm_model()— a hybrid family (an MMM that ALSO estimates a latent factor, e.g.LatentFactorMMM) is bothis_mmm_modelandhas_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:
- 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().
- 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_namesmust be a non-empty ordered list and equal the panel’s channels (the serializer raises on a mismatch).
- mmm_framework.garden.contract.validate_fitted(mmm)[source]¶
Runtime check on a FITTED model:
_tracepopulated, posterior coords align with the model, and the channel parameter-name conventions hold (so the extraction helpers return non-empty results).
- 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_MODELattribute, else the single class defined in the module that subclasses BayesianMMM, else the single class defined in the module that passesvalidate_class(). RaisesValueErrorwhen ambiguous or absent.- Return type:
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=Falsewith the exception text.- Return type:
- mmm_framework.garden.compat.BLOCKING_TIERS: frozenset[str] = frozenset({'build', 'fit', 'instance', 'ops_smoke', 'scaling', 'static', 'trace'})¶
Tiers that gate the
draft -> testedpromotion. 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).