Scenario Specifications

This page documents the public spec classes under abacus.scenarios.

abacus.scenario_planner still re-exports these classes for compatibility with existing imports, but new code should use abacus.scenarios.

Most users create one of the three concrete scenario specs:

  • CurrentScenarioSpec
  • ManualAllocationScenarioSpec
  • FixedBudgetOptimizedScenarioSpec

AMMM3 also exposes shared base models such as HistoricalReferenceScenarioSpec and SimulatedScenarioSpec, but you do not normally instantiate those directly.

Shared fields

All public scenario specs inherit these core fields:

FieldMeaning
nameDisplay name for the scenario
start_dateRequested scenario start date
end_dateRequested scenario end date
scenario_idStable scenario key used in outputs

If you do not set scenario_id, AMMM3 derives one by slugifying name.

Scenario IDs must be unique within one ScenarioPlanner.compare(...) call.

CurrentScenarioSpec

Use CurrentScenarioSpec for a historical reference plan.

from abacus.scenarios import CurrentScenarioSpec

spec = CurrentScenarioSpec(
    name="Current baseline",
    start_date="2025-01-06",
    end_date="2025-02-24",
)

Requirements:

  • the requested window must overlap observed data
  • no allocation or budget inputs are needed

Shared simulated-scenario fields

ManualAllocationScenarioSpec and FixedBudgetOptimizedScenarioSpec both inherit these fields:

FieldDefaultMeaning
budget_distribution_over_periodNoneOptional time distribution of the total budget
include_last_observationsFalsePassed through to response sampling for lag context
include_carryoverTrueExtend the evaluated window to capture lagged effects
noise_level0.001Response-sampling noise level

Set noise_level=0.0 when you want deterministic realised spend paths.

ManualAllocationScenarioSpec

Use ManualAllocationScenarioSpec when you already know the total allocation you want to simulate.

from abacus.scenarios import ManualAllocationScenarioSpec

spec = ManualAllocationScenarioSpec(
    name="Manual reallocation",
    start_date="2025-03-03",
    end_date="2025-03-24",
    noise_level=0.0,
    include_carryover=False,
    allocation={
        "channel_1": 420_000.0,
        "channel_2": 280_000.0,
        "channel_3": 200_000.0,
    },
)

Supported allocation shapes

allocation can be:

  • a dict of {channel: total_budget} for ("channel",) budgets only
  • an xarray.DataArray
  • a DataArraySpec

For panel budgets such as ("geo", "channel") or ("geo", "brand", "channel"), use xarray.DataArray or DataArraySpec.

Dict allocations must match the model’s channel coordinates exactly. Missing or extra keys raise ValueError.

FixedBudgetOptimizedScenarioSpec

Use FixedBudgetOptimizedScenarioSpec when you want AMMM3 to optimise the allocation.

from abacus.scenarios import FixedBudgetOptimizedScenarioSpec

spec = FixedBudgetOptimizedScenarioSpec(
    name="Optimised plan",
    start_date="2025-03-03",
    end_date="2025-03-24",
    noise_level=0.0,
    include_carryover=False,
    total_budget=900_000.0,
)

Optimisation fields

FieldMeaning
total_budgetTotal spend over the full scenario horizon
response_variableVariable used by the optimiser
budget_boundsExplicit lower and upper bounds
spend_constraint_lowerRelative lower bound when deriving defaults
spend_constraint_upperRelative upper bound when deriving defaults
default_constraintsPassed through to the underlying optimiser

The default response_variable is "total_media_contribution_original_scale".

Default bound derivation

If you do not pass budget_bounds, AMMM3 derives them from historical reference spend.

For each omitted relative constraint side, AMMM3 uses 0.3. That gives the default Meridian-style bounds:

  • lower bound: scaled reference spend × (1 - 0.3)
  • upper bound: scaled reference spend × (1 + 0.3)

If historical reference spend sums to zero, AMMM3 cannot derive those default bounds and raises ValueError.

Supported budget_bounds shapes

budget_bounds can be:

  • a dict of {channel: (lower, upper)} for ("channel",) budgets only
  • an xarray.DataArray
  • a DataArraySpec

For xarray or DataArraySpec, the dims must be (*budget_dims, "bound") with "lower" and "upper" values on the bound dimension.

budget_distribution_over_period

Both simulated scenario types support budget_distribution_over_period.

The object must:

  • have dims ("date", *budget_dims)
  • contain one weight per scenario period
  • sum to 1 across date for every budget cell

The date coordinates can be:

  • integer positions 0 .. num_periods - 1, or
  • exact dates that match the requested scenario window

If the dates do not match the scenario window exactly, AMMM3 raises ValueError.

DataArraySpec

Use DataArraySpec when you want JSON-friendly or YAML-friendly planner inputs.

from abacus.scenarios import DataArraySpec

allocation = DataArraySpec(
    values=[[420_000.0, 280_000.0], [300_000.0, 200_000.0]],
    dims=("geo", "channel"),
    coords={
        "geo": ["UK", "FR"],
        "channel": ["channel_1", "channel_2"],
    },
)

AMMM3 materialises DataArraySpec as an xarray.DataArray before it validates dims and coordinates.

Versioned scenario recipes

Use ScenarioRecipe to retain several scenario specifications as one versioned request. The recipe contract rejects unknown versions, empty scenario lists, duplicate scenario IDs, and unknown top-level fields.

scenario_contract_version: "1"
scenarios:
  - scenario_type: current
    name: Observed 13-week reference
    start_date: "2024-11-04"
    end_date: "2025-01-27"

  - scenario_type: manual_allocation
    name: Manual 13-week reallocation
    start_date: "2025-02-03"
    end_date: "2025-04-28"
    include_last_observations: true
    include_carryover: false
    noise_level: 0.0
    allocation:
      dims: [geo, channel]
      coords:
        geo: [DE, FR, UK]
        channel: [channel_1, channel_2]
      values:
        - [8000000, 2500000]
        - [7900000, 2450000]
        - [7800000, 2400000]

The complete executable FE and CRE examples are:

  • data/demo/geo_fe/scenario_recipe.yml
  • data/demo/geo_fe/scenario_recipe.py
  • data/demo/geo_cre/scenario_recipe.yml
  • data/demo/geo_cre/scenario_recipe.py

The Python files expose build_recipe() and are regression-tested for semantic equivalence with their YAML counterparts.

Run a YAML recipe against an existing fitted run:

python -m abacus.scenarios \
  --results-dir results/<fitted-run> \
  --recipe data/demo/geo_fe/scenario_recipe.yml

AMMM3 writes a new immutable evidence directory under <fitted-run>/scenario_planner/recipes/. It does not refit the model or alter the fitted run manifest.

Common pitfalls

  • Reusing the same scenario_id twice in one comparison
  • Using dict allocations or dict bounds for panel budgets
  • Passing an allocation or bounds object with missing coordinates
  • Providing a budget_distribution_over_period that does not sum to 1
  • Reusing an existing recipe output directory; retained evidence is never overwritten