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:
CurrentScenarioSpecManualAllocationScenarioSpecFixedBudgetOptimizedScenarioSpec
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:
| 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, 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:
| 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.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
| 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, 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
1acrossdatefor 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.ymldata/demo/geo_fe/scenario_recipe.pydata/demo/geo_cre/scenario_recipe.ymldata/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_idtwice 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_periodthat does not sum to1 - Reusing an existing recipe output directory; retained evidence is never overwritten