lambda-risk 0.0.0.1__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.
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: lambda-risk
3
+ Version: 0.0.0.1
4
+ Description-Content-Type: text/markdown
5
+ Requires-Dist: pandas>=2.2.3
6
+ Requires-Dist: requests>=2.32.3
7
+ Requires-Dist: numpy
8
+ Requires-Dist: scipy
9
+ Requires-Dist: PyQuantimClient
10
+ Requires-Dist: holidays
11
+ Dynamic: description
12
+ Dynamic: description-content-type
13
+ Dynamic: requires-dist
14
+
15
+ # Lambda SDK
16
+
17
+ Sistema integral de gestión de riesgo financiero
@@ -0,0 +1,3 @@
1
+ # Lambda SDK
2
+
3
+ Sistema integral de gestión de riesgo financiero
File without changes
@@ -0,0 +1,396 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import tempfile
5
+ import time
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any, Optional
9
+
10
+ import requests
11
+
12
+
13
+ @dataclass
14
+ class Config:
15
+ base_url: str
16
+ token: str
17
+ timeout: int = 120
18
+ poll_seconds: int = 3
19
+ max_polls: int = 120
20
+
21
+ def __post_init__(self) -> None:
22
+ self.base_url = self.base_url.rstrip("/")
23
+
24
+ @property
25
+ def headers(self) -> dict[str, str]:
26
+ return {
27
+ "Authorization": f"Bearer {self.token}",
28
+ "Content-Type": "application/json",
29
+ }
30
+
31
+ def _format_elapsed(self, seconds: float) -> str:
32
+ if seconds < 60:
33
+ return f"{seconds:.2f}s"
34
+ minutes, remaining_seconds = divmod(seconds, 60)
35
+ return f"{int(minutes)}m {remaining_seconds:.2f}s"
36
+
37
+ def _print(self, message: str, log: bool = False) -> None:
38
+ if log:
39
+ print(f"[LOG] {message}")
40
+
41
+ def _get(self, endpoint: str, params: Optional[dict[str, Any]] = None) -> dict[str, Any]:
42
+ response = requests.get(
43
+ f"{self.base_url}/{endpoint.lstrip('/')}",
44
+ headers=self.headers,
45
+ params=params,
46
+ timeout=self.timeout,
47
+ )
48
+ response.raise_for_status()
49
+ return response.json()
50
+
51
+ def _post(self, endpoint: str, payload: Optional[dict[str, Any]] = None) -> dict[str, Any]:
52
+ response = requests.post(
53
+ f"{self.base_url}/{endpoint.lstrip('/')}",
54
+ headers=self.headers,
55
+ json=payload or {},
56
+ timeout=self.timeout,
57
+ )
58
+ response.raise_for_status()
59
+ return response.json()
60
+
61
+ def _start_export(self, endpoint_export: str, payload: dict[str, Any], log: bool = False) -> dict[str, Any]:
62
+ started_at = time.perf_counter()
63
+ export_data = self._post(endpoint_export, payload)
64
+ elapsed = self._format_elapsed(time.perf_counter() - started_at)
65
+ self._print(
66
+ f"API create_export={elapsed} | endpoint=/{endpoint_export.lstrip('/')} | module={export_data['module_name']} "
67
+ f"| export_id={export_data['export_id']} | job_id={export_data['job_id']}",
68
+ log,
69
+ )
70
+ return export_data
71
+
72
+ def get_health(self) -> dict[str, Any]:
73
+ response = requests.get(f"{self.base_url}/health", timeout=self.timeout)
74
+ response.raise_for_status()
75
+ return response.json()
76
+
77
+ def get_export_status(
78
+ self,
79
+ module_name: str,
80
+ job_id: str,
81
+ export_id: str,
82
+ export_format: str = "parquet",
83
+ ) -> dict[str, Any]:
84
+ return self._get(
85
+ f"exports/{module_name}/{job_id}",
86
+ params={"export_id": export_id, "format": export_format},
87
+ )
88
+
89
+ def wait_until_done(
90
+ self,
91
+ module_name: str,
92
+ job_id: str,
93
+ export_id: str,
94
+ export_format: str = "parquet",
95
+ log: bool = False,
96
+ ) -> dict[str, Any]:
97
+ started_at = time.perf_counter()
98
+
99
+ for attempt in range(1, self.max_polls + 1):
100
+ status_data = self.get_export_status(module_name, job_id, export_id, export_format)
101
+ status = status_data["status"]
102
+ state = status_data["state"]
103
+ elapsed = self._format_elapsed(time.perf_counter() - started_at)
104
+
105
+ if status == "done":
106
+ self._print(
107
+ f"API export_job={elapsed} | polls={attempt} | module={module_name} | state={state}",
108
+ log,
109
+ )
110
+ return status_data
111
+ if status == "failed":
112
+ self._print(f"API export_job_failed={elapsed} | polls={attempt} | module={module_name}", log)
113
+ raise RuntimeError(
114
+ f"Export failed. error_result={status_data.get('error_result')} "
115
+ f"errors={status_data.get('errors')}"
116
+ )
117
+ time.sleep(self.poll_seconds)
118
+
119
+ raise TimeoutError("El export no termino dentro del tiempo maximo configurado.")
120
+
121
+ def _read_files(self, files: list[dict[str, Any]], log: bool = False):
122
+ import pandas as pd
123
+
124
+ if not files:
125
+ self._print("No hay archivos para descargar", log)
126
+ return pd.DataFrame()
127
+
128
+ dataframes: list[pd.DataFrame] = []
129
+ started_at = time.perf_counter()
130
+ total_files = len(files)
131
+ download_seconds = 0.0
132
+ read_seconds = 0.0
133
+ total_bytes = 0
134
+
135
+ with tempfile.TemporaryDirectory() as tmpdir:
136
+ for idx, file_info in enumerate(files, start=1):
137
+ file_name = file_info["file_name"]
138
+ local_path = os.path.join(tmpdir, f"{idx}_{file_name}")
139
+ file_started_at = time.perf_counter()
140
+
141
+ with requests.get(file_info["signed_url"], stream=True, timeout=600) as response:
142
+ response.raise_for_status()
143
+ with open(local_path, "wb") as file_obj:
144
+ for chunk in response.iter_content(chunk_size=1024 * 1024):
145
+ if chunk:
146
+ file_obj.write(chunk)
147
+
148
+ download_seconds += time.perf_counter() - file_started_at
149
+ total_bytes += os.path.getsize(local_path)
150
+
151
+ read_started_at = time.perf_counter()
152
+ dataframes.append(pd.read_parquet(local_path))
153
+ read_seconds += time.perf_counter() - read_started_at
154
+
155
+ if not dataframes:
156
+ return pd.DataFrame()
157
+
158
+ concat_started_at = time.perf_counter()
159
+ dataframe = pd.concat(dataframes, ignore_index=True)
160
+ concat_elapsed = self._format_elapsed(time.perf_counter() - concat_started_at)
161
+ total_elapsed = self._format_elapsed(time.perf_counter() - started_at)
162
+ self._print(
163
+ f"DataFrame listo | rows={len(dataframe)} | cols={len(dataframe.columns)} "
164
+ f"| files={total_files} | mb={total_bytes / (1024 * 1024):.2f} "
165
+ f"| signed_url_download={self._format_elapsed(download_seconds)} "
166
+ f"| parquet_read={self._format_elapsed(read_seconds)} "
167
+ f"| concat={concat_elapsed} | client_download_read_total={total_elapsed}",
168
+ log,
169
+ )
170
+ return dataframe
171
+
172
+ def _export_dataframe(
173
+ self,
174
+ module_name: str,
175
+ endpoint_export: str,
176
+ payload: dict[str, Any],
177
+ log: bool = False,
178
+ ):
179
+ started_at = time.perf_counter()
180
+ export_data = self._start_export(endpoint_export, payload, log=log)
181
+
182
+ final_status = self.wait_until_done(
183
+ module_name=module_name,
184
+ job_id=export_data["job_id"],
185
+ export_id=export_data["export_id"],
186
+ export_format=export_data.get("format", "parquet"),
187
+ log=log,
188
+ )
189
+ dataframe = self._read_files(final_status.get("files", []), log)
190
+ total_elapsed = self._format_elapsed(time.perf_counter() - started_at)
191
+ self._print(f"Flujo completo finalizado en {total_elapsed}", log)
192
+ return dataframe
193
+
194
+
195
+ class Datamart(Config):
196
+ def start_vc(
197
+ self,
198
+ fecha_inicio: str,
199
+ fecha_fin: str,
200
+ moneda: str,
201
+ categoria: Optional[str] = None,
202
+ agf: Optional[str] = None,
203
+ runs: Optional[list[str]] = None,
204
+ bruto: bool = True,
205
+ format: str = "parquet",
206
+ log: bool = False,
207
+ ) -> dict[str, Any]:
208
+ return self._start_export(
209
+ "exports/fm-valores-cuota",
210
+ {
211
+ "fecha_inicio": fecha_inicio,
212
+ "fecha_fin": fecha_fin,
213
+ "moneda": moneda,
214
+ "categoria": categoria,
215
+ "agf": agf,
216
+ "runs": runs or [],
217
+ "bruto": bruto,
218
+ "format": format,
219
+ },
220
+ log=log,
221
+ )
222
+
223
+ def export_vc(
224
+ self,
225
+ fecha_inicio: str,
226
+ fecha_fin: str,
227
+ moneda: str,
228
+ log: bool = False,
229
+ **params,
230
+ ):
231
+ payload = {"fecha_inicio": fecha_inicio, "fecha_fin": fecha_fin, "moneda": moneda, **params}
232
+ payload.setdefault("runs", [])
233
+ payload.setdefault("bruto", True)
234
+ payload["format"] = "parquet"
235
+ return self._export_dataframe("fm_valores_cuota", "exports/fm-valores-cuota", payload, log=log)
236
+
237
+ def start_fondos(self, ignorar_cambio_nombre: bool, log: bool = False, **params) -> dict[str, Any]:
238
+ payload = {"ignorar_cambio_nombre": ignorar_cambio_nombre, **params}
239
+ payload.setdefault("bruto", True)
240
+ return self._start_export("exports/fondos-mutuos", payload, log=log)
241
+
242
+ def export_fondos(self, ignorar_cambio_nombre: bool, log: bool = False, **params):
243
+ payload = {"ignorar_cambio_nombre": ignorar_cambio_nombre, **params}
244
+ payload.setdefault("bruto", True)
245
+ payload["format"] = "parquet"
246
+ return self._export_dataframe("fondos_mutuos", "exports/fondos-mutuos", payload, log=log)
247
+
248
+ def start_lva_aux(
249
+ self,
250
+ runs: Optional[list[str]] = None,
251
+ vigente: Optional[int] = None,
252
+ log: bool = False,
253
+ ) -> dict:
254
+ return self._start_export(
255
+ "exports/lva-aux-valores-cuota",
256
+ {"runs": runs or [], "vigente": vigente},
257
+ log=log,
258
+ )
259
+
260
+ def export_lva_aux(
261
+ self,
262
+ runs: Optional[list[str]] = None,
263
+ vigente: Optional[int] = None,
264
+ log: bool = False,
265
+ ):
266
+ return self._export_dataframe(
267
+ "lva_aux_valores_cuota",
268
+ "exports/lva-aux-valores-cuota",
269
+ {"runs": runs or [], "vigente": vigente, "format": "parquet"},
270
+ log=log,
271
+ )
272
+
273
+
274
+ class FrameworkRiesgo(Config):
275
+ def start_benchmarks(self, categoria: str, year: str, log: bool = False) -> dict[str, Any]:
276
+ return self._start_export("exports/benchmarks", {"categoria": categoria, "year": year}, log=log)
277
+
278
+ def export_benchmarks(self, categoria: str, year: str, log: bool = False):
279
+ return self._export_dataframe(
280
+ "benchmarks",
281
+ "exports/benchmarks",
282
+ {"categoria": categoria, "year": year, "format": "parquet"},
283
+ log=log,
284
+ )
285
+
286
+ def start_metricas(self, start_date: str, end_date: str, log: bool = False, **payload) -> dict[str, Any]:
287
+ return self._start_export(
288
+ "exports/metricas-expost",
289
+ {"start_date": start_date, "end_date": end_date, **payload},
290
+ log=log,
291
+ )
292
+
293
+ def export_metricas(
294
+ self,
295
+ start_date: str,
296
+ end_date: str,
297
+ log: bool = False,
298
+ **payload,
299
+ ):
300
+ return self._export_dataframe(
301
+ "metricas_expost",
302
+ "exports/metricas-expost",
303
+ {"start_date": start_date, "end_date": end_date, **payload, "format": "parquet"},
304
+ log=log,
305
+ )
306
+
307
+ def start_alertas(self, start_date: str, end_date: str, log: bool = False, **payload) -> dict[str, Any]:
308
+ return self._start_export(
309
+ "exports/alertas-metricas-expost",
310
+ {"start_date": start_date, "end_date": end_date, **payload},
311
+ log=log,
312
+ )
313
+
314
+ def export_alertas(
315
+ self,
316
+ start_date: str,
317
+ end_date: str,
318
+ log: bool = False,
319
+ **payload,
320
+ ):
321
+ return self._export_dataframe(
322
+ "alertas_metricas_expost",
323
+ "exports/alertas-metricas-expost",
324
+ {"start_date": start_date, "end_date": end_date, **payload, "format": "parquet"},
325
+ log=log,
326
+ )
327
+
328
+ def start_niveles(self, fecha_inicio: str, fecha_fin: str, log: bool = False, **payload) -> dict[str, Any]:
329
+ return self._start_export(
330
+ "exports/niveles-riesgo",
331
+ {"fecha_inicio": fecha_inicio, "fecha_fin": fecha_fin, **payload},
332
+ log=log,
333
+ )
334
+
335
+ def export_niveles(
336
+ self,
337
+ fecha_inicio: str,
338
+ fecha_fin: str,
339
+ log: bool = False,
340
+ **payload,
341
+ ):
342
+ return self._export_dataframe(
343
+ "niveles_riesgo",
344
+ "exports/niveles-riesgo",
345
+ {"fecha_inicio": fecha_inicio, "fecha_fin": fecha_fin, **payload, "format": "parquet"},
346
+ log=log,
347
+ )
348
+
349
+ def start_srri(self, log: bool = False) -> dict[str, Any]:
350
+ return self._start_export("exports/srri", {}, log=log)
351
+
352
+ def export_srri(self, log: bool = False):
353
+ return self._export_dataframe("srri", "exports/srri", {"format": "parquet"}, log=log)
354
+
355
+
356
+ class StorageFiles(Config):
357
+ def get_file_url(self, bucket: str, path: str, log: bool = False) -> dict[str, Any]:
358
+ started_at = time.perf_counter()
359
+ data = self._post("files/signed-url", {"bucket": bucket, "path": path})
360
+ elapsed = self._format_elapsed(time.perf_counter() - started_at)
361
+ self._print(
362
+ f"file_signed_url={elapsed} | bucket={bucket} | path={path} | size_bytes={data['size_bytes']}",
363
+ log,
364
+ )
365
+ return data
366
+
367
+ def download_file(
368
+ self,
369
+ bucket: str,
370
+ path: str,
371
+ destination: str | os.PathLike = ".",
372
+ log: bool = False,
373
+ ) -> Path:
374
+ file_info = self.get_file_url(bucket, path, log=log)
375
+ destination_path = Path(destination)
376
+ if destination_path.suffix:
377
+ output_path = destination_path
378
+ output_path.parent.mkdir(parents=True, exist_ok=True)
379
+ else:
380
+ destination_path.mkdir(parents=True, exist_ok=True)
381
+ output_path = destination_path / file_info["file_name"]
382
+
383
+ started_at = time.perf_counter()
384
+ with requests.get(file_info["signed_url"], stream=True, timeout=600) as response:
385
+ response.raise_for_status()
386
+ with output_path.open("wb") as file_obj:
387
+ for chunk in response.iter_content(chunk_size=1024 * 1024):
388
+ if chunk:
389
+ file_obj.write(chunk)
390
+
391
+ elapsed = self._format_elapsed(time.perf_counter() - started_at)
392
+ self._print(
393
+ f"file_download={elapsed} | path={output_path} | mb={output_path.stat().st_size / (1024 * 1024):.2f}",
394
+ log,
395
+ )
396
+ return output_path
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: lambda-risk
3
+ Version: 0.0.0.1
4
+ Description-Content-Type: text/markdown
5
+ Requires-Dist: pandas>=2.2.3
6
+ Requires-Dist: requests>=2.32.3
7
+ Requires-Dist: numpy
8
+ Requires-Dist: scipy
9
+ Requires-Dist: PyQuantimClient
10
+ Requires-Dist: holidays
11
+ Dynamic: description
12
+ Dynamic: description-content-type
13
+ Dynamic: requires-dist
14
+
15
+ # Lambda SDK
16
+
17
+ Sistema integral de gestión de riesgo financiero
@@ -0,0 +1,9 @@
1
+ README.md
2
+ setup.py
3
+ lambda-risk/__init__.py
4
+ lambda-risk/client.py
5
+ lambda_risk.egg-info/PKG-INFO
6
+ lambda_risk.egg-info/SOURCES.txt
7
+ lambda_risk.egg-info/dependency_links.txt
8
+ lambda_risk.egg-info/requires.txt
9
+ lambda_risk.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ pandas>=2.2.3
2
+ requests>=2.32.3
3
+ numpy
4
+ scipy
5
+ PyQuantimClient
6
+ holidays
@@ -0,0 +1 @@
1
+ lambda-risk
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,24 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ with open("README.md", "r") as f:
4
+ description = f.read()
5
+
6
+
7
+ setup(
8
+ name='lambda-risk',
9
+ version='0.0.0.1',
10
+ packages=find_packages(),
11
+ include_package_data=True,
12
+ install_requires=[
13
+ "pandas>=2.2.3",
14
+ "requests>=2.32.3",
15
+ 'numpy',
16
+ 'scipy',
17
+ 'PyQuantimClient',
18
+ 'holidays'
19
+
20
+ ],
21
+ long_description=description,
22
+ long_description_content_type="text/markdown",
23
+
24
+ )