Use this section when you are changing Abacus itself rather than using it as a
library. The contributor docs focus on three questions:
How do you get a working local environment?
Where should new code live?
What do you need to run before you consider a change complete?
Abacus is intentionally local-first. The source of truth for development
workflow is the combination of the repo Makefile,
ARCHITECTURE.md, and the verification scripts in
sandbox/.
Start Here
Development Setup explains the supported local
environment, editable install, and the verification commands you are expected
to use.
Architecture explains the module boundaries, dependency
direction, and where new code should land.
Testing explains the test layout, recommended pytest commands,
and when to run the heavier local verification scripts.
Recommended Contributor Flow
Create or refresh your local environment.
Read the architecture page before touching abacus/mmm/panel.py or the
extracted panel modules.
Make the smallest coherent code change that solves the task.
Run targeted lint and tests for the touched area.
For substantial work, run make verify_local.
If packaging, imports, or bundled assets changed, run make verify_package.
Related Documents
README.md for the product-level overview and quick-start
examples.
ARCHITECTURE.md for the fuller contributor-facing
module map.
Abacus is structured so that the public MMM API stays small while the
implementation can evolve behind stable seams. The most important rule is that
PanelMMM is a facade, not the place where new core behaviour should
accumulate.
For the complete module map, read ARCHITECTURE.md in
the repository root. This page summarises the parts that matter most when you
are deciding where to put new code.
Design Principles
PanelMMM stays thin. Constructor normalisation, data prep, graph
construction, prediction, calibration, runtime helpers, and serialisation
live under abacus/mmm/models/.
Compute comes before presentation. Diagnostics, summaries, and plotting
should consume structured outputs from the model layer rather than embedding
analytical logic in presentation code.
Dependencies flow downward. Shared root infrastructure can be imported by
MMM modules, but MMM-specific modules should not leak back into the shared
layer.
Compatibility is deliberate. If you move imports or rename internals, keep
facades or compatibility shims where public usage would otherwise break.
Data conversion, scaling, Mundlak support, prediction-data prep
abacus/mmm/models/panel_data.py
PyMC graph construction
abacus/mmm/models/panel_build.py
Posterior predictive or response-curve sampling
abacus/mmm/models/panel_predict.py
Serialisation or save/load compatibility
abacus/mmm/models/panel_serialize.py and shared helpers in abacus/modeling/io.py
Diagnostics compute
abacus/mmm/diagnostics/
Summary tables and exported curve summaries
abacus/mmm/summarization/
Static charts
abacus/mmm/plotting/
Budget optimisation logic
abacus/mmm/optimization/
Adstock or saturation behaviour
abacus/mmm/components/ and abacus/mmm/transforms/
Shared model-builder infrastructure
abacus/modeling/
Dependency Rules
Allowed
Shared root modules can be imported by MMM modules.
abacus/mmm/models/ can depend on MMM primitives and shared root modules.
Facades such as panel.py can depend on the extracted panel modules.
Plotting, summaries, diagnostics, and optimisation can depend on model
outputs and extracted helpers.
Avoid
Importing panel.py from abacus/mmm/models/*.
Adding plotting or summary logic to core model-building modules.
Adding MMM-specific behaviour to the shared abacus/modeling/ layer unless
it is genuinely reusable.
Defaulting to panel.py for new features just because it is visible.
Practical Guidance
When you touch a feature area, check whether there is already an extracted seam
for it before adding a new helper. Examples:
Plot behaviour should usually land in abacus/mmm/plotting/, not in
abacus/mmm/plot.py.
Serialisation changes should usually land in
abacus/mmm/models/panel_serialize.py, not directly in PanelMMM.
Time-varying parameter behaviour should use the HSGP and TVP support modules
rather than embedding new logic in plotting or builders.
Before You Merge
Confirm the change landed in the correct layer.
Keep public facades thin.
Preserve public API compatibility unless the change is explicitly breaking.
Add or update tests in the matching test area.
Run the local verification commands described in Testing.
Development Setup
This page describes the supported local setup for working on Abacus. The
project is maintained with local verification scripts rather than a CI-first
workflow, so your development environment needs to be able to run linting,
pytest, and the packaging smoke checks directly.
Prerequisites
Python 3.12
A local environment manager such as Conda
A writable temporary directory such as /tmp for PyTensor caches and package
verification artefacts
Create the Development Environment
The simplest supported path is the repository environment file:
If you know you will be running linting and tests frequently, install the
optional extras as well:
python3 -m pip install .[lint,test]
Local Runtime Defaults
Some parts of the stack need writable cache directories. In restricted or
sandboxed environments, set the same defaults used by the repo’s local
verification scripts:
The Makefile already applies these defaults for make test and
make smoke_mmm.
Common Commands
Lint and format
make check_lint
make lint
make check_format
make format
These targets cover the package, tests, scripts, and the local verification
entry points in sandbox/.
Tests
make testpytest tests/<path>/test_*.py -v
Use targeted pytest first when you are working on a narrow area. Run the wider
verification commands before closing substantial changes.
Local verification
make smoke_mmm
make verify_local
make verify_package
make verify_local_all
What these commands do:
make smoke_mmm runs a short end-to-end MMM smoke path against the demo
config and demo data.
make verify_local runs the retained local verification matrix from
sandbox/run_local_verification.py.
make verify_package builds package artefacts and validates an installed
package smoke path.
make verify_local_all runs the local verification matrix and includes the
packaging smoke step.
Important Working Files
File
Why it matters
Makefile
Primary local entry point for lint, test, smoke, and package verification
environment.yml
Supported dev environment definition
pyproject.toml
Packaging metadata, extras, Ruff, MyPy, and pytest configuration
sandbox/run_local_verification.py
Authoritative local verification matrix
sandbox/run_package_verification.py
Package build and installed-wheel smoke verification
ARCHITECTURE.md
Contributor-facing module map and dependency rules
Local-Only Areas
The repo contains some directories that are useful locally but are not part of
the shipped library surface:
.archive/ for archived planning and reference material
assets/engineering/standards/ for local documentation and writing standards
sandbox/ for local scripts and verification entry points
Keep temporary scripts in sandbox/ rather than mixing them into the package.
Troubleshooting
PyTensor cache permission errors
If you see errors related to .pytensor lock files or compiledir creation,
export the runtime defaults shown above and rerun the command.
Package verification fails because build is missing
Run:
python3 -m pip install build
The make verify_package target does this automatically.
You are not sure which command to run
As a rule:
run targeted pytest and ruff while iterating
run make verify_local before finishing non-trivial code changes
run make verify_package when packaging, imports, or bundled assets changed
Testing
Abacus uses pytest for automated tests, plus local verification scripts for the
broader confidence checks that glue linting, smoke paths, and packaging
together. The expected workflow is to run targeted tests while you iterate and
then run the wider local verification commands before you finish substantial
work.
Test Layout
Path
What it covers
tests/test_*.py
Shared infrastructure such as model IO, paths, package identity, and root-level helpers
tests/mmm/
MMM behaviour at the public surface
tests/mmm/models/
Extracted panel implementation seams
tests/mmm/components/
Adstock and saturation component behaviour
tests/mmm/plotting/
Static plotting helpers and theme/layout behaviour
tests/mmm/optimization/
Budget optimisation logic and wrappers
tests/mmm/diagnostics/
Structured diagnostics compute
tests/mmm/summarization/
Summary/export helpers
When you change a specific module seam, add or update tests in the matching
test area instead of only asserting through a broad end-to-end test.
If you change model serialisation, identity strings, or import compatibility,
add tests that prove older saved data or old import paths still work where that
compatibility is expected.
Packaging and bundled assets
If you add or move package data, use make verify_package so the change is
checked against an installed wheel rather than only the editable repo checkout.
What to Run Before You Finish
Small, localised change
Targeted pytest
Targeted ruff check
Moderate code change
Targeted pytest
make check_lint
make smoke_mmm
Broad or risky change
make verify_local
make verify_package if packaging or bundled assets changed
Writing Good Tests
Test observable behaviour, not implementation noise.
Keep fixtures close to the layer you are testing.
Prefer additive compatibility tests when preserving old behaviour.
Use small synthetic data where possible.
For plotting and serialisation, assert the stable contract rather than
fragile internals.