Scenario Specifications

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

Most users create one of the three concrete scenario specs:

  • CurrentScenarioSpec
  • ManualAllocationScenarioSpec
  • FixedBudgetOptimizedScenarioSpec

Abacus 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:

Field Meaning
name Display name for the scenario
start_date Requested scenario start date
end_date Requested scenario end date
scenario_id Stable scenario key used in outputs

If you do not set scenario_id, Abacus 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.scenario_planner 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:

Field Default Meaning
budget_distribution_over_period None Optional time distribution of the total budget
include_last_observations False Passed through to response sampling for lag context
include_carryover True Extend the evaluated window to capture lagged effects
noise_level 0.001 Response-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.scenario_planner 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 Abacus to optimise the allocation.

from abacus.scenario_planner 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

Field Meaning
total_budget Total spend over the full scenario horizon
response_variable Variable used by the optimiser
budget_bounds Explicit lower and upper bounds
spend_constraint_lower Relative lower bound when deriving defaults
spend_constraint_upper Relative upper bound when deriving defaults
default_constraints Passed 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, Abacus derives them from historical reference spend.

For each omitted relative constraint side, Abacus 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, Abacus 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, Abacus raises ValueError.

DataArraySpec

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

from abacus.scenario_planner 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"],
    },
)

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

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