Skip to content

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.