python-esios 2.4.0__py3-none-any.whl → 2.4.1__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.
@@ -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 spot | OMIE spot market price | ES, PT, FR, DE, BE, NL |
64
- | 1001 | Precio mercado diario | Day-ahead market price | ES |
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
 
@@ -221,6 +258,11 @@ class I90Sheet:
221
258
  return pd.DataFrame()
222
259
 
223
260
  columns_date = self._normalize_datetime_columns(columns_prior[idx_col_start:])
261
+ # _normalize_datetime_columns sets _n_columns_totals from the time-block
262
+ # content (NaN-filler vs sequential). Override with a header-label count
263
+ # so the index slice survives REE format revisions that add or drop a
264
+ # "Total" column without touching the time-axis encoding.
265
+ self._n_columns_totals = _count_header_separators(columns_prior, idx_col_start)
224
266
  columns_variable = columns[idx_col_start:]
225
267
  columns_index = columns[: idx_col_start - self._n_columns_totals]
226
268
 
@@ -230,6 +272,7 @@ class I90Sheet:
230
272
  self, idx_col_start: int, columns: np.ndarray
231
273
  ) -> tuple[np.ndarray, np.ndarray, np.ndarray, None]:
232
274
  columns_date = self._normalize_datetime_columns(columns[idx_col_start:])
275
+ self._n_columns_totals = _count_header_separators(columns, idx_col_start)
233
276
  columns_index = columns[: idx_col_start - self._n_columns_totals]
234
277
  return columns, columns_index, columns_date, None
235
278
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-esios
3
- Version: 2.4.0
3
+ Version: 2.4.1
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) # PVPC price
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 | PVPC | Voluntary price for small consumers |
103
- | 1001 | Day-ahead price | OMIE spot market price |
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=_5wCzMMB8FHWcAPeMA5vGklZFEGBEvU5wBOryNIogzM,6252
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=fI8DfY8CD2kF1_ZrAzuEDxN0m7Vh3CV3dIn32lxKffA,11687
35
+ esios/processing/i90.py,sha256=UZEI6f0pG6mbMaVTh_L3KtUp9PuvxvfXYaG9-ePNIxg,13622
36
36
  esios/processing/zip.py,sha256=12LbFHJTdX_h3JG-clEgQ4Haj-kw0UjfopGLlCRXfGM,1913
37
- python_esios-2.4.0.dist-info/METADATA,sha256=STVMDUwpgk6ZOx79KXOMPwn-t1aIvhB8MdsBmQtfdkk,3169
38
- python_esios-2.4.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
39
- python_esios-2.4.0.dist-info/entry_points.txt,sha256=7ngseyIyvJ4buTHFL9htaZ4tTFHpG4zzJNkc8B5Jr8U,40
40
- python_esios-2.4.0.dist-info/licenses/LICENSE,sha256=LorLs1-VeBW70Wo9fLAtLJN7nNd6Poy0xzvqdWVqFlE,35128
41
- python_esios-2.4.0.dist-info/RECORD,,
37
+ python_esios-2.4.1.dist-info/METADATA,sha256=_qFX68pzna9JIBW1zm57X8WNxZ70BO8zaxC4g1nbmTM,3194
38
+ python_esios-2.4.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
39
+ python_esios-2.4.1.dist-info/entry_points.txt,sha256=7ngseyIyvJ4buTHFL9htaZ4tTFHpG4zzJNkc8B5Jr8U,40
40
+ python_esios-2.4.1.dist-info/licenses/LICENSE,sha256=LorLs1-VeBW70Wo9fLAtLJN7nNd6Poy0xzvqdWVqFlE,35128
41
+ python_esios-2.4.1.dist-info/RECORD,,