Reproducibility Guide
This guide defines the shortest unambiguous path from a checkout to a verified Gridalyn workspace. It covers source setup, artifact policy, demo project checks, documentation, and dashboard readiness.
Reproducibility Contract
A reproducible run must have:
| Requirement | Contract | Verification |
|---|---|---|
| Source environment | dependencies installed through uv |
uv run gridalyn --help |
| Clean artifact policy | generated data and caches are not accidentally tracked | uv run gridalyn platform check-artifacts --summary-only |
| Valid project contract | project.yaml and workflow.yaml are readable and complete |
uv run gridalyn project validate projects/minimal_grid_project |
| Stable results | key numerical outputs match the baseline for larger workflows | uv run gridalyn project regression projects/ev_hosting_flex |
| Healthy code | tests pass | uv run --with pytest python -m pytest -q |
| Published docs | MkDocs builds strictly | uv run --extra docs mkdocs build --strict -f docs/mkdocs.yml |
The two paths a checkout can take — and only one of them ends in a citable number:
stateDiagram-v2
direction LR
[*] --> Checkout
Checkout --> Pinned : .python-version selects CPython 3.12
Pinned --> Frozen : frozen sync WITH the sim, ops and dev extras
Pinned --> Degraded : frozen sync, base deps only
Frozen --> Ready : doctor reports cvxpy and lightsim2grid true
Ready --> Run : gridalyn project run
Run --> Compared : gridalyn project regression
Compared --> [*] : baseline reproduces bit for bit
Degraded --> Moved : study still exits 0, on fallback code paths
Moved --> [*] : most of the 81 metrics move, not citable
classDef good fill:#e0f2f1,stroke:#00897b,color:#004d40
classDef bad fill:#ffebee,stroke:#c62828,color:#b71c1c
class Frozen,Ready,Run,Compared good
class Degraded,Moved bad
The lower branch is the dangerous one precisely because it never fails: exit code 0, a full set of artifacts, and different numbers.
0. The Canonical Reproducible Install (Pinned 3.12, Frozen Lock)
A citable ev_hosting_flex result must be rebuildable bit-for-bit on a stranger's
machine. Two pins make that possible and are non-negotiable:
- Interpreter pin — the repo commits
.python-version(3.12) at the root.uvandpyenvread it to select CPython 3.12, closing the divergence where a host's system interpreter (e.g. 3.10) or a stray.pyc(e.g. cpython-313) would otherwise resolve a different numeric stack and silently move the numbers. - Frozen dependency resolution — install against the committed
uv.lockwithout re-resolving, and with the capabilities the citable result needs:
uv sync --frozen fails loudly if uv.lock is stale rather than silently
upgrading a transitive dependency. This — pinned 3.12 + uv sync --frozen
--extra sim --extra ops --extra dev — is the reproducible install. Plain
uv sync (used in the convenience steps below) is for day-to-day work, not for
reproducing a published result.
The extras are not optional for the citable numbers. A bare
uv sync --frozeninstalls base deps only, leaving the optionalsim(lightsim2grid) andops(cvxpy) capabilities uninstalled —gridalyn doctorthen reportscvxpy=false,lightsim2grid=false. (lightgbmused to be in this list. It is now a base dependency, because a missing runtime silently swapped the load generator onto a different macro model — the worst shape of degradation, since it changed numbers without failing.) Theev_hosting_flexstudy silently degrades onto fallback code paths (still exit 0) and moves most of the 81 regression metrics. Thesim+opsextras are required to reproduce the committed baseline;devprovidespytestfor the determinism leg. This is a D-07 pinning/recipe correction (the install recipe was the defect) — not a re-baseline and not a tolerance change.
Stage uv run invocation (no mid-run re-resolve)
The ev_hosting_flex workflow runs each stage as a separate uv run python ...
subprocess. To stop any of those 20 stage commands from re-resolving the
environment mid-run, run the whole workflow inside the already-frozen
environment so each stage uv run no-ops against the same interpreter and lock:
# Activate the frozen environment once, then run the workflow.
uv sync --frozen --extra sim --extra ops --extra dev
source .venv/bin/activate # stage `uv run`s now reuse this env
gridalyn project run projects/ev_hosting_flex
Equivalently, pass --frozen / --no-sync to the stage invocations
(uv run --frozen ...) so no stage can silently re-resolve the lock.
Fresh-venv clean-room recipe
This is a clean-room rebuild from a fresh virtual environment that a stranger can copy-paste verbatim from the repo root. Run the whole block top-to-bottom; every step must be green and the committed baseline must reproduce bit-for-bit.
# 1. Build a clean, frozen environment on pinned 3.12 WITH the required extras.
# The citable ev_hosting_flex numbers depend on the optional `sim`
# (lightsim2grid) and `ops` (cvxpy) capabilities; `dev` provides
# pytest for the determinism leg below. A bare `uv sync --frozen` installs
# base deps only — the study then silently degrades and moves most of the 81 metrics
# (see the D-07 note above and the doctor check on the next line).
rm -rf .venv
uv sync --frozen --extra sim --extra ops --extra dev
# Tripwire (Pitfall 1): this MUST print Python 3.12.x — not 3.10 / 3.13.
# uv selects 3.12 from the committed .python-version; a different version here
# means the pin is not being honoured and the numbers will move.
uv run python --version
# Capability tripwire: cvxpy and lightsim2grid MUST all be true here.
# If any reads false, the extras above did not install and the regression will
# go red on degraded fallback paths — that is a recipe defect (D-07), not a
# baseline change.
uv run gridalyn doctor
# 2. Activate the frozen environment ONCE so every stage `uv run python ...`
# subprocess (20 of them) reuses this exact interpreter+lock and cannot
# re-resolve the environment mid-run (Pitfall 2). Then run the study.
source .venv/bin/activate
gridalyn project run projects/ev_hosting_flex # expect exit 0
# Equivalent without activating: prefix each stage with `uv run --frozen`
# (or `--no-sync`) so no stage can silently re-resolve the lock.
# 3. Regression gate — this is the DoD. The committed baseline must reproduce.
gridalyn project regression projects/ev_hosting_flex # expect "valid": true (81/81)
# 4. Determinism leg — the study's generators must be byte-identical under two
# PYTHONHASHSEED values (independent hash randomization, same numbers).
PYTHONHASHSEED=0 python -m pytest -q tests/ -k ev_hosting_flex # expect 0 exit
PYTHONHASHSEED=1 python -m pytest -q tests/ -k ev_hosting_flex # expect 0 exit
# 5. Network-free leg — the run must NOT depend on a live PVGIS fetch: the
# committed TMY CSV (inputs/tmy_trois_rivieres.csv) is the only weather
# source, `download_tmy` is never called, and an "auto" source is guarded.
# Confirm no PVGIS fetch appears in the stage logs and that the run
# manifest `provenance.python_version` records 3.12.x.
PYTHONHASHSEED, the interpreter version, the clearing-engine versions, the run
seeds, and the input file hashes are all recorded in the run manifest's
provenance block, so a divergence is an auditable pinning defect, not a
mystery.
D-07 — divergence is a pinning defect, never a re-baseline. If the fresh-venv re-run does not reproduce the committed baseline bit-for-bit, tighten the pin (
.python-version, the frozenuv.lock, a transitive dependency) until it matches. Never editresults_baseline.jsonand never loosen a tolerance to absorb the drift. A deliberate re-baseline is a separate, explicit, user-gated decision with a written rationale — it is never the automatic response to a red gate.
1. Start From The Repository Root
All commands in this guide run from the repository root unless a command says otherwise.
2. Check Artifact Hygiene
Expected result:
If the command reports generated files under projects/*/outputs,
instances/default/digital_twin/**/*.parquet, examples/generated/*, dashboard/public, or
compiled document outputs, remove those files from the source change before
committing.
3. Validate A Demo Project
uv run gridalyn project validate projects/minimal_grid_project
uv run gridalyn project plan projects/minimal_grid_project
The validation command checks the project contract. The plan command prints the stage order without executing the workflow.
4. Run Or Reuse The Workflow
Run the minimal demo when generated outputs need to be refreshed:
For larger workflows with a regression baseline, run:
Expected regression result:
5. Verify Reports And Figures
uv run gridalyn project status projects/minimal_grid_project --check-artifacts
uv run gridalyn project verify projects/minimal_grid_project
The status command verifies required reports and figures declared in
project.yaml. The verify command runs the project status and sense-check
ladder for the selected project.
6. Validate Code And Documentation
uv run --with pytest python -m pytest -q
uv run --extra docs mkdocs build --strict -f docs/mkdocs.yml
The test suite checks package boundaries, artifact policy, digital-twin contracts, reports, semantics, project behavior, and regression helpers. The MkDocs build verifies that the published documentation has no broken internal links or invalid navigation entries.
7. Dashboard Readiness
Build the dashboard only after the data contracts are valid:
npm install --prefix dashboard
npm --prefix dashboard run build
docker compose -f dashboard/docker-compose.yml up -d --build dashboard
The dashboard should consume canonical catalog and report artifacts. It should not depend on project-specific scripts unless those scripts are declared as a project workflow stage.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Artifact policy fails | generated outputs are present in Git-tracked paths | remove generated files from the source change and rerun the policy check |
| Project validation fails | project.yaml or workflow.yaml references a missing path |
update the manifest or generate the missing artifact |
| Regression fails | numerical behavior changed | inspect outputs/reports/regression_report.json and decide whether the baseline should change |
| MkDocs strict build fails | navigation points to a missing page or a link is broken | fix docs/mkdocs.yml or the referenced Markdown link |
| Dashboard build passes but data is stale | catalog/report artifacts were not regenerated | rerun the relevant gridalyn twin, project, or report command |