YAML Configuration
The pipeline runner reads the same YAML model specification used by
build_mmm_from_yaml(...), then adds a small set of runner-specific conventions
for data loading, prior-sensitivity planning, optional AI advisor guidance,
optional blocked holdout validation, and Stage 70 optimisation.
This page documents the keys that the runner actually consumes.
Root keys
| Key | Required | Used for |
|---|---|---|
data | Yes | Supply data.date_column and dataset paths unless paths are overridden through PipelineRunConfig |
target | Yes | Define the target column and business target type |
estimator | No | Declare a named estimator preset; time_series, fe, and cre are currently released |
dimensions | No | Declare panel-dimension columns such as geo or brand |
media | Yes | Define channel/control columns and transform types |
scaling | No | Configure target/channel scaling rules |
effects | No | Append additive effects in YAML order before build_model(...) |
priors | No | Override model-level priors and prefixed transform priors |
fit | No | Default sampler settings for Stage 20 fitting |
holidays | No | Add holiday events before model build |
original_scale_vars | No | Add original-scale contribution variables before fitting |
inference_data | No | Attach existing InferenceData when the file exists |
prior_sensitivity | No | Write a pre-fit scenario plan for prior robustness checks |
ai_advisor | No | Write privacy-safe AI advisor guidance before model fitting |
validation | No | Enable optional Stage 35 blocked holdout validation |
optimization | No | Enable Stage 70 budget optimisation |
diagnostics | No | Override Stage 50 runner diagnostics thresholds |
calibration | No | Post-build calibration under the selected estimator operation gates |
Minimal runner config
data:
dataset_path: dataset.csv
date_column: date
target:
column: revenue
type: revenue
estimator:
type: time_series
media:
channels: [channel_1, channel_2]
adstock:
type: geometric
l_max: 4
saturation:
type: logistic
fit:
draws: 1000
tune: 1000
chains: 4
cores: 4
random_seed: 42
Relative paths in YAML are resolved relative to the YAML file’s directory.
diagnostics is runner-only. The structured pipeline reads it, but
build_mmm_from_yaml(...) still validates only the public MMM model schema.
prior_sensitivity and ai_advisor are also runner-only. They are consumed by
the structured pipeline before model fitting and stripped before the public MMM
YAML builder validates the model specification.
validation is also runner-only. The structured pipeline reads it for Stage 35
blocked holdout scoring, but the public MMM YAML builder never sees it.
Core modeling blocks
The runner always builds a PanelMMM, so the public YAML no longer exposes a
model.class field. Instead, it reads:
data.date_columntarget.columntarget.typemedia.channelsmedia.controls, if anyestimator, for a named presetdimensions.panel, if anymedia.adstockmedia.saturationfit
estimator
The released named single-series contract is:
estimator:
type: time_series
It requires one observation per date and no panel unit. It builds the same
single-series graph as the established configuration with no
dimensions.panel for the same configuration. With the default model settings,
this means one global intercept and shared media, control, adstock, saturation,
and residual parameters. Other explicit single-series model options retain
their established behaviour; the estimator declaration does not silently
override them.
The released fixed-effects contract is:
estimator:
type: fe
unit: geo
estimability:
within_variation_share_warning: 0.05
max_vif_warning: 20
condition_number_warning: 30
It accepts one unit column and uses an exact within-unit orthonormal-contrast likelihood. Unit intercepts are absorbed. Media and control slopes, adstock, saturation, and residual scale are shared across units. The FE preset does not support common time effects, annual seasonality, custom additive effects, or time-varying parameters. See Fixed-effects Estimator for the estimability checks and interpretation limits.
The released correlated-random-effects contract is:
estimator:
type: cre
unit: geo
estimability:
within_variation_share_warning: 0.05
max_vif_warning: 20
condition_number_warning: 30
minimum_between_residual_df: 2
posterior_diagnostic_draws: 50
It accepts one balanced unit panel. Media and control slopes, geometric adstock, logistic saturation, and residual scale are shared across units. The graph uses an exact marginal Gaussian random-intercept likelihood and adds centred unit means of the transformed media basis and eligible time-varying controls. Common time effects, seasonality, custom effects, calibration, optimisation and fixed-budget scenario optimisation are not supported. Prediction and historical/manual scenarios require all fitted units and reject unseen units and unit subsets. Manual CRE scenarios retain the fitted training-period Mundlak summaries rather than recomputing them from planned spend. See Correlated-random-effects Estimator for the estimability and interpretation limits.
The re declaration validates as typed configuration but remains release-gated.
It fails before graph construction and does not fall back to the advanced
panel-dimension surface.
Do not combine estimator with dimensions.panel. AMMM3 rejects the mixed
declaration rather than guessing which semantics you intended.
data
The runner loads data before building the model. It supports two CSV layouts.
Combined dataset
data:
dataset_path: "dataset.csv"
The runner reads the CSV, removes the target column from X, and uses that
column as y.
Separate feature and target files
data:
x_path: "X.csv"
y_path: "y.csv"
When loading y_path:
- if the configured target column exists, the runner uses that column
- otherwise, if the file has exactly one column, the runner uses that column and renames it to the target name
Target column resolution
The runner resolves the target column in this order:
PipelineRunConfig.target_columnor CLI--target-columntarget.column"y"
Use the CLI override only when you want to change how the runner reads the CSV.
Keep it consistent with target.column in YAML.
fit
fit controls Stage 20 fitting because the fit stage
calls:
context.model.fit(X=context.X, y=context.y, progressbar=False)
The runner merges these CLI or PipelineRunConfig overrides onto the YAML
fit block when they are provided:
drawstunechainscoresrandom_seed
The public YAML schema currently supports these fit keys:
drawstunechainscoresrandom_seedtarget_acceptprogressbarcompute_convergence_checks
Unknown fit keys are rejected when the YAML is loaded.
effects
effects is an optional list of additive effect specifications:
effects:
- type: linear_trend
prefix: trend
n_changepoints: 8
- type: weekly_fourier
order: 3
The builder appends each effect to model.mu_effects in YAML order before
calling build_model(...).
holidays
The holidays block is optional.
Supported keys used by the builder include:
| Key | Meaning |
|---|---|
path | Holiday CSV path |
enabled | Set to false to disable holiday loading |
prefix | Prefix for generated holiday effect coordinates |
mode | Holiday handling mode: event, pooled_control, or prophet_component |
countries | Country filter for catalogue-style holiday CSV input |
Example:
holidays:
mode: prophet_component
path: "../../data/holidays.csv"
prefix: "holiday"
countries: "UK"
The CLI or PipelineRunConfig.holidays_path overrides holidays.path.
If you omit both path and the override but still configure holidays,
AMMM3 falls back to the bundled abacus.data:holidays.csv.
Country-selection rules:
- time-series configs default to
USwhenholidays.countriesis omitted - geo-panel configs must declare
holidays.countriesexplicitly - geo-panel configs must provide multiple countries, for example
["UK", "FR", "DE"]
If you provide a catalogue-style holiday CSV, AMMM3 only creates holiday
effects for the countries listed in holidays.countries.
Holiday modes:
eventcreates one latent holiday/event effect per holiday row, which is why posterior summaries include terms likeholiday_effect_size[...].pooled_controlcreates one pooled binary holiday regressor over time and estimates a single shared holiday coefficient. This is useful when you want a strict calendar-only single holiday term instead of one parameter per holiday.prophet_componentfits Prophet on the training target with the configured holiday calendar, extracts the continuousholidayscomponent, and uses that single smoothed series inside the MMM as one holiday term. For panel models, AMMM3 fits one Prophet holiday component per panel series and filters the holiday calendar bygeowhen that dimension is present.
For holiday effects, each model date labels the start of its observed period.
AMMM3 assigns an inclusive holiday date range to every model period it
overlaps. For example, on W-MON data, a Wednesday or Sunday holiday is
assigned to the Monday date that starts that week. Daily data retains its
existing date-by-date assignment.
Default behavior:
- configs default to
prophet_component - use
eventexplicitly when you want one latent holiday effect per holiday row
Current limitation:
pooled_controlcurrently supports only single-country, non-geo models.prophet_componentrequires exactly one holiday country unless the model has ageodimension, in which case it can route multiple holiday countries to the matching geo-level panel series.
original_scale_vars
Use original_scale_vars when you want specific contribution variables to be
available on the original target scale:
original_scale_vars:
- channel_contribution
- y
The builder applies these through
model.add_original_scale_contribution_variable(...) before fitting.
inference_data
inference_data.path is passed through to the YAML builder. If the file exists, AMMM3
attaches that InferenceData after graph construction. This occurs in Stage 00
for unlabelled models and Stage 10 for named estimators.
Important: the structured runner still executes Stage 20 and fits the model
again. inference_data.path does not currently skip fitting.
prior_sensitivity
The Python API reference covers scenario expansion, evidence construction and approved-patch application.
Use the optional prior_sensitivity block when you want the runner to write a
pre-fit prior scenario plan. This stage does not fit every scenario. It creates
resolved scenario configs that can be reviewed, approved, and run deliberately.
Conservative generated plan:
prior_sensitivity:
enabled: true
scenario_policy: conservative_mmm
reference: reference
Manual plan:
prior_sensitivity:
enabled: true
scenario_policy: manual
reference: reference
scenarios:
reference:
description: Current approved prior specification.
tighter_media_effect:
description: Lower media-effect amplitude on the scaled target space.
overrides:
media.saturation.priors.beta:
distribution: HalfNormal
sigma: 0.5
dims: ["channel"]
Supported keys:
| Key | Meaning |
|---|---|
enabled | Set to true to write Stage 05 prior-sensitivity artifacts |
scenario_policy | manual for declared scenarios or conservative_mmm for generated relative scenarios |
reference | Scenario name for the unchanged reference config |
scenarios | Optional manual scenario declarations |
allow_model_structure_overrides | Required before scenarios can change transform structure such as media.adstock.l_max |
Scenario names are slugs such as reference, longer_memory, or
tighter_media_effect. Avoid names such as baseline; in MMM, baseline has a
model meaning and should not be overloaded as a scenario label.
Allowed override paths are intentionally narrow:
media.adstock.priors.*media.saturation.priors.*priors.*- selected transform-structure paths such as
media.adstock.l_max, only whenallow_model_structure_overrides: true
The stage writes both a human-readable manifest and an LLM-safe manifest. Use the LLM-safe file when passing scenario context to an external model because it aliases override paths and avoids free-text descriptions.
ai_advisor
Use the optional ai_advisor block when you want privacy-safe, evidence-grounded
modelling guidance from deterministic rules and, optionally, OpenAI or
OpenRouter. The advisor proposes controlled tests. It does not approve a model,
establish causal identification, or apply a config patch to the run config.
ai_advisor:
enabled: true
provider: openrouter
mode: autopilot
privacy: anonymized_relative
approval: file_based
write_outputs: true
llm_enabled: true
diagnostics_review_enabled: true
openai_model: gpt-5-mini
openai_timeout_seconds: 60
openrouter_model: openai/gpt-5.2
openrouter_timeout_seconds: 60
Supported keys:
| Key | Meaning |
|---|---|
enabled | Set to true to write Stage 08 advisor artifacts |
provider | openai or openrouter |
mode | autopilot; the advisor prioritizes concise recommendations and approval-ready options |
privacy | anonymized_relative; raw channel names and raw business values are excluded from the LLM payload |
approval | file_based; proposed config changes are written as files for user approval |
write_outputs | Set to false to disable artifact writes even when the block is enabled |
llm_enabled | Set to false to run deterministic privacy/rule checks without an LLM call |
diagnostics_review_enabled | Defaults to true; set to false to skip the post-fit 55_ai_diagnostics_advisor LLM review after structured diagnostics |
openai_model | OpenAI model name used for the advisor call |
openai_timeout_seconds | Request timeout for the OpenAI call |
openrouter_model | OpenRouter model name used for the advisor call |
openrouter_timeout_seconds | Request timeout for the OpenRouter call |
The pipeline reads OPENAI_API_KEY or OPENROUTER_API_KEY from the process
environment based on provider. For local development, an untracked repo-root
.env file is also supported. Do not commit API keys.
The advisor stages complete even if an LLM call fails. In that case they write an error artifact and the rest of the pipeline can continue. Deterministic rules provide a minimum decision state: an LLM may make the state stricter, but cannot override a failed gate or weak-identification warning with a more favourable conclusion.
When the advisor proposes a valid config patch, Stage 08 writes:
config_patch_proposal.yamlapproval_request.yaml
The proposal format is deliberately narrow:
overrides:
media.saturation.priors.beta:
distribution: HalfNormal
sigma: 0.5
dims: ["channel"]
To approve it, edit approval_request.yaml so status: approved, then run:
python -m abacus.pipeline.approval \
--approval-request results/<run>/08_ai_advisor/approval_request.yaml \
--approved-by "model owner"
The approval command writes approved_config.resolved.yaml and
approval_record.yaml beside the advisor artifacts. It does not mutate the
source YAML config.
By default, an enabled ai_advisor block also runs
55_ai_diagnostics_advisor after structured diagnostics. That post-fit advisor
uses anonymized channel aliases, convergence counts, normalized predictive
metrics, coverage metrics, and scale-free design diagnostics. It intentionally
excludes raw target-scale fit errors from the LLM payload. Set
diagnostics_review_enabled: false to run only the pre-fit advisor.
optimization
Add an optimization block when you want Stage 70 to run. If this block is
absent, Stage 70 is marked skipped.
The YAML builder validates this block when the config is loaded. start_date
and end_date are always required, and you must provide exactly one of:
optimization.budgetfor the preferred user-facing budget specoptimization.total_budgetfor the legacy per-period budget input
Unknown top-level optimization keys are rejected.
Preferred example:
optimization:
start_date: "2024-11-11"
end_date: "2025-01-27"
budget:
mode: relative
value: 1.10
basis: reference_window_total
Optional keys read by Stage 70:
| Key | Default | Meaning |
|---|---|---|
budget | None | Preferred user-facing budget spec: absolute or relative |
total_budget | None | Legacy per-period budget input kept for backward compatibility |
response_variable | total_media_contribution_original_scale | Optimisation objective variable |
budget_distribution_over_period | None | Time weights over the optimisation window |
budget_bounds | Derived or default | Explicit spend bounds |
spend_constraint_lower | 0.3 when deriving bounds | Relative lower bound around scaled reference spend |
spend_constraint_upper | 0.3 when deriving bounds | Relative upper bound around scaled reference spend |
default_constraints | true | Whether to add the default equality budget constraint |
noise_level | 0.001 | Noise level for simulated response samples |
include_last_observations | false | Whether posterior predictive sampling includes trailing observed rows |
include_carryover | true | Whether simulated response sampling extends the window for carryover |
Budget spec modes:
budget.mode: absolutebudget.valueis total spend over the full optimisation horizon.budget.mode: relativebudget.valueis a multiplier on the chosen basis.budget.basis: reference_window_totalAMMM3 resolves the budget against the same reference-window total spend it already uses for current-plan comparison and default bound derivation.
Important budget-unit note
The preferred optimization.budget block uses total horizon spend. Stage
70 converts that to the wrapper’s per-period contract internally before calling
PanelBudgetOptimizerWrapper.optimize_budget(...).
The legacy optimization.total_budget field is still supported, but it keeps
the old wrapper-facing per-period spend contract.
See Budget Optimisation.
Xarray-like optimisation values in YAML
For panel bounds or time distributions, use the xarray-like mapping shape that Stage 70 expects:
optimization:
start_date: "2025-02-03"
end_date: "2025-02-24"
budget:
mode: absolute
value: 100000.0
budget_distribution_over_period:
values:
- [[0.25, 0.25], [0.25, 0.25]]
- [[0.25, 0.25], [0.25, 0.25]]
- [[0.25, 0.25], [0.25, 0.25]]
- [[0.25, 0.25], [0.25, 0.25]]
dims: ["date", "geo", "channel"]
coords:
date: [0, 1, 2, 3]
geo: ["UK", "FR"]
channel: ["channel_1", "channel_2"]
The same shape works for budget_bounds, but with an additional "bound"
dimension containing "lower" and "upper".
diagnostics
Stage 50 resolves a complete, versioned decision-gate profile and writes it to
50_diagnostics/diagnostic_gates.resolved.yaml. The packaged default is
abacus/pipeline/diagnostic_gates.default.yaml. A documented copy is available
at examples/diagnostic_gates.team.yaml. Copy that file when your team needs a
governed profile with different thresholds; keep the source profile in version
control with the model configuration.
Use gates_file to select that profile. Relative paths are resolved from the
model YAML file. Optional inline thresholds take precedence over the selected
profile and are recorded in the resolved artifact.
diagnostics:
gates_file: diagnostic_gates.team_v1.yaml
thresholds:
design_max_vif:
warn: 10.0
fail: 20.0
mcmc_max_rhat:
warn: 1.02
fail: 1.08
Supported threshold keys:
design_max_vifdesign_condition_numbermcmc_divergence_countmcmc_max_rhatmcmc_min_ess_bulkmcmc_bfmi_minbayesian_pareto_k_maxpredictive_nrmseresidual_ljung_box_presidual_max_abs_acf
Validation rules:
- upper-bound checks require
warn <= fail - lower-bound checks require
warn >= fail - equality triggers the relevant warn or fail boundary; the zero-divergence gate is the explicit exception, where zero passes and any positive count fails
- a selected gate file must be schema version 1 and define every supported gate
- omit the block entirely to use the packaged default profile
These gates classify available diagnostic evidence. Passing them does not prove parameter identification, prior robustness, model validity, or causal identification. In particular, VIF and condition number are raw-design screens. A warning indicates weak-identification risk and should trigger controlled reparameterisation or prior-sensitivity runs. A clean screen only means that no material warning was detected by those checks.
This block affects only the structured runner. It is stripped before Stage 00 model preparation so the public MMM YAML schema remains unchanged.
validation
Use the optional validation block when you want Stage 35 blocked holdout
scoring. This is the runner’s out-of-sample tail check: AMMM3 refits a clean
model on the earlier dates and scores only the final blocked window.
validation:
enabled: true
holdout_observations: 8
include_last_observations: true
coverage_levels: [0.5, 0.8, 0.94]
sampler:
draws: 500
tune: 500
chains: 2
cores: 2
random_seed: 42
Supported keys:
| Key | Meaning |
|---|---|
enabled | Set to false to skip Stage 35 while keeping the stage in the manifest |
holdout_observations | Number of unique dates to reserve for the blocked holdout window |
include_last_observations | Keep lag history for carryover-sensitive holdout scoring |
coverage_levels | Coverage levels reported in Stage 35; use the fixed 50, 80, and 94 percent defaults |
sampler | Optional validation-only sampler overrides for the train-window refit |
Stage 35 reports coverage as coverage_50, coverage_80, and
coverage_94. Keep those defaults unless the implementation and tests are
updated together.
The validation stage builds a clean train-window model for holdout scoring and
ignores inference_data.path so the refit does not inherit attached posterior
state from the full-sample model.
For a full explanation of why the split is blocked, how to read crps and
coverage, and rules of thumb for weekly MMM, see
Blocked Holdout Validation.
Override precedence
For the runner, precedence is:
| Setting | Higher precedence | Lower precedence |
|---|---|---|
| Combined dataset path | dataset_path / --dataset-path | data.dataset_path |
| Split CSV paths | x_path, y_path / --x-path, --y-path | data.x_path, data.y_path |
| Holiday CSV path | holidays_path / --holidays-path | holidays.path |
| Sampler settings | PipelineRunConfig or CLI overrides | fit |
| Target column for CSV loading | target_column / --target-column | target.column, then "y" |
| Diagnostics thresholds | diagnostics.thresholds | retained Stage 50 defaults |
Common pitfalls
- Using Parquet paths in the pipeline data block. The runner data loader reads CSV only.
- Providing only one of
data.x_pathordata.y_path. - Mixing the preferred horizon-based
optimization.budgetblock with the legacy per-periodoptimization.total_budgetfield. - Assuming
diagnosticsis part of the public MMM builder schema. It is a runner-only block. - Assuming
inference_data.pathskips Stage 20 fitting. It does not. - Forgetting that relative paths are resolved from the YAML file directory, not from the shell working directory.