Scenario Planning

Use this section when you want to compare historical, manual, and optimised future plans with abacus.scenarios.

The scenario planner is a higher-level planning surface than the low-level optimisation wrapper. It works in total horizon spend units, returns versioned structured comparison tables, and supports Python and CLI workflows.

abacus.scenario_planner remains the legacy compatibility namespace for existing statistical imports and advisory dashboard facades. The experimental abacus-dashboard application is deprecated. Its retained guide documents historical behaviour; use abacus.scenarios for current workflows.

Pages

  • Supported Surface: The recommended scenario entry points, compatibility facades, fitted-run contract, persisted workspace state, and estimator limits.
  • Overview and Workflow: What the planner does, how it differs from low-level optimisation, and how scenario windows work.
  • Scenario Specifications: The public scenario spec classes, versioned YAML recipes, allocation shapes, bounds, and budget distributions.
  • Python API: How to use ScenarioPlanner.evaluate(...) and run retained recipes from Python, plus the compatibility workspace helpers.
  • Comparison Outputs: The structure and meaning of ScenarioResult, ScenarioComparison, retained artefacts, and the dashboard payload contract.

Historical reference

  • Deprecated Dash App: Retained documentation for the experimental dashboard. It is not a recommended scenario workflow.

Subsections of Scenario Planning

Overview and Workflow

Use the scenario planner when you want to compare whole plans rather than run a single low-level optimisation call.

The planner combines typed scenario specifications, a Python comparison service, and a CLI for retained YAML recipes. Use abacus.scenarios for scenario workflows. The experimental abacus-dashboard application is deprecated; its legacy import facades remain in this checkout.

If you need the low-level optimiser instead, see Budget Optimisation.

For supported entry points and estimator limits, see Supported Surface.

What the planner compares

The retained planner supports three scenario types:

Scenario type Purpose Public spec
Current Use observed history as a reference plan CurrentScenarioSpec
Manual allocation Simulate a user-defined future plan ManualAllocationScenarioSpec
Fixed-budget optimised Optimise a future plan at a fixed budget FixedBudgetOptimizedScenarioSpec

Planner units versus optimiser units

The most important distinction is budget units.

Surface Public budget contract
PanelBudgetOptimizerWrapper Per-period spend
abacus.scenarios.ScenarioPlanner Total spend over the whole scenario horizon

For example, if a four-period scenario has a total budget of 900_000, the planner converts that to per-period units internally before it calls the wrapper or response sampler.

Requested and evaluated windows

Each scenario has a requested window from start_date to end_date.

For simulated scenarios, the evaluated window can be longer than the requested window when you set include_carryover=True. Abacus extends the synthetic future path so lagged adstock effects can continue after the requested end date.

The planner reports both windows in the metadata output.

Historical overlap for current scenarios

CurrentScenarioSpec is strict about history.

Its requested window must overlap observed data. Abacus does not reinterpret a future-only window as “use the latest history instead”.

Typical workflow

The common workflow is:

  1. Fit PanelMMM.
  2. Build one or more scenario specs.
  3. Run abacus.scenarios.ScenarioPlanner.compare(...), or evaluate a YAML recipe against a fitted run with python -m abacus.scenarios.
  4. Inspect the comparison tables, save workspaces, and export the planning outputs you need.

Minimal example

from abacus.scenarios import (
    CurrentScenarioSpec,
    ManualAllocationScenarioSpec,
    ScenarioPlanner,
)

planner = ScenarioPlanner(mmm)

comparison = planner.compare(
    [
        CurrentScenarioSpec(
            name="Current baseline",
            start_date="2025-01-06",
            end_date="2025-02-24",
        ),
        ManualAllocationScenarioSpec(
            name="Manual plan",
            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,
            },
        ),
    ]
)

For the full API, see Python API.

How the planner differs from post-model summaries

The scenario planner does not reuse mmm.summary tables directly. Instead, it builds comparison tables that are specific to planning:

  • totals
  • channels
  • contributions_over_time
  • allocations
  • metadata

See Comparison Outputs.

Common pitfalls

  • Mixing up total horizon spend and per-period spend
  • Using a future-only window in CurrentScenarioSpec
  • Forgetting that carryover can extend the evaluated window

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

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

The named FE and CRE presets support historical and manual scenarios for the complete set of fitted units; CRE also retains its frozen fitted summaries. They do not support FixedBudgetOptimizedScenarioSpec. The RE preset remains release-gated. Check the estimator support matrix before choosing a scenario type.

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.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 Abacus 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, 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.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"],
    },
)

Abacus 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

Abacus 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

Python API

Use ScenarioPlanner when you want to evaluate one scenario or compare multiple scenarios from Python.

The preferred public API lives under abacus.scenarios.

abacus.scenario_planner remains available for existing statistical compatibility imports, but new statistical scenario code should use abacus.scenarios. The experimental abacus-dashboard application is deprecated; dashboard helpers below are historical reference only.

For the recommended entry points and current scope, see Supported Surface.

Prerequisite

ScenarioPlanner requires a fitted PanelMMM with idata.

If you construct the planner before fitting, Abacus raises ValueError.

The named FE and CRE presets support historical and manual scenarios for the complete set of fitted units; CRE also retains its frozen fitted summaries. They do not support FixedBudgetOptimizedScenarioSpec. The RE preset remains release-gated. Check the estimator support matrix before choosing a scenario type.

Create a planner

from abacus.scenarios import ScenarioPlanner

planner = ScenarioPlanner(mmm)

You can inspect the modelled channel names with:

channels = planner.channels

Dashboard workspace helpers

These helpers belong to the deprecated experimental dashboard. This section records the former abacus_dashboard.app interface for existing integrations; it is not a recommended workflow for new code.

Helper What it returns Use it when
load_workspace_bundle(...) run_context, workspace_service, workspace you want the fitted run context and active workspace without starting Dash
create_app_from_results_dir(...) app, run_context, workspace_service, workspace you want to launch or embed the dashboard app from Python

Example:

from abacus_dashboard.app import create_app_from_results_dir

app, run_context, workspace_service, workspace = create_app_from_results_dir(
    "results/timeseries_20260308_144627",
    workspace_name="Timeseries planning workspace",
)

app.run(host="127.0.0.1", port=8050, debug=False)

These helpers expect a fitted results directory with run_manifest.json and a fit-stage idata artefact.

When available, the loader rebuilds the model from in-run metadata config artifacts in this order:

  1. 00_run_metadata/config.resolved.yaml
  2. 00_run_metadata/config.original.yaml
  3. the copied config file under 00_run_metadata/
  4. run_manifest.json["config_path"] as fallback

The returned run_context records both config_path and config_provenance_type so callers can tell which source was used.

Residual portability risk remains if the chosen config still points to dataset files outside the saved run directory.

Legacy imports from abacus.scenario_planner still resolve for compatibility and emit advisory DeprecationWarnings. No legacy dashboard app-layer facade will be removed before Abacus 4.0.

Evaluate one scenario

Use evaluate(...) when you want one scenario result:

from abacus.scenarios import ManualAllocationScenarioSpec, ScenarioPlanner

planner = ScenarioPlanner(mmm)

result = planner.evaluate(
    ManualAllocationScenarioSpec(
        name="Manual plan",
        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,
        },
    )
)

print(result.totals)
print(result.channels)
print(result.metadata)

evaluate(...) returns ScenarioResult with:

  • totals
  • channels
  • contributions_over_time
  • allocation
  • metadata

Compare multiple scenarios

Use compare(...) when you want one combined comparison object:

from abacus.scenarios import (
    CurrentScenarioSpec,
    FixedBudgetOptimizedScenarioSpec,
    ManualAllocationScenarioSpec,
    ScenarioPlanner,
)

planner = ScenarioPlanner(mmm)

comparison = planner.compare(
    [
        CurrentScenarioSpec(
            name="Current baseline",
            start_date="2025-01-06",
            end_date="2025-02-24",
        ),
        ManualAllocationScenarioSpec(
            name="Manual plan",
            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,
            },
        ),
        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,
        ),
    ]
)

print(comparison.totals)
print(comparison.allocations)

compare(...) returns ScenarioComparison with:

  • totals
  • channels
  • contributions_over_time
  • allocations
  • metadata

Unlike ScenarioResult, the combined object uses the plural allocations.

Run a versioned recipe

Use ScenarioRecipe for an in-memory Python request. Use load_scenario_recipe(...) for YAML and run_scenario_recipe(...) when the model has already been retained by the pipeline.

from abacus.scenarios import run_scenario_recipe

bundle = run_scenario_recipe(
    results_dir="results/geo_fe_20260824_120000",
    recipe_path="data/demo/geo_fe/scenario_recipe.yml",
)

print(bundle.output_dir)
print(bundle.comparison.totals)

The default output path is a new timestamped directory under scenario_planner/recipes/ in the fitted run. Pass output_dir= only when you need another new location. Abacus rejects an existing target directory so a later evaluation cannot silently replace retained evidence.

Use evaluate_scenario_recipe(...) when you already have the fitted model in memory:

from abacus.scenarios import ScenarioRecipe, evaluate_scenario_recipe

recipe = ScenarioRecipe(scenarios=(current_spec, manual_spec))
bundle = evaluate_scenario_recipe(
    model=mmm,
    recipe=recipe,
    output_dir="scenario-evidence/fe-plan-v1",
    source_run_id="geo-fe-run",
)

ScenarioArtifactBundle exposes the output directory, named artefact paths, and the in-memory ScenarioComparison.

Programmatic workspace orchestration

Use WorkspaceService from abacus.scenarios when you want to work with saved planner workspaces from Python without launching the dashboard beta.

Common operations include:

  • load_workspace(...)
  • save_workspace(...)
  • clone_workspace(...)
  • update_workspace_metadata(...)
  • create_template_draft(...)
  • replace_draft(...)
  • evaluate_draft(...)
  • run_sensitivity_sweep(...)
  • export_workspace_bundle(...)

Example:

from abacus.scenarios import WorkspaceService, load_planner_run_context

run_context = load_planner_run_context("results/timeseries_20260308_144627")
workspace_service = WorkspaceService(run_context)
workspace = workspace_service.load_or_create_default_workspace(
    workspace_name="Timeseries planning workspace",
)

draft = workspace_service.create_template_draft(
    workspace=workspace,
    scenario_type="fixed_budget_optimized",
)
workspace = workspace_service.replace_draft(workspace, draft)
workspace = workspace_service.evaluate_draft(workspace, draft)
workspace_service.save_workspace(
    workspace,
    action="evaluate_draft",
    changed_scenario_ids=[draft.scenario_id],
)

This example creates the default workspace if it is absent. Use load_workspace(...) instead when you require a specific existing workspace ID.

WorkspaceService defaults to synchronous jobs when you instantiate it directly. The dashboard app uses ThreadedScenarioPlannerJobRunner through the dashboard compatibility layer.

Prepare data for a client UI

Use to_store_payload() when you want a JSON-friendly version of the comparison tables:

payload = comparison.to_store_payload()

This method converts datetime columns to YYYY-MM-DD strings and returns a dict with a scalar contract_version plus record lists for the comparison tables. Compare contract_version with SCENARIO_CONTRACT_VERSION before a dashboard or external client assumes a payload shape.

Background-job helpers

For custom integrations, WorkspaceService also exposes queue/apply methods:

  • submit_draft_evaluation(...)
  • apply_draft_evaluation_job(...)
  • submit_sensitivity_sweep(...)
  • apply_sensitivity_sweep_job(...)

Use these only when you need the same background-job pattern as the current dashboard beta. For most scripted flows, the blocking methods are simpler.

Relationship to the low-level wrapper

ScenarioPlanner uses PanelBudgetOptimizerWrapper internally for simulated scenarios, but its public contract is different:

  • you pass total horizon budgets and allocations
  • the planner converts them to per-period units internally
  • the planner returns comparison tables rather than raw optimiser objects

If you want direct access to optimize_budget(...) or sample_response_distribution(...), use Budget Optimisation instead.

Common pitfalls

  • Passing per-period spend into ManualAllocationScenarioSpec or FixedBudgetOptimizedScenarioSpec
  • Expecting duplicate scenario_id values to be allowed in compare(...)
  • Forgetting that result.allocation and comparison.allocations use different attribute names

Comparison Outputs

ScenarioPlanner returns structured planning tables rather than a single optimiser object.

This page explains the output objects and the meaning of each table.

Import these objects from abacus.scenarios for new statistical scenario code. abacus.scenario_planner remains a compatibility namespace for existing statistical imports and legacy dashboard app-layer paths.

Output objects

Object Produced by Tables
ScenarioResult planner.evaluate(spec) totals, channels, contributions_over_time, allocation, metadata
ScenarioComparison planner.compare(specs) totals, channels, contributions_over_time, allocations, metadata

ScenarioComparison is a row-wise concatenation of the individual scenario results, with scenario identifiers added to every table.

totals

totals has one row per scenario.

It includes:

  • scenario_id
  • scenario_name
  • scenario_type
  • total_spend
  • contribution_mean
  • contribution_median
  • contribution_hdi_94_lower
  • contribution_hdi_94_upper
  • efficiency_metric
  • efficiency_mean
  • efficiency_median
  • efficiency_hdi_94_lower
  • efficiency_hdi_94_upper

efficiency_metric is ROAS for revenue targets and CPA for conversion targets.

channels

channels has one row per (scenario, channel).

It includes:

  • scenario identifiers
  • channel
  • spend
  • spend_share
  • spend_per_period
  • contribution summary columns
  • contribution-per-period columns
  • efficiency summary columns
  • efficiency_metric

The planner aggregates non-channel panel dims before it builds this table. For example, a (geo, channel) model still returns one row per channel here.

contributions_over_time

contributions_over_time has one row per (scenario, date, channel).

It includes:

  • scenario identifiers
  • date
  • channel
  • contribution_mean
  • contribution_median
  • contribution_hdi_94_lower
  • contribution_hdi_94_upper

Like channels, this table aggregates non-channel panel dims before summarising.

allocations

allocations keeps the original allocation grain.

It includes:

  • scenario identifiers
  • the allocation dims, such as channel, geo, or brand
  • allocation
  • realized_spend

For current scenarios, allocation is the summed historical spend over the reference window. For simulated scenarios, allocation is the requested total horizon allocation and realized_spend is the realised spend from the response simulation.

metadata

metadata is the audit table for each scenario.

Shared fields include:

  • scenario_id
  • scenario_name
  • scenario_type
  • start_date
  • end_date
  • evaluated_start_date
  • evaluated_end_date
  • num_periods
  • target_type
  • efficiency_metric

Additional fields depend on scenario type.

Current scenario metadata

Current scenarios add:

  • reference_window_dates

Manual scenario metadata

Manual scenarios add:

  • requested_total_budget
  • total_budget
  • reference_window_dates
  • budget_unit

Fixed-budget optimised metadata

Optimised scenarios add:

  • requested_total_budget
  • total_budget
  • optimization_success
  • optimization_status
  • optimization_message
  • optimization_objective_value
  • reference_window_dates
  • budget_unit

Requested versus evaluated windows

The metadata table is the best place to check whether the evaluated window matches the requested window.

When include_carryover=True, the evaluated end date can be later than the requested end_date.

Example inspection

comparison = planner.compare(specs)

totals = comparison.totals
metadata = comparison.metadata

optimised_metadata = metadata.loc[
    metadata["scenario_type"] == "fixed_budget_optimized"
].iloc[0]

print(optimised_metadata["optimization_success"])
print(optimised_metadata["optimization_message"])

to_store_payload()

ScenarioComparison.to_store_payload() converts the comparison tables into a JSON-friendly dict:

payload = comparison.to_store_payload()

The payload contains a scalar contract_version and record lists for totals, channels, contributions_over_time, allocations, and metadata. The current contract value is exported as SCENARIO_CONTRACT_VERSION from abacus.scenarios, so downstream clients can check whether they understand the result shape before rendering or importing it.

The deprecated experimental abacus-dashboard application consumed this payload format. The versioned contract remains available to machine consumers: Abacus owns the statistical result shape; clients own rendering and interaction.

Retained recipe artefacts

run_scenario_recipe(...) and evaluate_scenario_recipe(...) persist the comparison as an immutable bundle:

File Purpose
scenario_recipe.resolved.yaml versioned request after validation
scenario_validation.json scenario IDs, estimator, fitted-unit scope, estimand, and pass state
estimator_manifest.yaml fitted estimator contract copied with the scenario evidence
scenario_totals.csv total spend, contribution, efficiency, and uncertainty by scenario
scenario_channels.csv channel summaries and uncertainty
scenario_contributions_over_time.csv date-channel contribution summaries and uncertainty
scenario_allocations.csv requested allocation and realised spend at the original allocation grain
scenario_metadata.csv scenario semantics, dates, history policy, scale, and estimand
scenario_payload.json versioned five-table payload for machine consumers
scenario_artifact_manifest.json source run ID, file sizes, and SHA-256 checksums

The target directory must not exist before evaluation. Abacus validates the comparison before it creates the directory and never overwrites an earlier bundle.

Common pitfalls

  • Reading channels as if it retained non-channel panel dims
  • Ignoring metadata when carryover is enabled
  • Comparing requested allocation with realised spend without checking the allocations table

Supported Surface

Use abacus.scenarios for supported Python and CLI scenario workflows. The experimental abacus-dashboard application is deprecated. It is not a recommended entry point.

Legacy dashboard app-layer imports under abacus.scenario_planner remain as advisory compatibility facades in this checkout. Their warnings identify abacus_dashboard.* paths; those paths belong to the deprecated application.

Estimator prerequisites

The named FE and CRE presets support historical and manual scenarios for the complete set of fitted units; CRE also retains its frozen fitted summaries. They do not support FixedBudgetOptimizedScenarioSpec. The RE preset remains release-gated. Check the estimator support matrix before choosing a scenario type.

Dashboard compatibility code does not add estimator support.

Recommended entry points

Use these entry points in preference order.

Entry point Use it when you want to Notes
abacus.scenarios.ScenarioPlanner evaluate or compare scenarios from Python Preferred statistical API for notebooks, scripts, and testable planning flows
python -m abacus.scenarios evaluate a YAML recipe against a fitted run Writes an immutable, checksummed evidence bundle under the fitted run
abacus.scenarios.run_scenario_recipe(...) run the same retained recipe workflow from Python Preferred scripted handoff when the fitted model is already saved
abacus.scenarios.WorkspaceService work with saved workspaces programmatically Library-facing surface for cloning, saving, evaluating, sweeping, and exporting

Advanced integration surfaces

Abacus exposes lower-level statistical orchestration objects such as:

  • ThreadedScenarioPlannerJobRunner
  • SynchronousScenarioPlannerJobRunner
  • WorkspaceStore

These are public, but they are more implementation-shaped than the recommended entry points above. Use them only when you need to override job-runner or storage behaviour.

Results directory contract

The dashboard-specific sections below record the deprecated prototype, including its former compatibility plans. They do not recommend launching it or establish a future release commitment. The statistical recipe and workspace APIs remain available through abacus.scenarios.

The dashboard launcher and load_workspace_bundle(...) expect a fitted run directory, not raw modelling inputs.

The run directory must include:

Requirement Why it matters
run_manifest.json Abacus uses it to locate the config and saved artefacts
a fit-stage idata artefact Abacus attaches the saved posterior to the rebuilt model

When metadata-stage config artefacts are present, Abacus prefers those in-run files when rebuilding the saved PanelMMM:

  • 00_run_metadata/config.resolved.yaml
  • 00_run_metadata/config.original.yaml
  • the copied config file under 00_run_metadata/

Only when those in-run config artefacts are absent does the planner fall back to run_manifest.json["config_path"].

That makes the compatibility loader more portable when the original config path is no longer available, but it does not guarantee full relocation across machines. The chosen config can still reference dataset files outside the run directory.

The planner can also load these optional optimisation artefacts when they are present:

  • 70_optimisation/budget_response_curves.csv
  • 70_optimisation/budget_bounds_audit.csv

When these files are available, the app can show saved saturation-reference response-curve and bounds-audit views.

Companion dashboard handoff contract

A retained recipe writes scenario_payload.json for machine consumers. The companion abacus-dashboard package must:

  1. reject a missing or unknown contract_version;
  2. compare that value with SCENARIO_CONTRACT_VERSION before rendering;
  3. preserve all five record collections: totals, channels, contributions_over_time, allocations, and metadata;
  4. use scenario_validation.json, estimator_manifest.yaml, and scenario_artifact_manifest.json as provenance and integrity evidence; and
  5. label the outputs as posterior media contributions, not total outcomes, profits, causal effects, or model approval.

Abacus owns this statistical payload. The companion package owns rendering, interaction, and any application persistence. This repository does not add or change dashboard UI code as part of the recipe workflow.

The companion package also provides a read-only bundle viewer. It verifies the manifest checksums for every declared artifact and requires agreement among the payload, validation record, and estimator manifest before it renders the five collections. It rejects tampered or incompatible bundles rather than attempting a partial display.

What the app persists

The workspace app stores its own planning state under the fitted run directory:

Path Contents
scenario_planner/workspaces/<workspace_id>.json full persisted workspace state
scenario_planner/workspaces/<workspace_id>.manifest.json compact workspace manifest
scenario_planner/cache/cache_index.json evaluation cache index
scenario_planner/cache/evaluations/ cached evaluated scenarios
scenario_planner/exports/<workspace_id>/<export_id>/ export bundle contents
scenario_planner/exports/<workspace_id>/<export_id>.zip zipped export bundle
scenario_planner/recipes/<recipe>_<timestamp>/ immutable YAML-recipe evidence bundle

Workspaces persist:

  • workspace metadata such as name, owner, tags, and notes
  • draft metadata such as scenario owner, workflow status, approvals, pinning, notes, and tags
  • evaluated scenarios
  • sensitivity runs
  • revision history
  • job history
  • cache metadata

Background jobs in the dashboard app

The dashboard app launches with ThreadedScenarioPlannerJobRunner.

In this beta:

  • draft evaluation runs as a queued background job
  • sensitivity sweeps run as queued background jobs
  • export remains synchronous, but Abacus still records it in job history

The UI currently tracks one active planner job at a time. Wait for the current evaluation or sweep to finish before starting another one from the app.

Dashboard scope and current limits

The dashboard app scope is:

  • local use against fitted run directories
  • file-backed workspace persistence inside the run directory
  • interactive drafting, evaluation, comparison, sensitivity sweeps, and export

Current limits to keep in mind:

  • the app does not fit or refit PanelMMM
  • the launcher starts Dash’s built-in server for local evaluation
  • the UI does not yet manage multiple active planner jobs at the same time
  • legacy abacus.scenario_planner dashboard app-layer imports are advisory compatibility facades, not the preferred app API

Legacy removal criteria

No legacy dashboard app-layer compatibility facade under abacus.scenario_planner will be removed before Abacus 4.0. Removal also requires:

  • a documented abacus-dashboard release and install path
  • passing dashboard smoke checks against the supported Abacus scenario contract
  • zero known internal imports using legacy dashboard paths

Next pages

Dash App

Deprecated experimental application. Use the abacus.scenarios Python API or recipe CLI for current scenario workflows.

The rest of this page is a historical record of the abacus-dashboard prototype. Installation commands, launch procedures, screenshots, beta limits and compatibility plans below describe that prototype, not a current product recommendation or release commitment.

The app does not fit PanelMMM. It loads an existing fitted run, reuses the saved idata, and evaluates planner scenarios against that fitted model.

For the current statistical API and estimator limits, see Supported Surface.

Interface version and screenshots

This guide describes the workspace interface in the companion source declaring abacus-dashboard version 0.1.0a0, checked on 24 September 2026. The five-page navigation and the Explain → Diagnostics and audit section were checked against that source. This identifies the documented interface; it does not certify an installed build or a published release.

The screenshots below show an earlier read-only demonstration interface with four navigation entries. Their exact app version was not recorded. They are historical illustrations, not screenshots of the documented five-page workspace. Follow the page names and procedures in the text.

Legacy app-layer imports and launchers under abacus.scenario_planner still work as advisory compatibility facades, but new dashboard code should use abacus_dashboard.*.

Install the dashboard package

python -m pip install -e ../abacus -e ../abacus-dashboard

The dashboard package owns the Dash, Plotly, Flask, Werkzeug, and dash-ag-grid dependencies used by the app.

Launch the dashboard app

Use the companion module launcher for fitted pipeline results:

python -m abacus_dashboard \
  --results-dir results/timeseries_20260308_144627

This launcher is the workspace entry point for beta evaluation. It loads the fitted run, opens or seeds a planner workspace, and starts the app with the threaded job runner used by the UI.

To inspect a retained YAML-recipe result without opening or creating a workspace, use the mutually exclusive read-only evidence-review mode:

python -m abacus_dashboard \
  --scenario-bundle \
  results/timeseries_20260308_144627/scenario_planner/recipes/<recipe-output>

Before the dashboard renders, this mode verifies every manifest checksum and cross-checks the versioned payload, scenario validation evidence, and fitted estimator manifest. It renders all five retained record collections. It does not fit, refit, evaluate, optimise, mutate evidence, or infer total outcomes, profits, causal effects, or model approval.

Useful flags:

  • --workspace-id to open one previously saved workspace
  • --workspace-name to control the seeded workspace name
  • --current-periods and --future-periods to change the default seeded windows
  • --budget-scale to scale the default future budget
  • --build-only to validate the run and print a summary without starting Dash
  • --host, --port, and --debug for the Dash server

--workspace-id, --workspace-name, --current-periods, --future-periods, and --budget-scale apply only with --results-dir. The evidence-review mode rejects them rather than silently ignoring them.

For example:

python -m abacus_dashboard \
  --results-dir results/timeseries_20260308_144627 \
  --workspace-id timeseries-20260308-144627-planning-workspace \
  --host 127.0.0.1 \
  --port 8050

Create the app from Python

If you want to embed the UI in your own script, use the dashboard helper:

from abacus_dashboard.app import create_app_from_results_dir

app, run_context, workspace_service, workspace = create_app_from_results_dir(
    "results/timeseries_20260308_144627",
)

app.run(host="127.0.0.1", port=8050, debug=False)

For a verified, immutable recipe bundle, use the read-only helper instead:

from abacus_dashboard.app import create_app_from_scenario_bundle

app, evidence = create_app_from_scenario_bundle(
    "results/timeseries_20260308_144627/scenario_planner/recipes/<recipe-output>",
)

app.run(host="127.0.0.1", port=8050, debug=False)

The dashboard package also exposes the lower-level create_scenario_planner_dash_app(...) factory when you already have a ScenarioComparison or ScenarioWorkspace.

New statistical scenario code should use abacus.scenarios. Legacy abacus.scenario_planner app-layer imports remain available as advisory compatibility facades and emit DeprecationWarnings that name the replacement abacus_dashboard.* import.

What the launcher requires

The dashboard launcher expects a fitted results directory that contains:

  • run_manifest.json
  • a fit-stage idata artefact

When the metadata stage is present, the launcher prefers the in-run config artefacts under 00_run_metadata/ and only falls back to run_manifest.json["config_path"] if those files are absent.

In build-only mode, the launcher prints the selected config path and its provenance so you can see whether the planner loaded:

  • resolved_in_run
  • original_in_run
  • copied_in_run
  • external_manifest_path

This makes the launcher more portable when the original config path no longer exists, but the chosen config can still fail if it references dataset files that are not present on the current machine.

When these optional files are present, the app also loads them for richer UI views:

  • 70_optimisation/budget_response_curves.csv
  • 70_optimisation/budget_bounds_audit.csv

What the UI includes

The documented workspace interface has five pages:

  • Plan Setup for run context, workspace metadata, saved workspaces, draft inventory, and the launch path into Scenario Builder
  • Scenario Builder for editing one draft at a time and evaluating it back into the workspace
  • Review for cross-scenario totals, deltas, rankings, movers, and approval/export readiness
  • Explain for response curves, operating-region views, lift comparisons, and diagnostics/audit surfaces
  • Export for reproducible export bundles and deterministic sensitivity output selection

What the app saves

The workspace app persists planning state under the fitted run directory:

Path What Abacus saves
scenario_planner/workspaces/ workspace JSON files and compact manifests
scenario_planner/cache/ cached evaluated scenarios and cache index
scenario_planner/exports/ export bundles and zipped archives

This means a planner session stays attached to one fitted run.

Plan Setup page

The Plan Setup page shows the loaded run context and the active planner workspace. It also lets you:

  • open a different saved workspace for the same run
  • clone the current workspace into a new planning branch
  • edit workspace name, owner, tags, and notes
  • inspect revision history, job history, and evaluation-cache reuse
  • launch the current workspace into Scenario Builder

This page is the planner launch surface: planning context stays visible first, while operational details remain available through collapsed secondary sections.

Scenario Builder page

The Scenario Builder page is interactive. You can:

  • create current, manual_allocation, and fixed_budget_optimized drafts
  • duplicate or delete drafts
  • edit names, dates, carryover, budget, and manual allocations
  • capture scenario owner, workflow status, approvals, pinning, tags, and notes
  • evaluate and save the draft back into the workspace

When a draft has been evaluated, the page shows planned versus realised spend, allocation detail, and scenario metadata. When a draft has changed but has not yet been re-evaluated, the page shows a draft preview instead.

Historical illustration of allocation review:

Historical read-only Scenario Builder showing planned and realised spend Historical read-only Scenario Builder showing planned and realised spend

Earlier demonstration interface; exact version unrecorded. This view compares planned allocation with realised spend and shows requested and evaluated windows. It does not illustrate the current draft-editing controls. Use the workspace’s Scenario Builder procedure above for editing and evaluation.

Review page

The Review page focuses on scenario-to-scenario trade-offs and review readiness. It includes:

  • scenario summary cards
  • overview and delta charts
  • channel comparison charts
  • scenario ranking and top-mover tables
  • contribution-over-time comparisons

Historical illustration of scenario comparison:

Historical Compare Scenarios page showing contribution totals and differences Historical Compare Scenarios page showing contribution totals and differences

Earlier demonstration interface; exact version unrecorded. Its Compare Scenarios page illustrates totals and differences relative to a baseline. In the documented workspace, use Review for this task. These displayed example values are not evidence that a proposed allocation improves outcomes.

Explain and Export pages

The remaining pages build on the same workspace state:

  • Explain overlays scenario reference points on the saved Stage 70 saturation-only response-curve artefact when available
  • the plotted marker position follows the saved reference curve at each scenario’s spend-per-period level
  • marker hover text also shows the actual evaluated average contribution so you can compare the scenario outcome with the reference-curve position
  • Explain also surfaces scenario warnings, optimiser status, bounds audit, allocation reconciliation, operating-region views, and lift comparisons
  • Export writes reproducible bundles under the run directory and exposes any saved sensitivity output selections

Background jobs

The dashboard app runs draft evaluation and sensitivity sweeps as background jobs.

In this beta:

  • the app queues draft evaluation and sensitivity sweeps
  • the UI polls the active job and refreshes the workspace when the job completes
  • export runs synchronously, but Abacus still records it in job history

The UI currently tracks one active planner job at a time. Finish the current evaluation or sweep before starting another one.

Practical guidance

  • Launch the app from a fitted results directory, not from raw input data.
  • Use separate cloned workspaces for competing planning narratives.
  • Re-evaluate a draft after changing dates, budget, or allocation values.
  • Check both requested and evaluated windows when carryover is enabled.
  • Open Explain → Diagnostics and audit before exporting or sharing a scenario set. Diagnostics is a historical screenshot label, not a separate page in the documented workspace.
  • Treat the dashboard launcher as a local beta workflow rather than a production deployment surface.
  • Use abacus-dashboard as the dashboard app home; treat abacus.scenario_planner dashboard paths as legacy compatibility.

Legacy compatibility window

Compatibility remains advisory with no removal before Abacus 4.0. Removal of legacy dashboard app-layer facades under abacus.scenario_planner also requires:

  • a documented abacus-dashboard release and install path
  • passing dashboard smoke checks against the supported Abacus scenario contract
  • zero known internal imports using legacy dashboard paths

Common pitfalls

  • Launching the app without installing the companion abacus-dashboard package
  • Pointing the launcher at a directory without run_manifest.json and fit artefacts
  • Expecting the app to fit a model from scratch
  • Interpreting a draft preview as evaluated output before clicking Evaluate and Save
  • Starting a second evaluation or sweep while another planner job is still running