File: parser.py

package info (click to toggle)
python-aiopvpc 4.3.1-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 592 kB
  • sloc: python: 1,415; makefile: 7
file content (162 lines) | stat: -rw-r--r-- 4,961 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"""
Simple aio library to download Spanish electricity hourly prices.

* URL for JSON daily files
* Parser for the contents of the JSON files
"""

from datetime import datetime, timedelta
from itertools import groupby
from operator import itemgetter
from typing import Any

from aiopvpc.const import (
    DataSource,
    EsiosResponse,
    GEOZONE_ID2NAME,
    GEOZONES,
    KEY_PVPC,
    PRICE_PRECISION,
    REFERENCE_TZ,
    SENSOR_KEY_TO_DATAID,
    TARIFF2ID,
    TARIFFS,
    URL_ESIOS_TOKEN_RESOURCE,
    URL_PUBLIC_PVPC_RESOURCE,
    UTC_TZ,
    zoneinfo,
)


def _timezone_offset(tz: zoneinfo.ZoneInfo = REFERENCE_TZ) -> timedelta:
    ref_ts = datetime(2021, 1, 1, tzinfo=REFERENCE_TZ).astimezone(UTC_TZ)
    loc_ts = datetime(2021, 1, 1, tzinfo=tz).astimezone(UTC_TZ)
    return loc_ts - ref_ts


def extract_prices_from_esios_public(
    data: dict[str, Any], key: str, tz: zoneinfo.ZoneInfo = REFERENCE_TZ
) -> EsiosResponse:
    """Parse the contents of a daily PVPC json file."""
    ts_init = datetime(
        *datetime.strptime(data["PVPC"][0]["Dia"], "%d/%m/%Y").timetuple()[:3],
        tzinfo=tz,
    ).astimezone(UTC_TZ)

    def _parse_tariff_val(value, prec=PRICE_PRECISION) -> float:
        return round(float(value.replace(",", ".")) / 1000.0, prec)

    pvpc_prices = {
        ts_init + timedelta(hours=i): _parse_tariff_val(values_hour[key])
        for i, values_hour in enumerate(data["PVPC"])
    }

    return EsiosResponse(
        name="PVPC ESIOS",
        data_id="legacy",
        last_update=datetime.utcnow().replace(microsecond=0, tzinfo=UTC_TZ),
        unit="€/kWh",
        series={KEY_PVPC: pvpc_prices},
    )


def extract_prices_from_esios_token(
    data: dict[str, Any],
    sensor_key: str,
    geo_zone: str,
    tz: zoneinfo.ZoneInfo = REFERENCE_TZ,
) -> EsiosResponse:
    """Parse the contents of an 'indicator' json file from ESIOS API."""
    offset_timezone = _timezone_offset(tz)
    indicator_data = data.pop("indicator")
    unit = "•".join(mag["name"] for mag in indicator_data["magnitud"])
    unit_tiempo = "•".join(mag["name"] for mag in indicator_data["tiempo"])
    unit += f"/{unit_tiempo}"
    ts_update = datetime.utcnow().replace(microsecond=0, tzinfo=UTC_TZ)

    def _parse_dt(ts: str) -> datetime:
        return datetime.fromisoformat(ts).astimezone(UTC_TZ) + offset_timezone

    def _value_unit_conversion(value: float) -> float:
        # from €/MWh to €/kWh
        return round(float(value) / 1000.0, 5)

    value_gen = groupby(
        sorted(indicator_data["values"], key=itemgetter("geo_id")),
        itemgetter("geo_id"),
    )
    parsed_data = {
        GEOZONE_ID2NAME[key]: {
            _parse_dt(item["datetime"]): _value_unit_conversion(item["value"])
            for item in list(group)
        }
        for key, group in value_gen
    }
    if geo_zone in parsed_data:
        geo_data = parsed_data[geo_zone]
    elif "Península" in parsed_data:
        geo_data = parsed_data["Península"]
    else:
        geo_data = parsed_data["España"]

    return EsiosResponse(
        name=indicator_data["name"],
        data_id=str(indicator_data["id"]),
        last_update=ts_update,
        unit=unit,
        series={sensor_key: geo_data},
    )


def extract_esios_data(
    data: dict[str, Any],
    url: str,
    sensor_key: str,
    tariff: str,
    tz: zoneinfo.ZoneInfo = REFERENCE_TZ,
) -> EsiosResponse:
    """Parse the contents of a daily PVPC json file."""
    if url.startswith("https://api.esios.ree.es/archives"):
        return extract_prices_from_esios_public(data, TARIFF2ID[tariff], tz)

    if url.startswith("https://api.esios.ree.es/indicators"):
        # TODO adapt better to geozones
        if tariff == TARIFFS[0] and tz != REFERENCE_TZ:
            geo_zone = GEOZONES[1]
        elif tariff == TARIFFS[0]:
            geo_zone = GEOZONES[0]
        else:
            geo_zone = GEOZONES[3]

        return extract_prices_from_esios_token(data, sensor_key, geo_zone, tz)
    raise NotImplementedError(f"Data source not known: {url} >{data}")


def get_daily_urls_to_download(
    source: DataSource,
    sensor_keys: set[str],
    now_local_ref: datetime,
    next_day_local_ref: datetime,
) -> tuple[list[str], list[str]]:
    """Make URLs for ESIOS price series."""
    if source == "esios_public":
        assert sensor_keys == {KEY_PVPC}
        return (
            [URL_PUBLIC_PVPC_RESOURCE.format(day=now_local_ref.date())],
            [URL_PUBLIC_PVPC_RESOURCE.format(day=next_day_local_ref.date())],
        )

    assert source == "esios"
    today = [
        URL_ESIOS_TOKEN_RESOURCE.format(
            ind=SENSOR_KEY_TO_DATAID[key], day=now_local_ref.date()
        )
        for key in sensor_keys
    ]
    tomorrow = [
        URL_ESIOS_TOKEN_RESOURCE.format(
            ind=SENSOR_KEY_TO_DATAID[key], day=next_day_local_ref.date()
        )
        for key in sensor_keys
    ]
    return today, tomorrow