Skip to content

Commit 9bfb658

Browse files
tgilondaniel-rdt
andauthored
refactor: sort functions in clean_projects (#803)
* refactor: sort functions in clean_projects * doc: add release note * doc: add missing docstrings * Apply suggestions from code review Co-authored-by: Daniel Rüdt <117752024+daniel-rdt@users.noreply.github.com> --------- Co-authored-by: Daniel Rüdt <117752024+daniel-rdt@users.noreply.github.com>
1 parent bc12ea8 commit 9bfb658

2 files changed

Lines changed: 116 additions & 61 deletions

File tree

doc/release_notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@
8787

8888
* Reduce dependency on upstream retrieves ([#798](https://github.com/open-energy-transition/open-tyndp/pull/798)).
8989

90+
* Sort functions in `clean_projects` into a logical order ([#803](https://github.com/open-energy-transition/open-tyndp/pull/803)).
91+
9092

9193
## Upcoming PyPSA-Eur Release
9294

scripts/cba/clean_projects.py

Lines changed: 114 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,25 @@
77
Reads transmission projects from the "Trans.Projects" sheet of the CBA projects Excel file.
88
For projects with multiple borders (newline-separated in the Excel), the script explodes
99
these into separate rows, creating one row per border. Bus codes are extracted from the
10-
border strings (expected format: "BUS0-BUS1") and projects that don't match this format
11-
are filtered out with a warning.
10+
border strings (expected format: "BUS0-BUS1") and rows are filtered out with a warning
11+
when the format does not match, when either bus is absent from the TYNDP node list, or
12+
when no capacity is reported in either direction.
1213
1314
Storage project extraction is not yet implemented and returns an empty DataFrame.
1415
1516
**Inputs**
1617
1718
- `data/tyndp_2024_bundle/cba_projects/20250312_export_transmission.xlsx`: Excel file containing CBA transmission projects
1819
- `data/tyndp_2024_bundle/cba_projects/20250312_export_storage.xlsx`: Excel file containing CBA storage projects (not yet processed)
20+
- `rules.retrieve_tyndp.output.nodes`: TYNDP electricity node list used to validate borders
21+
- `rules.retrieve_cba_guidelines_reference_projects.output.file`: Table of projects as defined in the Implementation Guidelines Appendix B.1
1922
2023
**Outputs**
2124
2225
- `resources/cba/transmission_projects.csv`: Cleaned CSV with columns:
2326
- `project_id`: Integer project identifier
2427
- `project_name`: Project name
28+
- `is_crossborder`: Whether the project is reported as cross-border
2529
- `border`: Border string in format "BUS0-BUS1"
2630
- `p_nom 0->1`: Transfer capacity increase from bus0 to bus1 (MW)
2731
- `p_nom 1->0`: Transfer capacity increase from bus1 to bus0 (MW)
@@ -33,6 +37,8 @@
3337
3438
- `resources/cba/storage_projects.csv`: Empty CSV with columns project_id and project_name (stub implementation)
3539
40+
- `resources/cba/cba_project_methods.csv`: Table defining the assignment method of each project.
41+
3642
"""
3743

3844
import logging
@@ -59,48 +65,58 @@
5965
}
6066

6167

62-
def extract_investment_attributes(excel_path: Path) -> pd.DataFrame:
68+
def read_tyndp_electricity_buses(buses_fn: str) -> pd.Index:
6369
"""
64-
Extract length, CAPEX, and underwater fraction from Trans.Investments sheet.
70+
Read the list of electricity nodes from the TYNDP input data.
6571
66-
Aggregates investment-level data to the project level by summing route
67-
lengths and CAPEX, and computing the underwater fraction from offshore
68-
cable lengths.
69-
"""
70-
inv = pd.read_excel(
71-
excel_path,
72-
sheet_name="Trans.Investments",
73-
skiprows=1,
74-
usecols=[
75-
"This investment belongs to project number…",
76-
"Total route length (km)",
77-
"Estimated CAPEX (MEUR)",
78-
"Type of Element",
79-
],
80-
).rename(
81-
columns={
82-
"This investment belongs to project number…": "project_id",
83-
"Total route length (km)": "length_km",
84-
"Estimated CAPEX (MEUR)": "capex_meur",
85-
"Type of Element": "element_type",
86-
}
87-
)
72+
Parameters
73+
----------
74+
buses_fn : str
75+
Path to the list of nodes from the TYNDP bundle.
8876
89-
is_offshore = inv["element_type"].isin(OFFSHORE_ELEMENT_TYPES)
77+
Returns
78+
-------
79+
pd.Index
80+
Electricity buses as used in Open-TYNDP.
9081
91-
agg = inv.groupby("project_id").agg(
92-
length_km=("length_km", "sum"),
93-
capex_meur=("capex_meur", "sum"),
82+
See Also
83+
--------
84+
build_tyndp_network.build_buses
85+
"""
86+
buses = pd.Index(
87+
pd.read_excel(buses_fn)
88+
.replace("UK", "GB", regex=True)
89+
.rename({"NODE": "bus_id"}, axis=1)["bus_id"]
9490
)
95-
offshore_km = inv.loc[is_offshore].groupby("project_id")["length_km"].sum()
96-
agg["underwater_fraction"] = (offshore_km / agg["length_km"]).fillna(0).round(3)
9791

98-
return agg
92+
# Manually add Italian virtual nodes
93+
buses = buses.union(["ITCO", "ITVI"])
94+
95+
return buses
9996

10097

10198
def extract_transmission_projects(
10299
excel_path: Path, existing_buses: pd.Index
103100
) -> pd.DataFrame:
101+
"""
102+
Read and clean the transmission projects from the "Trans.Projects" sheet.
103+
104+
Projects reporting several expected capacity increases are exploded into one row per
105+
border. Rows are dropped when the border cannot be parsed or when no capacity is
106+
reported in either direction.
107+
108+
Parameters
109+
----------
110+
excel_path : Path
111+
Path to the Excel export defining the transmission projects.
112+
existing_buses : pd.Index
113+
Electricity buses as used in Open-TYNDP.
114+
115+
Returns
116+
-------
117+
pd.DataFrame
118+
List of projects with their detailed characteristics. One row per project and border.
119+
"""
104120
projects = (
105121
pd.read_excel(
106122
excel_path,
@@ -183,6 +199,55 @@ def extract_transmission_projects(
183199
return projects
184200

185201

202+
def extract_investment_attributes(excel_path: Path) -> pd.DataFrame:
203+
"""
204+
Extract length, CAPEX, and underwater fraction from Trans.Investments sheet.
205+
206+
Aggregates investment-level data to the project level by summing route
207+
lengths and CAPEX, and computing the underwater fraction from offshore
208+
cable lengths.
209+
210+
Parameters
211+
----------
212+
excel_path : Path
213+
Path to the Excel export defining the transmission projects and their investment attributes.
214+
215+
Returns
216+
-------
217+
pd.DataFrame
218+
Route length, CAPEX and underwater fraction per project, indexed by ``project_id``.
219+
"""
220+
inv = pd.read_excel(
221+
excel_path,
222+
sheet_name="Trans.Investments",
223+
skiprows=1,
224+
usecols=[
225+
"This investment belongs to project number…",
226+
"Total route length (km)",
227+
"Estimated CAPEX (MEUR)",
228+
"Type of Element",
229+
],
230+
).rename(
231+
columns={
232+
"This investment belongs to project number…": "project_id",
233+
"Total route length (km)": "length_km",
234+
"Estimated CAPEX (MEUR)": "capex_meur",
235+
"Type of Element": "element_type",
236+
}
237+
)
238+
239+
is_offshore = inv["element_type"].isin(OFFSHORE_ELEMENT_TYPES)
240+
241+
agg = inv.groupby("project_id").agg(
242+
length_km=("length_km", "sum"),
243+
capex_meur=("capex_meur", "sum"),
244+
)
245+
offshore_km = inv.loc[is_offshore].groupby("project_id")["length_km"].sum()
246+
agg["underwater_fraction"] = (offshore_km / agg["length_km"]).fillna(0).round(3)
247+
248+
return agg
249+
250+
186251
def extract_storage_projects(
187252
excel_path: Path, existing_buses: pd.Index
188253
) -> pd.DataFrame:
@@ -209,6 +274,22 @@ def compute_method(flag: str) -> str:
209274
def build_method_assignments(
210275
guidelines: pd.DataFrame, projects: pd.DataFrame
211276
) -> pd.DataFrame:
277+
"""
278+
Define the assignment method of the project. Can be TOOT (Take Out One at a Time) or PINT (Put IN one at a Time).
279+
Leverage the Implementation Guidelines to define the method.
280+
281+
Parameters
282+
----------
283+
guidelines : pd.DataFrame
284+
Table of projects as defined in the Implementation Guidelines Appendix B.1.
285+
projects: pd.DataFrame
286+
List of projects with their detailed characteristics.
287+
288+
Returns
289+
-------
290+
pd.DataFrame
291+
Table defining the assignment method of each project.
292+
"""
212293
guidelines = guidelines.rename(
213294
columns={
214295
"ID": "project_id",
@@ -262,34 +343,6 @@ def build_method_assignments(
262343
return projects.merge(assigned, on="project_id", how="left")
263344

264345

265-
def read_tyndp_electricity_buses(buses_fn: str):
266-
"""
267-
Read node list for electricity from tyndp data input.
268-
269-
Parameters
270-
----------
271-
- buses_fn (str): Path to "LIST OF NODES.xlsx" from tyndp bundle
272-
273-
Returns
274-
-------
275-
- buses: Index of electricity buses as used in Open-TYNDP
276-
277-
See Also
278-
--------
279-
build_tyndp_network.py : build_buses
280-
"""
281-
buses = pd.Index(
282-
pd.read_excel(buses_fn)
283-
.replace("UK", "GB", regex=True)
284-
.rename({"NODE": "bus_id"}, axis=1)["bus_id"]
285-
)
286-
287-
# Manually add Italian virtual nodes
288-
buses = buses.union(["ITCO", "ITVI"])
289-
290-
return buses
291-
292-
293346
if __name__ == "__main__":
294347
if "snakemake" not in globals():
295348
from scripts._helpers import mock_snakemake

0 commit comments

Comments
 (0)