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:
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:
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:
log_likelihood.pyraisesModelSelectionErrorwith a structured code.model_selection.pypropagates the error.runner.pycatches it, writesmodel_selection_status.json, and continues.- The manifest records the assessment stage as completed.
- The lifecycle layer can inspect
model_selection_statusto 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:
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:
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’stau_gvariable by slicing out the baseline geo index (concatenating the elements before and afterbaseline_geo_idx).- Geo deviations — Recovered from the posterior by solving
deviation = (target - base) / scalefor normal effects, ordeviation = (log(target) - base) / scalefor log-normal effects, with ascale == 0guard 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 |
Recommended upgrade procedure
- Pin the new Meridian version in a branch.
- Run the full default test suite:
pytest tests/ -v. - 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. - If model selection breaks, check
model_selection_status.jsonfor the reason code. - If private methods changed, update
log_likelihood.pyto match the new Meridian internals or accept graceful degradation. - Update
docs/project/release-baseline.mdwith the new verified state.