From ec029e5405588bc261a86ee94b11f45bf56b9fb7 Mon Sep 17 00:00:00 2001 From: Frank Boerman Date: Thu, 24 Oct 2024 14:17:48 +0200 Subject: [PATCH] added query_unavailability_of_offshore_grid for offshore grid outages. not very flexible at the moment but its a start. closes #344 --- README.md | 2 ++ entsoe/entsoe.py | 20 ++++++++++++++++++-- entsoe/parsers.py | 15 +++++++++++++++ entsoe/series_parsers.py | 5 +++-- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e00fc3e..975240b 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ client.query_imbalance_prices(country_code, start, end, psr_type=None) client.query_unavailability_of_generation_units(country_code, start, end, docstatus=None, periodstartupdate=None, periodendupdate=None) client.query_unavailability_of_production_units(country_code, start, end, docstatus=None, periodstartupdate=None, periodendupdate=None) client.query_unavailability_transmission(country_code_from, country_code_to, start, end, docstatus=None, periodstartupdate=None, periodendupdate=None) +client.query_unavailability_of_offshore_grid(area_code, start, end) client.query_withdrawn_unavailability_of_generation_units(country_code, start, end) ``` #### Dump result to file @@ -134,6 +135,7 @@ client.query_unavailability_of_generation_units(country_code, start=start, end=e client.query_unavailability_of_production_units(country_code, start, end, docstatus=None, periodstartupdate=None, periodendupdate=None) client.query_unavailability_transmission(country_code_from, country_code_to, start=start, end=end, docstatus=None, periodstartupdate=None, periodendupdate=None) client.query_withdrawn_unavailability_of_generation_units(country_code, start, end) +client.query_unavailability_of_offshore_grid(area_code, start, end) client.query_physical_crossborder_allborders(country_code, start, end, export=True) client.query_generation_import(country_code, start, end) client.query_procured_balancing_capacity(country_code, process_type, start=start, end=end, type_marketagreement_type=None) diff --git a/entsoe/entsoe.py b/entsoe/entsoe.py index 7da8343..0856869 100644 --- a/entsoe/entsoe.py +++ b/entsoe/entsoe.py @@ -15,7 +15,8 @@ parse_installed_capacity_per_plant, parse_crossborder_flows, \ parse_unavailabilities, parse_contracted_reserve, parse_imbalance_prices_zip, \ parse_imbalance_volumes_zip, parse_netpositions, parse_procured_balancing_capacity, \ - parse_water_hydro,parse_aggregated_bids, parse_activated_balancing_energy_prices + parse_water_hydro,parse_aggregated_bids, parse_activated_balancing_energy_prices, \ + parse_offshore_unavailability from .decorators import retry, paginated, year_limited, day_limited, documents_limited import warnings @@ -23,7 +24,7 @@ warnings.filterwarnings('ignore', category=XMLParsedAsHTMLWarning) __title__ = "entsoe-py" -__version__ = "0.6.14" +__version__ = "0.6.15" __author__ = "EnergieID.be, Frank Boerman" __license__ = "MIT" @@ -1057,6 +1058,11 @@ def query_unavailability_of_generation_units( periodendupdate=periodendupdate, mRID=mRID, offset=offset) return content + def query_unavailability_of_offshore_grid(self, area_code: Union[Area, str], start: pd.Timestamp, end: pd.Timestamp): + return self._query_unavailability( + country_code=area_code, start=start, end=end, doctype='A79' + ) + def query_unavailability_of_production_units( self, country_code: Union[Area, str], start: pd.Timestamp, end: pd.Timestamp, docstatus: Optional[str] = None, @@ -2067,6 +2073,16 @@ def _query_unavailability( df = df[(df['start'] < end) | (df['end'] > start)] return df + def query_unavailability_of_offshore_grid(self, area_code: Union[Area, str], + start: pd.Timestamp, end: pd.Timestamp + ) -> pd.DataFrame: + zipfile = super(EntsoePandasClient, self)._query_unavailability( + country_code=area_code, start=start, end=end, doctype='A79' + ) + df = parse_offshore_unavailability(zipfile) + return df + + def query_unavailability_of_generation_units( self, country_code: Union[Area, str], start: pd.Timestamp, end: pd.Timestamp, docstatus: Optional[str] = None, diff --git a/entsoe/parsers.py b/entsoe/parsers.py index 69eb823..da18dc1 100644 --- a/entsoe/parsers.py +++ b/entsoe/parsers.py @@ -942,6 +942,21 @@ def parse_unavailabilities(response: bytes, doctype: str) -> pd.DataFrame: return df +def parse_offshore_unavailability(response: bytes) -> pd.DataFrame: + """ + offshore has slightly different structure so use seperate parser. this also enables using the new generic parsers as well + """ + df = {} + with zipfile.ZipFile(BytesIO(response), 'r') as arc: + for f in arc.infolist(): + if f.filename.endswith('xml'): + for series in _extract_timeseries(arc.read(f)): + asset = series.find('Asset_RegisteredResource'.lower()) + name = "|".join([asset.find(x).text for x in ['mrid', 'name', 'location.name']]) + df[name] = _parse_timeseries_generic(series, merge_series=True, period_name='windpowerfeedin_period') + return pd.DataFrame(df) + + def _available_period(timeseries: bs4.BeautifulSoup) -> list: # if not timeseries: # return diff --git a/entsoe/series_parsers.py b/entsoe/series_parsers.py index 2fc7c0f..2f70597 100644 --- a/entsoe/series_parsers.py +++ b/entsoe/series_parsers.py @@ -31,6 +31,7 @@ def _resolution_to_timedelta(res_text: str) -> str: 'P1D': '1D', 'P7D': '7D', 'P1M': '1MS', + 'PT1M': '1min' } delta = resolutions.get(res_text) if delta is None: @@ -82,14 +83,14 @@ def _parse_datetimeindex(soup, tz=None): return index -def _parse_timeseries_generic(soup, label='quantity', to_float=True, merge_series=False): +def _parse_timeseries_generic(soup, label='quantity', to_float=True, merge_series=False, period_name='period'): series = { '15min': [], '30min': [], '60min': [] } - for period in soup.find_all('period'): + for period in soup.find_all(period_name): data = {} start = pd.Timestamp(period.find('start').text) end = pd.Timestamp(period.find('end').text)