From 9c78328ee2a8b8d70f18aef6354888c75d6f89b6 Mon Sep 17 00:00:00 2001 From: Jason Marechal Date: Thu, 9 Oct 2025 15:46:58 +0200 Subject: [PATCH 1/6] wip --- .../common_steps/solver_output_handler.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py index c0b6069f9fc..9dd1e7739c8 100644 --- a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py +++ b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py @@ -151,6 +151,47 @@ def get_hydro_pumping_mwh(self, area: str, year: int) -> int: def get_balance_mwh(self, area: str, year: int) -> int: return self.__get_values_hourly(area, year)["BALANCE"]["MWh"].sum() + # Add hourly series getters corresponding to annual metrics + def get_hourly_margin_price(self, area: str, year: int) -> pd.Series: + """Return hourly margin prices""" + df = self.__get_values_hourly(area, year) + sub = df.xs('MRG. PRICE', axis=1, level=0) + # sub is DataFrame with one column; return it as Series + return sub.iloc[:, 0] + + def get_hourly_load(self, area: str, year: int) -> pd.Series: + """Return hourly load in MWh""" + df = self.__get_values_hourly(area, year) + return df['LOAD']['MWh'] + + def get_hourly_gas(self, area: str, year: int) -> pd.Series: + """Return hourly gas production in MWh""" + df = self.__get_values_hourly(area, year) + # 'GAS' first-level header + return df.xs('GAS', axis=1, level=0).iloc[:, 0] + + def get_hourly_hard_coal(self, area: str, year: int) -> pd.Series: + """Return hourly hard coal production in MWh""" + df = self.__get_values_hourly(area, year) + # 'OTHER FUEL' corresponds to hard coal in annual results + return df.xs('OTHER FUEL', axis=1, level=0).iloc[:, 0] + + def get_hourly_unsupplied_energy(self, area: str, year: int) -> pd.Series: + """Return hourly unsupplied energy in MWh""" + df = self.__get_values_hourly(area, year) + return df['UNSP. ENRG']['MWh'] + + def get_hourly_spilled_energy(self, area: str, year: int) -> pd.Series: + """Return hourly spilled energy in MWh""" + df = self.__get_values_hourly(area, year) + return df['SPIL. ENRG']['MWh'] + + def get_hourly_n_dispatched_units_total(self, area: str, year: int) -> pd.Series: + """Return hourly total number of dispatched units""" + df = self.__get_values_hourly(area, year) + sub = df.xs('NODU', axis=1, level=0) + return sub.iloc[:, 0] + def get_unsupplied_energy_mwh(self, area: str, year: int, date: str = None) -> float: if date is None: return self.__get_values_hourly(area, year)["UNSP. ENRG"]["MWh"].sum() From 6bf16eebba10c6e5ff98a40366d31c7a4560a4ce Mon Sep 17 00:00:00 2001 From: Jason Marechal Date: Thu, 9 Oct 2025 15:51:14 +0200 Subject: [PATCH 2/6] refacto --- .../common_steps/solver_output_handler.py | 94 ++++++------------- 1 file changed, 29 insertions(+), 65 deletions(-) diff --git a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py index 9dd1e7739c8..afa469e1576 100644 --- a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py +++ b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py @@ -93,17 +93,22 @@ def __get_values_annual(self, area: str, year: int) -> pd.DataFrame: """Internal parser for annual values, using caching""" return self.__if_none_then_parse_annual(area, year, "values-annual.txt") - def __get_values_hourly(self, area: str, year: int): + def __get_values_hourly(self, area: str, year: int) -> pd.DataFrame: return self.__if_none_then_parse(result_type.VALUES, area.lower(), year, "values-hourly.txt") - def __get_values_hourly_for_specific_week(self, area: str, year: int, week: int): - df = self.__if_none_then_parse(result_type.VALUES, area.lower(), year, "values-hourly.txt") - return df[(df['hourly']['Unnamed: 1_level_1'] > (week - 1) * 168) & ( - df['hourly']['Unnamed: 1_level_1'] <= week * 168)] - - def __get_values_hourly_for_specific_hour(self, area: str, year: int, datetime: str): + # common helper to get hourly series by header + def _get_hourly_series(self, area: str, year: int, header: str) -> pd.Series: df = self.__get_values_hourly(area, year) - return df.loc[df['datetime'] == datetime] + return df.xs(header, axis=1, level=0).iloc[:, 0] + + # common helper to get annual series by header + def _get_annual_series(self, area: str, year: int, header: str) -> pd.Series: + df = self.__get_values_annual(area, year) + return df.xs(header, axis=1, level=0).iloc[:, 0] + + # common helper to get annual scalar by header + def _get_annual_scalar(self, area: str, year: int, header: str) -> float: + return float(self._get_annual_series(area, year, header).iloc[0]) def __get_sts_details_hourly(self, area: str, year: int): return self.__if_none_then_parse(result_type.DETAILS_STS, area.lower(), year, "details-STstorage-hourly.txt") @@ -140,7 +145,7 @@ def get_loss_of_load_duration_h(self, area: str, year: int) -> int: return self.__get_values_hourly(area, year)["LOLD"]["Hours"].sum() def get_spilled_energy_mwh(self, area: str, year: int) -> int: - return self.__get_values_hourly(area, year)["SPIL. ENRG"]["MWh"].sum() + return int(self._get_hourly_series(area, year, 'SPIL. ENRG').sum()) def get_hydro_production_mwh(self, area: str, year: int) -> int: return self.__get_values_hourly(area, year)["H. STOR"]["MWh"].sum() @@ -149,48 +154,29 @@ def get_hydro_pumping_mwh(self, area: str, year: int) -> int: return self.__get_values_hourly(area, year)["H. PUMP"]["MWh"].sum() def get_balance_mwh(self, area: str, year: int) -> int: - return self.__get_values_hourly(area, year)["BALANCE"]["MWh"].sum() + return int(self._get_hourly_series(area, year, 'BALANCE').sum()) # Add hourly series getters corresponding to annual metrics def get_hourly_margin_price(self, area: str, year: int) -> pd.Series: - """Return hourly margin prices""" - df = self.__get_values_hourly(area, year) - sub = df.xs('MRG. PRICE', axis=1, level=0) - # sub is DataFrame with one column; return it as Series - return sub.iloc[:, 0] + return self._get_hourly_series(area, year, 'MRG. PRICE') def get_hourly_load(self, area: str, year: int) -> pd.Series: - """Return hourly load in MWh""" - df = self.__get_values_hourly(area, year) - return df['LOAD']['MWh'] + return self._get_hourly_series(area, year, 'LOAD') def get_hourly_gas(self, area: str, year: int) -> pd.Series: - """Return hourly gas production in MWh""" - df = self.__get_values_hourly(area, year) - # 'GAS' first-level header - return df.xs('GAS', axis=1, level=0).iloc[:, 0] + return self._get_hourly_series(area, year, 'GAS') def get_hourly_hard_coal(self, area: str, year: int) -> pd.Series: - """Return hourly hard coal production in MWh""" - df = self.__get_values_hourly(area, year) - # 'OTHER FUEL' corresponds to hard coal in annual results - return df.xs('OTHER FUEL', axis=1, level=0).iloc[:, 0] + return self._get_hourly_series(area, year, 'OTHER FUEL') def get_hourly_unsupplied_energy(self, area: str, year: int) -> pd.Series: - """Return hourly unsupplied energy in MWh""" - df = self.__get_values_hourly(area, year) - return df['UNSP. ENRG']['MWh'] + return self._get_hourly_series(area, year, 'UNSP. ENRG') def get_hourly_spilled_energy(self, area: str, year: int) -> pd.Series: - """Return hourly spilled energy in MWh""" - df = self.__get_values_hourly(area, year) - return df['SPIL. ENRG']['MWh'] + return self._get_hourly_series(area, year, 'SPIL. ENRG') def get_hourly_n_dispatched_units_total(self, area: str, year: int) -> pd.Series: - """Return hourly total number of dispatched units""" - df = self.__get_values_hourly(area, year) - sub = df.xs('NODU', axis=1, level=0) - return sub.iloc[:, 0] + return self._get_hourly_series(area, year, 'NODU') def get_unsupplied_energy_mwh(self, area: str, year: int, date: str = None) -> float: if date is None: @@ -225,44 +211,22 @@ def get_values_annual(self, area: str, year: int) -> pd.DataFrame: # Specific getters for annual variables def get_annual_margin_price(self, area: str, year: int) -> float: - """Return margin price for annual results""" - df = self.__get_values_annual(area, year) - # select by first-level column name and extract first scalar - sub = df.xs('MRG. PRICE', axis=1, level=0) - return float(sub.iloc[0, 0]) + return self._get_annual_scalar(area, year, 'MRG. PRICE') def get_annual_load(self, area: str, year: int) -> float: - """Return load for annual results""" - df = self.__get_values_annual(area, year) - sub = df.xs('LOAD', axis=1, level=0) - return float(sub.iloc[0, 0]) + return self._get_annual_scalar(area, year, 'LOAD') def get_annual_gas(self, area: str, year: int) -> float: - """Return gas energy for annual results""" - df = self.__get_values_annual(area, year) - sub = df.xs('GAS', axis=1, level=0) - return float(sub.iloc[0, 0]) + return self._get_annual_scalar(area, year, 'GAS') def get_annual_hard_coal(self, area: str, year: int) -> float: - """Return hard coal energy (other fuel) for annual results""" - df = self.__get_values_annual(area, year) - sub = df.xs('HARD COAL', axis=1, level=0) - return float(sub.iloc[0, 0]) + return self._get_annual_scalar(area, year, 'HARD COAL') def get_annual_unsupplied_energy(self, area: str, year: int) -> float: - """Return unsupplied energy for annual results""" - df = self.__get_values_annual(area, year) - sub = df.xs('UNSP. ENRG', axis=1, level=0) - return float(sub.iloc[0, 0]) + return self._get_annual_scalar(area, year, 'UNSP. ENRG') def get_annual_spilled_energy(self, area: str, year: int) -> float: - """Return spilled energy for annual results""" - df = self.__get_values_annual(area, year) - sub = df.xs('SPIL. ENRG', axis=1, level=0) - return float(sub.iloc[0, 0]) + return self._get_annual_scalar(area, year, 'SPIL. ENRG') def get_annual_n_dispatched_units(self, area: str, year: int) -> int: - """Return number of dispatched units for annual results""" - df = self.__get_values_annual(area, year) - sub = df.xs('NODU', axis=1, level=0) - return int(sub.iloc[0, 0]) + return int(self._get_annual_scalar(area, year, 'NODU')) From 6384f70e0823f5d298e62334f5e057be80005307 Mon Sep 17 00:00:00 2001 From: Jason Marechal Date: Thu, 9 Oct 2025 15:54:33 +0200 Subject: [PATCH 3/6] refacto --- .../common_steps/solver_output_handler.py | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py index afa469e1576..eaf3e5aa52c 100644 --- a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py +++ b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py @@ -110,6 +110,26 @@ def _get_annual_series(self, area: str, year: int, header: str) -> pd.Series: def _get_annual_scalar(self, area: str, year: int, header: str) -> float: return float(self._get_annual_series(area, year, header).iloc[0]) + def get_metric(self, area: str, year: int, header: str, period: str = 'hourly'): + """ + Return a metric by header name: hourly Series if period='hourly', annual scalar if period='annual'. + """ + if period == 'hourly': + return self._get_hourly_series(area, year, header) + if period == 'annual': + return self._get_annual_scalar(area, year, header) + raise ValueError(f"Unknown period '{period}', expected 'hourly' or 'annual'") + + def get_load(self, area: str, year: int, period: str = 'hourly'): + """ + Return load for a given period ('hourly' returns a Series of hourly load, + 'annual' returns the annual scalar load). + """ + header = 'LOAD' + if period == 'hourly': + return self._get_hourly_series(area, year, header) + return self._get_annual_scalar(area, year, header) + def __get_sts_details_hourly(self, area: str, year: int): return self.__if_none_then_parse(result_type.DETAILS_STS, area.lower(), year, "details-STstorage-hourly.txt") @@ -158,25 +178,25 @@ def get_balance_mwh(self, area: str, year: int) -> int: # Add hourly series getters corresponding to annual metrics def get_hourly_margin_price(self, area: str, year: int) -> pd.Series: - return self._get_hourly_series(area, year, 'MRG. PRICE') + return self.get_metric(area, year, 'MRG. PRICE', period='hourly') def get_hourly_load(self, area: str, year: int) -> pd.Series: - return self._get_hourly_series(area, year, 'LOAD') + return self.get_metric(area, year, 'LOAD', period='hourly') def get_hourly_gas(self, area: str, year: int) -> pd.Series: - return self._get_hourly_series(area, year, 'GAS') + return self.get_metric(area, year, 'GAS', period='hourly') def get_hourly_hard_coal(self, area: str, year: int) -> pd.Series: - return self._get_hourly_series(area, year, 'OTHER FUEL') + return self.get_metric(area, year, 'OTHER FUEL', period='hourly') def get_hourly_unsupplied_energy(self, area: str, year: int) -> pd.Series: - return self._get_hourly_series(area, year, 'UNSP. ENRG') + return self.get_metric(area, year, 'UNSP. ENRG', period='hourly') def get_hourly_spilled_energy(self, area: str, year: int) -> pd.Series: - return self._get_hourly_series(area, year, 'SPIL. ENRG') + return self.get_metric(area, year, 'SPIL. ENRG', period='hourly') def get_hourly_n_dispatched_units_total(self, area: str, year: int) -> pd.Series: - return self._get_hourly_series(area, year, 'NODU') + return self.get_metric(area, year, 'NODU', period='hourly') def get_unsupplied_energy_mwh(self, area: str, year: int, date: str = None) -> float: if date is None: @@ -211,22 +231,22 @@ def get_values_annual(self, area: str, year: int) -> pd.DataFrame: # Specific getters for annual variables def get_annual_margin_price(self, area: str, year: int) -> float: - return self._get_annual_scalar(area, year, 'MRG. PRICE') + return self.get_metric(area, year, 'MRG. PRICE', period='annual') def get_annual_load(self, area: str, year: int) -> float: - return self._get_annual_scalar(area, year, 'LOAD') + return self.get_metric(area, year, 'LOAD', period='annual') def get_annual_gas(self, area: str, year: int) -> float: - return self._get_annual_scalar(area, year, 'GAS') + return self.get_metric(area, year, 'GAS', period='annual') def get_annual_hard_coal(self, area: str, year: int) -> float: - return self._get_annual_scalar(area, year, 'HARD COAL') + return self.get_metric(area, year, 'HARD COAL', period='annual') def get_annual_unsupplied_energy(self, area: str, year: int) -> float: - return self._get_annual_scalar(area, year, 'UNSP. ENRG') + return self.get_metric(area, year, 'UNSP. ENRG', period='annual') def get_annual_spilled_energy(self, area: str, year: int) -> float: - return self._get_annual_scalar(area, year, 'SPIL. ENRG') + return self.get_metric(area, year, 'SPIL. ENRG', period='annual') def get_annual_n_dispatched_units(self, area: str, year: int) -> int: - return int(self._get_annual_scalar(area, year, 'NODU')) + return int(self.get_metric(area, year, 'NODU', period='annual')) From 1b7732eefb55334d0b7cfb04c72fa3a1c83a5c08 Mon Sep 17 00:00:00 2001 From: Jason Marechal Date: Fri, 10 Oct 2025 09:38:10 +0200 Subject: [PATCH 4/6] Fix undefined method --- .../steps/common_steps/solver_output_handler.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py index eaf3e5aa52c..26a4f7a55a9 100644 --- a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py +++ b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py @@ -96,6 +96,16 @@ def __get_values_annual(self, area: str, year: int) -> pd.DataFrame: def __get_values_hourly(self, area: str, year: int) -> pd.DataFrame: return self.__if_none_then_parse(result_type.VALUES, area.lower(), year, "values-hourly.txt") + def __get_values_hourly_for_specific_hour(self, area: str, year: int, date: str) -> pd.DataFrame: + df = self.__get_values_hourly(area, year) + return df[df['datetime'] == date] + + def __get_values_hourly_for_specific_week(self, area: str, year: int, week: int) -> pd.DataFrame: + df = self.__get_values_hourly(area, year) + day_col = df['Unnamed: 2_level_0'] + week_calc = (day_col - 1) // 7 + 1 + return df[week_calc == week] + # common helper to get hourly series by header def _get_hourly_series(self, area: str, year: int, header: str) -> pd.Series: df = self.__get_values_hourly(area, year) From e0ad905c4497f9feee32eba8be3624a188b0ed81 Mon Sep 17 00:00:00 2001 From: Jason Marechal Date: Mon, 13 Oct 2025 09:55:20 +0200 Subject: [PATCH 5/6] Fix --- .../features/steps/common_steps/solver_output_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py index 26a4f7a55a9..2211cd4e343 100644 --- a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py +++ b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py @@ -98,13 +98,13 @@ def __get_values_hourly(self, area: str, year: int) -> pd.DataFrame: def __get_values_hourly_for_specific_hour(self, area: str, year: int, date: str) -> pd.DataFrame: df = self.__get_values_hourly(area, year) - return df[df['datetime'] == date] + return df.loc[df['datetime'] == date] def __get_values_hourly_for_specific_week(self, area: str, year: int, week: int) -> pd.DataFrame: df = self.__get_values_hourly(area, year) day_col = df['Unnamed: 2_level_0'] week_calc = (day_col - 1) // 7 + 1 - return df[week_calc == week] + return df.loc[week_calc == week] # common helper to get hourly series by header def _get_hourly_series(self, area: str, year: int, header: str) -> pd.Series: From ff360a32d1fe755f41ff9e7fce1c94ce57e90579 Mon Sep 17 00:00:00 2001 From: Jason Marechal Date: Mon, 13 Oct 2025 11:14:00 +0200 Subject: [PATCH 6/6] Fix --- .../features/steps/common_steps/solver_output_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py index 2211cd4e343..71fa33c41a6 100644 --- a/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py +++ b/src/tests/cucumber/features/steps/common_steps/solver_output_handler.py @@ -103,6 +103,8 @@ def __get_values_hourly_for_specific_hour(self, area: str, year: int, date: str) def __get_values_hourly_for_specific_week(self, area: str, year: int, week: int) -> pd.DataFrame: df = self.__get_values_hourly(area, year) day_col = df['Unnamed: 2_level_0'] + if isinstance(day_col, pd.DataFrame): + day_col = day_col.iloc[:, 0] week_calc = (day_col - 1) // 7 + 1 return df.loc[week_calc == week]