python-esios 2.4.1__tar.gz → 2.4.2__tar.gz
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.
- python_esios-2.4.2/.release-please-manifest.json +3 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/CHANGELOG.md +7 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/PKG-INFO +1 -1
- {python_esios-2.4.1 → python_esios-2.4.2}/pyproject.toml +1 -1
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/processing/i90.py +25 -5
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/test_i90.py +46 -0
- python_esios-2.4.1/.release-please-manifest.json +0 -3
- {python_esios-2.4.1 → python_esios-2.4.2}/.github/workflows/release-please.yml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/.gitignore +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/CLAUDE.md +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/LICENSE +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/README.md +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/.gitignore +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/01_Quickstart/01_Setup and First Query.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/01_Search Indicators.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/02_Historical Data.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/03_Multi-Geography Indicators.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/04_Compare Indicators.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/05_Market Prices.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/06_Generation and Demand.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/03_Archives/01_Download Archives.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/03_Archives/02_I90 Settlement Files.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/04_Caching/01_Cache Management.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/05_Advanced/01_Ad-hoc Pandas Expressions.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/05_Advanced/02_Async Client.ipynb +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/01_Quickstart/01_Setup and First Query.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/01_Search Indicators.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/02_Historical Data.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/03_Multi-Geography Indicators.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/04_Compare Indicators.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/05_Market Prices.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/06_Generation and Demand.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/03_Archives/01_Download Archives.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/03_Archives/02_I90 Settlement Files.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/04_Caching/01_Cache Management.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/05_Advanced/01_Ad-hoc Pandas Expressions.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/05_Advanced/02_Async Client.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/examples/generate.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/release-please-config.json +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/.agents/skills/esios/SKILL.md +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/__init__.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/async_client.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cache.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/catalog.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/__init__.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/app.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/archives.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/cache_cmd.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/catalog_cmd.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/config.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/config_cmd.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/exec_cmd.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/cli/indicators.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/client.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/constants.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/data/__init__.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/data/archives.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/data/geos.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/data/indicators.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/data/magnitudes.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/data/time_periods.yaml +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/exceptions.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/managers/__init__.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/managers/archives.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/managers/base.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/managers/indicators.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/managers/offer_indicators.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/models/__init__.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/models/archive.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/models/indicator.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/models/offer_indicator.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/processing/__init__.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/processing/dataframes.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/src/esios/processing/zip.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/__init__.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/conftest.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/test_cache.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/test_client.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/test_dataframes.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/test_exceptions.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/test_managers.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/test_models.py +0 -0
- {python_esios-2.4.1 → python_esios-2.4.2}/tests/test_zip.py +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.2](https://github.com/datons/python-esios/compare/python-esios-v2.4.1...python-esios-v2.4.2) (2026-06-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **i90:** pair MW and price per quarter-hour in multi-measure sheets ([06efdb6](https://github.com/datons/python-esios/commit/06efdb660077cc36c4435f0e36ae5f24e68e9189))
|
|
9
|
+
|
|
3
10
|
## [2.4.1](https://github.com/datons/python-esios/compare/python-esios-v2.4.0...python-esios-v2.4.1) (2026-05-19)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -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
|
|
@@ -200,18 +200,29 @@ class I90Sheet:
|
|
|
200
200
|
arr[arr == ""] = np.nan
|
|
201
201
|
return arr
|
|
202
202
|
|
|
203
|
-
def _normalize_datetime_columns(
|
|
203
|
+
def _normalize_datetime_columns(
|
|
204
|
+
self, columns: np.ndarray, n_measures: int = 1
|
|
205
|
+
) -> np.ndarray:
|
|
204
206
|
"""Normalize time column headers to integer period indices.
|
|
205
207
|
|
|
206
|
-
Handles
|
|
208
|
+
Handles five column formats found in I90 files:
|
|
207
209
|
|
|
208
210
|
1. Sequential integers: 1–24 (hourly) or 1–96 (quarterly)
|
|
209
211
|
2. H-Q format: "1-1", "1-2", "1-3", "1-4", "2-1", …
|
|
210
|
-
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).
|
|
211
214
|
4. Range format (DST days): "00-01", "01-02", "02-03a", "02-03b", …
|
|
212
215
|
where the first number is the start hour and a/b suffix marks
|
|
213
216
|
the repeated hour on fall-back days. Detected by the first
|
|
214
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.
|
|
215
226
|
"""
|
|
216
227
|
if any(pd.isna(columns)):
|
|
217
228
|
self._n_columns_totals = 3
|
|
@@ -240,6 +251,10 @@ class I90Sheet:
|
|
|
240
251
|
)
|
|
241
252
|
return ((hours - 1) * 4 + quarters).values
|
|
242
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
|
+
|
|
243
258
|
# NaN-filler quarterly format: after ffill the same hour number repeats
|
|
244
259
|
# four times (quarters share the hour label). Assign sequential indices.
|
|
245
260
|
if hours.duplicated().any():
|
|
@@ -257,13 +272,18 @@ class I90Sheet:
|
|
|
257
272
|
if idx_col_start == -1:
|
|
258
273
|
return pd.DataFrame()
|
|
259
274
|
|
|
260
|
-
|
|
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
|
+
)
|
|
261
282
|
# _normalize_datetime_columns sets _n_columns_totals from the time-block
|
|
262
283
|
# content (NaN-filler vs sequential). Override with a header-label count
|
|
263
284
|
# so the index slice survives REE format revisions that add or drop a
|
|
264
285
|
# "Total" column without touching the time-axis encoding.
|
|
265
286
|
self._n_columns_totals = _count_header_separators(columns_prior, idx_col_start)
|
|
266
|
-
columns_variable = columns[idx_col_start:]
|
|
267
287
|
columns_index = columns[: idx_col_start - self._n_columns_totals]
|
|
268
288
|
|
|
269
289
|
return columns, columns_index, columns_date, columns_variable
|
|
@@ -55,6 +55,17 @@ def _mixed_hq_columns(n_hours: int = 24) -> np.ndarray:
|
|
|
55
55
|
return np.array(cols, dtype=object)
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def _multi_measure_columns(n_periods: int = 96, n_measures: int = 2) -> np.ndarray:
|
|
59
|
+
"""Multi-measure paired columns: the period number labels a block of
|
|
60
|
+
n_measures sub-columns (e.g. MW + €/MWh), the rest NaN: [1, NaN, 2, NaN, …].
|
|
61
|
+
"""
|
|
62
|
+
cols: list = []
|
|
63
|
+
for p in range(1, n_periods + 1):
|
|
64
|
+
cols.append(p)
|
|
65
|
+
cols.extend([np.nan] * (n_measures - 1))
|
|
66
|
+
return np.array(cols, dtype=object)
|
|
67
|
+
|
|
68
|
+
|
|
58
69
|
# ---------------------------------------------------------------------------
|
|
59
70
|
# _any_value_greater_than_30
|
|
60
71
|
# ---------------------------------------------------------------------------
|
|
@@ -317,3 +328,38 @@ class TestPreprocessOverrideIntegration:
|
|
|
317
328
|
_, columns_index, _, _ = result
|
|
318
329
|
assert list(columns_index) == ["Redespacho", "Tipo"]
|
|
319
330
|
assert s._n_columns_totals == 2
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class TestNormalizeMultiMeasure:
|
|
334
|
+
"""Multi-measure paired columns ([1, NaN, 2, NaN, …]) share the same
|
|
335
|
+
NaN-filler shape as the single-measure format but mean the opposite: the
|
|
336
|
+
duplication is the measures (MW + €/MWh), so the period index must be kept
|
|
337
|
+
(paired), not expanded to sequential slots. Regression for offer sheets
|
|
338
|
+
(OFFER_BT / OFFER_BS_*) whose MW and price were otherwise scattered across
|
|
339
|
+
8 slots/hour instead of paired into 4 quarter-hours.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
def test_two_measures_keep_period_index(self):
|
|
343
|
+
s = _make_sheet()
|
|
344
|
+
result = s._normalize_datetime_columns(
|
|
345
|
+
_multi_measure_columns(4, n_measures=2), n_measures=2
|
|
346
|
+
)
|
|
347
|
+
# 4 quarters x 2 measures -> each quarter index repeated for its measures
|
|
348
|
+
assert list(result) == [1, 1, 2, 2, 3, 3, 4, 4]
|
|
349
|
+
|
|
350
|
+
def test_full_day_paired_96_periods(self):
|
|
351
|
+
s = _make_sheet()
|
|
352
|
+
result = s._normalize_datetime_columns(
|
|
353
|
+
_multi_measure_columns(96, n_measures=2), n_measures=2
|
|
354
|
+
)
|
|
355
|
+
assert len(result) == 192
|
|
356
|
+
assert result.max() == 96
|
|
357
|
+
_, counts = np.unique(result, return_counts=True)
|
|
358
|
+
assert set(counts.tolist()) == {2} # every period appears once per measure
|
|
359
|
+
assert _any_value_greater_than_30(result)
|
|
360
|
+
|
|
361
|
+
def test_single_measure_default_still_sequential(self):
|
|
362
|
+
# n_measures defaults to 1 -> old NaN-filler behaviour is preserved
|
|
363
|
+
s = _make_sheet()
|
|
364
|
+
result = s._normalize_datetime_columns(_full_nan_filler_columns())
|
|
365
|
+
assert list(result) == list(range(1, 97))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/01_Quickstart/01_Setup and First Query.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/03_Multi-Geography Indicators.ipynb
RENAMED
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/04_Compare Indicators.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/02_Indicators/06_Generation and Demand.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/03_Archives/02_I90 Settlement Files.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/05_Advanced/01_Ad-hoc Pandas Expressions.ipynb
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/01_Search Indicators.yaml
RENAMED
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/02_Historical Data.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/04_Compare Indicators.yaml
RENAMED
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/02_Indicators/05_Market Prices.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/03_Archives/01_Download Archives.yaml
RENAMED
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/03_Archives/02_I90 Settlement Files.yaml
RENAMED
|
File without changes
|
{python_esios-2.4.1 → python_esios-2.4.2}/examples/_specs/04_Caching/01_Cache Management.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|