ceps-api 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ceps_api/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """
2
+ Czech Electrical Power Transmission system data from ČEPS.
3
+
4
+ ČEPS publishes system imbalance, activated balancing energy, load and
5
+ generation over a SOAP service. It needs no key, and is not pleasant
6
+ to call directly. This wraps it: every method of the service has a
7
+ wrapper of the same name on Ceps, which returns a pandas DataFrame.
8
+
9
+ from ceps_api import Ceps
10
+
11
+ ceps = Ceps()
12
+ df = ceps.AktualniSystemovaOdchylkaCR("2026-09-14", "2026-09-15", aggregation="QH")
13
+
14
+ Ceps.parameters() # the methods
15
+ Ceps.parameters("Generation") # what one of them accepts
16
+ """
17
+ from importlib.metadata import version
18
+
19
+ from .catalog import CEPS_AGGREGATIONS, CEPS_DATA_VERSIONS, CEPS_FUNCTIONS, CEPS_METHODS
20
+ from .client import Ceps
21
+ from .models import CepsMethod, CepsResponseType
22
+ from .soap import CEPS_NS, CEPS_URL, CepsError
23
+
24
+ # The version in pyproject.toml, as installed.
25
+ __version__ = version("ceps-api")
26
+
27
+ __all__ = ["__version__", "Ceps", "CepsError", "CEPS_NS", "CEPS_URL",
28
+ "CEPS_METHODS", "CepsMethod", "CepsResponseType", "CEPS_AGGREGATIONS",
29
+ "CEPS_FUNCTIONS", "CEPS_DATA_VERSIONS"]
ceps_api/catalog.py ADDED
@@ -0,0 +1,231 @@
1
+ """
2
+ Catalog of the ČEPS service: its methods, their parameters and the aggregation, function and version values
3
+ they take.
4
+
5
+ What the parameters mean is described in the interface description that
6
+ ČEPS publishes at https://www.ceps.cz/cs/webove-sluzby ("Popis rozhraní pro
7
+ poskytování strukturovaných dat", version 3.2). Unfortunately the ČEPS service itself does
8
+ not strictly follow their own document; see the notes below.
9
+
10
+ Every available ČEPS method has a wrapper of the same name, which returns
11
+ a DataFrame; Ceps.parameters() lists what each of them accepts.
12
+
13
+ scripts/ceps_catalog.py writes a fresh version of this dict (for when the
14
+ service changes) to a separate file, to be reviewed and copied over by hand:
15
+ required_params and optional_params come from the WSDL and were verified by
16
+ trying.
17
+
18
+ Note the WSDL spells para1 without the "m" for PowerBalance, Generation
19
+ and GenerationRES, and spells aggregation as "agregation", so beware.
20
+
21
+ A value the ČEPS service does not know is not an error: everything comes back,
22
+ or nothing does, so the wrappers check the value.
23
+
24
+ Points in the ČEPS documentation which are not followed, because the
25
+ service does not adhere to them:
26
+ - it gives EmergencyExchangeACER an aggregation and a function, which
27
+ the method does not seem to take
28
+ - it has mFFR+ for the mFRR+ of AktualniCenaRE (the latter is correct, a typo of F for R)
29
+ - it allows only AVG for CrossborderPowerFlows and ExportImportSVR; however
30
+ other functions work too
31
+ - its tables of the aggregations allowed per period are not enforced
32
+ (see CEPS_AGGREGATIONS). They leave out QH for GenerationPlan and YR
33
+ for EmergencyExchange, which both return data, and have the peak and
34
+ off-peak variants for PowerBalance, which return nothing
35
+ - it has OdhadovanaCenaOdchylky per hour; it is per quarter hour now
36
+ - it does not describe Emise and OfferPrices at all
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ from .models import CepsMethod, CepsResponseType
42
+
43
+ #----------------------------------------------------------------------------------------------------------------------------
44
+ CEPS_METHODS = {
45
+ "AktivaceSVRvCR": CepsMethod(
46
+ "Activated balancing energy (aFRR+/-, mFRR+/-, mFRR5), [MW]",
47
+ optional_params=("agregation", "function", "param1"),
48
+ aggregations=("MI", "QH", "HR", "DY"),
49
+ param_values={"param1": ("all", "aFRR+", "aFRR-", "mFRR+", "mFRR-", "RR+", "RR-", "mFRR5")},
50
+ ),
51
+ "AktualniCenaRE": CepsMethod(
52
+ "Price of activated regulating energy (aFRR, mFRR+/-, mFRR5), [EUR/MWh]",
53
+ optional_params=("param1",),
54
+ param_values={"param1": ("All", "aFRR", "mFRR+", "mFRR-", "mFRR5")},
55
+ ),
56
+ "AktualniSystemovaOdchylkaCR": CepsMethod(
57
+ "Current system imbalance of the Czech control area, [MW]",
58
+ optional_params=("agregation", "function"),
59
+ aggregations=("MI", "QH", "HR"),
60
+ ),
61
+ "CrossborderPowerFlows": CepsMethod(
62
+ "Actual and planned flows per neighbouring operator, [MW]",
63
+ optional_params=("agregation", "function", "version"),
64
+ needed_params=("agregation", "function"),
65
+ aggregations=("MI", "QH", "HR", "DY", "WE", "MO", "YR"),
66
+ versions=("RT",),
67
+ ),
68
+ "DataAgregation": CepsMethod(
69
+ "Lists the aggregation codes. Not a time series",
70
+ required_params=(),
71
+ response_type=CepsResponseType.CODES,
72
+ ),
73
+ "DataVersion": CepsMethod(
74
+ "Lists the version codes. Not a time series",
75
+ required_params=(),
76
+ response_type=CepsResponseType.CODES,
77
+ ),
78
+ "EmergencyExchange": CepsMethod(
79
+ "Emergency assistance, redispatch and regulating energy from abroad, a row per border and direction. Only has rows when something happened",
80
+ optional_params=("agregation", "function", "version", "param1", "param2"),
81
+ needed_params=("agregation", "function", "version"),
82
+ aggregations=("QH", "HR", "DY", "DYO", "DYP", "WE", "WEO", "WEP", "MO", "MOO", "MOP", "YR", "YRO", "YRP"),
83
+ versions=("RT",),
84
+ param_values={"param1": ("All", "E", "R"), "param2": ("All", "APG", "SEPS", "TenneT", "PSE", "50HzT")},
85
+ response_type=CepsResponseType.TABLE,
86
+ ),
87
+ "EmergencyExchangeACER": CepsMethod(
88
+ "Emergency exchange as an ACER message. Not a time series",
89
+ optional_params=("param1", "param2"),
90
+ param_values={"param1": ("All", "E", "R"), "param2": ("All", "APG", "SEPS", "TenneT", "PSE", "50HzT")},
91
+ response_type=CepsResponseType.MESSAGE,
92
+ ),
93
+ "Emise": CepsMethod(
94
+ "CO2 intensity of generation, [g CO2/kWh]. Always per quarter hour, the service ignores the aggregation",
95
+ optional_params=("agregation", "function", "version", "param1"),
96
+ needed_params=("version",),
97
+ aggregations=("QH",),
98
+ versions=("RT",),
99
+ ),
100
+ "ExportImportSVR": CepsMethod(
101
+ "Exchange with the European balancing platforms (imbalance netting, MARI, PICASSO)",
102
+ optional_params=("agregation", "function", "param1"),
103
+ aggregations=("MI", "QH", "HR", "DY"),
104
+ param_values={"param1": ("PcorrHVNCER", "PcorrImbalanceNetting", "PcorrMari", "PcorrPicasso", "VYKON")},
105
+ ),
106
+ "ExportImportVykonu": CepsMethod(
107
+ "Export (+) and import (-) of power within the European platforms and foreign assistance, [MW]. Data from 2022-05-20 to 2023-08-25, then ExportImportSVR",
108
+ optional_params=("agregation", "function"),
109
+ aggregations=("MI", "QH", "HR", "DY"),
110
+ ),
111
+ "Frekvence": CepsMethod(
112
+ "System frequency, [Hz]",
113
+ ),
114
+ "Generation": CepsMethod(
115
+ "Gross generation by plant type, [MW]",
116
+ optional_params=("agregation", "function", "version", "para1"),
117
+ needed_params=("agregation", "function", "version", "para1"),
118
+ aggregations=("QH", "HR", "DY", "DYO", "DYP", "WE", "WEO", "WEP", "MO", "MOO", "MOP", "YR", "YRO", "YRP"),
119
+ versions=("RT",),
120
+ param_values={"para1": ("all", "AE", "JE", "PE", "PPE", "PVE", "VE", "ZE", "VTE", "FVE")},
121
+ ),
122
+ "GenerationPlan": CepsMethod(
123
+ "Planned gross generation, [MW]",
124
+ optional_params=("agregation", "function", "version"),
125
+ needed_params=("agregation", "function", "version"),
126
+ aggregations=("QH", "HR", "DY", "DYO", "DYP", "WE", "WEO", "WEP", "MO", "MOO", "MOP", "YR", "YRO", "YRP"),
127
+ versions=("RT",),
128
+ ),
129
+ "GenerationRES": CepsMethod(
130
+ "Estimated wind and solar generation, [MW]",
131
+ optional_params=("agregation", "function", "version", "para1"),
132
+ needed_params=("agregation", "function", "version", "para1"),
133
+ aggregations=("MI", "QH", "HR", "DY", "DYO", "DYP", "WE", "WEO", "WEP", "MO", "MOO", "MOP", "YR", "YRO", "YRP"),
134
+ versions=("RT",),
135
+ param_values={"para1": ("all", "VTE", "FVE")},
136
+ ),
137
+ "Load": CepsMethod(
138
+ "Load with and without pumping, [MW]; load is gross generation + import - export - pumping. The forecast versions have it without pumping only",
139
+ optional_params=("agregation", "function", "version"),
140
+ needed_params=("agregation", "function", "version"),
141
+ aggregations=("MI", "QH", "HR", "DY", "DYO", "DYP", "WE", "WEO", "WEP", "MO", "MOO", "MOP", "YR", "YRO", "YRP"),
142
+ versions=("RT", "DR", "DF", "WF", "MF", "YF"),
143
+ ),
144
+ "MaximalniCenySVRnaDT": CepsMethod(
145
+ "Maximum prices of balancing services on the day-ahead market, a row per day and currency. Data from 2022-08-26 to 2024-07-04",
146
+ response_type=CepsResponseType.TABLE,
147
+ ),
148
+ "NepredvidatelneOdmitnuteNabidky": CepsMethod(
149
+ "Unforeseeably rejected bids of one product (aFRR+ if none is given): per hour and bid the price offered [Kč] and the power not accepted [MW]",
150
+ optional_params=("param1",),
151
+ param_values={"param1": ("FCR", "aFRR+", "aFRR-", "mFRR+", "mFRR-", "mFRR5")},
152
+ response_type=CepsResponseType.TABLE,
153
+ ),
154
+ "OdhadovanaCenaOdchylky": CepsMethod(
155
+ "Estimated imbalance price, [Kč/MWh]. Not a plain time series",
156
+ response_type=CepsResponseType.ESTIMATED_PRICE,
157
+ ),
158
+ "OfferPrices": CepsMethod(
159
+ "Offer prices of regulating energy: per hour and bid the price [Kč] and the power [MW]. Data from November 2017 to 2022-03-31",
160
+ optional_params=("version", "param1"),
161
+ response_type=CepsResponseType.TABLE,
162
+ ),
163
+ "PowerBalance": CepsMethod(
164
+ "Yearly forecast of the power balance: domestic sources, outages, reserve of ancillary services and load, [MW]",
165
+ optional_params=("agregation", "function", "version", "para1"),
166
+ needed_params=("agregation", "function", "version", "para1"),
167
+ aggregations=("WE", "YR"),
168
+ versions=("YF",),
169
+ param_values={"para1": ("all", "BALANCE", "TDGENCAP", "TOUTAGES", "SYSSERVR", "LOAD")},
170
+ ),
171
+ }
172
+ #----------------------------------------------------------------------------------------------------------------------------
173
+ """
174
+ Values for aggregation.
175
+ Which codes a method supports differs, see CepsMethod.aggregations; a code it does
176
+ not support gives an empty document, not an error.
177
+
178
+ A time step comes back when it starts between dateFrom and dateTo, both
179
+ included, and it always covers the whole step, whichever part of it the
180
+ period asks for. So a period has to hold the start of a step to get it:
181
+ 1 January for YR, the first of the month for MO.
182
+
183
+ WE and WEO are wrong around the clock changes. For the week in which the
184
+ clocks go forward the service returns several rows, none of them the mean
185
+ of that week, which the wrappers refuse with a CepsError; the week after
186
+ lacks its first hour and the week of the change in autumn is slightly off
187
+ too. The days and months there (DY, MO) are right.
188
+ """
189
+ CEPS_AGGREGATIONS = {
190
+ "MI": "minute",
191
+ "QH": "quarter hour",
192
+ "HR": "hour",
193
+ "DY": "day",
194
+ "DYO": "day off-peak",
195
+ "DYP": "day peak",
196
+ "WE": "week",
197
+ "WEO": "week off-peak",
198
+ "WEP": "week peak",
199
+ "MO": "month",
200
+ "MOO": "month off-peak",
201
+ "MOP": "month peak",
202
+ "YR": "year",
203
+ "YRO": "year off-peak",
204
+ "YRP": "year peak",
205
+ }
206
+ #----------------------------------------------------------------------------------------------------------------------------
207
+ """
208
+ Values for function: how the raw values are combined within one
209
+ aggregation step.
210
+
211
+ An aggregation needs a function with it: without one the service fails
212
+ or returns rows that hold no values.
213
+ """
214
+ CEPS_FUNCTIONS = ("AVG", "MIN", "MAX", "SUM")
215
+ #----------------------------------------------------------------------------------------------------------------------------
216
+ """Labels for version, as listed by the DataVersion method. See ČEPS documentation for meaning"""
217
+ CEPS_DATA_VERSIONS = {
218
+ "RT": "real data",
219
+ "DF": "daily forecast",
220
+ "DR": "daily revision",
221
+ "IDF": "intraday forecast",
222
+ "WF": "weekly forecast",
223
+ "WR": "weekly revision",
224
+ "MF": "monthly forecast",
225
+ "MR": "monthly revision",
226
+ "QF": "quarterly forecast",
227
+ "QR": "quarterly revision",
228
+ "YF": "yearly forecast",
229
+ "YR": "yearly revision",
230
+ }
231
+ #----------------------------------------------------------------------------------------------------------------------------
ceps_api/client.py ADDED
@@ -0,0 +1,346 @@
1
+ """Client for the ČEPS SOAP service, with a wrapper for each of its methods."""
2
+ from __future__ import annotations
3
+
4
+ import inspect
5
+ from typing import Any
6
+
7
+ import pandas as pd
8
+
9
+ from . import soap
10
+ from .catalog import CEPS_AGGREGATIONS, CEPS_DATA_VERSIONS, CEPS_FUNCTIONS, CEPS_METHODS
11
+ from .convert import DateLike, to_ceps_time, to_dataframe
12
+ from .parsing import parse
13
+ from .soap import CepsError
14
+ from .validation import service_name, validate
15
+
16
+
17
+ class Ceps:
18
+ """
19
+ The ČEPS service
20
+
21
+ Every method of the service has a wrapper of the same name as
22
+ the ČEPS calls the method. The wrapper returns a DataFrame.
23
+
24
+ *Ceps.parameters()* lists the methods and
25
+ e.g. *Ceps.parameters("Generation")* lists the parameters it accepts.
26
+
27
+ *start* and *end* are Czech local time (see convert.to_ceps_time) and both are included:
28
+ with an *aggregation*, the steps that start between them come back, the
29
+ one that starts at end too. So a whole day is start="2026-10-05",
30
+ end="2026-10-05T23:59:00". A step that start cuts into is left out; one
31
+ that end cuts into comes back whole. The latest step keeps changing for
32
+ a few minutes after it is over, until all its minutes are in.
33
+
34
+ *aggregation* is the time step (see CEPS_AGGREGATIONS) and *function* is how
35
+ the values within it are combined (see CEPS_FUNCTIONS); function is only
36
+ sent with an aggregation.
37
+
38
+ Time series come back indexed by "timestamp", the start of the step in
39
+ Europe/Prague time, with a column per series as ČEPS names it.
40
+
41
+ A wrapper raises ValueError for arguments the service would not accept
42
+ (see validation.validate), before anything is sent, and CepsError if
43
+ the service fails or a SERIES method returns something that is not one
44
+ row per timestamp.
45
+ """
46
+ #----------------------------------------------------------------------------------------------------------------------------
47
+ def __init__(self, timeout: int = 30):
48
+ self.timeout = timeout # timeout in seconds
49
+ #----------------------------------------------------------------------------------------------------------------------------
50
+ def _fetch(self, method: str, **args: Any) -> pd.DataFrame:
51
+ """
52
+ Calls a method with the arguments of its wrapper: renames them to
53
+ what the service calls them, checks them, sends the request and
54
+ parses the answer. None arguments are left out, and so is function
55
+ without an aggregation.
56
+
57
+ The check gets the wrapper's names along, so that an error names
58
+ the argument of the wrapper (param1), not the service's (para1).
59
+ """
60
+ params: dict[str, Any] = {}
61
+ shown: dict[str, str] = {}
62
+ for name, value in args.items():
63
+ service = service_name(method, name)
64
+ # Every argument is named, a left out one is still reported as missing.
65
+ shown[service] = name
66
+ if value is not None:
67
+ params[service] = value
68
+ validate(method, params, shown)
69
+ # Dropped only after the check, which would otherwise report the
70
+ # function missing too when a needed aggregation is.
71
+ if "agregation" not in params:
72
+ params.pop("function", None)
73
+ for key in ("dateFrom", "dateTo"):
74
+ if key in params:
75
+ params[key] = to_ceps_time(params[key])
76
+ response_type = CEPS_METHODS[method].response_type
77
+ raw = soap.call(method, params, self.timeout)
78
+ try:
79
+ data = parse(response_type, raw)
80
+ except CepsError as e:
81
+ # The parser does not know which method it reads.
82
+ raise CepsError(f"{method}: {e}") from None
83
+ return to_dataframe(response_type, data)
84
+ #----------------------------------------------------------------------------------------------------------------------------
85
+ @staticmethod
86
+ def parameters(method: str | None = None) -> pd.DataFrame:
87
+ """
88
+ What the wrappers accept, for all methods or for just one.
89
+
90
+ Without *method* it gives an overview, a row per method of
91
+ CEPS_METHODS, indexed by "method", with the columns:
92
+
93
+ - description: what the method returns, as in the catalog
94
+ - arguments: the arguments of its wrapper, comma separated
95
+ - response_type: the shape of the answer (see CepsResponseType)
96
+
97
+ With *method* it gives a row per argument of that wrapper, indexed
98
+ by "parameter", with the columns:
99
+
100
+ - required: True if the wrapper has no default for it
101
+ - default: the wrapper's default, None for a required one
102
+ - allowed: the values the service accepts, comma separated, or
103
+ "not known" where the catalog does not list them
104
+
105
+ The arguments are read off the wrapper's signature, so they are
106
+ named as the wrapper names them (param1, aggregation), not as the
107
+ service does (para1, agregation). A method that takes no arguments,
108
+ e.g. DataAgregation, gives an empty frame.
109
+
110
+ Raises ValueError for a method that is not in CEPS_METHODS.
111
+
112
+ >>> Ceps.parameters() # every method
113
+ >>> Ceps.parameters("Generation") # the arguments of one
114
+ """
115
+ if method is None:
116
+ # The wrapper is looked up by name, Ceps has one for every
117
+ # method of the catalog.
118
+ return pd.DataFrame(
119
+ [{"method": name,
120
+ "description": info.description,
121
+ "arguments": ", ".join(p for p in inspect.signature(getattr(Ceps, name)).parameters
122
+ if p != "self"),
123
+ "response_type": info.response_type.value}
124
+ for name, info in CEPS_METHODS.items()]).set_index("method")
125
+ if method not in CEPS_METHODS:
126
+ raise ValueError(f"Unknown ČEPS method {method!r}, "
127
+ f"expected one of {', '.join(CEPS_METHODS)}")
128
+ info = CEPS_METHODS[method]
129
+ rows = []
130
+ for name, p in inspect.signature(getattr(Ceps, method)).parameters.items():
131
+ # Reached through the class, the wrapper still lists self.
132
+ if name == "self":
133
+ continue
134
+ if name in ("start", "end"):
135
+ # Anything convert.to_ceps_time takes.
136
+ allowed = "date, datetime or ISO string, Czech local time"
137
+ elif name == "aggregation":
138
+ # A method without its own list in the catalog takes any
139
+ # aggregation of the service.
140
+ allowed = ", ".join(info.aggregations or CEPS_AGGREGATIONS)
141
+ elif name == "function":
142
+ # The same for every method.
143
+ allowed = ", ".join(CEPS_FUNCTIONS)
144
+ elif name == "version":
145
+ # As with aggregation, no list of its own means all of them.
146
+ allowed = ", ".join(info.versions or CEPS_DATA_VERSIONS)
147
+ else:
148
+ # param1, param2, ...: the catalog keys their values by the
149
+ # service's name, which for some methods is para1, so the
150
+ # wrapper's name is translated first.
151
+ values = info.param_values.get(service_name(method, name))
152
+ allowed = ", ".join(values) if values else "not known"
153
+ # An argument without a default has to be given.
154
+ required = p.default is inspect.Parameter.empty
155
+ rows.append({"parameter": name,
156
+ "required": required,
157
+ "default": None if required else p.default,
158
+ "allowed": allowed})
159
+ return pd.DataFrame(rows, columns=["parameter", "required", "default", "allowed"]).set_index("parameter")
160
+ #----------------------------------------------------------------------------------------------------------------------------
161
+ #----------------------------------------------------------------------------------------------------------------------------
162
+
163
+ # START OF WRAPPERS FOR THE SERVICE METHODS
164
+
165
+ #----------------------------------------------------------------------------------------------------------------------------
166
+ #----------------------------------------------------------------------------------------------------------------------------
167
+ def AktivaceSVRvCR(self, start: DateLike, end: DateLike, aggregation: str | None = None,
168
+ function: str = "AVG", param1: str | None = None) -> pd.DataFrame:
169
+ """
170
+ Activated balancing energy in the Czech area, MW.
171
+
172
+ A column per product (aFRR+, aFRR-, mFRR+, mFRR-, mFRR5); the
173
+ downward ones (aFRR-, mFRR-) are negative. param1 picks one
174
+ product. Without an aggregation it is per minute.
175
+ """
176
+ return self._fetch("AktivaceSVRvCR", start=start, end=end, aggregation=aggregation,
177
+ function=function, param1=param1)
178
+ #----------------------------------------------------------------------------------------------------------------------------
179
+ def AktualniCenaRE(self, start: DateLike, end: DateLike, param1: str | None = None) -> pd.DataFrame:
180
+ """
181
+ Price of activated regulating energy, EUR/MWh.
182
+
183
+ A column per product (aFRR, mFRR+, mFRR-, mFRR5); param1 picks
184
+ one.
185
+
186
+ Always per minute: the method takes no aggregation. Resample the
187
+ frame for quarter hours.
188
+ """
189
+ return self._fetch("AktualniCenaRE", start=start, end=end, param1=param1)
190
+ #----------------------------------------------------------------------------------------------------------------------------
191
+ def AktualniSystemovaOdchylkaCR(self, start: DateLike, end: DateLike, aggregation: str | None = None,
192
+ function: str = "AVG") -> pd.DataFrame:
193
+ """
194
+ System imbalance of the Czech control area, MW. Negative means the
195
+ system is short (generation below consumption).
196
+
197
+ Without an aggregation it is per minute; the minute series has
198
+ gaps of a few minutes a day.
199
+ """
200
+ return self._fetch("AktualniSystemovaOdchylkaCR", start=start, end=end,
201
+ aggregation=aggregation, function=function)
202
+ #----------------------------------------------------------------------------------------------------------------------------
203
+ def CrossborderPowerFlows(self, start: DateLike, end: DateLike, aggregation: str = "QH",
204
+ function: str = "AVG", version: str | None = None) -> pd.DataFrame:
205
+ """Actual and planned flows per neighbouring operator, MW."""
206
+ return self._fetch("CrossborderPowerFlows", start=start, end=end, aggregation=aggregation,
207
+ function=function, version=version)
208
+ #----------------------------------------------------------------------------------------------------------------------------
209
+ def DataAgregation(self) -> pd.DataFrame:
210
+ """The aggregation codes of the service and their descriptions."""
211
+ return self._fetch("DataAgregation")
212
+ #----------------------------------------------------------------------------------------------------------------------------
213
+ def DataVersion(self) -> pd.DataFrame:
214
+ """The data version codes of the service and their descriptions."""
215
+ return self._fetch("DataVersion")
216
+ #----------------------------------------------------------------------------------------------------------------------------
217
+ def EmergencyExchange(self, start: DateLike, end: DateLike, aggregation: str = "QH", function: str = "AVG",
218
+ version: str = "RT", param1: str | None = None,
219
+ param2: str | None = None) -> pd.DataFrame:
220
+ """
221
+ Emergency assistance, redispatch and regulating energy from abroad,
222
+ a row per step, border and direction; only has rows when something
223
+ happened. param1 picks the kind (E, R), param2 the operator.
224
+ """
225
+ return self._fetch("EmergencyExchange", start=start, end=end, aggregation=aggregation,
226
+ function=function, version=version, param1=param1, param2=param2)
227
+ #----------------------------------------------------------------------------------------------------------------------------
228
+ def EmergencyExchangeACER(self, start: DateLike, end: DateLike, param1: str | None = None,
229
+ param2: str | None = None) -> pd.DataFrame:
230
+ """
231
+ The emergency exchange as an ACER message: one row however long
232
+ the period, a column per field, all text. The remarks field holds
233
+ the exchanges themselves, one "Direction-Type-MWh|" line each.
234
+ """
235
+ return self._fetch("EmergencyExchangeACER", start=start, end=end, param1=param1, param2=param2)
236
+ #----------------------------------------------------------------------------------------------------------------------------
237
+ def Emise(self, start: DateLike, end: DateLike, aggregation: str | None = None, function: str = "AVG",
238
+ version: str = "RT", param1: str | None = None) -> pd.DataFrame:
239
+ """
240
+ CO2 intensity of generation, g CO2/kWh. Always per quarter hour,
241
+ the service ignores the aggregation. The values of param1 are not
242
+ known.
243
+ """
244
+ return self._fetch("Emise", start=start, end=end, aggregation=aggregation, function=function,
245
+ version=version, param1=param1)
246
+ #----------------------------------------------------------------------------------------------------------------------------
247
+ def ExportImportSVR(self, start: DateLike, end: DateLike, aggregation: str | None = None,
248
+ function: str = "AVG", param1: str | None = None) -> pd.DataFrame:
249
+ """
250
+ Exchange with the European balancing platforms (imbalance netting,
251
+ MARI, PICASSO), MW. param1 picks one series.
252
+ """
253
+ return self._fetch("ExportImportSVR", start=start, end=end, aggregation=aggregation,
254
+ function=function, param1=param1)
255
+ #----------------------------------------------------------------------------------------------------------------------------
256
+ def ExportImportVykonu(self, start: DateLike, end: DateLike, aggregation: str | None = None,
257
+ function: str = "AVG") -> pd.DataFrame:
258
+ """
259
+ Export (+) and import (-) of power within the European platforms
260
+ and foreign assistance, MW. Data from 2022-05-20 to 2023-08-25,
261
+ then ExportImportSVR.
262
+ """
263
+ return self._fetch("ExportImportVykonu", start=start, end=end, aggregation=aggregation,
264
+ function=function)
265
+ #----------------------------------------------------------------------------------------------------------------------------
266
+ def Frekvence(self, start: DateLike, end: DateLike) -> pd.DataFrame:
267
+ """System frequency, Hz."""
268
+ return self._fetch("Frekvence", start=start, end=end)
269
+ #----------------------------------------------------------------------------------------------------------------------------
270
+ def Generation(self, start: DateLike, end: DateLike, aggregation: str = "QH", function: str = "AVG",
271
+ version: str = "RT", param1: str = "all") -> pd.DataFrame:
272
+ """Gross generation by plant type, MW. param1 picks one type."""
273
+ return self._fetch("Generation", start=start, end=end, aggregation=aggregation, function=function,
274
+ version=version, param1=param1)
275
+ #----------------------------------------------------------------------------------------------------------------------------
276
+ def GenerationPlan(self, start: DateLike, end: DateLike, aggregation: str = "QH", function: str = "AVG",
277
+ version: str = "RT") -> pd.DataFrame:
278
+ """Planned gross generation, MW."""
279
+ return self._fetch("GenerationPlan", start=start, end=end, aggregation=aggregation,
280
+ function=function, version=version)
281
+ #----------------------------------------------------------------------------------------------------------------------------
282
+ def GenerationRES(self, start: DateLike, end: DateLike, aggregation: str = "QH", function: str = "AVG",
283
+ version: str = "RT", param1: str = "all") -> pd.DataFrame:
284
+ """Estimated wind (VTE) and solar (FVE) generation, MW. param1 picks one."""
285
+ return self._fetch("GenerationRES", start=start, end=end, aggregation=aggregation,
286
+ function=function, version=version, param1=param1)
287
+ #----------------------------------------------------------------------------------------------------------------------------
288
+ def Load(self, start: DateLike, end: DateLike, aggregation: str = "QH", function: str = "AVG",
289
+ version: str = "RT") -> pd.DataFrame:
290
+ """
291
+ Load with and without pumping, MW; load is gross generation +
292
+ import - export - pumping. The forecast versions have it without
293
+ pumping only.
294
+ """
295
+ return self._fetch("Load", start=start, end=end, aggregation=aggregation, function=function,
296
+ version=version)
297
+ #----------------------------------------------------------------------------------------------------------------------------
298
+ def MaximalniCenySVRnaDT(self, start: DateLike, end: DateLike) -> pd.DataFrame:
299
+ """
300
+ Maximum prices of balancing services on the day-ahead market, a row
301
+ per day and currency. Data from 2022-08-26 to 2024-07-04.
302
+ """
303
+ return self._fetch("MaximalniCenySVRnaDT", start=start, end=end)
304
+ #----------------------------------------------------------------------------------------------------------------------------
305
+ def NepredvidatelneOdmitnuteNabidky(self, start: DateLike, end: DateLike,
306
+ param1: str | None = None) -> pd.DataFrame:
307
+ """
308
+ Unforeseeably rejected bids of one product (param1, aFRR+ if none
309
+ is given): per hour and bid the price offered (Kč) and the power
310
+ not accepted (MW).
311
+ """
312
+ return self._fetch("NepredvidatelneOdmitnuteNabidky", start=start, end=end, param1=param1)
313
+ #----------------------------------------------------------------------------------------------------------------------------
314
+ def OdhadovanaCenaOdchylky(self, start: DateLike, end: DateLike) -> pd.DataFrame:
315
+ """
316
+ ČEPS's estimate of the imbalance price, Kč/MWh, per quarter hour
317
+ (per hour for older data, e.g. 2020). A range in the future or with
318
+ end before start gives an empty frame.
319
+
320
+ A price of exactly 0 is common (about one interval in five) and is
321
+ a real value. The service ignores aggregation and function here.
322
+ """
323
+ return self._fetch("OdhadovanaCenaOdchylky", start=start, end=end)
324
+ #----------------------------------------------------------------------------------------------------------------------------
325
+ def OfferPrices(self, start: DateLike, end: DateLike, version: str | None = None,
326
+ param1: str | None = None) -> pd.DataFrame:
327
+ """
328
+ Offer prices of regulating energy: per hour and bid the price (Kč)
329
+ and the power (MW). Data from November 2017 to 2022-03-31.
330
+ """
331
+ return self._fetch("OfferPrices", start=start, end=end, version=version, param1=param1)
332
+ #----------------------------------------------------------------------------------------------------------------------------
333
+ def PowerBalance(self, start: DateLike, end: DateLike, aggregation: str = "WE", function: str = "AVG",
334
+ version: str = "YF", param1: str = "all") -> pd.DataFrame:
335
+ """
336
+ Yearly forecast of the power balance: domestic sources, outages,
337
+ reserve of ancillary services and load, MW. param1 picks one part.
338
+ """
339
+ return self._fetch("PowerBalance", start=start, end=end, aggregation=aggregation,
340
+ function=function, version=version, param1=param1)
341
+ #----------------------------------------------------------------------------------------------------------------------------
342
+
343
+ # A method added to the catalog needs its wrapper too.
344
+ _missing = [m for m in CEPS_METHODS if not callable(getattr(Ceps, m, None))]
345
+ if _missing:
346
+ raise ImportError(f"Ceps has no wrapper for {', '.join(_missing)}")