Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Belgian prices are not fetched because of different resolutions in api response #195

Open
Roeland54 opened this issue Oct 7, 2024 · 11 comments

Comments

@Roeland54
Copy link
Collaborator

So since the api changes of 10/04 the response for the Belgian zone contains PT15M (15 min) resolution data mixed with PT60M (60 min) data. The current parsing logic only parses PT60M data because that was al that was needed until now.

It is currently unclear to me what the solution here should be. We could change the parsing logic to also parse PT15M data. The issue with that is that the German zone also returns PT15M data we explicitly don't want to parse. We could program in some logic to only parse PT15M when the Belgian data is requested but that feels like a 'hack'.

image
image

@Pluimvee
Copy link
Contributor

Pluimvee commented Oct 7, 2024

As a starter we could ignore any 2nd, 3rd, 4th price entries
In a second phase we can average the 2nd, 3rd, 4th prices to get back to hour prices

Changing the complete integration to support 15min intervals is quite a large change

@CBK482
Copy link

CBK482 commented Oct 7, 2024

This is the debug output. Did find 24 hours

2024-10-07 15:58:40.865 DEBUG (MainThread) [custom_components.entsoe.coordinator] ENTSO-e DataUpdateCoordinator data update
2024-10-07 15:58:40.865 DEBUG (MainThread) [custom_components.entsoe.coordinator] BE
2024-10-07 15:58:40.866 DEBUG (MainThread) [custom_components.entsoe.coordinator] fetching prices for start date: 2024-10-06 00:00:00+02:00 to end date: 2024-10-08 23:00:00+02:00
2024-10-07 15:58:40.868 DEBUG (SyncWorker_34) [custom_components.entsoe.api_client] Performing request to https://web-api.tp.entsoe.eu/api with params {'documentType': 'A44', 'in_Domain': '10YBE----------2', 'out_Domain': '10YBE----------2', 'securityToken': 'b71ec393-638c-48a1-9c14-3861983d55c1', 'periodStart': '202410060000', 'periodEnd': '202410082300'}
2024-10-07 15:58:43.228 DEBUG (SyncWorker_34) [custom_components.entsoe.api_client] content: <Element 'Publication_MarketDocument' at 0x7f68682980>
2024-10-07 15:58:43.233 DEBUG (MainThread) [custom_components.entsoe.coordinator] received data = {datetime.datetime(2024, 10, 8, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 64.98, datetime.datetime(2024, 10, 8, 1, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 57.86, datetime.datetime(2024, 10, 8, 2, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 53.73, datetime.datetime(2024, 10, 8, 3, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 47.52, datetime.datetime(2024, 10, 8, 4, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 47.05, datetime.datetime(2024, 10, 8, 5, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 56.89, datetime.datetime(2024, 10, 8, 6, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 77.77, datetime.datetime(2024, 10, 8, 7, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 88.24, datetime.datetime(2024, 10, 8, 8, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 100.0, datetime.datetime(2024, 10, 8, 9, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 84.92, datetime.datetime(2024, 10, 8, 10, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 74.6, datetime.datetime(2024, 10, 8, 11, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 68.82, datetime.datetime(2024, 10, 8, 12, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 60.56, datetime.datetime(2024, 10, 8, 13, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 63.86, datetime.datetime(2024, 10, 8, 14, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 68.1, datetime.datetime(2024, 10, 8, 15, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 68.37, datetime.datetime(2024, 10, 8, 16, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 76.35, datetime.datetime(2024, 10, 8, 17, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 54.04, datetime.datetime(2024, 10, 8, 18, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 98.97, datetime.datetime(2024, 10, 8, 19, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 115.47, datetime.datetime(2024, 10, 8, 20, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 86.85, datetime.datetime(2024, 10, 8, 21, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 69.59, datetime.datetime(2024, 10, 8, 22, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 57.42, datetime.datetime(2024, 10, 8, 23, 0, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')): 50.0}
2024-10-07 15:58:43.235 DEBUG (MainThread) [custom_components.entsoe.coordinator] received pricing data from entso-e for 24 hours
2024-10-07 15:58:43.235 DEBUG (MainThread) [custom_components.entsoe.coordinator] Finished fetching ENTSO-e coordinator data in 2.373 seconds (success: True)
2024-10-07 15:58:43.236 WARNING (MainThread) [custom_components.entsoe.sensor] Unable to update entity 'entsoe.Current electricity market price': No valid data for today available.
2024-10-07 15:58:43.238 WARNING (MainThread) [custom_components.entsoe.sensor] Unable to update entity 'entsoe.Next hour electricity market price': No valid data for today available.
2024-10-07 15:58:43.238 WARNING (MainThread) [custom_components.entsoe.sensor] Unable to update entity 'entsoe.Lowest energy price': No valid data for today available.
2024-10-07 15:58:43.241 WARNING (MainThread) [custom_components.entsoe.sensor] Unable to update entity 'entsoe.Highest energy price': No valid data for today available.
2024-10-07 15:58:43.242 WARNING (MainThread) [custom_components.entsoe.sensor] Unable to update entity 'entsoe.Average electricity price': No valid data for today available.
2024-10-07 15:58:43.243 WARNING (MainThread) [custom_components.entsoe.sensor] Unable to update entity 'entsoe.Current percentage of highest electricity price': No valid data for today available.
2024-10-07 15:58:43.244 WARNING (MainThread) [custom_components.entsoe.sensor] Unable to update entity 'entsoe.Time of highest price': No valid data for today available.
2024-10-07 15:58:43.245 WARNING (MainThread) [custom_components.entsoe.sensor] Unable to update entity 'entsoe.Time of lowest price': No valid data for today available.

@fboerman
Copy link

fboerman commented Oct 7, 2024

dear all, entsoe-py maintainer dropping in. Sorry to hear you had to build your own implementation due to pandas issue.
please note that the SDAC is currently on hourly basis and the 15 min data from Belgium is a bug. For my own datalake I have added a hardcoded exception that resamples the belgium prices.

for the future it is important to note that the SDAC will go to 15 min resolution somewhere Q1 or Q2 2025. This means that you will need the 15 min parser eventually since ALL countries will swap their day ahead prices to that resolution at the same time.

@Roeland54
Copy link
Collaborator Author

Thank you for your explanation that is really useful.
Best course of action would be to extend the parser to handle all possible resolutions and make the integration handle all resolutions gracefully. Which sounds like a big change but a lot of the logic that was using 60min as the hardcoded resolution was already removed in the refactors of the last month.

@wonko
Copy link

wonko commented Oct 7, 2024

this might fix this (for now, not ideal as I don't really care about real 15m resolutions

#196

@micvdh
Copy link

micvdh commented Oct 7, 2024

Proposing temporary workaround with the change below to api_client.py for mixed resolution in API response :

def query_day_ahead_prices(
    self, country_code: Union[Area, str], start: datetime, end: datetime
) -> str:
    """
    Parameters
    ----------
    country_code : Area|str
    start : datetime
    end : datetime

    Returns
    -------
    str
    """
    area = Area[country_code.upper()]
    params = {
        "documentType": "A44",
        "in_Domain": area.code,
        "out_Domain": area.code,
    }
    response = self._base_request(params=params, start=start, end=end)

    if response.status_code == 200:
        try:
            root = self._remove_namespace(ET.fromstring(response.content))
            _LOGGER.debug(f"content: {root}")
            series = {}

            # Extract TimeSeries data
            for timeseries in root.findall(".//TimeSeries"):
                for period in timeseries.findall(".//Period"):
                    resolution = period.find(".//resolution").text

                    _LOGGER.debug(f"Resolution {resolution}")
                    if (resolution != "PT60M" and resolution != "PT30M" and resolution != "PT15M"):
                        continue

                    response_start = period.find(".//timeInterval/start").text
                    start_time = (
                        datetime.strptime(response_start, "%Y-%m-%dT%H:%MZ")
                        .replace(tzinfo=pytz.UTC)
                        .astimezone()
                    )

                    response_end = period.find(".//timeInterval/end").text
                    end_time = (
                        datetime.strptime(response_end, "%Y-%m-%dT%H:%MZ")
                        .replace(tzinfo=pytz.UTC)
                        .astimezone()
                    )

                    _LOGGER.debug(f"Period found is from {start_time} till {end_time}")

                    for point in period.findall(".//Point"):
                        position = point.find(".//position").text
                        price = point.find(".//price.amount").text
                        if resolution == "PT60M":
                            hour = int(position) - 1
                        elif resolution == "PT30M":
                            hour = ((int(position) - 1) / 2)
                        elif resolution == "PT15M":
                            hour = ((int(position) - 1) / 4)
                        else:
                            continue

                        try:
                            series[start_time + timedelta(hours=hour)] = float(price)
                        except:
                            continue

                    # Now fill in any missing hours 
                    current_time = start_time
                    last_price = series[current_time]

                    while current_time < end_time:  # upto excluding! the endtime
                        if current_time in series:
                            last_price = series[current_time]  # Update to the current price
                        else:
                            _LOGGER.debug(f"Extending the price {last_price} of the previous hour to {current_time}")
                            series[current_time] = last_price  # Fill with the last known price
                        current_time += timedelta(hours=1)

            return dict(sorted(series.items()))

        except Exception as exc:
            _LOGGER.debug(f"Failed to parse response content:{response.content}")
            raise exc
    else:
        print(f"Failed to retrieve data: {response.status_code}")
        return None

With the following result for BE now :
image

@fboerman
Copy link

fboerman commented Oct 7, 2024 via email

@Roeland54
Copy link
Collaborator Author

dear all, entsoe-py maintainer dropping in. Sorry to hear you had to build your own implementation due to pandas issue. please note that the SDAC is currently on hourly basis and the 15 min data from Belgium is a bug. For my own datalake I have added a hardcoded exception that resamples the belgium prices.

for the future it is important to note that the SDAC will go to 15 min resolution somewhere Q1 or Q2 2025. This means that you will need the 15 min parser eventually since ALL countries will swap their day ahead prices to that resolution at the same time.

I was wondering if you know on how the SDAC and EXAA will be distinguishable from each other when the SDAC changes to 15 min data.

If it helps I was thinking about making a small library only pulling in prices with no pandas dependency for projects such as these. Would that help? ⁣

I think that a lightweight low dependency api library could be useful. It would definitely be easier to wait for a package update when entso-e breaks the api again than fixing and maintaining it in this project. In a perfect world that library could then be the core of a library that provides pandas objects. That would eliminate maintenance of redundant functionality. I just don't know how feasible that all is.

Belgian data for today is in the correct format. So maybe it is fixed. If prices of tomorrow break it again I will look into merging the patch @wonko

@wonko
Copy link

wonko commented Oct 8, 2024

Belgian data for today is in the correct format. So maybe it is fixed. If prices of tomorrow break it again I will look into merging the patch @wonko

Keep in mind that the response sent was valid according to the specs as set out in the XML Schema guide. The patch allows to accept these valid responses, doesn't break anything (and even adds tests on the XML, which can be easily extended with other cases in the future). Until there's a uniform solution through some external (or internal) package which deals with everything, it only adds robustness.

But, your call in the end.

@Roeland54
Copy link
Collaborator Author

Yes I agree and i like the tests. And have the intention on merging the PR. But this just takes the pressure off releasing a rushed version.

@Pluimvee
Copy link
Contributor

Pluimvee commented Oct 9, 2024

PR #202 is now tested and should resolve this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants