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 +29 -0
- ceps_api/catalog.py +231 -0
- ceps_api/client.py +346 -0
- ceps_api/convert.py +88 -0
- ceps_api/models.py +63 -0
- ceps_api/parsing.py +251 -0
- ceps_api/py.typed +0 -0
- ceps_api/soap.py +99 -0
- ceps_api/validation.py +77 -0
- ceps_api-0.1.0.dist-info/METADATA +346 -0
- ceps_api-0.1.0.dist-info/RECORD +13 -0
- ceps_api-0.1.0.dist-info/WHEEL +4 -0
- ceps_api-0.1.0.dist-info/licenses/LICENSE +21 -0
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)}")
|