Python API

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

The public API lives under abacus.scenario_planner.

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

Prerequisite

ScenarioPlanner requires a fitted PanelMMM with idata.

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

Create a planner

from abacus.scenario_planner import ScenarioPlanner

planner = ScenarioPlanner(mmm)

You can inspect the modelled channel names with:

channels = planner.channels

Supported workspace helpers

If you want to work with the supported app from Python, use these helpers:

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 supported app from Python

Example:

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

Evaluate one scenario

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

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

Programmatic workspace orchestration

Use WorkspaceService when you want to work with saved planner workspaces from Python.

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.scenario_planner import load_workspace_bundle

run_context, workspace_service, workspace = load_workspace_bundle(
    "results/timeseries_20260308_144627",
)

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],
)

WorkspaceService defaults to synchronous jobs when you instantiate it directly. The supported app uses ThreadedScenarioPlannerJobRunner automatically.

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 of record lists that works well for client-side UIs such as Dash.

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 supported app. 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