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

KeyRequiredUsed for
dataYesSupply data.date_column and dataset paths unless paths are overridden through PipelineRunConfig
targetYesDefine the target column and business target type
estimatorNoDeclare a named estimator preset; time_series, fe, and cre are currently released
dimensionsNoDeclare panel-dimension columns such as geo or brand
mediaYesDefine channel/control columns and transform types
scalingNoConfigure target/channel scaling rules
effectsNoAppend additive effects in YAML order before build_model(...)
priorsNoOverride model-level priors and prefixed transform priors
fitNoDefault sampler settings for Stage 20 fitting
holidaysNoAdd holiday events before model build
original_scale_varsNoAdd original-scale contribution variables before fitting
inference_dataNoAttach existing InferenceData when the file exists
prior_sensitivityNoWrite a pre-fit scenario plan for prior robustness checks
ai_advisorNoWrite privacy-safe AI advisor guidance before model fitting
validationNoEnable optional Stage 35 blocked holdout validation
optimizationNoEnable Stage 70 budget optimisation
diagnosticsNoOverride Stage 50 runner diagnostics thresholds
calibrationNoPost-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_column
  • target.column
  • target.type
  • media.channels
  • media.controls, if any
  • estimator, for a named preset
  • dimensions.panel, if any
  • media.adstock
  • media.saturation
  • fit

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:

  1. PipelineRunConfig.target_column or CLI --target-column
  2. target.column
  3. "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:

  • draws
  • tune
  • chains
  • cores
  • random_seed

The public YAML schema currently supports these fit keys:

  • draws
  • tune
  • chains
  • cores
  • random_seed
  • target_accept
  • progressbar
  • compute_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:

KeyMeaning
pathHoliday CSV path
enabledSet to false to disable holiday loading
prefixPrefix for generated holiday effect coordinates
modeHoliday handling mode: event, pooled_control, or prophet_component
countriesCountry 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 US when holidays.countries is omitted
  • geo-panel configs must declare holidays.countries explicitly
  • 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:

  • event creates one latent holiday/event effect per holiday row, which is why posterior summaries include terms like holiday_effect_size[...].
  • pooled_control creates 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_component fits Prophet on the training target with the configured holiday calendar, extracts the continuous holidays component, 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 by geo when 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 event explicitly when you want one latent holiday effect per holiday row

Current limitation:

  • pooled_control currently supports only single-country, non-geo models.
  • prophet_component requires exactly one holiday country unless the model has a geo dimension, 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:

KeyMeaning
enabledSet to true to write Stage 05 prior-sensitivity artifacts
scenario_policymanual for declared scenarios or conservative_mmm for generated relative scenarios
referenceScenario name for the unchanged reference config
scenariosOptional manual scenario declarations
allow_model_structure_overridesRequired 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 when allow_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:

KeyMeaning
enabledSet to true to write Stage 08 advisor artifacts
provideropenai or openrouter
modeautopilot; the advisor prioritizes concise recommendations and approval-ready options
privacyanonymized_relative; raw channel names and raw business values are excluded from the LLM payload
approvalfile_based; proposed config changes are written as files for user approval
write_outputsSet to false to disable artifact writes even when the block is enabled
llm_enabledSet to false to run deterministic privacy/rule checks without an LLM call
diagnostics_review_enabledDefaults to true; set to false to skip the post-fit 55_ai_diagnostics_advisor LLM review after structured diagnostics
openai_modelOpenAI model name used for the advisor call
openai_timeout_secondsRequest timeout for the OpenAI call
openrouter_modelOpenRouter model name used for the advisor call
openrouter_timeout_secondsRequest 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.yaml
  • approval_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.budget for the preferred user-facing budget spec
  • optimization.total_budget for 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:

KeyDefaultMeaning
budgetNonePreferred user-facing budget spec: absolute or relative
total_budgetNoneLegacy per-period budget input kept for backward compatibility
response_variabletotal_media_contribution_original_scaleOptimisation objective variable
budget_distribution_over_periodNoneTime weights over the optimisation window
budget_boundsDerived or defaultExplicit spend bounds
spend_constraint_lower0.3 when deriving boundsRelative lower bound around scaled reference spend
spend_constraint_upper0.3 when deriving boundsRelative upper bound around scaled reference spend
default_constraintstrueWhether to add the default equality budget constraint
noise_level0.001Noise level for simulated response samples
include_last_observationsfalseWhether posterior predictive sampling includes trailing observed rows
include_carryovertrueWhether simulated response sampling extends the window for carryover

Budget spec modes:

  • budget.mode: absolute budget.value is total spend over the full optimisation horizon.
  • budget.mode: relative budget.value is a multiplier on the chosen basis.
  • budget.basis: reference_window_total AMMM3 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_vif
  • design_condition_number
  • mcmc_divergence_count
  • mcmc_max_rhat
  • mcmc_min_ess_bulk
  • mcmc_bfmi_min
  • bayesian_pareto_k_max
  • predictive_nrmse
  • residual_ljung_box_p
  • residual_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:

KeyMeaning
enabledSet to false to skip Stage 35 while keeping the stage in the manifest
holdout_observationsNumber of unique dates to reserve for the blocked holdout window
include_last_observationsKeep lag history for carryover-sensitive holdout scoring
coverage_levelsCoverage levels reported in Stage 35; use the fixed 50, 80, and 94 percent defaults
samplerOptional 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:

SettingHigher precedenceLower precedence
Combined dataset pathdataset_path / --dataset-pathdata.dataset_path
Split CSV pathsx_path, y_path / --x-path, --y-pathdata.x_path, data.y_path
Holiday CSV pathholidays_path / --holidays-pathholidays.path
Sampler settingsPipelineRunConfig or CLI overridesfit
Target column for CSV loadingtarget_column / --target-columntarget.column, then "y"
Diagnostics thresholdsdiagnostics.thresholdsretained 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_path or data.y_path.
  • Mixing the preferred horizon-based optimization.budget block with the legacy per-period optimization.total_budget field.
  • Assuming diagnostics is part of the public MMM builder schema. It is a runner-only block.
  • Assuming inference_data.path skips Stage 20 fitting. It does not.
  • Forgetting that relative paths are resolved from the YAML file directory, not from the shell working directory.