77Reads transmission projects from the "Trans.Projects" sheet of the CBA projects Excel file.
88For projects with multiple borders (newline-separated in the Excel), the script explodes
99these 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
1314Storage 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)
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
3844import logging
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
10198def 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+
186251def extract_storage_projects (
187252 excel_path : Path , existing_buses : pd .Index
188253) -> pd .DataFrame :
@@ -209,6 +274,22 @@ def compute_method(flag: str) -> str:
209274def 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-
293346if __name__ == "__main__" :
294347 if "snakemake" not in globals ():
295348 from scripts ._helpers import mock_snakemake
0 commit comments