Reproducibility & Provenance¶
Every FLB run can emit a run-provenance manifest: a single machine-readable JSON document that records everything needed to reproduce the result — the resolved configuration, the exact plugin versions that ran, the Python and R environments, and a content hash of each input pedigree.
The manifest is produced by the core PipelineRunner, so the CLI and the
Streamlit frontend share one implementation and emit byte-for-byte identical
manifests for the same run.
When is a manifest written?¶
Manifests are on by default for both run and batch. Each run writes a
sidecar file named <pedigree-stem>.manifest.json next to the pedigree:
# Writes Belman.manifest.json next to Belman.ped
heredicalc run Belman.ped -c config.yml
# All sidecars collected in one directory
heredicalc run Belman.ped -c config.yml --manifest-dir ./manifests
# Disable manifest generation entirely
heredicalc run Belman.ped -c config.yml --no-manifest
# batch behaves identically, one manifest per pedigree
heredicalc batch ./pedigrees -c config.yml --manifest-dir ./manifests
The manifest is always a separate file — it is never part of stdout. The
existing stdout contract is unchanged, including --format json, which still
prints only {"pedigree": ..., "flb": ...}. The path of each written manifest is
reported on stderr.
Schema¶
| Field | Type | Description |
|---|---|---|
heredicalc_version |
str |
Installed HerediCalc version (importlib.metadata). |
python_version |
str |
Running CPython version, e.g. 3.12.5. |
python_packages |
dict[str, str] |
Versions of the declared runtime dependencies plus heredicalc. |
r_session |
object \| null |
R version, platform, and all namespaces loaded after the FLB() call (null if the calculator exposes no session info). |
resolved_config |
object |
The PipelineConfig after the penetrance-model defaults cascade — the effective parameters, not the raw input. |
inputs |
list |
One entry per pedigree file: filename and the SHA-256 over its raw bytes. |
plugins |
list |
kind, name, and version for all ten plugin selections, including the injected rr_model and crhf_model sub-plugins. |
timestamp_utc |
str |
Run time as an ISO 8601 UTC timestamp. |
flb |
float |
The computed FLB factor (identical to the run output). |
Notes:
resolved_configis captured after the cascade, so plugin-specific defaults (e.g.victor_cool3selectingannual_rate_cool3as itshazard_model) are recorded as the values that actually ran.inputs[].sha256is computed over the raw file bytes, so it is stable and reproducible across machines for an unchanged pedigree.pluginsis derived fromresolved_configvia the registry, capturing the resolved version of every selected plugin — including sub-plugins that are injected into other plugins and never instantiated directly by the runner.r_sessioncomes from thesegregatrR subprocess, which reports its version, platform, andloadedNamespaces()in the same JSON object as the FLB.
Example manifest¶
Produced by the primary validation case brca1_belman_latvia_ci5ix
(penetrance_model: victor). The loaded_namespaces map is abbreviated here:
{
"heredicalc_version": "4.1.0",
"python_version": "3.12.5",
"python_packages": {
"pandas": "2.3.2",
"numpy": "2.3.2",
"pydantic": "2.13.4",
"packaging": "25.0",
"platformdirs": "4.4.0",
"PyYAML": "6.0.3",
"typer": "0.25.1",
"rich": "15.0.0",
"heredicalc": "4.1.0"
},
"r_session": {
"r_version": "R version 4.4.1 (2024-06-14)",
"platform": "aarch64-apple-darwin20",
"loaded_namespaces": {
"base": "4.4.1",
"pedtools": "2.7.0",
"segregatr": "0.5.0",
"stats": "4.4.1",
"utils": "4.4.1"
}
},
"resolved_config": {
"computation": {
"genetic_entity": "BRCA1",
"allele_freq": 0.0001
},
"plugins": {
"incidence_source": "ci5_ix",
"phenotype_model": "hbopc",
"trait_mapper": "ci5_ix_hbopc",
"hazard_model": "annual_rate",
"penetrance_model": "victor",
"rr_model": "tabular",
"crhf_model": "lookup",
"liability_assigner": "victor_standard",
"flb_calculator": "segregatr",
"pedigree_format": "cool3_tsv",
"params": {
"population": "Latvia",
"age_bands": [30, 40, 50, 60, 65, 70, 80],
"rr_model": "tabular",
"crhf_model": "lookup"
}
}
},
"inputs": [
{
"filename": "Belman.ped",
"sha256": "57d5eca9fa68803e21a997ab79cbf203415bea49760e0a47fd2cef4b9904d52e"
}
],
"plugins": [
{ "kind": "pedigree_format", "name": "cool3_tsv", "version": "1.0.0" },
{ "kind": "phenotype_model", "name": "hbopc", "version": "1.0.0" },
{ "kind": "incidence_source", "name": "ci5_ix", "version": "1.0.0" },
{ "kind": "trait_mapper", "name": "ci5_ix_hbopc", "version": "1.0.0" },
{ "kind": "hazard_model", "name": "annual_rate", "version": "1.0.0" },
{ "kind": "penetrance_model", "name": "victor", "version": "1.0.0" },
{ "kind": "liability_assigner", "name": "victor_standard", "version": "1.0.0" },
{ "kind": "flb_calculator", "name": "segregatr", "version": "1.0.0" },
{ "kind": "rr_model", "name": "tabular", "version": "1.0.0" },
{ "kind": "crhf_model", "name": "lookup", "version": "1.0.0" }
],
"timestamp_utc": "2026-06-14T10:56:01.381292+00:00",
"flb": 25.6540503665
}
Programmatic access¶
The manifest is a Pydantic model, so any frontend can obtain it directly instead of parsing the sidecar:
from pathlib import Path
from heredicalc.core.pipeline.runner import PipelineRunner
manifest = PipelineRunner(registry=registry).run_with_manifest(
Path("Belman.ped"), config
)
print(manifest.flb)
print(manifest.model_dump_json(indent=2))
run() remains unchanged and returns the bare FLB float; run_with_manifest()
runs the same pipeline and additionally assembles the manifest.