Meridian integration

This document describes how meridian-tools integrates with Google Meridian, the boundaries of that integration, and the risks associated with different coupling levels.

Integration philosophy

meridian-tools wraps Meridian without forking it. Meridian remains the modelling engine; meridian-tools adds workflow orchestration, validation, diagnostics bundling, model selection, and lifecycle management on top.

This approach means:

  • Meridian upgrades can be adopted without merging fork changes.
  • The upstream project’s API stability directly affects meridian-tools.
  • Any use of Meridian-internal APIs must be explicitly managed.

Coupling levels

Public API (low risk)

These are documented, versioned Meridian surfaces:

Surface Used by
Meridian (model class) runner.py
ModelSpec runner.py
CsvDataLoader, CoordToColumns runner.py
Analyzer exports.py, diagnostics.py
Summarizer exports.py
BudgetOptimizer exports.py
ModelReviewer diagnostics.py
MediaEffects, MediaSummary, ModelDiagnostics, ModelFit exports.py
save_meridian (schema serde) exports.py

These are unlikely to break without a Meridian major version bump. The exact google-meridian==1.5.3 pin keeps these assumptions aligned with the validated release baseline.

Semi-public API (medium risk)

These are accessible attributes on Meridian model objects that are used but not formally documented as stable:

Surface Used by Purpose
model.inference_data log_likelihood.py, model_selection.py Access ArviZ InferenceData
model.model_context log_likelihood.py, exports.py Access model structure
model.input_data exports.py Access input data for spend computation
model.posterior_sampler_callable log_likelihood.py Access posterior sampler

These are stable in practice (they are used by Meridian’s own analysis surfaces) but are not guaranteed to be stable across versions.

Private API (high risk)

These are _-prefixed methods on Meridian’s posterior_sampler_callable, used exclusively in log_likelihood.py for log-likelihood reconstruction:

_get_joint_dist_unpinned
_prepare_latents_for_reconstruction
_reconstruct_posteriors

These methods are Meridian-internal and may change or be removed in any Meridian release, including patch versions. They are necessary because Meridian does not provide a public API for pointwise log-likelihood computation.

Risk mitigation

Compatibility guard

log_likelihood.py checks for the presence of all three private methods before attempting reconstruction:

required_sampler_methods = (
    "_get_joint_dist_unpinned",
    "_prepare_latents_for_reconstruction",
    "_reconstruct_posteriors",
)
if any(not hasattr(posterior_sampler, method) for method in required_sampler_methods):
    raise ModelSelectionError(
        "...",
        reason_code="meridian_internal_seam_incompatible",
    )

If any method is missing, the error is caught and recorded as a model_selection_status.json artefact with reason_code: meridian_internal_seam_incompatible. The rest of the pipeline continues normally.

Graceful degradation

Model selection incompatibility is non-fatal at every level:

  1. log_likelihood.py raises ModelSelectionError with a structured code.
  2. model_selection.py propagates the error.
  3. runner.py catches it, writes model_selection_status.json, and continues.
  4. The manifest records the assessment stage as completed.
  5. The lifecycle layer can inspect model_selection_status to understand why model selection was unavailable.

Version pinning

The pyproject.toml pins Meridian to google-meridian[schema]==1.5.3. Any Meridian upgrade must refresh the private log-likelihood reconstruction baseline before the version guard is relaxed.

Integration testing

The test suite includes a gated live Meridian verification command:

MERIDIAN_TOOLS_ENABLE_REAL_FIT=1 pytest tests/test_demo_integration.py::test_real_pipeline_refresh_smoke tests/test_log_likelihood.py::test_compute_log_likelihood_dataset_real_posterior_smoke -m real_fit -v

This command proves two different real seams:

  • one reduced real pipeline run over bundled demo data, including stored-run refresh after the original YAML is removed
  • the lower-level live log-likelihood reconstruction path

It is excluded from the default test suite because it requires real MCMC sampling, but it should be run after every Meridian version upgrade.

Constants dependency

log_likelihood.py uses Meridian constants for posterior parameter names:

from meridian import constants
# constants.BETA_GM, constants.TAU_G, constants.ETA_M, etc.

These are stable string constants but are not versioned. A Meridian release that renames these constants would cause import-time failures.

Unsaved posterior parameter recovery

Meridian does not persist all posterior parameters to InferenceData. The _recover_unsaved_state function in log_likelihood.py reconstructs:

  • tau_g_excl_baseline — Recovered from the posterior’s tau_g variable by slicing out the baseline geo index (concatenating the elements before and after baseline_geo_idx).
  • Geo deviations — Recovered from the posterior by solving deviation = (target - base) / scale for normal effects, or deviation = (log(target) - base) / scale for log-normal effects, with a scale == 0 guard that maps to zero.

This recovery is mathematically correct for the supported model families (log-normal and normal media effects). It is tested against both geo-panel and national models in test_log_likelihood.py.

What breaks on a Meridian upgrade

Change type Impact Detection
Public API signature change runner.py, exports.py break Default test suite
Semi-public attribute rename log_likelihood.py, exports.py break Default test suite
Private method removal/rename Model selection disabled Live smoke test or model_selection_status.json
Constant rename Import-time failure Default test suite
New posterior parameter Log-likelihood may be incorrect Manual review + live smoke test
Changed likelihood formula Log-likelihood may be incorrect Live smoke test
  1. Pin the new Meridian version in a branch.
  2. Run the full default test suite: pytest tests/ -v.
  3. Run the live Meridian verification command: MERIDIAN_TOOLS_ENABLE_REAL_FIT=1 pytest tests/test_demo_integration.py::test_real_pipeline_refresh_smoke tests/test_log_likelihood.py::test_compute_log_likelihood_dataset_real_posterior_smoke -m real_fit -v.
  4. If model selection breaks, check model_selection_status.json for the reason code.
  5. If private methods changed, update log_likelihood.py to match the new Meridian internals or accept graceful degradation.
  6. Update docs/project/release-baseline.md with the new verified state.