python-esios 2.0.0__tar.gz → 2.0.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.
Files changed (79) hide show
  1. {python_esios-2.0.0 → python_esios-2.0.2}/.github/workflows/publish.yml +2 -0
  2. {python_esios-2.0.0 → python_esios-2.0.2}/.gitignore +1 -0
  3. {python_esios-2.0.0 → python_esios-2.0.2}/PKG-INFO +1 -1
  4. {python_esios-2.0.0 → python_esios-2.0.2}/pyproject.toml +1 -1
  5. {python_esios-2.0.0 → python_esios-2.0.2/src/esios/.agents}/skills/esios/SKILL.md +49 -4
  6. python_esios-2.0.2/src/esios/cli/archives.py +217 -0
  7. python_esios-2.0.2/src/esios/data/catalogs/__init__.py +0 -0
  8. python_esios-2.0.2/src/esios/data/catalogs/archives/__init__.py +5 -0
  9. python_esios-2.0.2/src/esios/data/catalogs/archives/catalog.py +163 -0
  10. python_esios-2.0.2/src/esios/data/catalogs/archives/refresh.py +95 -0
  11. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/managers/archives.py +28 -15
  12. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/managers/indicators.py +5 -0
  13. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/processing/i90.py +88 -24
  14. python_esios-2.0.2/tests/__init__.py +0 -0
  15. python_esios-2.0.2/tests/test_i90.py +167 -0
  16. {python_esios-2.0.0 → python_esios-2.0.2}/tests/test_managers.py +47 -0
  17. python_esios-2.0.0/.claude-plugin/marketplace.json +0 -15
  18. python_esios-2.0.0/.claude-plugin/plugin.json +0 -12
  19. python_esios-2.0.0/src/esios/cli/archives.py +0 -66
  20. {python_esios-2.0.0 → python_esios-2.0.2}/CHANGELOG.md +0 -0
  21. {python_esios-2.0.0 → python_esios-2.0.2}/LICENSE +0 -0
  22. {python_esios-2.0.0 → python_esios-2.0.2}/README.md +0 -0
  23. {python_esios-2.0.0 → python_esios-2.0.2}/examples/.gitignore +0 -0
  24. {python_esios-2.0.0 → python_esios-2.0.2}/examples/01_Quickstart/01_Setup and First Query.ipynb +0 -0
  25. {python_esios-2.0.0 → python_esios-2.0.2}/examples/02_Indicators/01_Search Indicators.ipynb +0 -0
  26. {python_esios-2.0.0 → python_esios-2.0.2}/examples/02_Indicators/02_Historical Data.ipynb +0 -0
  27. {python_esios-2.0.0 → python_esios-2.0.2}/examples/02_Indicators/03_Multi-Geography Indicators.ipynb +0 -0
  28. {python_esios-2.0.0 → python_esios-2.0.2}/examples/02_Indicators/04_Compare Indicators.ipynb +0 -0
  29. {python_esios-2.0.0 → python_esios-2.0.2}/examples/02_Indicators/05_Market Prices.ipynb +0 -0
  30. {python_esios-2.0.0 → python_esios-2.0.2}/examples/02_Indicators/06_Generation and Demand.ipynb +0 -0
  31. {python_esios-2.0.0 → python_esios-2.0.2}/examples/03_Archives/01_Download Archives.ipynb +0 -0
  32. {python_esios-2.0.0 → python_esios-2.0.2}/examples/03_Archives/02_I90 Settlement Files.ipynb +0 -0
  33. {python_esios-2.0.0 → python_esios-2.0.2}/examples/04_Caching/01_Cache Management.ipynb +0 -0
  34. {python_esios-2.0.0 → python_esios-2.0.2}/examples/05_Advanced/01_Ad-hoc Pandas Expressions.ipynb +0 -0
  35. {python_esios-2.0.0 → python_esios-2.0.2}/examples/05_Advanced/02_Async Client.ipynb +0 -0
  36. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/01_Quickstart/01_Setup and First Query.yaml +0 -0
  37. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/02_Indicators/01_Search Indicators.yaml +0 -0
  38. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/02_Indicators/02_Historical Data.yaml +0 -0
  39. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/02_Indicators/03_Multi-Geography Indicators.yaml +0 -0
  40. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/02_Indicators/04_Compare Indicators.yaml +0 -0
  41. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/02_Indicators/05_Market Prices.yaml +0 -0
  42. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/02_Indicators/06_Generation and Demand.yaml +0 -0
  43. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/03_Archives/01_Download Archives.yaml +0 -0
  44. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/03_Archives/02_I90 Settlement Files.yaml +0 -0
  45. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/04_Caching/01_Cache Management.yaml +0 -0
  46. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/05_Advanced/01_Ad-hoc Pandas Expressions.yaml +0 -0
  47. {python_esios-2.0.0 → python_esios-2.0.2}/examples/_specs/05_Advanced/02_Async Client.yaml +0 -0
  48. {python_esios-2.0.0 → python_esios-2.0.2}/examples/generate.py +0 -0
  49. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/__init__.py +0 -0
  50. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/async_client.py +0 -0
  51. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/cache.py +0 -0
  52. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/cli/__init__.py +0 -0
  53. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/cli/app.py +0 -0
  54. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/cli/cache_cmd.py +0 -0
  55. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/cli/config.py +0 -0
  56. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/cli/config_cmd.py +0 -0
  57. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/cli/exec_cmd.py +0 -0
  58. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/cli/indicators.py +0 -0
  59. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/client.py +0 -0
  60. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/constants.py +0 -0
  61. {python_esios-2.0.0/tests → python_esios-2.0.2/src/esios/data}/__init__.py +0 -0
  62. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/exceptions.py +0 -0
  63. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/managers/__init__.py +0 -0
  64. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/managers/base.py +0 -0
  65. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/managers/offer_indicators.py +0 -0
  66. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/models/__init__.py +0 -0
  67. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/models/archive.py +0 -0
  68. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/models/indicator.py +0 -0
  69. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/models/offer_indicator.py +0 -0
  70. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/processing/__init__.py +0 -0
  71. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/processing/dataframes.py +0 -0
  72. {python_esios-2.0.0 → python_esios-2.0.2}/src/esios/processing/zip.py +0 -0
  73. {python_esios-2.0.0 → python_esios-2.0.2}/tests/conftest.py +0 -0
  74. {python_esios-2.0.0 → python_esios-2.0.2}/tests/test_cache.py +0 -0
  75. {python_esios-2.0.0 → python_esios-2.0.2}/tests/test_client.py +0 -0
  76. {python_esios-2.0.0 → python_esios-2.0.2}/tests/test_dataframes.py +0 -0
  77. {python_esios-2.0.0 → python_esios-2.0.2}/tests/test_exceptions.py +0 -0
  78. {python_esios-2.0.0 → python_esios-2.0.2}/tests/test_models.py +0 -0
  79. {python_esios-2.0.0 → python_esios-2.0.2}/tests/test_zip.py +0 -0
@@ -24,3 +24,5 @@ jobs:
24
24
 
25
25
  - name: Publish to PyPI
26
26
  uses: pypa/gh-action-pypi-publish@release/v1
27
+ with:
28
+ skip-existing: true
@@ -31,6 +31,7 @@ docs/
31
31
  *.xls
32
32
  *.xlsx
33
33
  data/
34
+ !src/esios/data/
34
35
 
35
36
  # Legacy code (superseded by src/)
36
37
  esios_legacy/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-esios
3
- Version: 2.0.0
3
+ Version: 2.0.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "python-esios"
7
- version = "2.0.0"
7
+ version = "2.0.2"
8
8
  description = "A Python wrapper for the ESIOS API (Spanish electricity market)"
9
9
  readme = "README.md"
10
10
  license = "GPL-3.0-only"
@@ -39,8 +39,22 @@ esios indicators exec 600 -s 2025-01-01 -e 2025-01-31 --expr "df.resample('D').m
39
39
  ### Archives
40
40
 
41
41
  ```bash
42
+ # List all available archives
42
43
  esios archives list
43
- esios archives download 1 --start 2025-01-01 --end 2025-01-31 --output ./data
44
+
45
+ # Download archive files
46
+ esios archives download 34 --start 2025-05-01 --end 2025-05-31 --output ./data
47
+ esios archives download 34 --date 2025-06-01
48
+
49
+ # List sheets (table of contents) in an I90 file
50
+ esios archives sheets 34 --date 2025-06-01
51
+
52
+ # Parse and query archive data (like indicators exec but for archives)
53
+ esios archives exec 34 --sheet I90DIA03 --date 2025-06-01
54
+ esios archives exec 34 --sheet I90DIA03 --date 2025-06-01 -x "df.describe()"
55
+ esios archives exec 34 --sheet I90DIA03 -s 2025-05-05 -e 2025-06-08 \
56
+ -x "df[df['Sentido']=='Bajar'].groupby('Unidad de Programación')['value'].sum().sort_values()"
57
+ esios archives exec 34 --sheet I90DIA26 --date 2025-06-01 --format csv --output pbf.csv
44
58
  ```
45
59
 
46
60
  ### Cache Management
@@ -132,11 +146,42 @@ df = client.indicators.compare([600, 10034, 10035], "2025-01-01", "2025-01-07")
132
146
 
133
147
  ## I90 Settlement Files
134
148
 
149
+ ### CLI (quickest path)
150
+
151
+ ```bash
152
+ # Discover available sheets
153
+ esios archives sheets 34 --date 2025-06-01
154
+
155
+ # Key I90DIA sheets:
156
+ # I90DIA03 — Restricciones en el Mercado Diario (curtailment)
157
+ # I90DIA08 — Restricciones en Tiempo Real
158
+ # I90DIA26 — Programa Base de Funcionamiento (PBF, generation program)
159
+ # I90DIA01 — Programa PVP
160
+ # I90DIA07 — Regulación Terciaria (mFRR)
161
+
162
+ # Total curtailment by direction
163
+ esios archives exec 34 --sheet I90DIA03 --date 2025-06-01 \
164
+ -x "df.groupby('Sentido')['value'].sum()"
165
+
166
+ # Multi-day curtailment analysis
167
+ esios archives exec 34 --sheet I90DIA03 -s 2025-05-05 -e 2025-06-08 \
168
+ -x "df[df['Sentido']=='Bajar'].groupby('Unidad de Programación')['value'].sum().sort_values().head(20)"
169
+ ```
170
+
171
+ ### Python library
172
+
135
173
  ```python
136
- from esios.processing import I90Book
174
+ from esios import ESIOSClient
175
+ from esios.processing.i90 import I90Book
176
+
177
+ # Download + parse in one step
178
+ client = ESIOSClient()
179
+ archive = client.archives.get(34)
180
+ books = I90Book.from_archive(archive, start="2025-05-05", end="2025-06-08")
137
181
 
138
- book = I90Book("path/to/I90DIA_20250101.xls")
139
- sheet = book["3.1"] # Access specific sheet
182
+ # Access a sheet
183
+ book = books[0]
184
+ sheet = book["I90DIA03"]
140
185
  df = sheet.df # Preprocessed DataFrame with datetime index
141
186
  print(sheet.frequency) # "hourly" or "hourly-quarterly"
142
187
  ```
@@ -0,0 +1,217 @@
1
+ """CLI subcommands for archives."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ archives_app = typer.Typer(no_args_is_help=True)
12
+ console = Console()
13
+
14
+
15
+ @archives_app.command("list")
16
+ def list_archives(
17
+ token: Optional[str] = typer.Option(None, "--token", "-t"),
18
+ format: str = typer.Option("table", "--format", "-f"),
19
+ ):
20
+ """List all available ESIOS archives."""
21
+ from esios.cli.app import get_client
22
+
23
+ client = get_client(token)
24
+ df = client.archives.list()
25
+
26
+ if format == "csv":
27
+ typer.echo(df.to_csv())
28
+ elif format == "json":
29
+ typer.echo(df.to_json(orient="records", indent=2))
30
+ else:
31
+ table = Table(title="ESIOS Archives")
32
+ cols = list(df.columns)[:8]
33
+ for col in cols:
34
+ table.add_column(str(col))
35
+ for _, row in df.head(50).iterrows():
36
+ table.add_row(*[str(row[c]) for c in cols])
37
+ if len(df) > 50:
38
+ table.caption = f"Showing 50 of {len(df)} rows"
39
+ console.print(table)
40
+
41
+
42
+ @archives_app.command("download")
43
+ def download_archive(
44
+ archive_id: int = typer.Argument(..., help="Archive ID"),
45
+ start: Optional[str] = typer.Option(None, "--start", "-s", help="Start date (YYYY-MM-DD)"),
46
+ end: Optional[str] = typer.Option(None, "--end", "-e", help="End date (YYYY-MM-DD)"),
47
+ date: Optional[str] = typer.Option(None, "--date", "-d", help="Single date (YYYY-MM-DD)"),
48
+ output: str = typer.Option(".", "--output", "-o", help="Output directory"),
49
+ token: Optional[str] = typer.Option(None, "--token", "-t"),
50
+ ):
51
+ """Download an archive for a date or date range."""
52
+ from esios.cli.app import get_client
53
+
54
+ if not date and not (start and end):
55
+ typer.echo("Provide --date or both --start and --end.", err=True)
56
+ raise typer.Exit(1)
57
+
58
+ client = get_client(token)
59
+ client.archives.download(
60
+ archive_id,
61
+ start=start,
62
+ end=end,
63
+ date=date,
64
+ output_dir=output,
65
+ )
66
+ typer.echo(f"Download complete → {output}")
67
+
68
+
69
+ def _download_files(
70
+ archive_id: int,
71
+ token: str | None,
72
+ date: str | None,
73
+ start: str | None,
74
+ end: str | None,
75
+ ) -> list:
76
+ """Download archive files (shared by sheets and exec commands)."""
77
+ from esios.cli.app import get_client
78
+
79
+ if not date and not (start and end):
80
+ typer.echo("Provide --date or both --start and --end.", err=True)
81
+ raise typer.Exit(1)
82
+
83
+ client = get_client(token)
84
+
85
+ if date:
86
+ files = client.archives.download(archive_id, date=date)
87
+ else:
88
+ files = client.archives.download(archive_id, start=start, end=end)
89
+
90
+ if not files:
91
+ typer.echo("No files found for the given date range.", err=True)
92
+ raise typer.Exit(1)
93
+
94
+ return files
95
+
96
+
97
+ @archives_app.command("sheets")
98
+ def sheets(
99
+ archive_id: int = typer.Argument(..., help="Archive ID (e.g. 34 for I90DIA)"),
100
+ date: Optional[str] = typer.Option(None, "--date", "-d", help="Single date (YYYY-MM-DD)"),
101
+ start: Optional[str] = typer.Option(None, "--start", "-s", help="Start date (YYYY-MM-DD)"),
102
+ end: Optional[str] = typer.Option(None, "--end", "-e", help="End date (YYYY-MM-DD)"),
103
+ token: Optional[str] = typer.Option(None, "--token", "-t"),
104
+ ):
105
+ """List sheets (table of contents) in an archive file.
106
+
107
+ Downloads and parses the first file to show available sheets.
108
+
109
+ \b
110
+ Examples:
111
+ esios archives sheets 34 --date 2025-06-01
112
+ esios archives sheets 34 --start 2025-06-01 --end 2025-06-01
113
+ """
114
+ from esios.processing.i90 import I90Book
115
+
116
+ files = _download_files(archive_id, token, date, start, end)
117
+
118
+ try:
119
+ book = I90Book(files[0])
120
+ except Exception as exc:
121
+ typer.echo(f"Error parsing {files[0].name}: {exc}", err=True)
122
+ raise typer.Exit(1)
123
+
124
+ table = Table(title=f"Sheets in {files[0].name}")
125
+ table.add_column("Sheet", style="cyan")
126
+ table.add_column("Description")
127
+
128
+ for sheet_name, description in book.table_of_contents.items():
129
+ if not isinstance(sheet_name, str) or not sheet_name.strip():
130
+ continue
131
+ table.add_row(str(sheet_name), str(description))
132
+
133
+ console.print(table)
134
+
135
+
136
+ @archives_app.command("exec")
137
+ def exec_archive(
138
+ archive_id: int = typer.Argument(..., help="Archive ID (e.g. 34 for I90DIA)"),
139
+ sheet: str = typer.Option(..., "--sheet", help="Sheet name (e.g. I90DIA03, I90DIA26)"),
140
+ date: Optional[str] = typer.Option(None, "--date", "-d", help="Single date (YYYY-MM-DD)"),
141
+ start: Optional[str] = typer.Option(None, "--start", "-s", help="Start date (YYYY-MM-DD)"),
142
+ end: Optional[str] = typer.Option(None, "--end", "-e", help="End date (YYYY-MM-DD)"),
143
+ expr: str = typer.Option("df", "--expr", "-x", help="Python expression to evaluate (df, pd, np available)"),
144
+ format: str = typer.Option("table", "--format", "-f", help="Output format: table, csv, json"),
145
+ output: Optional[str] = typer.Option(None, "--output", "-o", help="Output file path"),
146
+ token: Optional[str] = typer.Option(None, "--token", "-t"),
147
+ ):
148
+ """Parse archive files and evaluate a Python expression on the data.
149
+
150
+ Downloads archive files (cached), parses the specified sheet from each
151
+ file using I90Book, concatenates the DataFrames, and evaluates the
152
+ expression. The parsed data is available as `df` (pandas DataFrame).
153
+ `pd` (pandas) and `np` (numpy) are also available.
154
+
155
+ \b
156
+ Examples:
157
+ # Show curtailment data for a single day
158
+ esios archives exec 34 --sheet I90DIA03 --date 2025-06-01
159
+
160
+ # Curtailment by technology over a month
161
+ esios archives exec 34 --sheet I90DIA03 -s 2025-05-05 -e 2025-06-08 \\
162
+ -x "df[df['Sentido']=='Bajar'].groupby('Tecnología')['value'].sum().sort_values()"
163
+
164
+ # Export PBF generation program to CSV
165
+ esios archives exec 34 --sheet I90DIA26 --date 2025-06-01 -f csv -o pbf.csv
166
+
167
+ # Descriptive statistics
168
+ esios archives exec 34 --sheet I90DIA03 --date 2025-06-01 -x "df.describe()"
169
+ """
170
+ import numpy as np
171
+ import pandas as pd
172
+
173
+ from esios.processing.i90 import I90Book
174
+
175
+ files = _download_files(archive_id, token, date, start, end)
176
+
177
+ # Parse all files and extract the requested sheet
178
+ all_dfs = []
179
+ for f in files:
180
+ try:
181
+ book = I90Book(f)
182
+ s = book[sheet]
183
+ if s.df is not None and not s.df.empty:
184
+ all_dfs.append(s.df.reset_index())
185
+ except KeyError:
186
+ # Sheet not found — show available sheets
187
+ try:
188
+ book = I90Book(f)
189
+ available = list(book.table_of_contents.keys())
190
+ except Exception:
191
+ available = []
192
+ typer.echo(f"Sheet '{sheet}' not found in {f.name}.", err=True)
193
+ if available:
194
+ typer.echo(f"Available sheets: {', '.join(str(s) for s in available)}", err=True)
195
+ raise typer.Exit(1)
196
+ except Exception as exc:
197
+ typer.echo(f"Warning: skipping {f.name}: {exc}", err=True)
198
+ continue
199
+
200
+ if not all_dfs:
201
+ typer.echo("No data extracted from the specified sheet.", err=True)
202
+ raise typer.Exit(1)
203
+
204
+ df = pd.concat(all_dfs, ignore_index=True)
205
+
206
+ # Evaluate expression
207
+ namespace = {"df": df, "pd": pd, "np": np}
208
+ try:
209
+ result = eval(expr, {"__builtins__": {}}, namespace) # noqa: S307
210
+ except Exception as exc:
211
+ typer.echo(f"Error evaluating expression: {exc}", err=True)
212
+ raise typer.Exit(1)
213
+
214
+ # Render output — reuse _render from exec_cmd
215
+ from esios.cli.exec_cmd import _render
216
+
217
+ _render(result, format, output)
File without changes
@@ -0,0 +1,5 @@
1
+ """Archives catalog — static reference of all known ESIOS archives."""
2
+
3
+ from esios.data.catalogs.archives.catalog import ARCHIVES_CATALOG
4
+
5
+ __all__ = ["ARCHIVES_CATALOG"]
@@ -0,0 +1,163 @@
1
+ """Static catalog of ESIOS archives.
2
+
3
+ Auto-generated by refresh.py — do not edit manually.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ ARCHIVES_CATALOG: dict[int, dict[str, str]] = {
9
+ 2: {"name": "A1_liquicomun", "description": "Avance de liquidación A1", "horizon": "M", "archive_type": "zip"},
10
+ 3: {"name": "A2_liquicomun", "description": "Avance de liquidación A2", "horizon": "M", "archive_type": "zip"},
11
+ 8: {"name": "C2_liquicomun", "description": "Cierre de liquidación C2", "horizon": "M", "archive_type": "zip"},
12
+ 9: {"name": "C3_liquicomun", "description": "Cierre de liquidación C3", "horizon": "M", "archive_type": "zip"},
13
+ 10: {"name": "C4_liquicomun", "description": "Cierre de liquidación C4", "horizon": "M", "archive_type": "zip"},
14
+ 11: {"name": "C5_liquicomun", "description": "Cierre de liquidación C5", "horizon": "M", "archive_type": "zip"},
15
+ 12: {"name": "C6_liquicomun", "description": "Cierre de liquidación C6", "horizon": "M", "archive_type": "zip"},
16
+ 13: {"name": "C7_liquicomun", "description": "Cierre de liquidación C7", "horizon": "M", "archive_type": "zip"},
17
+ 14: {"name": "C8_liquicomun", "description": "Cierre de liquidación C8", "horizon": "M", "archive_type": "zip"},
18
+ 15: {"name": "hemeroteca_DD_ent", "description": "Fichero de hemeroteca de entrada.", "horizon": "D", "archive_type": "zip"},
19
+ 16: {"name": "hemeroteca_DD_sal", "description": "Fichero de hemeroteca de salida", "horizon": "D", "archive_type": "zip"},
20
+ 17: {"name": "liquicierre", "description": "Resultados de la Regulación Secundaria exigidos para la Liquidación", "horizon": "D", "archive_type": "xml"},
21
+ 18: {"name": "totalpdbf", "description": "Programa agregado resultante del PBF", "horizon": "D", "archive_type": "xml"},
22
+ 19: {"name": "totalrpdvpprec", "description": "Solución de Restricciones Técnicas", "horizon": "D", "archive_type": "xml"},
23
+ 20: {"name": "totalpdvp", "description": "PVP Agregado Horizonte Diario", "horizon": "D", "archive_type": "xml"},
24
+ 21: {"name": "totalasigsec", "description": "Asignación Regulación Secundaria", "horizon": "D", "archive_type": "xml"},
25
+ 22: {"name": "totalrpibcirest", "description": "Solución de Restricciones Técnicas MI", "horizon": "H", "archive_type": "xml"},
26
+ 23: {"name": "totalasigdesv", "description": "Mercado de Gestión de Desvíos", "horizon": "H", "archive_type": "xml"},
27
+ 24: {"name": "totalasigter", "description": "Mercado de Regulación Terciaria", "horizon": "D", "archive_type": "xml"},
28
+ 25: {"name": "totalenersec", "description": "Energía Secundaria utilizada", "horizon": "D", "archive_type": "xml"},
29
+ 26: {"name": "totalliquicierre", "description": "Cálculo Variación Coste Fijo", "horizon": "D", "archive_type": "xml"},
30
+ 27: {"name": "totalrp48prec", "description": "Redespachos Seguridad Tiempo Real (Horario)", "horizon": "H", "archive_type": "xml"},
31
+ 28: {"name": "totalrp48preccierre", "description": "Redespachos Seguridad Tiempo Real (Cierre)", "horizon": "D", "archive_type": "xml"},
32
+ 29: {"name": "totalp48", "description": "Programa Agregado P48 (Horario)", "horizon": "H", "archive_type": "xml"},
33
+ 30: {"name": "totalp48cierre", "description": "Programa Agregado P48 (Cierre)", "horizon": "D", "archive_type": "xml"},
34
+ 32: {"name": "I3DIA", "description": "Información agregada diaria por tecnología (I3DIA)", "horizon": "D", "archive_type": "zip"},
35
+ 33: {"name": "IMES", "description": "Cuotas mensuales por Sujetos de Liquidación (IMES)", "horizon": "M", "archive_type": "zip"},
36
+ 34: {"name": "I90DIA", "description": "Información de detalle por Unidad de Programación (I90DIA)", "horizon": "D", "archive_type": "zip"},
37
+ 35: {"name": "COEF_PERD_PEN_MM", "description": "Coeficientes horarios de pérdidas en el sistema peninsular", "horizon": "M", "archive_type": "xls"},
38
+ 36: {"name": "COEF_PERD_MEL_MM", "description": "Coeficientes horarios de pérdidas en el sistema de Melilla", "horizon": "M", "archive_type": "xls"},
39
+ 37: {"name": "COEF_PERD_CEU_MM", "description": "Coeficientes horarios de pérdidas en el sistema de Ceuta", "horizon": "M", "archive_type": "xls"},
40
+ 38: {"name": "COEF_PERD_CAN_MM", "description": "Coeficientes horarios de pérdidas en el sistema de Canarias", "horizon": "M", "archive_type": "xls"},
41
+ 39: {"name": "COEF_PERD_BAL_MM", "description": "Coeficientes horarios de pérdidas en el sistema de Baleares", "horizon": "M", "archive_type": "xls"},
42
+ 40: {"name": "COEF_KEST_MM", "description": "Coeficiente de ajuste horario estimado en el sistema peninsular y no peninsular", "horizon": "M", "archive_type": "xls"},
43
+ 42: {"name": "Espec_Subasta_Diaria_EF", "description": "Especificación de subastas explícitas Diarias de capacidad en la interconexión con Francia", "horizon": "D", "archive_type": "pdf"},
44
+ 43: {"name": "Espec_Subasta_Diaria_FE", "description": "Especificación de subastas explícitas Diarias de capacidad en la interconexión con Francia", "horizon": "D", "archive_type": "pdf"},
45
+ 44: {"name": "Espec_Subasta_Intra1_EF", "description": "Especificación de subastas explícitas Intradiarias 1 de capacidad en la interconexión con Francia", "horizon": "D", "archive_type": "pdf"},
46
+ 45: {"name": "Espec_Subasta_Intra1_FE", "description": "Especificación de subastas explícitas Intradiarias 1 de capacidad en la interconexión con Francia", "horizon": "D", "archive_type": "pdf"},
47
+ 46: {"name": "Espec_Subasta_Intra2_EF", "description": "Especificación de subastas explícitas Intradiarias 2 de capacidad en la interconexión con Francia", "horizon": "D", "archive_type": "pdf"},
48
+ 47: {"name": "Espec_Subasta_Intra2_FE", "description": "Especificación de subastas explícitas Intradiarias 2 de capacidad en la interconexión con Francia", "horizon": "D", "archive_type": "pdf"},
49
+ 48: {"name": "Espec_Subasta_Mensual_EF", "description": "Especificación de subastas explícitas Mensuales de capacidad en la interconexión con Francia", "horizon": "M", "archive_type": "pdf"},
50
+ 49: {"name": "Espec_Subasta_Mensual_FE", "description": "Especificación de subastas explícitas Mensuales de capacidad en la interconexión con Francia", "horizon": "M", "archive_type": "pdf"},
51
+ 50: {"name": "Espec_Subasta_Anual_EF", "description": "Especificación de subastas explícitas Anuales de capacidad en la interconexión con Francia", "horizon": "A", "archive_type": "pdf"},
52
+ 51: {"name": "Espec_Subasta_Anual_FE", "description": "Especificación de subastas explícitas Anuales de capacidad en la interconexión con Francia", "horizon": "A", "archive_type": "pdf"},
53
+ 56: {"name": "Espec_Subasta_Intra2_EF_Cancel", "description": "Especificación de subastas explícitas Intradiarias 2 de capacidad en la interconexión con Francia", "horizon": "D", "archive_type": "pdf"},
54
+ 57: {"name": "Espec_Subasta_Intra2_FE_Cancel", "description": "Especificación de subastas explícitas Intradiarias 2 de capacidad en la interconexión con Francia", "horizon": "D", "archive_type": "pdf"},
55
+ 58: {"name": "Espec_Subasta_Mensual_EF_Cancel", "description": "Especificación de subastas explícitas Mensuales de capacidad en la interconexión con Francia", "horizon": "M", "archive_type": "pdf"},
56
+ 59: {"name": "Espec_Subasta_Mensual_FE_Cancel", "description": "Especificación de subastas explícitas Mensuales de capacidad en la interconexión con Francia", "horizon": "M", "archive_type": "pdf"},
57
+ 62: {"name": "IND_DemandaInterrumpible", "description": "IND_DemandaInterrumpible", "horizon": "D", "archive_type": "json"},
58
+ 63: {"name": "IND_Interconexiones", "description": "IND_Interconexiones", "horizon": "D", "archive_type": "json"},
59
+ 64: {"name": "IND_PotenciaInstalada", "description": "IND_PotenciaInstalada", "horizon": "M", "archive_type": "json"},
60
+ 65: {"name": "IND_PrecioDesvios", "description": "IND_PrecioDesvios", "horizon": "D", "archive_type": "json"},
61
+ 66: {"name": "IND_PrecioFinal", "description": "IND_PrecioFinal", "horizon": "D", "archive_type": "json"},
62
+ 67: {"name": "IND_Umbrales", "description": "IND_Umbrales", "horizon": "D", "archive_type": "json"},
63
+ 68: {"name": "INF_SUBASTA_AA", "description": "Informe anual de subastas de capacidad con Francia", "horizon": "A", "archive_type": "pdf"},
64
+ 69: {"name": "INF_SUBASTA_MM", "description": "Informe mensual de subastas de capacidad con Francia", "horizon": "M", "archive_type": "pdf"},
65
+ 70: {"name": "PVPC_CURV_DD", "description": "Datos origen de curva PVPC", "horizon": "D", "archive_type": "json"},
66
+ 71: {"name": "PVPC_DETALLE_DD", "description": "PVPC Término de facturación energía activa – Desglose", "horizon": "D", "archive_type": "xls"},
67
+ 72: {"name": "PVPC_GEN_P1_DD", "description": "Datos origen de tarifa general P1", "horizon": "D", "archive_type": "json"},
68
+ 73: {"name": "PVPC_NOC_P1_DD", "description": "Datos origen de tarifa nocturna P1", "horizon": "D", "archive_type": "json"},
69
+ 74: {"name": "PVPC_NOC_P2_DD", "description": "Datos origen de tarifa nocturna P2", "horizon": "D", "archive_type": "json"},
70
+ 75: {"name": "PVPC_VHC_P1_DD", "description": "Datos origen de tarifa vehículo eléctrico P1", "horizon": "D", "archive_type": "json"},
71
+ 76: {"name": "PVPC_VHC_P2_DD", "description": "Datos origen de tarifa vehículo eléctrico P2", "horizon": "D", "archive_type": "json"},
72
+ 77: {"name": "PVPC_VHC_P3_DD", "description": "Datos origen de tarifa vehículo eléctrico P3", "horizon": "D", "archive_type": "json"},
73
+ 78: {"name": "perfilconsumo", "description": "PVPC Coeficientes de perfilado", "horizon": "S", "archive_type": "xml"},
74
+ 79: {"name": "preciovoluntariopconsumidor", "description": "PVPC por ciclo de lectura", "horizon": "D", "archive_type": "xml"},
75
+ 80: {"name": "pvpcdesglosehorario", "description": "PVPC Término de facturación energía activa", "horizon": "D", "archive_type": "xml"},
76
+ 81: {"name": "UnidadesFisicas", "description": "Datos Estructurales de unidades físicas", "horizon": "NA", "archive_type": "json"},
77
+ 82: {"name": "UnidadesProgramacion", "description": "Datos estructurales de unidades de programación", "horizon": "NA", "archive_type": "json"},
78
+ 83: {"name": "SujetosMercado", "description": "Datos estructurales de sujetos de mercado", "horizon": "NA", "archive_type": "json"},
79
+ 84: {"name": "ParticipantesSubasta", "description": "Datos estructurales de participantes de subasta", "horizon": "NA", "archive_type": "json"},
80
+ 87: {"name": "Resultado_Subasta_Mensual_FRA", "description": "Subastas mensuales del calendariode subastas de Francia", "horizon": "M", "archive_type": "xls"},
81
+ 88: {"name": "Resultado_Subasta_Anual_FRA", "description": "Subastas anuales del calendariode subastas de Francia", "horizon": "A", "archive_type": "xls"},
82
+ 89: {"name": "Descargos_AND_Planificados", "description": "Descargos Andorra", "horizon": "O", "archive_type": "xml"},
83
+ 91: {"name": "Descargos_MAR_Planificados", "description": "Descargos Marruecos", "horizon": "O", "archive_type": "xml"},
84
+ 93: {"name": "Descargos_POR_Planificados", "description": "Descargos Portugal", "horizon": "O", "archive_type": "xml"},
85
+ 95: {"name": "Descargos_FRA_Planificados", "description": "Descargos Francia", "horizon": "O", "archive_type": "xml"},
86
+ 101: {"name": "REE_InterChangeAvailab_FRA", "description": "Cambios en disponibilidad real de capacidad en interconexión con Francia", "horizon": "O", "archive_type": "xml"},
87
+ 102: {"name": "REE_InterChangeAvailab_POR", "description": "Cambios en disponibilidad real de capacidad en interconexión con Portugal", "horizon": "O", "archive_type": "xml"},
88
+ 105: {"name": "Indisponibilidades", "description": "Indisponibilidades de las unidades de generación y consumo", "horizon": "D", "archive_type": "xls"},
89
+ 106: {"name": "PlanesMantenimiento", "description": "Plan de mantenimiento", "horizon": "M", "archive_type": "xls"},
90
+ 107: {"name": "p48cierre", "description": "Cierre de programa Desagregado P48", "horizon": "D", "archive_type": "xml"},
91
+ 108: {"name": "REE_AggGenOutput", "description": "Generación real por tipo de producción", "horizon": "QM", "archive_type": "xml"},
92
+ 109: {"name": "REE_ActualGenOutput", "description": "Generación real por unidad física", "horizon": "D", "archive_type": "xml"},
93
+ 110: {"name": "GenerationUnits", "description": "Generation Units Structural Data", "horizon": "NA", "archive_type": "json"},
94
+ 111: {"name": "ProgrammingUnits", "description": "Programming Units Structural Data", "horizon": "NA", "archive_type": "json"},
95
+ 112: {"name": "BalanceResponsibleParties", "description": "Balance Responsible Parties Structural Data", "horizon": "NA", "archive_type": "json"},
96
+ 113: {"name": "EntitledParticipants", "description": "Entitled Participants Structural Data", "horizon": "NA", "archive_type": "json"},
97
+ 114: {"name": "IND_DemandaPrevProg", "description": "IND_DemandaPrevProg", "horizon": "D", "archive_type": "json"},
98
+ 115: {"name": "IND_DemandaRealGen", "description": "IND_DemandaRealGen", "horizon": "D", "archive_type": "json"},
99
+ 116: {"name": "IND_MaxMin", "description": "IND_MaxMin", "horizon": "D", "archive_type": "json"},
100
+ 117: {"name": "IND_MaxMinRenovEol", "description": "IND_MaxMinRenovEol", "horizon": "D", "archive_type": "json"},
101
+ 118: {"name": "ActividadesSubactividades", "description": "Informe de actividades y subactividades", "horizon": "NA", "archive_type": "pdf"},
102
+ 119: {"name": "AgregacionesValidas", "description": "Informe de agregaciones válidas", "horizon": "NA", "archive_type": "pdf"},
103
+ 120: {"name": "CNAEValidos", "description": "Informe de códigos CNAE válidos", "horizon": "NA", "archive_type": "pdf"},
104
+ 121: {"name": "Comercializadores", "description": "Informe de comercializadores", "horizon": "NA", "archive_type": "pdf"},
105
+ 122: {"name": "Concentradores", "description": "Informe de concentradores", "horizon": "NA", "archive_type": "pdf"},
106
+ 123: {"name": "DiscriminacionHoraria", "description": "Informe de discriminaciones horarias", "horizon": "NA", "archive_type": "pdf"},
107
+ 124: {"name": "Distribuidores", "description": "Informe de distribuidores", "horizon": "NA", "archive_type": "pdf"},
108
+ 125: {"name": "Fabricantes", "description": "Informe de fabricantes", "horizon": "NA", "archive_type": "pdf"},
109
+ 126: {"name": "Magnitudes", "description": "Informe de magnitudes", "horizon": "NA", "archive_type": "pdf"},
110
+ 127: {"name": "ModeloContadores", "description": "Informe de modelo de contadores", "horizon": "NA", "archive_type": "pdf"},
111
+ 128: {"name": "ModeloRegistradores", "description": "Informe de modelos de registradores", "horizon": "NA", "archive_type": "pdf"},
112
+ 129: {"name": "ModeloTransformadores", "description": "Informe de modelo de transformadores", "horizon": "NA", "archive_type": "pdf"},
113
+ 130: {"name": "NivelesTension", "description": "Informe de niveles de tensión", "horizon": "NA", "archive_type": "pdf"},
114
+ 131: {"name": "Provincias", "description": "Informe de provincias", "horizon": "NA", "archive_type": "pdf"},
115
+ 132: {"name": "Representantes", "description": "Informe de representantes", "horizon": "NA", "archive_type": "pdf"},
116
+ 133: {"name": "Subsistemas", "description": "Informe de subsistemas", "horizon": "NA", "archive_type": "pdf"},
117
+ 134: {"name": "ZonasGeograficas", "description": "Informe de zonas geográficas", "horizon": "NA", "archive_type": "pdf"},
118
+ 135: {"name": "TarifaAcceso", "description": "Informe de tarifas de acceso", "horizon": "NA", "archive_type": "pdf"},
119
+ 136: {"name": "A6_liquicomun", "description": "Avance de liquidación A6", "horizon": "M", "archive_type": "zip"},
120
+ 137: {"name": "A7_liquicomun", "description": "Avance de liquidación A7", "horizon": "M", "archive_type": "zip"},
121
+ 139: {"name": "IND_EnergiaMensual", "description": "IND_EnergiaMensual", "horizon": "M", "archive_type": "json"},
122
+ 140: {"name": "IND_EnergiaAnual", "description": "IND_EnergiaAnual", "horizon": "A", "archive_type": "json"},
123
+ 142: {"name": "ConsumidoresDirectos", "description": "Informe de Consumidores Directos", "horizon": "NA", "archive_type": "pdf"},
124
+ 143: {"name": "MotivosObjecionEdLD", "description": "Informe de Motivos Objeción EdLD", "horizon": "NA", "archive_type": "pdf"},
125
+ 144: {"name": "MotivosObjecionEdLOS", "description": "Informe de Motivos Objeción EdLOS", "horizon": "NA", "archive_type": "pdf"},
126
+ 145: {"name": "TiposMedidaPuntoMedida", "description": "Informe de tipos de medida de punto medida", "horizon": "NA", "archive_type": "pdf"},
127
+ 146: {"name": "TiposMedidaPuntoFrontera", "description": "Informe de tipos de medida de punto frontera", "horizon": "NA", "archive_type": "pdf"},
128
+ 147: {"name": "Espec_Subasta_Mensual_EP", "description": "Especificación de subastas explícitas Mensuales de capacidad en la interconexión con Portugal", "horizon": "M", "archive_type": "pdf"},
129
+ 148: {"name": "Espec_Subasta_Mensual_PE", "description": "Especificación de subastas explícitas Mensuales de capacidad en la interconexión con Portugal", "horizon": "M", "archive_type": "pdf"},
130
+ 149: {"name": "Espec_Subasta_Trimestral_EP", "description": "Especificación de subastas explícitas Trimestrales de capacidad en la interconexión con Portugal", "horizon": "T", "archive_type": "pdf"},
131
+ 150: {"name": "Espec_Subasta_Trimestral_PE", "description": "Especificación de subastas explícitas Trimestrales de capacidad en la interconexión con Portugal", "horizon": "T", "archive_type": "pdf"},
132
+ 151: {"name": "Espec_Subasta_Anual_EP", "description": "Especificación de subastas explícitas Trimestrales de capacidad en la interconexión con Portugal", "horizon": "A", "archive_type": "pdf"},
133
+ 152: {"name": "Espec_Subasta_Anual_PE", "description": "Especificación de subastas explícitas Trimestrales de capacidad en la interconexión con Portugal", "horizon": "A", "archive_type": "pdf"},
134
+ 159: {"name": "Resultado_Subasta_Mensual_POR", "description": "Subastas mensuales del calendario de subastas de Portugal", "horizon": "M", "archive_type": "xls"},
135
+ 160: {"name": "Resultado_Subasta_Anual_POR", "description": "Subastas anuales del calendario de subastas de Portugal", "horizon": "A", "archive_type": "xls"},
136
+ 161: {"name": "Resultado_Subasta_Trimestral_POR", "description": "Subastas trimestrales del calendario de subastas de Portugal", "horizon": "T", "archive_type": "xls"},
137
+ 164: {"name": "CodigosTensiones", "description": "Informe de códigos de tensiones de suministro", "horizon": "NA", "archive_type": "pdf"},
138
+ 165: {"name": "UnidadesConsumoGeneracion", "description": "Informe de unidades de consumo de generación", "horizon": "NA", "archive_type": "pdf"},
139
+ 166: {"name": "CodigosAutoconsumo", "description": "Informe de códigos de autoconsumo", "horizon": "NA", "archive_type": "pdf"},
140
+ 167: {"name": "IND_PotenciaInstaladaNacional", "description": "IND_PotenciaInstaladaNacional", "horizon": "M", "archive_type": "json"},
141
+ 168: {"name": "IND_EnergiaMensualNacional", "description": "IND_EnergiaMensualNacional", "horizon": "M", "archive_type": "json"},
142
+ 169: {"name": "IND_EnergiaAnualNacional", "description": "IND_EnergiaAnualNacional", "horizon": "A", "archive_type": "json"},
143
+ 170: {"name": "IND_CoeficientesCO2", "description": "IND_CoeficientesCO2", "horizon": "NA", "archive_type": "json"},
144
+ 171: {"name": "IND_InformacionApp", "description": "IND_InformacionApp", "horizon": "NA", "archive_type": "json"},
145
+ 172: {"name": "IND_PreguntasApp", "description": "IND_PreguntasApp", "horizon": "NA", "archive_type": "json"},
146
+ 173: {"name": "IND_VersionApp", "description": "IND_VersionApp", "horizon": "NA", "archive_type": "json"},
147
+ 174: {"name": "IND_PrecioMinorista", "description": "IND_PrecioMinorista", "horizon": "D", "archive_type": "json"},
148
+ 181: {"name": "REE_BalancingEnerBids", "description": "Mercado de Regulación Terciaria", "horizon": "QM", "archive_type": "csv"},
149
+ 183: {"name": "IND_EnergiaAnualAutoconsumoNacional", "description": "IND_EnergiaAnualAutoconsumoNacional", "horizon": "A", "archive_type": "json"},
150
+ 184: {"name": "IND_EnergiaMensualAutoconsumoNacional", "description": "IND_EnergiaMensualAutoconsumoNacional", "horizon": "M", "archive_type": "json"},
151
+ 185: {"name": "ActivacionesDelServicio", "description": "Activaciones del Servicio", "horizon": "NA", "archive_type": "json"},
152
+ 186: {"name": "ServiceActivation", "description": "Service Activation", "horizon": "NA", "archive_type": "json"},
153
+ 187: {"name": "C2_PrecioFinal", "description": "Precio final de la energía C2", "horizon": "M", "archive_type": "zip"},
154
+ 188: {"name": "C5_PrecioFinal", "description": "Precio final de la energía C5", "horizon": "M", "archive_type": "zip"},
155
+ 193: {"name": "IND_Novedades", "description": "IND_Novedades", "horizon": "NA", "archive_type": "json"},
156
+ 194: {"name": "IND_Novedades_Paso1", "description": "Json lottie Paso1", "horizon": "NA", "archive_type": "json"},
157
+ 195: {"name": "IND_Novedades_Paso2", "description": "Json lottie Paso2", "horizon": "NA", "archive_type": "json"},
158
+ 196: {"name": "IND_Novedades_Paso3", "description": "Json lottie Paso3", "horizon": "NA", "archive_type": "json"},
159
+ 197: {"name": "IND_Novedades_Paso4", "description": "Json lottie Paso3", "horizon": "NA", "archive_type": "json"},
160
+ 198: {"name": "IND_Novedades_Step1", "description": "Json lottie Step1", "horizon": "NA", "archive_type": "json"},
161
+ 199: {"name": "IND_Novedades_Step2", "description": "Json lottie Step2", "horizon": "NA", "archive_type": "json"},
162
+ }
163
+
@@ -0,0 +1,95 @@
1
+ """Refresh the static archives catalog by scanning the ESIOS API.
2
+
3
+ Usage:
4
+ uv run python -m esios.data.catalogs.archives.refresh
5
+
6
+ Scans archive IDs 1-200 against the live API and regenerates catalog.py.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from pathlib import Path
13
+
14
+
15
+ def scan_archives(max_id: int = 200) -> dict[int, dict[str, str]]:
16
+ """Scan ESIOS API for all accessible archives."""
17
+ from esios import ESIOSClient
18
+
19
+ client = ESIOSClient()
20
+ catalog: dict[int, dict[str, str]] = {}
21
+
22
+ for i in range(1, max_id + 1):
23
+ try:
24
+ data = client.get(f"archives/{i}")
25
+ a = data.get("archive", {})
26
+ catalog[i] = {
27
+ "name": a.get("name", ""),
28
+ "description": a.get("description", ""),
29
+ "horizon": a.get("horizon", ""),
30
+ "archive_type": a.get("archive_type", ""),
31
+ }
32
+ except Exception:
33
+ continue
34
+
35
+ return catalog
36
+
37
+
38
+ def generate_catalog_py(catalog: dict[int, dict[str, str]]) -> str:
39
+ """Generate the catalog.py source code."""
40
+ lines = [
41
+ '"""Static catalog of ESIOS archives.',
42
+ "",
43
+ "Auto-generated by refresh.py — do not edit manually.",
44
+ '"""',
45
+ "",
46
+ "from __future__ import annotations",
47
+ "",
48
+ "ARCHIVES_CATALOG: dict[int, dict[str, str]] = {",
49
+ ]
50
+
51
+ for id_ in sorted(catalog):
52
+ entry = catalog[id_]
53
+ name = entry["name"]
54
+ desc = entry["description"].replace('"', '\\"')
55
+ horizon = entry["horizon"]
56
+ atype = entry["archive_type"]
57
+ lines.append(
58
+ f' {id_}: {{"name": "{name}", "description": "{desc}", '
59
+ f'"horizon": "{horizon}", "archive_type": "{atype}"}},'
60
+ )
61
+
62
+ lines.append("}")
63
+ lines.append("")
64
+ return "\n".join(lines)
65
+
66
+
67
+ def main() -> None:
68
+ from esios.data.catalogs.archives.catalog import ARCHIVES_CATALOG
69
+
70
+ old_ids = set(ARCHIVES_CATALOG.keys())
71
+
72
+ print(f"Scanning ESIOS API for archives (IDs 1-200)...")
73
+ catalog = scan_archives()
74
+ new_ids = set(catalog.keys())
75
+
76
+ # Diff summary
77
+ added = new_ids - old_ids
78
+ removed = old_ids - new_ids
79
+
80
+ print(f"\nFound {len(catalog)} archives (was {len(old_ids)})")
81
+ if added:
82
+ print(f" Added: {sorted(added)}")
83
+ if removed:
84
+ print(f" Removed: {sorted(removed)}")
85
+ if not added and not removed:
86
+ print(" No changes in archive IDs.")
87
+
88
+ # Write catalog.py
89
+ catalog_path = Path(__file__).parent / "catalog.py"
90
+ catalog_path.write_text(generate_catalog_py(catalog))
91
+ print(f"\nWrote {catalog_path}")
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()