Workflow YAML Reference
The project and workflow files use a small declarative schema inspired by Kubernetes-style resources. The goal is readability, stable paths, and simple automation rather than a full external workflow engine.
Project Resource
apiVersion: gridalyn.io/v1alpha1
kind: StudyProject
metadata:
name: minimal_grid_project
version: 0.1.0
description: Minimal Gridalyn project.
spec:
pathBase: project
problem:
type: powerflow_validation
dataset: five_bus_teaching_feeder
environment: pandapower_powerflow
objective: Validate the smallest reproducible project contract.
model:
type: simulation_model
name: pandapower
scenarios:
- id: baseline
role: deterministic_baseline
description: Five-bus feeder with fixed loads.
inputs: {}
artifacts: {}
workflow:
file: workflow.yaml
validation:
requiredReports: []
requiredFigures: []
spec.problem is required — the schema demands it and the loader reads it
unguarded, so an example without it produces a project that cannot load. The
example above is modelled on the real projects/minimal_grid_project/project.yaml
and validates.
Fields
| Field | Required | Meaning |
|---|---|---|
apiVersion |
yes | Version of the Gridalyn project resource schema. |
kind |
yes | Must be StudyProject. |
metadata.name |
yes | Stable project identifier. |
metadata.version |
required | Project contract version. |
spec.pathBase |
recommended | project (the default) or repo. Sets the directory stage commands run from, and nothing else. It does not govern any declared path: spec.workflow.file, spec.validation.* and stage inputs/outputs are always relative to the project directory — see Path Rules. |
spec.inputs |
yes | Raw geography, grid configuration, external datasets, and assumptions. |
spec.artifacts |
no | Accepted and not read. Output directories are fixed by ProjectScript (outputs/data, figures, reports, manifests, operations, cache), not by this block; declaring them here governs nothing. |
spec.workflow.file |
yes | Workflow resource path. Relative to the project directory. |
spec.validation.requiredReports |
recommended | Report JSON files that must exist and satisfy the report contract. Relative to the project directory. |
spec.validation.requiredFigures |
recommended | Figures that must exist and be non-empty. Relative to the project directory. |
Workflow Resource
apiVersion: gridalyn.io/v1alpha1
kind: Workflow
metadata:
name: minimal_grid_project
spec:
stages:
- id: prepare_workspace
command: "{python} -m gridalyn.interfaces.cli.project prepare-workspace ."
outputs:
- outputs/data
- outputs/figures
- outputs/manifests
- outputs/operations
- outputs/reports
- outputs/cache
- id: run_minimal_powerflow
needs: [prepare_workspace]
command: "{python} scripts/run_minimal_powerflow.py"
inputs: []
outputs:
- outputs/reports/minimal_grid_report.json
The Stage DAG
needs: is the only thing that orders a run. The runner topologically sorts
the stages, raises on a cycle, and executes the result sequentially — so
the DAG describes what could run in parallel, not what does. It is also what
--stage <id> resolves against: asking for one stage pulls in its transitive
ancestors and nothing else.
The two-stage example above is a straight line. A real study is not — this is
the flagship ev_hosting_flex workflow, 23 stages, drawn from its own
workflow.yaml:
flowchart LR
prepare_workspace["prepare_workspace"]
prepare_topology_cache["prepare_topology_cache"]
export_twin_network_model["export_twin_network_model"]
generate_annual_mc["generate_annual_mc"]
compute_congestion_annual["compute_congestion_annual"]
apply_curtailment_contracts["apply_curtailment_contracts"]
compute_curtailment_economics["compute_curtailment_economics"]
analyze_credibility["analyze_credibility"]
analyze_cold_insurance["analyze_cold_insurance"]
build_study_reports["build_study_reports"]
analyze_cold_coupling["analyze_cold_coupling"]
analyze_network_characterization["analyze_network_characterization"]
analyze_clustered_adoption["analyze_clustered_adoption"]
analyze_flexibility_incentive["analyze_flexibility_incentive"]
analyze_network_performance["analyze_network_performance"]
analyze_congestion_risk["analyze_congestion_risk"]
analyze_fleet_triage["analyze_fleet_triage"]
analyze_locational_contracts["analyze_locational_contracts"]
analyze_nonwires_value["analyze_nonwires_value"]
analyze_phase_imbalance["analyze_phase_imbalance"]
analyze_voltage_risk["analyze_voltage_risk"]
analyze_voltage_risk_network["analyze_voltage_risk_network"]
validate_powerflow["validate_powerflow"]
prepare_workspace --> prepare_topology_cache
prepare_topology_cache --> export_twin_network_model
prepare_topology_cache --> generate_annual_mc
generate_annual_mc --> compute_congestion_annual
compute_congestion_annual --> apply_curtailment_contracts
compute_congestion_annual --> compute_curtailment_economics
generate_annual_mc --> analyze_credibility
compute_congestion_annual --> analyze_credibility
apply_curtailment_contracts --> analyze_credibility
generate_annual_mc --> analyze_cold_insurance
analyze_credibility --> analyze_cold_insurance
generate_annual_mc --> analyze_cold_coupling
generate_annual_mc --> analyze_network_characterization
generate_annual_mc --> analyze_clustered_adoption
generate_annual_mc --> analyze_flexibility_incentive
generate_annual_mc --> analyze_network_performance
generate_annual_mc --> analyze_congestion_risk
analyze_congestion_risk --> analyze_fleet_triage
analyze_fleet_triage --> analyze_locational_contracts
analyze_network_characterization --> analyze_nonwires_value
analyze_congestion_risk --> analyze_nonwires_value
generate_annual_mc --> analyze_phase_imbalance
generate_annual_mc --> analyze_voltage_risk
generate_annual_mc --> analyze_voltage_risk_network
compute_congestion_annual --> validate_powerflow
apply_curtailment_contracts --> validate_powerflow
classDef entry fill:#fff3e0,stroke:#ef6c00,color:#e65100,stroke-width:2px
classDef hub fill:#e8eaf6,stroke:#3f51b5,color:#1a237e,stroke-width:2px
classDef step fill:#e0f2f1,stroke:#00897b,color:#004d40
analyze_cold_insurance --> build_study_reports
validate_powerflow --> build_study_reports
analyze_nonwires_value --> build_study_reports
class prepare_workspace,build_study_reports entry
class generate_annual_mc hub
class prepare_topology_cache,export_twin_network_model,compute_congestion_annual,apply_curtailment_contracts,compute_curtailment_economics,analyze_credibility,analyze_cold_insurance,analyze_cold_coupling,analyze_network_characterization,analyze_clustered_adoption,analyze_flexibility_incentive,analyze_network_performance,analyze_congestion_risk,analyze_fleet_triage,analyze_locational_contracts,analyze_nonwires_value,analyze_phase_imbalance,analyze_voltage_risk,analyze_voltage_risk_network,validate_powerflow step
generate_annual_mc is the hub: eleven stages depend on it and nothing else,
which is why --stage analyze_voltage_risk still costs a Monte Carlo run.
The {python} Placeholder
Stage commands run through a shell, so a bare python is resolved against
PATH. On a virtualenv or a python3-only system there is no such executable
and the stage dies with exit 127. Write {python} instead: the runner replaces
every occurrence with the interpreter executing the workflow
(sys.executable), shell-quoted so an interpreter path containing spaces
survives. Quote the whole scalar in YAML — a leading { would otherwise start
a flow mapping.
A compatibility fallback keeps older contracts running: when a command contains
no {python}, a leading bare python token is rewritten to the same
interpreter. It applies to the first token only, so uv run python …, an
argument named python, and a script named python_helper.py are
never touched. Treat the fallback as support for contracts already written, and
{python} as the form to author.
Stage Fields
| Field | Required | Meaning |
|---|---|---|
id |
yes | Stable stage identifier used in logs and run manifests. |
command |
yes | Shell command executed from repository root when pathBase: repo. Use {python} for the interpreter. |
needs |
optional | Stage IDs that should run before this stage. |
inputs |
optional | Artifacts the stage reads, relative to the project directory. Declarative: nothing checks them at run time yet, so keep them to artifacts (not source files — git and the manifest's git_commit are the provenance of code) and keep them true. |
outputs |
optional | Files the stage produces, relative to the project directory. Enforced: after the stage exits zero, every listed path must exist, or the run fails naming the stage and the missing paths. A stage that lists none is not checked. |
Path Rules
Validation paths are relative to the project directory, whatever spec.pathBase
says. That covers spec.validation.requiredReports, requiredFigures,
objectiveArtifacts and each declarative sense check's report. Write them as the
project sees them:
spec:
pathBase: repo
validation:
requiredReports:
- outputs/reports/minimal_grid_report.json # not projects/<study>/outputs/...
A path that repeats the project directory, such as projects/<study>/outputs/...,
is reported as a doubled prefix, naming the corrected declaration, by both
gridalyn project validate and gridalyn project sense-check — the latter lists
it ahead of the missing required report it would otherwise surface as.
Breaking change, 2026-09-10
Until this date, requiredReports, requiredFigures and sense-check report
paths resolved against spec.pathBase, so a pathBase: repo study wrote
them repository-relative. They now resolve against the project directory
(bd 6ns.2). Drop the leading projects/<study>/ from those entries.
Breaking change, 2026-09-11
Until this date, spec.workflow.file resolved against spec.pathBase, so a
pathBase: repo study wrote file: projects/<study>/workflow.yaml. It now
resolves against the project directory (bd 6ns.2): write file:
workflow.yaml. A stale entry stops the project from loading, with an error
naming the path it tried, the expected form and the corrected declaration;
gridalyn project validate reports the same message.
Breaking change, 2026-09-11: stage inputs and outputs
Until this date, a stage's inputs and outputs in workflow.yaml resolved
against spec.pathBase, so a pathBase: repo study wrote them as
projects/<study>/outputs/.... They now resolve against the project
directory (bd 6ns.2): drop the leading projects/<study>/. gridalyn project
validate reports a stale entry as a doubled prefix and names the corrected
declaration, and gridalyn project run refuses to start on one, before any
stage runs, with the same message.
spec.pathBase still matters, for one thing: repo makes stage commands run
from the repository root, which a stage invoked as
{python} -m projects.<study>.scripts... needs. No declared path follows it.
spec.workflow.file and a stage's inputs and outputs are relative to the
project directory, like the validation paths above: a stage of a pathBase: repo
study still declares outputs/reports/analysis_report.json, not
projects/<study>/outputs/reports/analysis_report.json, even though its command
runs from the repository root.
spec.inputs entries may point outside the project, to shared data such as
instances/default/digital_twin/base/buildings.parquet or
configs/geography/tr01.json. That does not extend to a stage's inputs in
workflow.yaml: those must land inside the project, and gridalyn project
validate rejects one that does not. Avoid nested relative paths such as
../../instances/default/digital_twin/... in published project manifests.
Report Validation
Required reports are parsed with gridalyn.foundation.validate_report.
They must include:
report_id;schema_version;created_at;source_domain;inputs;artifacts;summary;validation.
Use Reports for the full report contract.
Commands
uv run gridalyn project validate projects/minimal_grid_project --check-artifacts
uv run gridalyn project plan projects/minimal_grid_project
uv run gridalyn project run projects/minimal_grid_project
uv run gridalyn project status projects/minimal_grid_project --check-artifacts
These are the four lifecycle commands, not a top-to-bottom recipe for a fresh
checkout: --check-artifacts requires the reports and figures declared under
spec.validation to exist already, so the first line fails until
gridalyn project run has produced them. On a project that has never been run,
validate without the flag first, run, and then use --check-artifacts.