Task-oriented workflow documentation for configuration, validation, demos, lifecycle, and troubleshooting.
Pages
Configuration guide — meridian-tools is driven by one YAML configuration file. This guide explains every section, its purpose, and its constraints. For a field-level schema reference, see yaml-schema.md.
Validation guide — This guide explains how to choose and configure validation strategies in meridian-tools. Validation is the process of evaluating a candidate model specification on held-out data before committing to a final production fit.
Model selection guide — This guide explains how meridian-tools supports Bayesian model selection using Leave-One-Out (LOO) cross-validation and the Watanabe-Akaike Information Criterion (WAIC). It covers when model selection is available, how to interpret the outputs, and how to compare multiple candidate models.
Lifecycle management guide — meridian-tools treats completed runs as immutable artefacts. The lifecycle module provides tools to load, compare, and refresh past runs without mutating them. This guide explains each lifecycle operation and when to use it.
Meridian Tools workflow guide — This guide shows the supported end-to-end agency workflow for meridian-tools. It starts with one YAML config, moves through candidate validation, separates the final full-sample fit from the validation runs, and ends with the artefacts you should hand over or inspect later. The examples in this guide stay inside the implemented package surface. They do not assume notebooks, dashboards, or unpublished helper scripts.
Meridian Tools demo guide — This is the canonical guide to the bundled meridian-tools demos. Use it when you want one safe, reproducible, end-to-end example without client data.
Troubleshooting — Common issues and solutions when working with meridian-tools.
Subsections of Guides
Configuration guide
meridian-tools is driven by one YAML configuration file. This guide explains
every section, its purpose, and its constraints. For a field-level schema
reference, see yaml-schema.md.
Configuration philosophy
The YAML file owns the authored project definition: project metadata, data
paths, model specification, fit settings, validation strategy, and export
switches. Runtime-only values — output_dir, run_name, and concrete
validation_spec — belong in PipelineRunConfig or CLI flags, not in the YAML
file. This separation ensures that the same YAML file can drive multiple runs
with different runtime options while remaining reproducible.
This is the smallest config that will pass validation. It uses defaults for
everything else: no validation, all exports enabled, no response curves, no
optimisation.
name — Human-readable project name. Used as the base for run directory
names unless overridden by --run-name at runtime.
data
CSV data loader configuration. Maps directly to Meridian’s CsvDataLoader.
data:path:./client_dataset.csvkpi_type:revenue # "revenue" (default) or "non-revenue"coord_to_columns:time:weekgeo:market # optional for national modelskpi:revenuepopulation:populationmedia:[impressions_tv, impressions_search]media_spend:[spend_tv, spend_search]controls:[promo_flag, price_index]media_to_channel:null# optional channel mapping overridesmedia_spend_to_channel:nullreach_to_channel:nullfrequency_to_channel:nullrf_spend_to_channel:nullorganic_reach_to_channel:nullorganic_frequency_to_channel:null
path — Path to the CSV data file. Relative paths are resolved against
the directory containing the YAML config file, not the current working
directory.
kpi_type — Either "revenue" or "non-revenue". Controls how Meridian
interprets the KPI column.
coord_to_columns — Maps Meridian coordinate names to CSV column names.
time is required. geo is optional (omit for national models).
model_spec
Raw keyword arguments forwarded to Meridian’s ModelSpec.
model_spec:kwargs:max_lag:8media_prior_type:roi
kwargs — Dictionary passed through to ModelSpec(**kwargs). Supports
any argument that Meridian’s ModelSpec accepts.
Special handling for holdout_id: if present in kwargs, the run is treated
as an “authored holdout” validation run. See the
validation guide for details.
fit
Sampling configuration for Meridian posterior fitting.
fit:sample_prior_draws:null# Optional prior-only samplingn_chains:4# Number of MCMC chainsn_adapt:500# Adaptation steps per chainn_burnin:500# Burn-in steps per chainn_keep:1000# Posterior samples to keep per chainseed:20260331# Reproducibility seed (int, list[int], or null)max_tree_depth:10# NUTS max tree depthmax_energy_diff:500.0# NUTS max energy differenceunrolled_leapfrog_steps:1# NUTS leapfrog stepsparallel_iterations:10# TF parallel iterations
All fields have sensible defaults. Override only what you need.
seed — Accepts a single integer, a list of integers (one per chain), or
null for non-deterministic sampling.
sample_prior_draws — If set, prior predictive samples are drawn before
posterior sampling. This is optional and primarily for model diagnostics.
validation
Validation and holdout orchestration settings. See the
validation guide for strategy selection advice.
# Option 1: No validation (default)validation:strategy:none# Option 2: Blocked tailvalidation:strategy:blocked_tailholdout_size:8# Option 3: Rolling originvalidation:strategy:rolling_origininitial_train_size:52test_size:4step_size:4# Must equal test_sizemax_splits:3# At least 2
strategy — One of "none", "blocked_tail", or "rolling_origin".
holdout_size — Required for blocked_tail. Number of time periods to
hold out from the end of the series.
initial_train_size, test_size — Required for rolling_origin.
step_size — Optional for rolling_origin. Must equal test_size if
set. Defaults to test_size.
max_splits — Optional for rolling_origin. Must be at least 2.
Validation rules:
blocked_tail rejects rolling-origin parameters.
rolling_origin rejects holdout_size.
none rejects all holdout and rolling-origin parameters.
Legacy holdout_size without explicit strategy is rejected.
exports
Output switches for diagnostics and model-selection artefacts.
exports:use_kpi:false# Use KPI-based metricsbatch_size:1000# Batch size for Meridian analysisexport_predictive_accuracy:true# Write predictive_accuracy.csvexport_review_summary:true# Write review_summary.jsonexport_model_selection:true# Write LOO/WAIC outputsexport_plots:true# Write PNG plot artefacts
All fields have defaults. If the entire exports section is omitted,
all exports are enabled with default settings.
response_curves
Optional. If omitted, the response curves stage is skipped.
spend_multipliers — Required. Non-empty list of non-negative floats.
confidence_level — Must be strictly between 0 and 1.
optimisation
Optional. If omitted, the optimisation stage is skipped.
optimisation:start_date:"2025-01-01"end_date:"2025-12-31"budget:mode:fixed_total # or "relative_reference_window_total"value:1000000.0use_posterior:trueuse_optimal_frequency:trueconfidence_level:0.9
start_date, end_date — ISO format YYYY-MM-DD. end_date must
be on or after start_date.
budget.mode — Either "fixed_total" (absolute budget) or
"relative_reference_window_total" (multiplier against the reference window’s
total spend).
budget.value — Positive float. For fixed_total, this is the absolute
budget. For relative_reference_window_total, this is a multiplier (e.g.
1.1 means 110% of the reference window total).
Validation strictness
All configuration models use Pydantic’s extra="forbid" mode. Any unexpected
key in the YAML file will produce a clear validation error. This prevents
silent misconfiguration from typos or outdated keys.
$ meridian-tools run --config bad.yml
# pydantic.ValidationError: 1 validation error for MeridianToolsConfig# exports -> export_pridictive_accuracy# Extra inputs are not permitted
Path resolution
Relative paths in data.path are resolved against the directory containing the
YAML config file, not the current working directory. This means:
# If config is at /workspace/configs/project.ymldata:path:../inputs/weekly.csv# Resolves to /workspace/inputs/weekly.csv
The resolved path is written to config.resolved.yaml in the run directory.
The original authored path is preserved in config.source.yaml.
Wrapper-owned preflight
Before meridian-tools creates a dated run directory, it performs one narrow
wrapper-owned preflight check on the authored config and the resolved input
CSV. Phase 10 keeps this boundary intentionally small so the wrapper does not
become a second Meridian schema layer.
The wrapper checks exactly:
the resolved data.path exists and is a regular file
the CSV header row can be read
the parsed header is non-empty
no parsed header cell is blank after trimming whitespace
every authored scalar entry in data.coord_to_columns exists in the header
every authored list member in data.coord_to_columns exists in the header
every authored key in data.media_to_channel exists in the header
every authored key in data.media_spend_to_channel exists in the header
every authored key in data.reach_to_channel exists in the header
every authored key in data.frequency_to_channel exists in the header
every authored key in data.rf_spend_to_channel exists in the header
every authored key in data.organic_reach_to_channel exists in the header
every authored key in data.organic_frequency_to_channel exists in the
header
authored list-valued coord families are non-empty
authored mapping fields above are non-empty
coord_to_columns.media and media_to_channel must be authored together
coord_to_columns.media_spend and media_spend_to_channel must be authored
together
coord_to_columns.reach, coord_to_columns.frequency,
reach_to_channel, and frequency_to_channel must be authored together
coord_to_columns.rf_spend and rf_spend_to_channel must be authored
together
coord_to_columns.organic_reach and organic_reach_to_channel must be
authored together
coord_to_columns.organic_frequency and
organic_frequency_to_channel must be authored together
Matching is exact and case-sensitive. The wrapper does not normalise headers,
apply aliases, or use fuzzy matching.
What remains Meridian-owned:
deep ModelSpec semantics
fit-dependent tensor or shape constraints
statistical validity checks that depend on model construction or sampling
So Phase 10 moves obvious wrapper-detectable mistakes earlier, but it does not
promise to catch everything Meridian may reject later.
This guide explains how to choose and configure validation strategies in
meridian-tools. Validation is the process of evaluating a candidate model
specification on held-out data before committing to a final production fit.
Why validation matters for MMM
Marketing Mix Models are fitted to time series data. Unlike standard supervised
learning, the temporal structure of the data means that naive IID
cross-validation (random train/test splits) is statistically inappropriate.
meridian-tools does not implement random shuffling or naive k-fold splits.
Instead, it provides two time-respecting validation strategies and a clear
separation between validation runs and the final production fit.
Validation strategies
none — No validation
validation:strategy:none
The model is fitted on the full dataset with no holdout. Use this when you
do not need candidate evaluation — for example, when rerunning a previously
validated specification.
blocked_tail — Single contiguous tail holdout
validation:strategy:blocked_tailholdout_size:8
Reserves the last holdout_size time periods as a test block. The model is
fitted on all preceding periods. This is the recommended default for short
MMM time series where you want one simple candidate evaluation.
When to use: Most standard MMM projects with fewer than 150 weekly
observations.
The holdout mask is generated automatically and injected into Meridian’s
holdout_id parameter. For geo-panel models, the mask is broadcast across
all geos.
Creates multiple expanding-window splits where each successive split adds
more training data. This provides a more robust evaluation signal than a
single blocked tail, but requires enough history to support multiple splits.
When to use: Projects with longer time series (typically 100+ weekly
observations) where you want multiple evaluation windows.
How it works:
Time axis: [t1, t2, ..., t52, t53, ..., t56, t57, ..., t60]
Split 1: Train [t1..t52], Test [t53..t56]
Split 2: Train [t1..t56], Test [t57..t60]
Constraints:
step_size must equal test_size (non-overlapping test windows).
max_splits must be at least 2.
initial_train_size + test_size must not exceed the number of observations.
The plan must yield at least two splits.
authored_holdout — User-provided holdout mask
This is not a YAML strategy setting. Instead, you provide holdout_id
directly in model_spec.kwargs:
When the runner detects an authored holdout_id in the YAML, it treats the
run as an authored_holdout validation run. The mask is passed through to
Meridian verbatim and recorded in the validation spec artefact.
When to use: When you need a specific holdout pattern that does not
follow blocked-tail or rolling-origin conventions.
CLI vs Python API
Blocked tail from the CLI
blocked_tail runs directly from the CLI because they produce one run:
meridian-tools run --config project.yml --output-dir runs
Rolling origin requires the Python API
rolling_origin is a Python-first planning surface because it produces
multiple runs — one per split plus a final fit. The CLI will reject direct
rolling_origin execution:
# This will fail:meridian-tools run --config project.yml # with strategy: rolling_origin# ValueError: cannot execute `rolling_origin` directly
Instead, use the Python API:
frompathlibimportPathimportpandasaspdfrommeridian_tools.configimportPipelineRunConfig,load_yaml_configfrommeridian_tools.cvimportbuild_validation_planfrommeridian_tools.runnerimportrun_pipelineconfig_path=Path("project.yml")config=load_yaml_config(config_path)# Read the time index from your datadata_path=config.data.pathifnotdata_path.is_absolute():data_path=(config_path.parent/data_path).resolve()frame=pd.read_csv(data_path)time_column=config.data.coord_to_columns["time"]geo_column=config.data.coord_to_columns.get("geo")time_index=frame[time_column].drop_duplicates().tolist()geo_index=Noneifgeo_columnisnotNone:geo_index=frame[geo_column].drop_duplicates().tolist()# Build the validation planvalidation_plan=build_validation_plan(config.validation,time_index=time_index,geo_index=geo_index,)# Execute each validation splitforrun_specinvalidation_plan.validation_runs:run_pipeline(PipelineRunConfig(config_path=config_path,output_dir=Path("runs"),validation_spec=run_spec,))
Separating validation from the final fit
Validation runs and the final production fit are different jobs. First you
evaluate candidate specifications on held-out splits. Then, once you have
chosen the specification, you run a separate full-sample fit with no holdout.
Do not reuse a validation fit as the production artefact. The validation fit
was trained on a subset of the data and its posterior reflects that subset.
Final fit after blocked tail
For blocked_tail, build_validation_plan provides a final_fit_run spec:
validation_plan=build_validation_plan(config.validation,time_index,geo_index)# Run the final fit on all datafinal_result=run_pipeline(PipelineRunConfig(config_path=config_path,output_dir=Path("runs"),validation_spec=validation_plan.final_fit_run,))
Final fit after rolling origin
The same pattern works for rolling origin:
# After running all validation splits...final_result=run_pipeline(PipelineRunConfig(config_path=config_path,output_dir=Path("runs"),validation_spec=validation_plan.final_fit_run,))
The final_fit_run spec has mode="final_fit", strategy="none", and
holdout_id=None. It trains on the full time axis with no holdout.
Run directory naming
The runner automatically appends a validation-aware suffix to the run name:
Scenario
Run name pattern
No validation
<project_name>_<timestamp>
Blocked tail
<project_name>_blocked_tail_<timestamp>
Rolling origin split 1
<project_name>_split_01_<timestamp>
Final fit
<project_name>_final_fit_<timestamp>
Authored holdout
<project_name>_authored_holdout_<timestamp>
Override the name with --run-name or PipelineRunConfig(run_name=...).
Validation spec artefact
Every validation-aware run writes a validation_spec.json artefact in the
10_validation/ stage directory. This JSON records:
mode — "validation" or "final_fit"
strategy — the validation strategy used
split_label — human-readable split identifier
holdout_source — "generated_validation", "authored_model_spec", or "none"
generated_holdout — whether the holdout mask was auto-generated
holdout_shape — shape of the holdout mask (without the actual data)
train_indices / test_indices — integer indices into the time axis
train_dates / test_dates — corresponding date values
The actual holdout mask is not stored in the JSON artefact (it can be large).
It is injected into the model at runtime.
Interaction with model selection
Bayesian model selection (LOO/WAIC) is only available for runs where
holdout_id is None — meaning full-sample fitted models and final-fit runs.
Validation fits and authored-holdout runs write a
model_selection_status.json artefact instead of LOO/WAIC outputs. See the
model selection guide for details.
Model selection guide
This guide explains how meridian-tools supports Bayesian model selection
using Leave-One-Out (LOO) cross-validation and the Watanabe-Akaike Information
Criterion (WAIC). It covers when model selection is available, how to interpret
the outputs, and how to compare multiple candidate models.
What model selection provides
Bayesian model selection uses information criteria computed from pointwise
log-likelihood values to compare model specifications. Unlike predictive
accuracy on a held-out set, LOO and WAIC evaluate the model’s expected
predictive performance using the full posterior without requiring a separate
validation split.
meridian-tools wraps ArviZ’s az.loo and az.waic with:
Automatic log-likelihood reconstruction for fitted Meridian models
Structured error handling when model selection is not possible
A compare_models surface for ranking multiple candidates
Artefact-level compatibility status in every run directory
Compatibility boundary
Model selection is only available for models where holdout_id is None.
This means:
Run type
Model selection available
Full-sample fit (no validation)
Yes
Final-fit run (mode: final_fit)
Yes
Blocked-tail validation run
No
Rolling-origin validation split
No
Authored-holdout run
No
Bare InferenceData without log_likelihood
No
This restriction exists because LOO and WAIC require the full observed
likelihood surface. A holdout fit has a modified likelihood that does not
represent the full data generating process. Comparing a holdout fit’s ELPD
against a full fit’s ELPD would be statistically meaningless.
How it works in the pipeline
When exports.export_model_selection: true in the YAML config, the runner’s
30_model_assessment stage attempts model selection after writing diagnostics.
Compatible runs
For compatible models, the stage writes:
loo_summary.json — LOO summary statistics (ELPD, p_loo, SE, etc.)
waic_summary.json — WAIC summary statistics
loo_pointwise.csv — Per-observation LOO values and Pareto k diagnostics
waic_pointwise.csv — Per-observation WAIC values
model_comparison.csv — Ranked comparison table (single-model for individual runs)
Incompatible runs
For incompatible models, the stage writes a single status artefact:
model_selection_status.json
{"status":"unavailable","reason_code":"holdout_fit_unsupported","reason":"Model selection requires holdout_id is None ..."}
Known reason codes:
Code
Meaning
holdout_fit_unsupported
The model was fitted with a holdout mask
requires_fitted_meridian_model
Missing posterior samples or ArviZ InferenceData
missing_log_likelihood_group
Bare InferenceData without reconstructable likelihood
meridian_internal_seam_incompatible
Meridian version lacks required internal reconstruction methods
Incompatibility is non-fatal. The pipeline completes successfully and
records the reason in the artefact.
Using the Python API directly
Compute LOO for a single model
frommeridian_tools.model_selectionimportcompute_looresult=compute_loo(fitted_model,pointwise=True)print(result.kind)# "loo"print(result.summary)# {"kind": "loo", "elpd_loo": -123.4, ...}print(result.pointwise)# DataFrame with loo_i, pareto_k per observation
Meridian does not store pointwise log-likelihood in its InferenceData by
default. meridian-tools reconstructs it automatically when you pass a
fitted Meridian model to compute_loo, compute_waic, or compare_models.
Rebuilds the joint distribution from the posterior samples
Computes observation-level log-likelihood
Returns a new InferenceData with the log_likelihood group attached
The original model is never mutated. The reconstruction produces a
temporary copy used only for the ArviZ computation.
You can also control this explicitly:
frommeridian_tools.log_likelihoodimportattach_log_likelihood# Returns new InferenceData with log_likelihood group (original unchanged)idata_with_ll=attach_log_likelihood(fitted_model,in_place=False)# Mutates the model's inference_data in placeattach_log_likelihood(fitted_model,in_place=True)
Interpreting the outputs
LOO summary
Field
Meaning
elpd_loo
Expected log pointwise predictive density (higher is better)
p_loo
Effective number of parameters
se
Standard error of elpd_loo
warning
Whether Pareto k diagnostics indicate unreliable estimates
WAIC summary
Field
Meaning
elpd_waic
Expected log pointwise predictive density (WAIC estimate)
The pointwise LOO output includes a pareto_k column. Values above 0.7
indicate that the LOO approximation is unreliable for those observations.
ArviZ will emit a warning if any Pareto k values exceed the threshold.
Model comparison
When comparing two or more models:
elpd_diff — Difference in ELPD from the best model (0 for the best)
dse — Standard error of the ELPD difference
weight — Stacking weight (how much to trust each model)
Models are ranked by ELPD (rank 0 is best)
A single-model comparison returns a one-row table with rank=0,
elpd_diff=0, and weight=1.0.
Error handling
All model-selection errors are raised as ModelSelectionError with a
structured reason_code:
frommeridian_tools.model_selectionimportModelSelectionError,compute_lootry:result=compute_loo(candidate)exceptModelSelectionErrorasexc:print(exc.reason_code)# e.g. "holdout_fit_unsupported"print(str(exc))# Human-readable explanation
In the pipeline, these errors are caught and written to
model_selection_status.json rather than failing the run.
Lifecycle management guide
meridian-tools treats completed runs as immutable artefacts. The lifecycle
module provides tools to load, compare, and refresh past runs without mutating
them. This guide explains each lifecycle operation and when to use it.
Core concepts
Run records
A RunRecord encapsulates a run’s metadata and artefact paths. It is loaded
from a run directory by reading run_manifest.json and resolving all artefact
paths against the directory.
frommeridian_tools.lifecycleimportload_run_recordrecord=load_run_record("runs/my-project_blocked_tail_20260402_073500")print(record.run_dir)# Path to the run directoryprint(record.manifest)# RunManifest with stages, timestamps, versionsprint(record.config_source_path)# Path to config.source.yamlprint(record.config_resolved_path)# Path to config.resolved.yamlprint(record.input_data_provenance_path)# Path to input_data_provenance.json (or None for older runs)print(record.diagnostics_bundle_path)# Path to diagnostics_bundle.json (or None)print(record.validation_spec_path)# Path to validation_spec.json (or None)print(record.model_selection_status_path)# Path to model_selection_status.json (or None)
All paths in the record are absolute. Required artefacts (config_source,
config_resolved) are validated at load time and always present.
input_data_provenance is also required for manifest version 3 runs.
Optional artefacts (diagnostics_bundle, validation_spec,
model_selection_status) are None if not present in the manifest.
Immutability
Lifecycle operations never modify a source run directory. When you refresh a
run, the output goes to a new sibling directory. When you compare runs, both
source directories remain untouched.
All lifecycle functions raise LifecycleError (a RuntimeError subclass)
when they encounter invalid state.
list_run_records discovers all direct child directories that contain a
run_manifest.json and returns them sorted by started_at timestamp
(most recent first), with run directory name as a secondary sort key.
The function requires a directory path (not a file). It will raise an error
if any discovered run directory contains an invalid manifest — it does not
silently skip broken runs.
Refreshing a run
Refreshing re-executes a run using its stored configuration but writes the
output to a new directory. The source run is never modified.
When to refresh
After a Meridian upgrade — to check whether the new version produces
comparable results with the same specification.
After a code change — to verify that refactoring did not change model
outputs.
After extending the dataset — to refit the model with additional
observations using the same validated specification.
If the source run was a validation run (blocked tail or rolling origin),
build_refresh_run_config reconstructs the validation spec from the stored
artefact, including the holdout mask geometry. For authored-holdout runs, it
reuses the YAML-owned holdout from the copied config.
For final-fit runs, the refresh produces another final-fit run with the same
full-sample training specification.
compare_run_records accepts run directory paths (not RunRecord objects)
and returns a pandas DataFrame with columns field, left, right,
status, and changed. The compared fields include:
run_name and status — basic identity.
meridian_tools_version and meridian_version — version drift.
has_validation_spec and has_diagnostics_bundle — artefact presence.
predictive_accuracy_status and review_summary_status — diagnostics.
has_model_selection_outputs and model_selection_reason_code — model selection.
input_authored_path, input_resolved_path, input_sha256,
input_size_bytes, input_mtime_utc, input_row_count,
input_column_count, and input_ordered_columns — dataset identity
and shape.
This is useful for auditing whether a refresh or a specification change
produced materially different results.
If either run predates manifest version 3, provenance rows are reported with
status == "legacy_unknown" and changed == None. That distinguishes
“no stored provenance exists” from “the dataset definitely changed”.
Lifecycle workflow example
A typical lifecycle workflow for a quarterly model refresh:
frompathlibimportPathfrommeridian_tools.lifecycleimport(load_run_record,list_run_records,build_refresh_run_config,)frommeridian_tools.runnerimportrun_pipeline# 1. Find the most recent production runrecords=list_run_records("runs/")production_run=records[0]# Most recent by started_at# 2. Refresh with the updated datasetrefresh_config=build_refresh_run_config(production_run.run_dir,output_dir=Path("runs/quarterly-refresh"),)refresh_result=run_pipeline(refresh_config)# 3. Compare the resultscomparison=compare_run_records(production_run.run_dir,refresh_result.run_dir)print(comparison)
Manifest versioning
The lifecycle layer supports manifest versions 0, 1, 2, and 3. Older
manifests are handled gracefully with default values for fields that were
added in later versions. The current version is 3.
This means you can load run directories created by earlier versions of
meridian-tools without issues. The loaded RunRecord keeps the same shape,
but input_data_provenance_path is None for pre-v3 runs because those
manifests predate provenance capture.
Meridian Tools workflow guide
This guide shows the supported end-to-end agency workflow for
meridian-tools. It starts with one YAML config, moves through candidate
validation, separates the final full-sample fit from the validation runs, and
ends with the artefacts you should hand over or inspect later. The examples in
this guide stay inside the implemented package surface. They do not assume
notebooks, dashboards, or unpublished helper scripts.
Before you start
Install Meridian first, then install meridian-tools in the same environment:
Use the CLI for ordinary run execution. Use the Python API when you need
rolling-origin planning, an explicit final-fit run, or lifecycle compare and
refresh operations. Phase 07 does not provide a lifecycle CLI.
If you want packaged reference examples before authoring your own YAML, use the
bundled demo guide in demos.md. The packaged demo launcher is
meridian-tools demo .... The repo-root python runme.py ... wrapper remains
available when you are working from a source checkout.
Author one YAML config
Keep the authored project definition in YAML. Keep runtime-only choices out of
the YAML file. In practice, that means your source file owns the project
metadata, data path, model specification, fit settings, validation settings,
and export switches. Runtime-only values such as output_dir, run_name, and
one concrete validation_spec belong in PipelineRunConfig or the CLI call,
not in config.resolved.yaml.
Use blocked_tail when you want one contiguous future block for candidate
evaluation. This is often the right default for short MMM time series. Use
rolling_origin when you have enough history to evaluate more than one
expanding-window split. Do not treat rolling_origin as ordinary k-fold
cross-validation. The package does not implement naive IID folds or random
shuffling because that is not the right statistical workflow for MMM time
series.
Validation runs and the final production fit are different jobs. First, you
evaluate candidate specifications on blocked time splits. Then, once you have
chosen the specification, you run a separate full-sample fit with no holdout.
Run one blocked-tail candidate from the CLI
Once the YAML file is authored, you can execute a blocked-tail candidate run
directly through the CLI:
meridian-tools run --config project.yml --output-dir runs
The same packaged runner surface is available through the thin repo-root
wrapper:
python runme.py run --config project.yml --output-dir runs
This command creates a dated run directory under runs/. If you need to
change the output location or the visible run name, pass --output-dir or
--run-name at execution time. Those are runtime-only overrides. They affect
the run directory and manifest, but they do not become part of the authored
YAML contract.
Plan and run rolling-origin validation through the Python API
rolling_origin is a Python-first planning surface because you need one
concrete split at a time. Start with an explicit YAML definition:
For rolling_origin and blocked_tail workflows, validation_plan.final_fit_run
is the explicit no-holdout runtime spec. It keeps the boundary clear. Candidate
validation and final production fitting are separate steps.
Know which artefacts matter for handoff
Each successful run directory is the handoff unit. The important files are:
run_manifest.json for stage status, versions, timestamps, and top-level
artefact links
00_run_metadata/config.source.yaml for the authored source config
00_run_metadata/config.resolved.yaml for the YAML-owned config after path
resolution
00_run_metadata/input_data_provenance.json for the exact dataset identity
used by the run
10_validation/validation_spec.json when the run is validation-aware
30_model_assessment/diagnostics_bundle.json for stable diagnostics
metadata
30_model_assessment/model_results_summary.html for the wrapped Meridian
assessment summary
30_model_assessment/plots/ for assessment PNG plots such as model fit and
rhat review
40_decomposition/summary_metrics.csv and summary_metrics.nc for
decomposition exports
40_decomposition/plots/ for decomposition PNG plots
60_response_curves/plots/response_curves_plot.png when response-curve
export is enabled
70_optimisation/plots/ when optimisation export is enabled
30_model_assessment model-selection outputs when the run is compatible, or
30_model_assessment/model_selection_status.json when it is not
Read those artefacts together.
30_model_assessment/diagnostics_bundle.json tells you whether predictive
accuracy and review summary were exported or disabled. The assessment stage
either contains the real Bayesian model-selection outputs or one explicit
compatibility status artefact.
The supported Bayesian model-selection boundary is narrow and deliberate. The
package supports fitted Meridian models where holdout_id is None. That means
full-sample fitted models and explicit final-fit runs are compatible. Validation
fits and authored holdout fits are not.
Use lifecycle helpers after a run exists
Once you have stored run directories, the lifecycle API lets you reload,
compare, and refresh them without going back to notebook state.
compare_run_records(...) gives you a metadata-level comparison. It does not
attempt a raw-file diff across every output. refresh_run(...) rebuilds a new
sibling run from the stored run-local artefacts. It does not overwrite the
source run. Phase 07 does not provide lifecycle CLI commands, so use the
Python API for these operations.
For the bundled reference examples and the exact stage-level file set, see
demos.md.
A practical analyst sequence
If you want one concrete operating pattern, use this one. Author a YAML file.
Run a blocked-tail candidate through the CLI when you need one held-out tail
block. Use rolling_origin through build_validation_plan(...) when you need
multiple expanding-window validation splits. Choose the modelling
specification. Run the final full-sample fit as its own job. Review the run
directory artefacts. Then use compare_run_records(...) and refresh_run(...)
when you need to inspect or rerun stored work later.
Meridian Tools demo guide
This is the canonical guide to the bundled meridian-tools demos. Use it when
you want one safe, reproducible, end-to-end example without client data.
The public story is simple:
Meridian is the modelling engine.
meridian-tools is the workflow wrapper.
The bundled demos are launched through meridian-tools surfaces, not by
calling Meridian directly.
What the bundled demos are for
Phase 08 adds two bundled reference workflows:
timeseries
a national timeseries demo shipped as packaged demo data
geo_panel
a geo-panel demo shipped as packaged demo data
Both datasets are bundled non-client reference data. They exist so analysts and
stakeholders can inspect the workflow, run structure, and review artefacts
without using client material.
What the package adds on top of Meridian
Meridian remains responsible for the modelling and analysis primitives.
meridian-tools adds the operational surface that agencies usually need around
it:
typed YAML configuration
blocked-tail and rolling-origin validation workflow
a thin demo launcher for bundled reference workflows
This is why the demos are useful. They show the wrapper workflow directly,
rather than asking users to reconstruct it from notebooks or internal scripts.
Demo entrypoints
List the supported demos:
meridian-tools demo --list
Run the bundled timeseries demo:
meridian-tools demo timeseries
Run the bundled geo-panel demo:
meridian-tools demo geo_panel
By default, demo runs are written under runs/demos/. If you want a different
root, pass --output-dir. If you want a custom visible run name, pass
--run-name.
The same package can also run an explicit authored config:
meridian-tools run --config /path/to/project.yml --output-dir runs
The repo-root wrapper can run an explicit authored config too:
python runme.py run --config /path/to/project.yml --output-dir runs
Bundled YAML surface
The bundled demo YAML files are real meridian-tools configs. They are not
legacy Abacus-style placeholders.
The authored sections used in Phase 08 are:
project
data
model_spec
fit
validation
exports
response_curves
optimisation
The Phase 08 additions are:
response_curves
required if you want the response-curve export stage to run
optimisation
required if you want the optimisation export stage to run
The bundled demos include both sections so that the full staged schema is
exercised.
The default demo configs use validation.strategy: none. That keeps the
reference runs model-selection compatible, so LOO and WAIC outputs are
written by default.
Output schema
Each successful demo run writes one manifest-backed staged directory layout:
run identity, versions, timestamps, stage status, and top-level artefact
links
00_run_metadata/config.source.yaml
the authored YAML
00_run_metadata/config.resolved.yaml
the same YAML after runtime path resolution
10_validation/validation_spec.json
validation provenance for validation-aware runs only
not present in the default bundled demos because they run as full-sample fits
30_model_assessment/diagnostics_bundle.json
the stable machine-readable record of diagnostics export state
30_model_assessment/model_results_summary.html
the wrapped Meridian assessment summary
40_decomposition/summary_metrics.csv
the easiest tabular decomposition output to inspect first
For model selection, keep the boundary honest:
LOO and WAIC are only available for compatible fitted Meridian models
validation fits and other incompatible cases will record
model_selection_status.json instead
the package does not pretend unsupported runs have valid Bayesian comparison
outputs
the bundled demos are configured as full-sample fits, so they should write
loo_summary.json and waic_summary.json by default
For response curves and optimisation:
these outputs are useful for scenario and allocation review
they are not a substitute for checking diagnostics, validation provenance, or
model-selection compatibility first
For visual review, each stage now keeps its PNG exports inside a local plots/
subdirectory rather than mixing image files into the stage root. That keeps the
machine-readable exports and the human-review plots in one predictable place.
inspect 60_response_curves/ and 70_optimisation/ if those stages ran
If you are working from a source checkout, python runme.py demo --list and
python runme.py demo ... remain equivalent convenience wrappers.
That sequence shows the wrapper value quickly: one YAML config in, one
structured run directory out, with the Meridian and meridian-tools artefacts
kept in one predictable place.
Troubleshooting
Common issues and solutions when working with meridian-tools.
Installation issues
meridian-tools --help fails with ImportError
Cause: The package is not installed in the active environment, or Meridian
is missing.
Fix:
pip install -e ".[dev]"
If Meridian is not installed:
pip install "google-meridian[schema]==1.5.3"
RuntimeError: Saving meridian_model.binpb requires Meridian schema support
Cause: Meridian was installed without the [schema] extra.
Cause: A required wrapper dependency check failed before config/data
preflight or run-directory creation.
Common triggers:
google-meridian[schema] support is unavailable
exports.export_plots: true is set but vl-convert-python PNG support is
unavailable
Fix: Install or repair the missing runtime dependency first, then rerun.
ConfigPreflightError
Cause:meridian-tools found a wrapper-owned config or input-data issue
before run-directory creation.
Common triggers:
data.path resolves to a missing file or a directory
the CSV header row cannot be read
the header is empty or contains blank cells
an authored column name does not appear in the header exactly
a supported media/RF family is only half-authored
Fix: Correct the authored YAML or the input CSV first, then rerun. Header
matching is exact and case-sensitive in Phase 10.
ValidationExecutionContractError
Cause: The requested single-run execution path is incompatible with the
authored validation setup.
Common triggers:
you tried to run a rolling_origin config directly from the CLI or
run_pipeline(...)
you passed PipelineRunConfig.validation_spec while the YAML already
authors model_spec.kwargs.holdout_id
Fix: For rolling_origin, build a validation plan and execute one concrete
split at a time through the Python API. For authored holdouts, either keep the
YAML-authored holdout_id path or remove it before supplying a runtime
validation_spec. See the validation guide for the full
workflow.
ModelSelectionError with reason_code: holdout_fit_unsupported
Cause: LOO/WAIC was requested for a model fitted with a holdout mask.
Not a bug. Model selection is only available for full-sample fits. The
pipeline records the incompatibility in model_selection_status.json and
continues. See the model selection guide.
ModelSelectionError with reason_code: meridian_internal_seam_incompatible
Cause: The installed Meridian version does not expose the internal
reconstruction methods needed for log-likelihood computation.
Fix: Check the Meridian version. This package requires google-meridian[schema]==1.5.3.
If you recently upgraded Meridian, the private reconstruction seams may have
changed. Check the Meridian integration notes.
Run fails mid-pipeline
If a run fails after the dated run directory already exists, meridian-tools
raises PipelineRunFailure. The CLI and runme.py print the concrete failed
run directory, manifest path, and stage name when available.
The original exception is preserved as __cause__, so --traceback still
shows the underlying failure.
The manifest is written to disk after each stage. If a run fails, the
run_manifest.json is left on disk and marked failed. You can inspect it to
determine which stage failed:
Look at the stages array. A failed stage is recorded with status: "failed" and an error message.
Validation errors
time_index must be strictly increasing with no duplicate values
Cause: The time column in your data contains duplicates or is not sorted.
Fix: Ensure your CSV data has unique, monotonically increasing time values.
For geo-panel data, the time column should be unique per time period (not per
geo × time combination — the function expects the deduplicated time axis).
rolling_origin must yield at least two splits
Cause: The combination of initial_train_size, test_size, and data length
does not produce enough splits.
Fix: Either reduce initial_train_size, reduce test_size, or use
blocked_tail instead for shorter series.
holdout_size must be smaller than the time axis
Cause: The holdout size is greater than or equal to the number of time
periods.
Fix: Reduce holdout_size to leave at least one training period.
Lifecycle errors
LifecycleError when loading a run record
Cause: The run manifest is missing required entries, references a file that
does not exist, or has a malformed JSON structure.
Fix: Check that the run directory was not manually modified. Required
artefacts are config.source.yaml and config.resolved.yaml.
diagnostics_bundle.json is optional for loading but required for new runs.
Path traversal rejection
Cause: An artefact path in the manifest resolves outside the run directory.
Not fixable by editing the manifest. This is a security check. The manifest
was likely corrupted or manually edited with an invalid path.
Performance issues
Pipeline takes very long
MCMC sampling (the 20_model_fit stage) dominates wall-clock time. The
meridian-tools orchestration layer adds negligible overhead.
For production runs, use the defaults or increase these values for better
posterior quality.
Out-of-memory during model selection
Log-likelihood reconstruction loads the full posterior into memory and creates
a temporary copy of the InferenceData. For large models, this can double
memory usage temporarily.
Mitigation: Reduce n_keep or n_chains if memory is constrained.
Warnings
ArviZ Pareto k warnings
Estimated shape parameter of Pareto distribution is greater than 0.7 ...
This means the LOO approximation is unreliable for some observations. Check
the pointwise pareto_k values in loo_pointwise.csv. Values above 0.7
indicate influential observations.
Meridian national model auto-zeroing warnings
Hierarchical distribution parameters must be deterministically zero for
national models. eta_orf has been automatically set to Deterministic(0).
This is expected for national (non-geo) models. Meridian automatically zeros
out geo-level hierarchical parameters. The warning is informational.
TensorFlow deprecation warnings
These come from TensorFlow and Meridian internals. meridian-tools groups and
deduplicates them in the terminal output to reduce noise. They do not indicate
a problem with your run.