python-esios 2.4.0__py3-none-any.whl → 2.4.2__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.
- esios/.agents/skills/esios/SKILL.md +2 -2
- esios/processing/i90.py +67 -4
- {python_esios-2.4.0.dist-info → python_esios-2.4.2.dist-info}/METADATA +4 -4
- {python_esios-2.4.0.dist-info → python_esios-2.4.2.dist-info}/RECORD +7 -7
- {python_esios-2.4.0.dist-info → python_esios-2.4.2.dist-info}/WHEEL +1 -1
- {python_esios-2.4.0.dist-info → python_esios-2.4.2.dist-info}/entry_points.txt +0 -0
- {python_esios-2.4.0.dist-info → python_esios-2.4.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -60,8 +60,8 @@ print(sheet.frequency) # "hourly" or "hourly-quarterly"
|
|
|
60
60
|
|
|
61
61
|
| ID | Name | Description | Geos |
|
|
62
62
|
|----|------|-------------|------|
|
|
63
|
-
| 600 | Precio mercado
|
|
64
|
-
| 1001 |
|
|
63
|
+
| 600 | Precio mercado SPOT Diario | OMIE spot / day-ahead market price | ES, PT, FR, DE, BE, NL |
|
|
64
|
+
| 1001 | PVPC T. 2.0TD | Término de facturación de energía activa del PVPC (voluntary price for small consumers) | Península, Canarias, Baleares, Ceuta, Melilla |
|
|
65
65
|
| 10033 | Demanda real | Real-time electricity demand | ES |
|
|
66
66
|
| 10034 | Generación eólica | Real-time wind generation | ES |
|
|
67
67
|
| 10035 | Generación solar FV | Real-time solar PV generation | ES |
|
esios/processing/i90.py
CHANGED
|
@@ -33,6 +33,43 @@ def _any_value_greater_than_30(series: np.ndarray) -> bool:
|
|
|
33
33
|
return any(v > 30 for v in series if isinstance(v, (int, float, np.integer, np.floating)) and not np.isnan(v))
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
# Labels that REE uses for the cells sitting between the index columns and the
|
|
37
|
+
# per-period value columns of an I90 sheet. Match is exact (case-insensitive,
|
|
38
|
+
# trimmed). The count of these cells per sheet has varied across REE format
|
|
39
|
+
# revisions — historically 2 ("Hora" / "Cuarto de Hora del dia" + "Total");
|
|
40
|
+
# from Oct 2025 the MTU 15-min transition dropped "Total" on several sheets,
|
|
41
|
+
# leaving 1. Counting them dynamically keeps the parser resilient to either.
|
|
42
|
+
_SEPARATOR_LABELS = frozenset({
|
|
43
|
+
"cuarto de hora del dia",
|
|
44
|
+
"hora del dia",
|
|
45
|
+
"hora",
|
|
46
|
+
"total",
|
|
47
|
+
"indicadores",
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _count_header_separators(row: np.ndarray, idx_col_start: int) -> int:
|
|
52
|
+
"""Count separator cells immediately preceding the time-value block.
|
|
53
|
+
|
|
54
|
+
Walks ``row`` backwards from ``idx_col_start - 1``, incrementing on each
|
|
55
|
+
cell whose text matches a known separator label and stopping at the first
|
|
56
|
+
non-matching cell or NaN. A NaN cell signals the start of the index-column
|
|
57
|
+
placeholder zone in double-header layouts (where index labels live on the
|
|
58
|
+
other header row, leaving the date row blank under each index position).
|
|
59
|
+
"""
|
|
60
|
+
n = 0
|
|
61
|
+
for i in range(idx_col_start - 1, -1, -1):
|
|
62
|
+
cell = row[i]
|
|
63
|
+
if cell is None or (isinstance(cell, float) and np.isnan(cell)):
|
|
64
|
+
break
|
|
65
|
+
text = str(cell).strip().lower()
|
|
66
|
+
if text in _SEPARATOR_LABELS:
|
|
67
|
+
n += 1
|
|
68
|
+
continue
|
|
69
|
+
break
|
|
70
|
+
return n
|
|
71
|
+
|
|
72
|
+
|
|
36
73
|
class I90Book:
|
|
37
74
|
"""Represents an I90DIA workbook (XLS) with lazy sheet preprocessing.
|
|
38
75
|
|
|
@@ -163,18 +200,29 @@ class I90Sheet:
|
|
|
163
200
|
arr[arr == ""] = np.nan
|
|
164
201
|
return arr
|
|
165
202
|
|
|
166
|
-
def _normalize_datetime_columns(
|
|
203
|
+
def _normalize_datetime_columns(
|
|
204
|
+
self, columns: np.ndarray, n_measures: int = 1
|
|
205
|
+
) -> np.ndarray:
|
|
167
206
|
"""Normalize time column headers to integer period indices.
|
|
168
207
|
|
|
169
|
-
Handles
|
|
208
|
+
Handles five column formats found in I90 files:
|
|
170
209
|
|
|
171
210
|
1. Sequential integers: 1–24 (hourly) or 1–96 (quarterly)
|
|
172
211
|
2. H-Q format: "1-1", "1-2", "1-3", "1-4", "2-1", …
|
|
173
|
-
3. NaN-filler format: [1, NaN, NaN, NaN, 2, …]
|
|
212
|
+
3. NaN-filler format: [1, NaN, NaN, NaN, 2, …] — one period number per
|
|
213
|
+
hour, three NaN fillers for the remaining quarters (single measure).
|
|
174
214
|
4. Range format (DST days): "00-01", "01-02", "02-03a", "02-03b", …
|
|
175
215
|
where the first number is the start hour and a/b suffix marks
|
|
176
216
|
the repeated hour on fall-back days. Detected by the first
|
|
177
217
|
column starting with "0" (e.g. "00-01").
|
|
218
|
+
5. Multi-measure format: [1, NaN, 2, NaN, …] — each period number labels
|
|
219
|
+
a block of `n_measures` sub-columns (e.g. MW + €/MWh paired per
|
|
220
|
+
quarter-hour in offer sheets), the NaN fillers being the extra
|
|
221
|
+
measures. The period number is on every n_measures-th column.
|
|
222
|
+
|
|
223
|
+
`n_measures` is the count of distinct measure sub-columns per period
|
|
224
|
+
(1 for single-measure sheets). It disambiguates format 3 from format 5,
|
|
225
|
+
which share the same NaN-filler shape but mean opposite things.
|
|
178
226
|
"""
|
|
179
227
|
if any(pd.isna(columns)):
|
|
180
228
|
self._n_columns_totals = 3
|
|
@@ -203,6 +251,10 @@ class I90Sheet:
|
|
|
203
251
|
)
|
|
204
252
|
return ((hours - 1) * 4 + quarters).values
|
|
205
253
|
|
|
254
|
+
# Multi-measure format: each period number labels a block of n_measures sub-columns (e.g. MW + €/MWh). The ffilled value already IS the period index and the duplication is the measures — keep it so every measure of a period shares one datetime (paired), instead of spreading them across sequential slots.
|
|
255
|
+
if n_measures > 1:
|
|
256
|
+
return hours.values
|
|
257
|
+
|
|
206
258
|
# NaN-filler quarterly format: after ffill the same hour number repeats
|
|
207
259
|
# four times (quarters share the hour label). Assign sequential indices.
|
|
208
260
|
if hours.duplicated().any():
|
|
@@ -220,8 +272,18 @@ class I90Sheet:
|
|
|
220
272
|
if idx_col_start == -1:
|
|
221
273
|
return pd.DataFrame()
|
|
222
274
|
|
|
223
|
-
columns_date = self._normalize_datetime_columns(columns_prior[idx_col_start:])
|
|
224
275
|
columns_variable = columns[idx_col_start:]
|
|
276
|
+
# Distinct measure labels per period (e.g. {MW, €/MWh} -> 2). Disambiguates the NaN-filler time header (single measure, expand to sequential quarters) from the multi-measure paired layout (keep the period index so measures pair).
|
|
277
|
+
measure_labels = pd.Series(columns_variable).dropna()
|
|
278
|
+
n_measures = int(measure_labels.nunique()) if len(measure_labels) else 1
|
|
279
|
+
columns_date = self._normalize_datetime_columns(
|
|
280
|
+
columns_prior[idx_col_start:], n_measures=n_measures
|
|
281
|
+
)
|
|
282
|
+
# _normalize_datetime_columns sets _n_columns_totals from the time-block
|
|
283
|
+
# content (NaN-filler vs sequential). Override with a header-label count
|
|
284
|
+
# so the index slice survives REE format revisions that add or drop a
|
|
285
|
+
# "Total" column without touching the time-axis encoding.
|
|
286
|
+
self._n_columns_totals = _count_header_separators(columns_prior, idx_col_start)
|
|
225
287
|
columns_index = columns[: idx_col_start - self._n_columns_totals]
|
|
226
288
|
|
|
227
289
|
return columns, columns_index, columns_date, columns_variable
|
|
@@ -230,6 +292,7 @@ class I90Sheet:
|
|
|
230
292
|
self, idx_col_start: int, columns: np.ndarray
|
|
231
293
|
) -> tuple[np.ndarray, np.ndarray, np.ndarray, None]:
|
|
232
294
|
columns_date = self._normalize_datetime_columns(columns[idx_col_start:])
|
|
295
|
+
self._n_columns_totals = _count_header_separators(columns, idx_col_start)
|
|
233
296
|
columns_index = columns[: idx_col_start - self._n_columns_totals]
|
|
234
297
|
return columns, columns_index, columns_date, None
|
|
235
298
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-esios
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: A Python wrapper for the ESIOS API (Spanish electricity market)
|
|
5
5
|
Project-URL: Homepage, https://github.com/datons/python-esios
|
|
6
6
|
Project-URL: Repository, https://github.com/datons/python-esios
|
|
@@ -85,7 +85,7 @@ from esios import ESIOSClient
|
|
|
85
85
|
client = ESIOSClient()
|
|
86
86
|
|
|
87
87
|
# Get indicator data as DataFrame
|
|
88
|
-
handle = client.indicators.get(600) #
|
|
88
|
+
handle = client.indicators.get(600) # Day-ahead spot price (OMIE)
|
|
89
89
|
df = handle.historical("2025-01-01", "2025-01-31")
|
|
90
90
|
|
|
91
91
|
# Search indicators
|
|
@@ -99,8 +99,8 @@ client.archives.download(1, start="2025-01-01", end="2025-01-31", output_dir="./
|
|
|
99
99
|
|
|
100
100
|
| ID | Name | Description |
|
|
101
101
|
|----|------|-------------|
|
|
102
|
-
| 600 |
|
|
103
|
-
| 1001 |
|
|
102
|
+
| 600 | Day-ahead price | OMIE spot market price |
|
|
103
|
+
| 1001 | PVPC | Voluntary price for small consumers (2.0TD) |
|
|
104
104
|
| 10033 | Demand | Real-time electricity demand |
|
|
105
105
|
| 10034 | Wind generation | Real-time wind generation |
|
|
106
106
|
| 10035 | Solar PV generation | Real-time solar generation |
|
|
@@ -5,7 +5,7 @@ esios/catalog.py,sha256=xWwMx5I32m34npjAXHh-Ua4e_0pfG89yxUC_Vy9VlAA,16811
|
|
|
5
5
|
esios/client.py,sha256=rLgdyPFII6CC_TJwgkHaScJ7nBUpt85N94mujKAn0d0,5825
|
|
6
6
|
esios/constants.py,sha256=yfxSNG37i4dkpa7x0CBvXTroyddn5jhNTuWGDhAq3-0,1074
|
|
7
7
|
esios/exceptions.py,sha256=AiWLdRDWj50JEsld9CvVBsfLnZZKFmW62_bZmZ7Z_eA,899
|
|
8
|
-
esios/.agents/skills/esios/SKILL.md,sha256=
|
|
8
|
+
esios/.agents/skills/esios/SKILL.md,sha256=D1wXiKyk7HoFw6CapccoORrtMXUpS2BuAVEChLu3AJE,6375
|
|
9
9
|
esios/cli/__init__.py,sha256=9gd5ZDIH1-yNP_xcd60ethOFXm9w6un0CJ9CX0Qvb2A,256
|
|
10
10
|
esios/cli/app.py,sha256=j1d8QWtKTTsWozSqqQitTkzzRjBE6OXY0ZZWYdS19wE,1524
|
|
11
11
|
esios/cli/archives.py,sha256=Re9ZMauTiJlHdmiE7F3ZlV2wfaEyShS0C7Z4M2X4Ra8,7715
|
|
@@ -32,10 +32,10 @@ esios/models/indicator.py,sha256=u1AJyEA3YeOqQFjV08_lzyMaofuCiMoLPjvosls9gfE,111
|
|
|
32
32
|
esios/models/offer_indicator.py,sha256=nA80Y7Yp0utDaDOdZ-ObcWTsAdhvuXlfJjJBpdVQ7Lo,758
|
|
33
33
|
esios/processing/__init__.py,sha256=1kLt_gO_wDhXM1BbY0zTyfAYo-CjYKW1ljgRRDZ7USM,278
|
|
34
34
|
esios/processing/dataframes.py,sha256=OitzBvAerssGP2VXNC-sSO48XsHdIB2nKTUgByN5eYQ,2524
|
|
35
|
-
esios/processing/i90.py,sha256=
|
|
35
|
+
esios/processing/i90.py,sha256=oo7CWKiHvCJjRIifr-hdXsqEH4JfFoFzY-LWVfw-brE,15107
|
|
36
36
|
esios/processing/zip.py,sha256=12LbFHJTdX_h3JG-clEgQ4Haj-kw0UjfopGLlCRXfGM,1913
|
|
37
|
-
python_esios-2.4.
|
|
38
|
-
python_esios-2.4.
|
|
39
|
-
python_esios-2.4.
|
|
40
|
-
python_esios-2.4.
|
|
41
|
-
python_esios-2.4.
|
|
37
|
+
python_esios-2.4.2.dist-info/METADATA,sha256=KeSOrUKqPGOOx0LfWfovkaAahwtMFEAReh_C96OKyJo,3194
|
|
38
|
+
python_esios-2.4.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
39
|
+
python_esios-2.4.2.dist-info/entry_points.txt,sha256=7ngseyIyvJ4buTHFL9htaZ4tTFHpG4zzJNkc8B5Jr8U,40
|
|
40
|
+
python_esios-2.4.2.dist-info/licenses/LICENSE,sha256=LorLs1-VeBW70Wo9fLAtLJN7nNd6Poy0xzvqdWVqFlE,35128
|
|
41
|
+
python_esios-2.4.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|