Benchmarks¶
Cross-client benchmark meta-analysis (v1.5): pooling fitted MMMs and archived
platform lift studies from many client projects into industry-vertical
benchmarks, without any one client’s results being recoverable by another
client. That last clause is why this is nine modules and not a groupby.
K-anonymity alone is not privacy — given an exact cell mean, n-1 colluding
contributors recover the held-out brand’s number by arithmetic — so every value
that crosses the export boundary is a clipped simple statistic released under
Laplace noise, charged to a per-contributor ε ledger whose annual cap is a
hard gate rather than a report. The meta-model posterior, whose sensitivity to
any single contributor has no clean bound, stays internal and is never released
directly. The layering enforces this at one boundary: contribute and
ingest run project-side, store, meta_model and
publish run in the central benchmark tenant, and only
publish’s ledgered releases reach another client. Design:
technical-docs/benchmark-meta-analysis-design.md; worked walkthrough:
nbs/demos/benchmark_framework.ipynb.
Cross-client, privacy-preserving benchmark meta-analysis.
Pools findings across client projects — fitted MMMs and platform lift
studies — into industry-vertical benchmarks, without any client’s results
being recoverable by another client. Design:
technical-docs/benchmark-meta-analysis-design.md and the whitepaper
technical-docs/benchmark-meta-analysis-whitepaper.docx; the working
walkthrough is nbs/demos/benchmark_framework.ipynb.
Layered so privacy is enforced at a single boundary: contribute and
ingest run project-side and emit summary records; store,
meta_model and publish run in the central benchmark tenant;
only publish’s ledgered DP releases ever reach another client.
Imports are lazy (PEP 562): the meta-model pulls in PyMC only when fitted.
- class mmm_framework.benchmarks.BenchmarkEstimand(name, description, scale='log', requires_finance_basis=False, performance=True, provenance=('model', 'experiment', 'calibrated_model'), notes='', reference_point=<factory>)[source]
Bases:
objectOne entry in the benchmark estimand catalog.
scaleis the pooling scale. Ratio quantities pool on the log scale: effects are multiplicative across brands, and a symmetric-normal meta-model on the raw scale gets dragged by the right tail.-
name:
str
-
description:
str
-
scale:
str= 'log'
-
requires_finance_basis:
bool= False
-
performance:
bool= True
-
notes:
str= ''
- __init__(name, description, scale='log', requires_finance_basis=False, performance=True, provenance=('model', 'experiment', 'calibrated_model'), notes='', reference_point=<factory>)
-
name:
- class mmm_framework.benchmarks.BenchmarkStore(path=':memory:')[source]
Bases:
objectSQLite-backed contribution store with an audit trail.
- __init__(path=':memory:')[source]
- contributions(*, vertical=None, channel_code=None, estimand=None, contributor_hash=None)[source]
- Return type:
- cells()[source]
All contributions grouped by benchmark cell.
- Return type:
- delete_contributor(contributor_hash, *, now_ts=None)[source]
Consent revocation: remove every record from one contributor.
Already-published vintages are immutable (published noise cannot be un-released); deletion takes effect from the next vintage. The audit entry is the record consent requests are answered from.
- Return type:
- ledger()[source]
- Return type:
- get_vintage(vintage)[source]
- Return type:
- latest_vintage()[source]
- Return type:
- class mmm_framework.benchmarks.CellKey(**data)[source]
Bases:
BaseModelThe unit of gating and publication.
- model_config: ClassVar[ConfigDict] = {'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- vertical: str
- channel_code: str
- estimand: str
- schema_version: str
- class mmm_framework.benchmarks.ContributionRecord(**data)[source]
Bases:
BaseModelOne (contributor × channel × estimand) summary crossing the boundary.
Summaries, not draws: mean/sd/quantiles on the pooled scale cap the information a contribution carries.
sddoubles as the known-SE the meta-model consumes as data, so the ingest SE floor must already be applied.- schema_version: str
- taxonomy_version: str
- estimand: str
- channel_code: str
- vertical: str
- scale: Literal['log', 'linear']
- mean: float
- sd: float
- quantiles: dict[float, float]
- provenance: Provenance
- covariates: dict[str, Any]
- contributor_hash: str
- consent_version: str
- source_id: str | None
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- exception mmm_framework.benchmarks.EligibilityError[source]
Bases:
ValueErrorA gate refused the contribution; the message says which and why.
- class mmm_framework.benchmarks.EpsilonLedger(releases=None)[source]
Bases:
objectAppend-only record of every number that crossed the boundary.
Composition is basic (sum of ε per member across releases). Derived and carried-forward releases enter at ε = 0 — post-processing is free.
- __init__(releases=None)[source]
- spent_by_member(vintage_prefix=None)[source]
Per-contributor ε spend, optionally within one annual vintage prefix (e.g.
"2026"sums 2026Q1..Q4).
- class mmm_framework.benchmarks.IngestResult(records=<factory>, rejects=<factory>)[source]
Bases:
objectAccepted contribution records plus the per-study reject audit.
-
records:
list[ContributionRecord]
-
rejects:
list[RejectedStudy]
- __init__(records=<factory>, rejects=<factory>)
-
records:
- class mmm_framework.benchmarks.LiftStudyRecord(**data)[source]
Bases:
BaseModelOne archived platform lift study, as an archive actually holds it.
Optional fields are optional because real archives omit them; the ingest gates in
ingestdecide whether an omission is fatal (it usually is).contributor_hashis the salted, tenant-stable brand identifier — the raw brand/tenant id never appears in this schema.- study_id: str
- contributor_hash: str
- vertical: str
- channel_code: str
- form: StudyForm
- audience: str | None
- quarter: str | None
- point: float | None
- ci_low: float | None
- ci_high: float | None
- ci_level: float | None
- incremental_conversions: float | None
- se_conversions: float | None
- lift_pct: float | None
- se_lift_pct: float | None
- baseline_conversions: float | None
- baseline_rel_se: float | None
- value_basis: str | None
- value_per_conversion: float | None
- value_rel_se: float | None
- spend_in_study: float | None
- impressions_m: float | None
- attribution_window: str | None
- preregistered: bool | None
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.MetaModelResult(idata, verticals, contributors, contributor_vertical, moderators, reference=<factory>, has_provenance_bias=False)[source]
Bases:
objectFitted meta-model plus the indexing needed to read it.
-
idata:
Any
-
has_provenance_bias:
bool= False
- population(vertical, prob=0.95)[source]
Population mean and heterogeneity for one vertical, with the two intervals every consumer must distinguish: the CI for the mean and the prediction interval for a new brand (
tauincluded).
- shrinkage_table()[source]
Per-contributor pooled posterior vs vertical mean (internal view).
- Return type:
- __init__(idata, verticals, contributors, contributor_vertical, moderators, reference=<factory>, has_provenance_bias=False)
-
idata:
- class mmm_framework.benchmarks.ModeratorSpec(name, covariate, kind='binary', value=None, reference=None)[source]
Bases:
objectOne meta-regression moderator built from a covariate.
kind="binary"codescovariates[covariate] == valueas 1;kind="continuous"codescovariates[covariate] - reference(withreferencedefaulting to the pool mean), so the vertical means stay interpretable at the reference operating point.-
name:
str
-
covariate:
str
-
kind:
str= 'binary'
-
value:
Any= None
- __init__(name, covariate, kind='binary', value=None, reference=None)
-
name:
- class mmm_framework.benchmarks.PrivacyPolicy(**data)[source]
Bases:
BaseModelEvery privacy knob in one place, so the whole posture is reviewable.
- k_min: int
- spend_dominance_max: float
- precision_dominance_max: float
- epsilon_per_release: float
- epsilon_year: float
- clip_lo: float
- clip_hi: float
- deviation_clip: float
- contrast_clip: float
- se_floor: float
- min_refresh_churn: int
- property clip_width: float
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.Provenance(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]
-
How a contributed number was produced.
- MODEL = 'model'
- EXPERIMENT = 'experiment'
- CALIBRATED_MODEL = 'calibrated_model'
- class mmm_framework.benchmarks.PublishedCell(**data)[source]
Bases:
BaseModelA client-visible benchmark cell, assembled only from ledgered releases.
- vertical: str
- channel_code: str
- estimand: str
- n_band: str
- center: float
- spread: float
- noise_scale: float
- epsilon_spent: float
- derived: bool
- offsets: dict[str, float]
- stale_since: str | None
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.PublishedVintage(**data)[source]
Bases:
BaseModelThe full published artifact for one vintage (client-safe).
- vintage: str
- cells: list[PublishedCell]
- suppressed: list[SuppressedCell]
- epsilon_per_release: float
- worst_case_annual_epsilon: float
- frozen: list[str]
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.ReleaseRecord(**data)[source]
Bases:
BaseModelOne entry in the ε ledger: a number that crossed the boundary.
epsilon == 0marks a post-processed (derived) or carried-forward release — free under DP, recorded for lineage.- release_id: str
- vintage: str
- name: str
- statistic: str
- value: float
- epsilon: float
- laplace_scale: float
- members: tuple[str, ...]
- vertical: str | None
- estimand: str | None
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.StudyForm(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]
-
The estimand form a lift study was archived as (whitepaper §2.5).
- IROAS = 'iroas'
- ABSOLUTE_LIFT = 'absolute_lift'
- RELATIVE_LIFT = 'relative_lift'
- ATTRIBUTED_ROAS = 'attributed_roas'
- BRAND_LIFT = 'brand_lift'
- class mmm_framework.benchmarks.VerticalPriorPreset(vertical, channel_code, estimand, mu, sd, scale, experimental_share, anchored, source_vintage)[source]
Bases:
objectA default-prior preset for one (vertical × channel × estimand) cell.
-
vertical:
str
-
channel_code:
str
-
estimand:
str
-
mu:
float
-
sd:
float
-
scale:
str
-
experimental_share:
float
-
anchored:
bool
-
source_vintage:
str
- __init__(vertical, channel_code, estimand, mu, sd, scale, experimental_share, anchored, source_vintage)
-
vertical:
- mmm_framework.benchmarks.all_estimands()[source]
- Return type:
- mmm_framework.benchmarks.budget_band(annual_media_spend)[source]
Coarsen an exact annual budget to its order-of-magnitude band.
- Return type:
- mmm_framework.benchmarks.build_contribution(*, estimand, channel_code, vertical, draws, contributor, provenance=Provenance.MODEL, covariates=None, consent=False, consent_version='consent-v1', source_id=None, policy=None)[source]
Build one contribution record from posterior draws on the pooled scale.
drawsmust already be on the catalog scale for the estimand (log for ratio quantities). Catalog rules are enforced here: a finance-basis estimand without a declaredvalue_basiscovariate is rejected, not defaulted.- Return type:
- mmm_framework.benchmarks.check_model_eligibility(results, *, channel, min_weeks=52, min_contraction=0.1, contraction=None)[source]
Raise
EligibilityErrorunless a fitted model may contribute.resultsis anMMMResults-like object (approximate,converged,panel).contractionis the channel’s prior→posterior contraction frommmm_framework.diagnostics.learning.parameter_learning(); pass it when a prior-predictive group is available —Noneskips that gate (the caller accepts the echo-chamber risk knowingly, not by default).- Return type:
- mmm_framework.benchmarks.contributions_from_model(results, *, vertical, contributor, channel_draws, covariates=None, consent=False, contraction_by_channel=None, min_weeks=52, policy=None, source_id=None)[source]
Extract every eligible contribution from one fitted model.
channel_drawsmapschannel_code -> {estimand_name: draws}with draws already on the catalog scale (e.g. log-elasticity draws from an analyzer or estimand evaluation). Gates run per channel; one ineligible channel does not block the others, but a model-level gate (approximate fit, failed convergence) blocks everything.- Return type:
- mmm_framework.benchmarks.contributor_hash(tenant_id, salt)[source]
Salted, tenant-stable pseudonym. The raw tenant id never leaves.
- Return type:
- mmm_framework.benchmarks.experimental_share(records)[source]
Precision-weighted share of non-model evidence in a cell.
- Return type:
- mmm_framework.benchmarks.fit_meta_model(records, *, moderators=(ModeratorSpec(name='retargeting', covariate='audience', kind='binary', value='retargeting', reference=None), ModeratorSpec(name='log_spend', covariate='log_spend', kind='continuous', value=None, reference=None)), include_provenance_bias=True, draws=1000, tune=1000, chains=4, random_seed=11, progressbar=False)[source]
Fit the hierarchical random-effects pool over one estimand’s records.
All records must share a scale (enforced); mixed-estimand input is allowed only when the caller has a reason to pool them (normally: don’t).
delta_mis added when both model-family and experimental provenances are present.- Return type:
- mmm_framework.benchmarks.gate_cell(cell, records, policy)[source]
Apply the three per-cell gates: k, spend dominance, precision dominance.
Spend dominance is skipped (
None) when no record carries aspendcovariate; precision dominance always applies because the meta-model’s pooled mean is precision-weighted.- Return type:
- mmm_framework.benchmarks.ingest_studies(studies, policy=None)[source]
Run the ingest gates over an archive; nothing is silently dropped.
- Return type:
- mmm_framework.benchmarks.laplace_release(value, sensitivity, epsilon, rng)[source]
Laplace mechanism for a bounded statistic. Clip before calling — without hard bounds the sensitivity is unbounded and no finite noise gives a guarantee.
- Return type:
- mmm_framework.benchmarks.n_band(n)[source]
Band a contributor count for publication (exact counts difference).
- Return type:
- mmm_framework.benchmarks.normalize_study(study, policy)[source]
Route one archived study: a contribution record, or a reject with reason.
- Return type:
- mmm_framework.benchmarks.placement(client_value, cell, *, policy=None, n_replicates=400, rng=None)[source]
Percentile placement against a published cell — as a range, honestly.
At small cells and tight budgets the release noise exceeds the between-brand spread, so a point percentile would be noise theater. The placement is reported as the range across replicate releases (an analysis over hypothetical noise draws — nothing extra is published) plus a usability verdict with the cell size a stable placement would need.
- Return type:
- mmm_framework.benchmarks.publish_vintage(store, *, vintage, policy=None, rng=None, contrasts=(ContrastSpec(name='retargeting_offset', covariate='audience', level_hi='retargeting', level_lo='prospecting'),), now_ts=None)[source]
Publish one vintage from the store’s current contributions.
vintagesorts lexicographically (“2026Q3”) and its leading 4 characters are the annual accounting prefix for the ε_year gate.- Return type:
- mmm_framework.benchmarks.vertical_prior_preset(cell, *, vintage, experimental_share, floor_sd=0.5, anchor_threshold=0.3, scale='log')[source]
Map one published cell into a default-prior preset.
The predictive width is
sqrt(spread² + noise²)— a new client is a draw from the population, not a re-measurement of its mean. Unanchored cells are floored atfloor_sdregardless of how tight the pool looks.- Return type:
Privacy¶
Privacy machinery: cell gates, DP releases, and the ε ledger.
Doctrine (whitepaper §4–§5, demonstrated in the companion notebook):
k-anonymity alone is not privacy —
n-1colluding contributors invert an exact cell mean to machine precision. The Laplace noise is the mechanism that makes the goal statement (“no client’s results recoverable by another client”) true; the gates only decide which cells are worth releasing.DP applies to clipped simple statistics (per-contributor means, MADs, contrasts), never to posterior functionals — the sensitivity of a partially-pooled, precision-weighted posterior mean has no clean bound.
Every boundary crossing lands in one ledger. A contributor’s privacy loss is the sum of ε over every release it appears in; the annual cap is a hard gate, not a report.
- mmm_framework.benchmarks.privacy.GENERIC_SUPPRESSION = 'insufficient contributors'¶
Generic client-facing suppression reason. Anything more specific (“dominance 67%”) would itself disclose cell structure.
- class mmm_framework.benchmarks.privacy.PrivacyPolicy(**data)[source]¶
Bases:
BaseModelEvery privacy knob in one place, so the whole posture is reviewable.
- k_min: int¶
- spend_dominance_max: float¶
- precision_dominance_max: float¶
- epsilon_per_release: float¶
- epsilon_year: float¶
- clip_lo: float¶
- clip_hi: float¶
- deviation_clip: float¶
- contrast_clip: float¶
- se_floor: float¶
- min_refresh_churn: int¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- mmm_framework.benchmarks.privacy.n_band(n)[source]¶
Band a contributor count for publication (exact counts difference).
- Return type:
- mmm_framework.benchmarks.privacy.per_contributor_means(records, policy)[source]¶
Clipped unweighted mean per contributor — the DP release primitive.
Unweighted within contributor and clipped to the policy range, so the sensitivity of the cell mean is exactly
clip_width / n.
- mmm_framework.benchmarks.privacy.gate_cell(cell, records, policy)[source]¶
Apply the three per-cell gates: k, spend dominance, precision dominance.
Spend dominance is skipped (
None) when no record carries aspendcovariate; precision dominance always applies because the meta-model’s pooled mean is precision-weighted.- Return type:
- class mmm_framework.benchmarks.privacy.LaplaceRelease(value, scale)[source]¶
Bases:
objectA noised statistic plus the scale it was noised at.
- __init__(value, scale)¶
- mmm_framework.benchmarks.privacy.laplace_release(value, sensitivity, epsilon, rng)[source]¶
Laplace mechanism for a bounded statistic. Clip before calling — without hard bounds the sensitivity is unbounded and no finite noise gives a guarantee.
- Return type:
- class mmm_framework.benchmarks.privacy.EpsilonLedger(releases=None)[source]¶
Bases:
objectAppend-only record of every number that crossed the boundary.
Composition is basic (sum of ε per member across releases). Derived and carried-forward releases enter at ε = 0 — post-processing is free.
Schema¶
Schemas for the cross-client benchmark system.
Two record families cross this module:
LiftStudyRecord— the archive-side schema for a platform lift study (whitepaper §2.5). This is what an ingest job receives; most archives violate it somewhere, which is the point of the ingest gates.ContributionRecord— the only thing allowed to cross the privacy boundary from a project into the central benchmark store. Summaries on the pooled (log) scale, never draws, never raw data.
Everything downstream (store, gates, meta-model, publication) speaks these types. A bad record fails loudly at construction, the same doctrine as the garden contract.
- mmm_framework.benchmarks.schema.SCHEMA_VERSION = 'bench-contrib-v1'¶
Version stamped on every contribution record. Bump on any change to the fields or their semantics; the store partitions cells by it, so records made under different schema versions never pool together silently.
- mmm_framework.benchmarks.schema.DEFAULT_TAXONOMY_VERSION = 'chan-tax-v1'¶
Version of the channel taxonomy contributions are coded against. A re-mapping of what “digital video” means requires a bump plus a back-mapping table — records under different taxonomies never pool.
- class mmm_framework.benchmarks.schema.Provenance(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]¶
-
How a contributed number was produced.
- MODEL = 'model'¶
- EXPERIMENT = 'experiment'¶
- CALIBRATED_MODEL = 'calibrated_model'¶
- class mmm_framework.benchmarks.schema.StudyForm(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)[source]¶
-
The estimand form a lift study was archived as (whitepaper §2.5).
- IROAS = 'iroas'¶
- ABSOLUTE_LIFT = 'absolute_lift'¶
- RELATIVE_LIFT = 'relative_lift'¶
- ATTRIBUTED_ROAS = 'attributed_roas'¶
- BRAND_LIFT = 'brand_lift'¶
- class mmm_framework.benchmarks.schema.LiftStudyRecord(**data)[source]¶
Bases:
BaseModelOne archived platform lift study, as an archive actually holds it.
Optional fields are optional because real archives omit them; the ingest gates in
ingestdecide whether an omission is fatal (it usually is).contributor_hashis the salted, tenant-stable brand identifier — the raw brand/tenant id never appears in this schema.- study_id: str¶
- contributor_hash: str¶
- vertical: str¶
- channel_code: str¶
- form: StudyForm¶
- audience: str | None¶
- quarter: str | None¶
- point: float | None¶
- ci_low: float | None¶
- ci_high: float | None¶
- ci_level: float | None¶
- incremental_conversions: float | None¶
- se_conversions: float | None¶
- lift_pct: float | None¶
- se_lift_pct: float | None¶
- baseline_conversions: float | None¶
- baseline_rel_se: float | None¶
- value_basis: str | None¶
- value_per_conversion: float | None¶
- value_rel_se: float | None¶
- spend_in_study: float | None¶
- impressions_m: float | None¶
- attribution_window: str | None¶
- preregistered: bool | None¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.schema.ContributionRecord(**data)[source]¶
Bases:
BaseModelOne (contributor × channel × estimand) summary crossing the boundary.
Summaries, not draws: mean/sd/quantiles on the pooled scale cap the information a contribution carries.
sddoubles as the known-SE the meta-model consumes as data, so the ingest SE floor must already be applied.- schema_version: str¶
- taxonomy_version: str¶
- estimand: str¶
- channel_code: str¶
- vertical: str¶
- scale: Literal['log', 'linear']¶
- mean: float¶
- sd: float¶
- quantiles: dict[float, float]¶
- provenance: Provenance¶
- covariates: dict[str, Any]¶
- contributor_hash: str¶
- consent_version: str¶
- source_id: str | None¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.schema.CellKey(**data)[source]¶
Bases:
BaseModelThe unit of gating and publication.
- model_config: ClassVar[ConfigDict] = {'frozen': True}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- vertical: str¶
- channel_code: str¶
- estimand: str¶
- schema_version: str¶
- class mmm_framework.benchmarks.schema.RejectedStudy(**data)[source]¶
Bases:
BaseModelIngest-gate rejection, with the reason kept for the internal audit.
- study_id: str¶
- contributor_hash: str¶
- reason: str¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.schema.CellGateAudit(**data)[source]¶
Bases:
BaseModelPer-cell gate outcome.
internal_reasonnever publishes.The published reason is deliberately generic: a suppression reason like “dominance 67%” would itself disclose that one brand carries the cell.
- cell: CellKey¶
- n_records: int¶
- n_contributors: int¶
- spend_dominance: float | None¶
- precision_dominance: float¶
- publish: bool¶
- internal_reason: str¶
- published_reason: str¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.schema.ReleaseRecord(**data)[source]¶
Bases:
BaseModelOne entry in the ε ledger: a number that crossed the boundary.
epsilon == 0marks a post-processed (derived) or carried-forward release — free under DP, recorded for lineage.- release_id: str¶
- vintage: str¶
- name: str¶
- statistic: str¶
- value: float¶
- epsilon: float¶
- laplace_scale: float¶
- members: tuple[str, ...]¶
- vertical: str | None¶
- estimand: str | None¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.schema.PublishedCell(**data)[source]¶
Bases:
BaseModelA client-visible benchmark cell, assembled only from ledgered releases.
- vertical: str¶
- channel_code: str¶
- estimand: str¶
- n_band: str¶
- center: float¶
- spread: float¶
- noise_scale: float¶
- epsilon_spent: float¶
- derived: bool¶
- offsets: dict[str, float]¶
- stale_since: str | None¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.schema.SuppressedCell(**data)[source]¶
Bases:
BaseModelClient-visible record of a cell that did not publish.
- vertical: str¶
- channel_code: str¶
- estimand: str¶
- reason: str¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class mmm_framework.benchmarks.schema.PublishedVintage(**data)[source]¶
Bases:
BaseModelThe full published artifact for one vintage (client-safe).
- vintage: str¶
- cells: list[PublishedCell]¶
- suppressed: list[SuppressedCell]¶
- epsilon_per_release: float¶
- worst_case_annual_epsilon: float¶
- frozen: list[str]¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Estimand Catalog¶
The versioned catalog of benchmarkable estimands.
Only estimands defined to be comparable across brands may enter the pool.
Average ROI is deliberately absent: it confounds margin structure, price
point, market scale, and curve position (pitfalls doc §3). bench_iroas
is quoted at a reference operating point and pooled with a spend moderator
for the same reason — raw average pooling of iROAS is banned, moderated
reference-point iROAS is not.
- mmm_framework.benchmarks.estimands.CATALOG_VERSION = 'bench-est-v1'¶
Bump when the catalog’s definitions change; contributions carry it via the schema version, and cells never pool across catalog generations.
- class mmm_framework.benchmarks.estimands.BenchmarkEstimand(name, description, scale='log', requires_finance_basis=False, performance=True, provenance=('model', 'experiment', 'calibrated_model'), notes='', reference_point=<factory>)[source]¶
Bases:
objectOne entry in the benchmark estimand catalog.
scaleis the pooling scale. Ratio quantities pool on the log scale: effects are multiplicative across brands, and a symmetric-normal meta-model on the raw scale gets dragged by the right tail.- __init__(name, description, scale='log', requires_finance_basis=False, performance=True, provenance=('model', 'experiment', 'calibrated_model'), notes='', reference_point=<factory>)¶
- mmm_framework.benchmarks.estimands.get(name)[source]¶
Look up a catalog entry; unknown names fail loudly.
- Return type:
Contribute¶
Project-side contribution extraction: gates, coarsening, consent.
Runs inside the client’s project and sees raw posteriors; only the summary
ContributionRecord it emits ever leaves. Three
responsibilities, in order:
Eligibility gates — approximate fits are excluded (their uncertainty is not calibrated and the meta-model consumes the SE as data); the posterior must actually have contracted from its prior (a prior-dominated read launders the default prior into the benchmark, which then becomes the next default prior — the echo-chamber loop); minimum panel length.
Coarsening at the source — covariates leave as bands/tiers, never exact values. This module is the single place the coarsening rules live.
Consent — no contribution without an explicit opt-in; the record carries the consent version it was made under so revocation can be honored retroactively (delete-by-contributor-hash on the store).
- mmm_framework.benchmarks.contribute.CONTRIBUTION_QUANTILES = (0.05, 0.25, 0.5, 0.75, 0.95)¶
Quantiles carried on every contribution record.
- mmm_framework.benchmarks.contribute.BUDGET_BANDS = ((1000000.0, '<$1M'), (10000000.0, '$1–10M'), (100000000.0, '$10–100M'), (inf, '>$100M'))¶
Order-of-magnitude annual media budget bands (coarse for privacy).
- exception mmm_framework.benchmarks.contribute.EligibilityError[source]¶
Bases:
ValueErrorA gate refused the contribution; the message says which and why.
- mmm_framework.benchmarks.contribute.contributor_hash(tenant_id, salt)[source]¶
Salted, tenant-stable pseudonym. The raw tenant id never leaves.
- Return type:
- mmm_framework.benchmarks.contribute.budget_band(annual_media_spend)[source]¶
Coarsen an exact annual budget to its order-of-magnitude band.
- Return type:
- mmm_framework.benchmarks.contribute.summarize_draws(draws, policy=None)[source]¶
Posterior draws → the summary that crosses the boundary.
The SE floor applies here too: a contribution claiming near-zero sd would own the precision-weighted pool exactly like a poisoned lift study.
- Return type:
- mmm_framework.benchmarks.contribute.check_model_eligibility(results, *, channel, min_weeks=52, min_contraction=0.1, contraction=None)[source]¶
Raise
EligibilityErrorunless a fitted model may contribute.resultsis anMMMResults-like object (approximate,converged,panel).contractionis the channel’s prior→posterior contraction frommmm_framework.diagnostics.learning.parameter_learning(); pass it when a prior-predictive group is available —Noneskips that gate (the caller accepts the echo-chamber risk knowingly, not by default).- Return type:
- mmm_framework.benchmarks.contribute.build_contribution(*, estimand, channel_code, vertical, draws, contributor, provenance=Provenance.MODEL, covariates=None, consent=False, consent_version='consent-v1', source_id=None, policy=None)[source]¶
Build one contribution record from posterior draws on the pooled scale.
drawsmust already be on the catalog scale for the estimand (log for ratio quantities). Catalog rules are enforced here: a finance-basis estimand without a declaredvalue_basiscovariate is rejected, not defaulted.- Return type:
- mmm_framework.benchmarks.contribute.contributions_from_model(results, *, vertical, contributor, channel_draws, covariates=None, consent=False, contraction_by_channel=None, min_weeks=52, policy=None, source_id=None)[source]¶
Extract every eligible contribution from one fitted model.
channel_drawsmapschannel_code -> {estimand_name: draws}with draws already on the catalog scale (e.g. log-elasticity draws from an analyzer or estimand evaluation). Gates run per channel; one ineligible channel does not block the others, but a model-level gate (approximate fit, failed convergence) blocks everything.- Return type:
Ingest¶
Ingest gates and estimand normalization for archived lift studies.
Whitepaper §2.5 as code. The pool has exactly one experimental estimand:
log incremental ROAS at a declared value basis (bench_iroas). Archives
hold at least five things all called “the result of the test”; they are not
interchangeable:
iROAS (conversion lift): pools directly,
y = log(iROAS).Absolute lift: converts via the declared value basis; the basis is an estimate, so its relative error enters the SE in quadrature. Treating the basis as exact understates every converted SE and overweights converted records in the precision-weighted pool.
Relative lift: needs the counterfactual baseline volume too; archived as “+12%” alone it has no path to a dollar scale and is rejected.
Attributed ROAS (pixel): a different estimand, not a noisier version of iROAS — rejected from the causal pool at any weight.
Brand lift (survey): different outcome, routed out of the ROI pool.
One addition beyond the reject rules: the SE floor. The meta-model’s mean is precision-weighted, so a study claiming near-zero SE — winner’s-curse selection, archival error, or deliberate poisoning — would own the pool.
- mmm_framework.benchmarks.ingest.REASON_BRAND_LIFT = 'brand-lift: wrong estimand family'¶
Reject reasons (stable strings — the internal audit groups by them).
- mmm_framework.benchmarks.ingest.se_from_ci(lo, hi, level)[source]¶
Recover the log-scale SE from a ratio-scale CI at its reported level.
Using the reported level matters: treating a 90% CI as a 95% one misstates every weight downstream.
- Return type:
- class mmm_framework.benchmarks.ingest.IngestResult(records=<factory>, rejects=<factory>)[source]¶
Bases:
objectAccepted contribution records plus the per-study reject audit.
-
records:
list[ContributionRecord]¶
-
rejects:
list[RejectedStudy]¶
- __init__(records=<factory>, rejects=<factory>)¶
-
records:
- mmm_framework.benchmarks.ingest.normalize_study(study, policy)[source]¶
Route one archived study: a contribution record, or a reject with reason.
- Return type:
Store¶
The central contribution store: append-only, deduped, audited.
Everything right of the export boundary sees only what this store holds —
contribution records, the ε ledger, and published vintages. Stdlib sqlite so
the lean core stays dependency-free; pass ":memory:" (the default) for
tests and notebooks, a path for the platform deployment.
Dedup rule: the natural key is (cell, contributor_hash, source_id) — a
re-fit or re-ingest of the same source replaces its record rather than
duplicating it, so frequently-refitting clients cannot dominate the pool.
Distinct studies from one contributor keep distinct source_id values and
legitimately coexist.
- class mmm_framework.benchmarks.store.BenchmarkStore(path=':memory:')[source]¶
Bases:
objectSQLite-backed contribution store with an audit trail.
- contributions(*, vertical=None, channel_code=None, estimand=None, contributor_hash=None)[source]¶
- Return type:
- delete_contributor(contributor_hash, *, now_ts=None)[source]¶
Consent revocation: remove every record from one contributor.
Already-published vintages are immutable (published noise cannot be un-released); deletion takes effect from the next vintage. The audit entry is the record consent requests are answered from.
- Return type:
Meta Model¶
Bayesian random-effects meta-analysis over contribution records.
The pooling engine of the benchmark: per (vertical × channel × estimand)
family, contributed means with known SEs are pooled hierarchically —
between-contributor heterogeneity tau_v per vertical, moderators for
market structure and curve position, and a provenance bias term delta_m
identified by contributors who supply both a model read and an experimental
read of the same channel.
The posterior is internal. Nothing here is released directly: the
sensitivity of a posterior functional to one contributor’s data has no clean
bound, so publication happens only through the clipped-statistic DP releases
in publish. The posterior’s role is the internal benchmark, moderator
science, and the shrinkage machinery of the internal view.
PyMC imports are local to fit_meta_model(), keeping the lean core’s
import path free of the sampler stack.
- class mmm_framework.benchmarks.meta_model.ModeratorSpec(name, covariate, kind='binary', value=None, reference=None)[source]¶
Bases:
objectOne meta-regression moderator built from a covariate.
kind="binary"codescovariates[covariate] == valueas 1;kind="continuous"codescovariates[covariate] - reference(withreferencedefaulting to the pool mean), so the vertical means stay interpretable at the reference operating point.- __init__(name, covariate, kind='binary', value=None, reference=None)¶
- mmm_framework.benchmarks.meta_model.DEFAULT_LIFT_MODERATORS = (ModeratorSpec(name='retargeting', covariate='audience', kind='binary', value='retargeting', reference=None), ModeratorSpec(name='log_spend', covariate='log_spend', kind='continuous', value=None, reference=None))¶
audience scope is the largest hidden heterogeneity in lift archives, and log-spend is the curve-position term that makes an iROAS pool defensible at all.
- Type:
Moderators applied by default to lift-study pools
- class mmm_framework.benchmarks.meta_model.MetaModelResult(idata, verticals, contributors, contributor_vertical, moderators, reference=<factory>, has_provenance_bias=False)[source]¶
Bases:
objectFitted meta-model plus the indexing needed to read it.
- population(vertical, prob=0.95)[source]¶
Population mean and heterogeneity for one vertical, with the two intervals every consumer must distinguish: the CI for the mean and the prediction interval for a new brand (
tauincluded).
- shrinkage_table()[source]¶
Per-contributor pooled posterior vs vertical mean (internal view).
- Return type:
- __init__(idata, verticals, contributors, contributor_vertical, moderators, reference=<factory>, has_provenance_bias=False)¶
- mmm_framework.benchmarks.meta_model.design_matrix(records, moderators)[source]¶
Build the moderator matrix; continuous moderators are centered on the reference point (recorded, so readouts quote the operating point).
- mmm_framework.benchmarks.meta_model.fit_meta_model(records, *, moderators=(ModeratorSpec(name='retargeting', covariate='audience', kind='binary', value='retargeting', reference=None), ModeratorSpec(name='log_spend', covariate='log_spend', kind='continuous', value=None, reference=None)), include_provenance_bias=True, draws=1000, tune=1000, chains=4, random_seed=11, progressbar=False)[source]¶
Fit the hierarchical random-effects pool over one estimand’s records.
All records must share a scale (enforced); mixed-estimand input is allowed only when the caller has a reason to pool them (normally: don’t).
delta_mis added when both model-family and experimental provenances are present.- Return type:
Publish¶
Vintage publication: orthogonalized DP releases, carry-forward, hard caps.
The release design is deliberately small and orthogonal per (vertical × estimand): a clipped population mean, a clipped mean absolute deviation (a naive DP variance is hopeless at k ≈ 5–10 cell sizes; the MAD’s sensitivity survives), and clipped contrasts for declared binary splits (channel, audience). Per-channel cell centers are then post-processing of the released mean and channel contrast — free under DP — so adding published cells never re-bills the same contributors.
Refresh discipline (whitepaper §5b):
Carry-forward is free. Republishing last vintage’s noised values costs nothing; only touching the data spends. The refresh trigger is membership churn — metadata the store knows without reading any value. (A value-dependent trigger leaks and must itself be accounted; that upgrade is a sparse-vector test, out of scope.)
ε_year is a hard gate, not a report. A release that would push any member past the annual cap is refused; the cell freezes at its last published values with the staleness disclosed. Exhaustion surfacing as staleness is the system working.
- mmm_framework.benchmarks.publish.MAD_TO_SD = 1.2533¶
E|X - μ| = σ·√(2/π) for a normal, so σ ≈ 1.2533 × MAD (mean absolute deviation).
- Type:
Normal-consistency factor
- mmm_framework.benchmarks.publish.SPREAD_FLOOR = 0.05¶
Floor on the published spread so a small noised MAD cannot collapse the client band to a point.
- class mmm_framework.benchmarks.publish.ContrastSpec(name, covariate, level_hi, level_lo)[source]¶
Bases:
objectA clipped per-contributor binary contrast to release (e.g. audience).
- __init__(name, covariate, level_hi, level_lo)¶
- mmm_framework.benchmarks.publish.publish_vintage(store, *, vintage, policy=None, rng=None, contrasts=(ContrastSpec(name='retargeting_offset', covariate='audience', level_hi='retargeting', level_lo='prospecting'),), now_ts=None)[source]¶
Publish one vintage from the store’s current contributions.
vintagesorts lexicographically (“2026Q3”) and its leading 4 characters are the annual accounting prefix for the ε_year gate.- Return type:
- mmm_framework.benchmarks.publish.placement(client_value, cell, *, policy=None, n_replicates=400, rng=None)[source]¶
Percentile placement against a published cell — as a range, honestly.
At small cells and tight budgets the release noise exceeds the between-brand spread, so a point percentile would be noise theater. The placement is reported as the range across replicate releases (an analysis over hypothetical noise draws — nothing extra is published) plus a usability verdict with the cell size a stable placement would need.
- Return type:
Prior Feedback¶
Prior feedback: published benchmarks → vertical default-prior presets.
The last stage of the loop, and the most dangerous one: the population posterior feeds back into ROI-parameterized default media priors for new clients in the same vertical. Two hard rules break the model → benchmark → prior → model echo chamber:
Prior width comes from the predictive distribution (between-brand τ and release noise included), never the posterior SE of μ — otherwise new clients get overconfident priors and a re-measurement of the pool’s mean masquerades as knowledge about their brand.
Experimental anchoring: a preset may only tighten beyond the floor width when the cell’s experimental share exceeds a threshold. A benchmark built purely from models is not allowed to sharpen the priors those models were fitted under.
- mmm_framework.benchmarks.priors.DEFAULT_FLOOR_SD = 0.5¶
A preset may not tighten below this sd (pooled/log scale) unless anchored.
- mmm_framework.benchmarks.priors.DEFAULT_ANCHOR_THRESHOLD = 0.3¶
Minimum weight of non-model contributions for a cell to count as anchored.
- class mmm_framework.benchmarks.priors.VerticalPriorPreset(vertical, channel_code, estimand, mu, sd, scale, experimental_share, anchored, source_vintage)[source]¶
Bases:
objectA default-prior preset for one (vertical × channel × estimand) cell.
- __init__(vertical, channel_code, estimand, mu, sd, scale, experimental_share, anchored, source_vintage)¶
Precision-weighted share of non-model evidence in a cell.
- Return type:
- mmm_framework.benchmarks.priors.vertical_prior_preset(cell, *, vintage, experimental_share, floor_sd=0.5, anchor_threshold=0.3, scale='log')[source]¶
Map one published cell into a default-prior preset.
The predictive width is
sqrt(spread² + noise²)— a new client is a draw from the population, not a re-measurement of its mean. Unanchored cells are floored atfloor_sdregardless of how tight the pool looks.- Return type: