|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Create one GitHub issue per tool in the data-schema-excercise repository. |
| 3 | +
|
| 4 | +This script is idempotent: it checks whether an open issue with the expected |
| 5 | +title already exists before creating a new one. Run it via the companion |
| 6 | +GitHub Actions workflow, or locally (requires the ``gh`` CLI and a token |
| 7 | +with ``issues: write`` permission): |
| 8 | +
|
| 9 | + python3 .github/scripts/create_tool_issues.py |
| 10 | +""" |
| 11 | + |
| 12 | +import json |
| 13 | +import os |
| 14 | +import subprocess |
| 15 | +import sys |
| 16 | +import tempfile |
| 17 | + |
| 18 | +REPO = "G-PST/data-schema-excercise" |
| 19 | +BASE_URL = "https://github.com/G-PST/data-schema-excercise" |
| 20 | +INSTRUCTIONS_URL = ( |
| 21 | + f"{BASE_URL}/blob/main/.github/ISSUE_TEMPLATE/fill_out_schema.md" |
| 22 | +) |
| 23 | + |
| 24 | +# (Display name, YAML filename) for every tool in data_schemas/ |
| 25 | +TOOLS = [ |
| 26 | + ("Sienna Data Model", "sienna_data_model.yaml"), |
| 27 | + ("GenX Data Model", "genx_data_model.yaml"), |
| 28 | + ("Grid Data Model", "grid_data_model.yaml"), |
| 29 | + ("CommonEnergySystemModel", "common_energy_system_model.yaml"), |
| 30 | + ("PyPSA Data Model", "pypsa_data_model.yaml"), |
| 31 | + ("Encoord Data Model", "encoord_data_model.yaml"), |
| 32 | + ("CIM/ENTSO-E", "cim_entso_e.yaml"), |
| 33 | +] |
| 34 | + |
| 35 | + |
| 36 | +def gh(*args): |
| 37 | + """Run a ``gh`` CLI command and return the CompletedProcess.""" |
| 38 | + return subprocess.run(["gh"] + list(args), capture_output=True, text=True) |
| 39 | + |
| 40 | + |
| 41 | +def ensure_label(): |
| 42 | + """Create the ``fill-out-schema`` label if it does not already exist.""" |
| 43 | + r = gh( |
| 44 | + "label", "create", "fill-out-schema", |
| 45 | + "--repo", REPO, |
| 46 | + "--color", "0075ca", |
| 47 | + "--description", "Fill out existing tool data schema sheet", |
| 48 | + "--force", |
| 49 | + ) |
| 50 | + if r.returncode != 0: |
| 51 | + print(f"Warning: could not create label: {r.stderr}", file=sys.stderr) |
| 52 | + |
| 53 | + |
| 54 | +def issue_exists(title): |
| 55 | + """Return True if an open issue with this title already exists.""" |
| 56 | + r = gh( |
| 57 | + "issue", "list", |
| 58 | + "--repo", REPO, |
| 59 | + "--state", "open", |
| 60 | + "--json", "title", |
| 61 | + "--limit", "100", |
| 62 | + ) |
| 63 | + if r.returncode != 0: |
| 64 | + return False |
| 65 | + issues = json.loads(r.stdout or "[]") |
| 66 | + return any(i["title"] == title for i in issues) |
| 67 | + |
| 68 | + |
| 69 | +def build_body(tool_name, yaml_file): |
| 70 | + """Return the Markdown body for the issue.""" |
| 71 | + yaml_url = f"{BASE_URL}/blob/main/data_schemas/{yaml_file}" |
| 72 | + branch_name = yaml_file.replace(".yaml", "") |
| 73 | + |
| 74 | + return f"""\ |
| 75 | +## Tool |
| 76 | +
|
| 77 | +**Tool / Schema Name:** {tool_name} |
| 78 | +**YAML file:** [`data_schemas/{yaml_file}`]({yaml_url}) |
| 79 | +
|
| 80 | +--- |
| 81 | +
|
| 82 | +## Background |
| 83 | +
|
| 84 | +This repository collects **data schema information sheets** for power systems |
| 85 | +planning tools used for cross-project comparison at the |
| 86 | +**G-PST Power System Planning Interoperability Data Schema Workshop**. |
| 87 | +
|
| 88 | +A placeholder YAML file for **{tool_name}** already exists at |
| 89 | +`data_schemas/{yaml_file}`. Please fill it out and open a Pull Request. |
| 90 | +
|
| 91 | +--- |
| 92 | +
|
| 93 | +## Instructions |
| 94 | +
|
| 95 | +Full step-by-step instructions are in |
| 96 | +[`fill_out_schema.md`]({INSTRUCTIONS_URL}). |
| 97 | +
|
| 98 | +### Quick Summary |
| 99 | +
|
| 100 | +1. Open [`data_schemas/{yaml_file}`]({yaml_url}). |
| 101 | +2. Replace every `<...>` placeholder with real information. |
| 102 | +3. Use `~` (YAML null) for any fields that do not apply. |
| 103 | +4. Validate locally (see **Validation** below). |
| 104 | +5. Open a Pull Request targeting `main` and link this issue in the description. |
| 105 | +
|
| 106 | +### File Location and Naming Convention |
| 107 | +
|
| 108 | +- **File to edit:** `data_schemas/{yaml_file}` |
| 109 | +- Keep the existing filename — do not rename it. |
| 110 | +- The file lives in the `data_schemas/` directory at the repository root. |
| 111 | +
|
| 112 | +### Required Sections |
| 113 | +
|
| 114 | +All sections in the YAML must be addressed: |
| 115 | +
|
| 116 | +| Section | Key Fields | |
| 117 | +|---------|------------| |
| 118 | +| `identity` | schema_name, organization, maintainers, repository, documentation, license, version, maturity | |
| 119 | +| `summary` | description, modeling_domains_supported, what_does_it_NOT_cover, data_captured, conceptual_structure | |
| 120 | +| `design` | key_decisions, schema_format, implementation_languages, interoperability, units_handling, validation_approach, governance | |
| 121 | +| `usage` | tools_built_on_schema, largest_real_world_dataset, who_is_using_it, data_available | |
| 122 | +| `challenges` | known_limitations, hardest_problems_encountered | |
| 123 | +| `interoperability` | areas_of_overlap_with_other_schemas, what_would_convergence_require, biggest_thing_others_should_know | |
| 124 | +| `card_metadata` | prepared_by, date | |
| 125 | +
|
| 126 | +### Validation |
| 127 | +
|
| 128 | +Run `yamllint` locally before pushing: |
| 129 | +
|
| 130 | +```bash |
| 131 | +pip install yamllint |
| 132 | +yamllint -c .yamllint.yaml data_schemas/{yaml_file} |
| 133 | +``` |
| 134 | +
|
| 135 | +CI will also lint your file automatically when you open a PR. |
| 136 | +
|
| 137 | +### Opening a Pull Request |
| 138 | +
|
| 139 | +**External contributors (Fork & PR):** |
| 140 | +1. Fork this repository. |
| 141 | +2. Edit `data_schemas/{yaml_file}` in your fork. |
| 142 | +3. Open a PR to `main` titled: `Fill out data schema sheet: {tool_name}`. |
| 143 | +4. Paste this issue's URL in the PR description. |
| 144 | +
|
| 145 | +**Contributors with write access (Branch & PR):** |
| 146 | +
|
| 147 | +```bash |
| 148 | +git checkout main && git pull |
| 149 | +git checkout -b fill-schema/{branch_name} |
| 150 | +# Edit data_schemas/{yaml_file} |
| 151 | +git add data_schemas/{yaml_file} |
| 152 | +git commit -m "Fill out data schema sheet: {tool_name}" |
| 153 | +git push origin fill-schema/{branch_name} |
| 154 | +``` |
| 155 | +
|
| 156 | +Then open a PR on GitHub targeting `main` and link this issue. |
| 157 | +
|
| 158 | +--- |
| 159 | +
|
| 160 | +## Acceptance Criteria |
| 161 | +
|
| 162 | +- [ ] All `<...>` placeholders in `data_schemas/{yaml_file}` replaced with real content (or `~` where not applicable) |
| 163 | +- [ ] `identity` section complete: schema_name, organization, maintainers, repository, license, version, maturity |
| 164 | +- [ ] `summary` section describes what the schema covers **and** what it does NOT cover |
| 165 | +- [ ] `design` section covers key decisions, schema format, and implementation languages |
| 166 | +- [ ] `usage` section includes real-world datasets and known users |
| 167 | +- [ ] `challenges` section lists known limitations |
| 168 | +- [ ] `interoperability` section addresses overlap with other schemas in this comparison |
| 169 | +- [ ] `card_metadata` filled in (prepared_by, date) |
| 170 | +- [ ] `yamllint` passes locally with no errors |
| 171 | +- [ ] Pull Request opened targeting `main` with this issue linked in the PR description |
| 172 | +- [ ] PR reviewed and merged by a maintainer |
| 173 | +
|
| 174 | +--- |
| 175 | +
|
| 176 | +_Questions? Comment on this issue._ |
| 177 | +""" |
| 178 | + |
| 179 | + |
| 180 | +def create_issue(tool_name, yaml_file): |
| 181 | + title = f"Fill out data schema sheet: {tool_name}" |
| 182 | + |
| 183 | + if issue_exists(title): |
| 184 | + print(f"Issue already exists for '{tool_name}', skipping.") |
| 185 | + return |
| 186 | + |
| 187 | + body = build_body(tool_name, yaml_file) |
| 188 | + |
| 189 | + with tempfile.NamedTemporaryFile( |
| 190 | + mode="w", suffix=".md", delete=False |
| 191 | + ) as tmp: |
| 192 | + tmp.write(body) |
| 193 | + tmp_path = tmp.name |
| 194 | + |
| 195 | + try: |
| 196 | + r = gh( |
| 197 | + "issue", "create", |
| 198 | + "--repo", REPO, |
| 199 | + "--title", title, |
| 200 | + "--body-file", tmp_path, |
| 201 | + "--label", "fill-out-schema", |
| 202 | + ) |
| 203 | + if r.returncode == 0: |
| 204 | + print(f"Created issue for '{tool_name}': {r.stdout.strip()}") |
| 205 | + else: |
| 206 | + print( |
| 207 | + f"ERROR creating issue for '{tool_name}': {r.stderr}", |
| 208 | + file=sys.stderr, |
| 209 | + ) |
| 210 | + sys.exit(1) |
| 211 | + finally: |
| 212 | + os.unlink(tmp_path) |
| 213 | + |
| 214 | + |
| 215 | +def main(): |
| 216 | + ensure_label() |
| 217 | + for tool_name, yaml_file in TOOLS: |
| 218 | + create_issue(tool_name, yaml_file) |
| 219 | + |
| 220 | + |
| 221 | +if __name__ == "__main__": |
| 222 | + main() |
0 commit comments