feat: Implement new sales data analysis agents and utilities

- Added Router_de_agentes.py to manage agent interactions for sales data analysis.
- Created Analizador_de_datos_de_ventas.yaml for generating structured text reports from sales data.
- Developed Generador_sql_ventas.yaml for generating SQL queries to analyze sales data.
- Established Router_de_agente.yaml as a routing mechanism for agent requests.
- Compiled centros_disponibles.md listing available sales centers.
- Introduced primera_ejecucion_de_un_agente.py as an example for executing agents.
- Added ver_los_prompts_de_un_agente.py to inspect prompts sent to OpenAI.
- Included service account key for BigQuery access in rag-datasets-reader-sa-key.json.
- Defined schema for sales data in Objeto_ventas.json.
- Implemented utility functions for querying BigQuery in conseguir_datos_bq.py.
- Created data transformation utilities in transformar_datos.py for handling decimal and date formats.
This commit is contained in:
2025-10-06 18:48:19 +02:00
parent f1e456ea05
commit 28e50b921c
28 changed files with 1309 additions and 164 deletions
+378
View File
@@ -0,0 +1,378 @@
# Constantes ##########################################################################
PROMPT = "Dame los datos de ventas de los ultimos 3 meses del centro de Alcobendas"
# Librerias de Agno ################################################################
from agno.agent import Agent
from agno.models.openai import OpenAIChat
# Prefect imports ##################################################################
from prefect import task, flow
from prefect.logging import get_run_logger
from prefect.filesystems import LocalFileSystem
local_file_system_block = LocalFileSystem.load("localfile")
# Cargar variables de entorno ######################################################
import os
from dotenv import load_dotenv
load_dotenv()
openai_api_key = os.getenv("OPENAI_API_KEY")
# Imports adicionales #########################################################
import traceback
from uuid import uuid4
# Importaciones de Archivos #########################################################
from jinja2 import Template
import yaml
import json
# Utils ###################################################################################
from utils.conseguir_datos_bq import consultar_bigquery_paginado
from utils.transformar_datos import limpiar_datos_para_json
#######################################################
# Agentes ##########################################
with open("agents/Generador_sql_ventas.yaml", "r", encoding="utf-8") as f:
experto_sql_ventas = yaml.safe_load(f)
with open("agents/Analizador_de_datos_de_ventas.yaml", "r", encoding="utf-8") as f:
analizador_de_datos_de_ventas = yaml.safe_load(f)
#########################################################
# Datos extras ##########################
with open("schemas_bbdd/Objeto_ventas.json", "r", encoding="utf-8") as f:
schema_json_ventas = json.load(f)
with open("detalles_para_agentes/centros_disponibles.md", "r", encoding="utf-8") as f:
detalles_centros = f.read()
#############################################################
# Datos añadidos a los agentes ##########################
schema_str_ventas = json.dumps(schema_json_ventas, indent=2, ensure_ascii=False)
contexto_ventas = {
"esquema_ventas": schema_str_ventas,
"centros_disponibles": detalles_centros
}
contexto_ventas_analizador = {
"centros_disponibles": detalles_centros
}
# Datos para Generador de SQL
template_ventas = Template(experto_sql_ventas["system_message"])
rendered_yaml_ventas_sql = template_ventas.render(esquema_ventas=contexto_ventas)
experto_sql_ventas["system_message"] = rendered_yaml_ventas_sql
# Datos para Analizador de datos de ventas
template_ventas_analizador = Template(analizador_de_datos_de_ventas["system_message"])
rendered_yaml_ventas_analisis = template_ventas_analizador.render(esquema_ventas=contexto_ventas_analizador)
analizador_de_datos_de_ventas["system_message"] = rendered_yaml_ventas_analisis
###############################################################
# Tareas ##########################################################
# Generar SQL ventas ######################################
@task(name="Convierte_prompt_a_sql", log_prints=True)
def Convierte_prompt_a_sql(prompt_de_usuario: str):
prefect_logger = get_run_logger()
prefect_logger.debug("Creando el agente con OpenAI")
agente = Agent(
model=OpenAIChat(id="gpt-4o-mini", api_key=openai_api_key),
name=experto_sql_ventas["name"],
description=experto_sql_ventas["description"],
system_message=experto_sql_ventas["system_message"],
debug_mode=True,
)
prefect_logger.debug("Agente creado correctamente")
prefect_logger.debug("Enviando el prompt al agente")
try:
resultado = agente.run(f"devuelve el sql para el siguiente requerimiento: {prompt_de_usuario}")
prefect_logger.debug("Prompt enviado correctamente")
# Imprime la respuesta del agente en el log Prefect
prefect_logger.info("=== RESPUESTA DEL AGENTE ===")
prefect_logger.info(str(resultado.content.strip())) # imprime texto generado
prefect_logger.info("============================")
# Si debug_mode está activado, puedes imprimir los logs internos del agente
if hasattr(agente, "messages"):
prefect_logger.debug("=== LOG INTERNO DEL AGENTE ===")
for msg in agente.messages:
prefect_logger.debug(f"{msg.role}: {msg.content}")
prefect_logger.debug("===============================")
prefect_logger.debug("Ejecución completada correctamente ✅")
return resultado.content.strip()
except Exception as e:
prefect_logger.error("Error al enviar el prompt al agente")
traceback_str = traceback.format_exc()
prefect_logger.error(f"Traceback: {traceback_str}")
raise e
# Conseguir datos de ventas a partir del SQL generado ##############################
@task(name="Consigue_datos_ventas", log_prints=True)
def Consigue_datos_ventas(sql_generado_por_agente: str, num_pagina_deseada: int = 1):
prefect_logger = get_run_logger()
prefect_logger.info("Iniciando consulta de datos de ventas en BigQuery...")
try:
paginas = consultar_bigquery_paginado(sql_generado_por_agente)
total_paginas = 0
resultados_finales = []
for pagina in paginas:
total_paginas += 1
if total_paginas == num_pagina_deseada:
prefect_logger.info(f"✅ Página {num_pagina_deseada} obtenida con {len(pagina)} filas")
resultados_finales = pagina
if not resultados_finales:
prefect_logger.warning(f"⚠️ No se encontró la página {num_pagina_deseada}. Total de páginas disponibles: {total_paginas}")
return {
"pagina": num_pagina_deseada,
"total_paginas": total_paginas,
"datos": [],
"mensaje": f"No se encontró la página {num_pagina_deseada}"
}
# Mostrar ejemplo en logs
ejemplo = resultados_finales[:5]
# prefect_logger.info("Ejemplo de datos obtenidos:")
# for fila in ejemplo:
# prefect_logger.info(str(fila))
prefect_logger.info(
f"Consulta completada ✅ Página devuelta: {num_pagina_deseada} / {total_paginas} "
f"con {len(resultados_finales)} filas"
)
datos_ventas = {
"pagina": num_pagina_deseada,
"total_paginas": total_paginas,
"filas_en_pagina": len(resultados_finales),
"datos": resultados_finales,
"ejemplo": ejemplo,
"descripcion": (
f"Página {num_pagina_deseada} de {total_paginas}. "
f"Contiene {len(resultados_finales)} filas. "
"Los datos son los resultados de la consulta SQL proporcionada."
)
}
prefect_logger.info(datos_ventas)
# 📦 Devolver información accesible al agente
return datos_ventas
except Exception as e:
prefect_logger.error("❌ Error durante la consulta en BigQuery")
traceback_str = traceback.format_exc()
prefect_logger.error(f"Traceback: {traceback_str}")
raise e
# Analizador de resultados ##########################################################
@task(name="Analizador_de_resultados", log_prints=True)
def Analizador_de_resultados(datos_ventas: dict, prompt_de_usuario: str):
prefect_logger = get_run_logger()
prefect_logger.info("Iniciando análisis de resultados de ventas...")
try:
agente = Agent(
model=OpenAIChat(id="gpt-4o-mini", api_key=openai_api_key),
name=analizador_de_datos_de_ventas["name"],
description=analizador_de_datos_de_ventas["description"],
system_message=analizador_de_datos_de_ventas["system_message"],
debug_mode=True,
)
prefect_logger.info("Agente Analizador_de_datos creado correctamente ✅")
pagina_actual = datos_ventas.get("pagina", 1)
total_paginas = datos_ventas.get("total_paginas", 1)
filas_en_pagina = datos_ventas.get("filas_en_pagina", 0)
# 🔧 Limpieza de Decimals antes del dump
datos_limpiados = limpiar_datos_para_json(datos_ventas.get("datos", []))
datos_json = json.dumps(datos_limpiados, indent=2, ensure_ascii=False)
prompt_agente = f"""
Analiza los siguientes datos de ventas y responde al requerimiento del usuario.
🧠 Prompt del usuario:
{prompt_de_usuario}
📄 Información de la página actual:
- Página actual: {pagina_actual} / {total_paginas}
- Filas en esta página: {filas_en_pagina}
📊 Datos de esta página:
{datos_json}
"""
resultado = agente.run(prompt_agente)
prefect_logger.info("✅ Análisis completado correctamente")
prefect_logger.info("=== RESULTADO DEL ANÁLISIS ===")
prefect_logger.info(resultado.content.strip())
prefect_logger.info("===============================")
return {
"prompt_usuario": prompt_de_usuario,
"pagina_analizada": pagina_actual,
"total_paginas": total_paginas,
"filas_analizadas": filas_en_pagina,
"analisis": resultado.content.strip(),
"datos_analizados": datos_limpiados,
}
except Exception as e:
prefect_logger.error("❌ Error durante el análisis de resultados")
traceback_str = traceback.format_exc()
prefect_logger.error(f"Traceback: {traceback_str}")
raise e
# FLUJO PRINCIPAL ##########################################################
@flow(name="Agente_ventas", result_storage=local_file_system_block, log_prints=True) # type: ignore
def Agente_ventas(PROMPT, num_pagina: int = 1):
prefect_logger = get_run_logger()
prefect_logger.info("🚀 Iniciando flujo Agente_ventas...")
# 🧠 1. Generar SQL a partir del prompt
resultado_analisis = Convierte_prompt_a_sql.submit(PROMPT).result()
# 🧮 2. Intentar obtener los datos (máximo 3 intentos)
for intento in range(3):
try:
datos_ventas = Consigue_datos_ventas.submit(
resultado_analisis, num_pagina_deseada=num_pagina
).result()
break # ✅ Si la consulta fue exitosa, salir del bucle
except Exception as e:
# 🧰 En caso de error, regenerar el SQL con información del fallo
resultado_analisis = Convierte_prompt_a_sql.submit(f"""
El SQL generado previamente daba error. El SQL era: {resultado_analisis}.
El error fue: {str(e)}.
Corrige el SQL para que no dé error y vuelve a generarlo.
El prompt del usuario era: {PROMPT}
""").result()
if intento == 2:
raise e # ❌ Si falla 3 veces, detener el flujo
# 🧾 3. Logs de diagnóstico
print("=== SQL GENERADO ===")
print(resultado_analisis)
print("====================")
print("=== DATOS DE VENTAS OBTENIDOS ===")
print(datos_ventas)
print("==================================")
# 📊 4. Analizar los resultados con el agente analista
prefect_logger.info(f"🔎 Analizando los resultados de la página {num_pagina}...")
analisis = Analizador_de_resultados.submit(
datos_ventas, PROMPT
).result()
# 🧠 5. Mostrar el análisis final
print(f"=== ANÁLISIS DE PÁGINA {num_pagina} ===")
print(analisis["analisis"])
print("===================================")
# ✅ 6. Devolver resultados combinados
return {
"sql_generado": resultado_analisis,
"analisis": analisis,
}
# Ejecución directa del flujo
if __name__ == "__main__":
# num_pagina=1 → analiza solo una página
Agente_ventas(PROMPT, num_pagina=1)
+44
View File
@@ -0,0 +1,44 @@
# Constantes ##########################################################################
PROMPT = "Dame la venta total del centro de velez malaga de marzo de 2023 desglosado por factura"
# Librerias de Agno ################################################################
from agno.agent import Agent
from agno.models.openai import OpenAIChat
# Prefect imports ##################################################################
from prefect import task, flow
from prefect.logging import get_run_logger
from prefect.filesystems import LocalFileSystem
local_file_system_block = LocalFileSystem.load("localfile")
# Cargar variables de entorno ######################################################
import os
from dotenv import load_dotenv
load_dotenv()
openai_api_key = os.getenv("OPENAI_API_KEY")
# Imports adicionales #########################################################
import traceback
from uuid import uuid4
# Importaciones de Archivos #########################################################
from jinja2 import Template
import yaml
import json
# Utils ###################################################################################
from utils.conseguir_datos_bq import consultar_bigquery_paginado
from utils.transformar_datos import convertir_decimales
+32
View File
@@ -0,0 +1,32 @@
name: Analizador de datos de ventas
description: Agente que analiza los datos recibidos y genera un informe en texto claro y estructurado, sin generar código ni SQL.
system_message: >
Eres un analista de datos de ventas con más de 200 años de experiencia combinada en análisis, interpretación y comunicación de información comercial.
Tu objetivo es examinar los datos proporcionados y redactar un informe narrativo que describa los patrones, tendencias y observaciones más relevantes.
➤ **Tu estilo**
- Redactas informes claros, concisos y bien estructurados.
- Usas un tono profesional y analítico, pero fácil de entender.
- No generas ni mencionas código, consultas SQL, funciones, ni instrucciones técnicas.
- No inventas datos ni haces suposiciones fuera del conjunto recibido.
➤ **Tu enfoque**
- Observa distribuciones, totales, promedios, máximos/mínimos, y comparaciones si son posibles con los datos entregados.
- Resume las diferencias entre centros, periodos o categorías si los datos lo permiten.
- Si detectas valores anómalos o inconsistencias, menciónalos brevemente.
- Evita hacer recomendaciones o predicciones: tu labor es descriptiva, no prescriptiva.
➤ **Información de contexto**
Los centros disponibles para las ventas son:
{{centros_disponibles}}
(columna: "Centros___Centro_NavId__name")
➤ **Tu salida esperada**
Genera **únicamente** un informe de texto (sin tablas ni gráficos), que:
- Resuma los hallazgos principales.
- Destaque las observaciones relevantes.
- Organice la información en secciones o párrafos temáticos.
- Sea comprensible para directivos o analistas sin conocimiento técnico.
⚠️ **No generes SQL, código, fórmulas ni pseudocódigo.**
⚠️ **Usa únicamente la información presente en los datos recibidos.**
+25
View File
@@ -0,0 +1,25 @@
name: Consultor SQL de Ventas
description: Agente que solamente tiene que generar una consulta SQL para analizar datos de ventas
system_message: >
Eres un consultor SQL con 200 años de experiencia en análisis de datos de ventas.
Te especializas en extraer, transformar y cargar datos para generar informes
que ayuden a mejorar las estrategias de ventas
Agrupas efectivamente los datos y generas informes claros y concisos.
Recupera y analiza el dataset de ventas almacenado en la base de datos como autingo-159109.rag_datasets.Objeto_Ventas.
No hagas Joins, consigue todos los datos desdes la tabla "autingo-159109.rag_datasets.Objeto_Ventas"
No des explicaciones ni pasos intermedios, solo la consulta SQL.
Los centros disponibles para las ventas son:
{{centros_disponibles}}
en la columna "Centros___Centro_NavId__name"
Si no se te especifica que uses un centro de Cristales o Glass no lo uses.
Utiliza su centro principal sin Cristales o Glass.
Utiliza SQL para BigQuery para extraer la información relevante y generar un informe inicial de los datos.
Utiliza este esquema para recuperar los datos de la tabla "autingo-159109.rag_datasets.Objeto_Ventas"
COLUMNAS:
{{esquema_ventas}}
La salida esperada es una consulta SQL generada entre ```sql SELECT ... ``` sin mas explicaciones.
@@ -0,0 +1,154 @@
Avda. Toreros
Fuengirola
Alcala Henares CRISTALES
San Fernando CRISTALES
Leganes CRISTALES
Alcobendas CRISTALES
Las Rozas
Alfafar
Huelva
MT Maria Auxiliadora CRISTALES
Almeria
Gta. Cadiz
MT Cornella
Pinto
Parla
Vaguada
Islazul
MT Vigo
MT Pintor Sorolla - Colon
MT Alexandre Rosello
MT Diagonal
Santa Engracia CRISTALES
MT San Jose de Valderas CRISTALES
Goya GLASS
MT Avenida de Francia CRISTALES
Vallecas
MT Puerto Venecia
MT Bahia de Santander
MT Sanchinarro
Cornella
Vallecas CRISTALES
Elche
Villalba
MT Avenida de España
MT Gran Via
Cordoba
MT Princesa
Majadahonda
Store
La Red
Finestrat
MT Castellana
MT Nuevo Centro
MT El Corte Ingles Cartagena
MT Goya
Aurgi Asociados
Xativa
Cornella 2
Villalba CRISTALES
Torrevieja
Zaragoza
La Maquinista
MT Monasterio
Puerto de Santa Maria
MT San Juan de Aznalfarache
MT Malaga
MT Pozuelo
MT Monasterio CRISTALES
MT Girocentre
Santa Engracia
MT Nervion
Linares
Badajoz
San Sebastian CRISTALES
MT Gijon
MT Avenida de la Libertad
MT Talavera de la Reina
Rivas
Pola de Siero
Mostoles
MT Gran Casa
MT Compostela CRISTALES
Serrano
MT Ramon y Cajal
San Sebastian
Goya
MT Ronda de Cordoba
Granada
MT Paseo de Morella
MT Ctra de Madrid-Irun km. 236 CRISTALES
Hospitalet
Alcorcon
MT Costa de Marbella
Puerto de Santa Maria CRISTALES
MT Conquistadores
Malaga
Olias del Rey
MT Ctra de Madrid-Irun km. 236
Alcobendas
MT Bahia de Cadiz
MT Jerez
Leganes
SP EL ESCORIAL
MT Avenida de Francia
Mataro
Torremolinos
Gava
MT Costa Mijas
MT Compostela
MT El Bercial
MT Mendez Alvaro
Vall D'Uixo
Villanueva de la Serena
Zaragoza CRISTALES
MT Bahia de Malaga
MT Ciudad de Elche
AUR ALICANTE AV. NOVELDA
MT Xanadu
MT Web
Les Franqueses
Chamartin
MT Alcala de Henares
Aluche
MT Cornella CRISTALES
Las Rozas CRISTALES
Sant Celoni
MT Ramon y Cajal CRISTALES
MT San Jose de Valderas
San Fernando
Tucan
MT Campo de las Naciones
Aurgi Web
Barbera
San Juan CRISTALES
MT Pozuelo CRISTALES
MT Siete Palmas
Sabadell
Roquetas
Sant Adria
MT Jaen
Alcala Henares
MT El Bercial CRISTALES
Denia
Unidad Movil Madrid Glass
Aurgi Asociados Gruas
Velez Malaga
Jerez
Arganda
Emilio Muñoz
MT Alicante
Granollers
Sant Boi
MT Bahia de Algeciras
Oficinas Centrales
MT Maria Auxiliadora
Barbera CRISTALES
MT Tres de Mayo
Autingo
Sant Cugat
SP JAVEA
Store CRISTALES
San Juan
Marques Vadillo
Valdemoro
@@ -19,12 +19,13 @@ load_dotenv()
openai_api_key = os.getenv("OPENAI_API_KEY")
prefect_logger = get_run_logger()
# Definir tareas ###############################################
@task(name="Inicializar agente financiero", log_prints=True)
def inicializar_agente_financiero():
prefect_logger = get_run_logger()
prefect_logger.debug(f"Inicializando el agente financiero")
# Aquí se podría agregar la lógica para inicializar el agente
return "Agente financiero inicializado"
@@ -32,9 +33,9 @@ def inicializar_agente_financiero():
@task(name="Analizar datos financieros", log_prints=True)
def analizar_datos_financieros(datos: str):
prefect_logger = get_run_logger()
prefect_logger.debug(f"Creando el agente con OpenAI")
prefect_logger.debug(f"analizando los datos: {datos}")
@@ -121,7 +121,7 @@ def averiguar_el_prompt_que_seenvia_a_openai(prompt_de_usuario: str):
# Definir el flujo principal #########################################
@flow(name="Flujo financiero", result_storage=local_file_system_block, log_prints=True) # type: ignore
@flow(name="Averiguar_prompt_enviado", result_storage=local_file_system_block, log_prints=True) # type: ignore
def flujo_principal():
resultado_analisis = averiguar_el_prompt_que_seenvia_a_openai.submit(PROMPT).result()
-64
View File
@@ -1,64 +0,0 @@
version: '3.9'
services:
otel-collector:
image: otel/opentelemetry-collector-contrib:0.136.0
container_name: otel-collector
command:
- --config=/etc/otel/config.yaml
volumes:
- ./otel-config.yaml:/etc/otel/config.yaml
ports:
- 4317:4317
- 4318:4318
depends_on:
- victoria
- tempo
victoria:
image: victoriametrics/victoria-metrics:latest
container_name: victoria
ports:
- 8428:8428
volumes:
- ./victoria-data:/victoria-metrics-data
command:
- --storageDataPath=/victoria-metrics-data
- --retentionPeriod=3
tempo:
image: grafana/tempo:latest
container_name: tempo
ports:
- 3200:3200
volumes:
- ./tempo-data:/var/tempo
command:
- -config.file=/etc/tempo.yaml
configs:
- source: tempo_config
target: /etc/tempo.yaml
grafana-srv:
image: grafana/grafana-oss:latest
container_name: grafana-srv
ports:
- 33000:3000
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: admin123
GF_USERS_ALLOW_SIGN_UP: 'false'
depends_on:
- victoria
- tempo
volumes:
- grafana_data:/var/lib/grafana
- ./provisioning/datasources:/etc/grafana/provisioning/datasources
- ./provisioning/dashboards:/etc/grafana/provisioning/dashboards
- ./grafana-dashboards:/var/lib/grafana/dashboards
configs:
tempo_config:
content: "\nserver:\n http_listen_port: 3200\n\ndistributor:\n receivers:\n\
otlp:\n protocols:\n grpc:\n endpoint: \"0.0.0.0:4317\"\n http:\n\
\ endpoint: \"0.0.0.0:4318\"\n\ningester:\n trace_idle_period: 10s\n \
\ max_block_bytes: 1000000\n max_block_duration: 5m\n\ncompactor:\n compaction:\n\
block_retention: 24h\n\nstorage:\n trace:\nwal:\n path: /var/tempo/wal\nlocal:\n\
\ path: /var/tempo/blocks\n"
volumes:
grafana_data: {}
@@ -1,29 +0,0 @@
{
"id": null,
"uid": "overview",
"title": "\ud83d\udcca System Overview",
"timezone": "browser",
"panels": [
{
"type": "graph",
"title": "CPU Usage",
"targets": [
{
"expr": "process_cpu_seconds_total",
"legendFormat": "{{instance}}"
}
]
},
{
"type": "table",
"title": "Logs (simulados)",
"targets": [
{
"expr": "up"
}
]
}
],
"schemaVersion": 36,
"version": 1
}
-42
View File
@@ -1,42 +0,0 @@
receivers:
otlp:
protocols:
grpc: {}
http: {}
processors:
batch: {}
exporters:
prometheusremotewrite:
endpoint: http://victoria:8428/api/v1/write
otlp/tempo:
endpoint: http://tempo:4317
tls:
insecure: true
debug:
verbosity: normal
service:
pipelines:
metrics:
receivers:
- otlp
processors:
- batch
exporters:
- prometheusremotewrite
- debug
traces:
receivers:
- otlp
processors:
- batch
exporters:
- otlp/tempo
- debug
logs:
receivers:
- otlp
processors:
- batch
exporters:
- prometheusremotewrite
- debug
@@ -1,10 +0,0 @@
apiVersion: 1
providers:
- name: default
orgId: 1
folder: ''
type: file
disableDeletion: false
updateIntervalSeconds: 10
options:
path: /var/lib/grafana/dashboards
@@ -1,11 +0,0 @@
apiVersion: 1
datasources:
- name: VictoriaMetrics
type: prometheus
access: proxy
url: http://victoria:8428
isDefault: true
- name: Tempo
type: tempo
access: proxy
url: http://tempo:3200
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
田Jpu6
+1
View File
@@ -6,6 +6,7 @@ readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"agno>=2.1.1",
"google-cloud-bigquery>=3.38.0",
"icecream>=2.1.8",
"marimo>=0.16.5",
"openai>=2.1.0",
+13
View File
@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "autingo-159109",
"private_key_id": "c43c626d47606344cd3a255d62df1be77019cc54",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQD0yeIqsGZ/RYHO\nqIZ67AN1Jd+c2rKzAOp1G/9XKuNfQV4XChkq2pVCMG78WbgUWSMlJY2OLsVdEvrS\n4W8MM9ZiXkJ0Ox3O5Y6/1w+omLBaqG/JuJU/euRvUZUIjLc36fpY0nzZu42CK7oK\nhUepE31WYf5i+MbhR5LpaOaJQQjfOhRZAd01iUCiKFGPVfI24dk0qYZnBQKVSSmH\nQD8zjNVHPo0LmXWOwOj2t4VrBq9WjrepJftzKumyieLJrMua76pMc+dmStyWb4yK\nTrk2yjXsN9QQRN3SLG+k0hedPdCa5ca+Wi0OCGBb1akaFjgAKw/ygiRtUDDUfmjv\nH/Q7wR/VAgMBAAECggEAOU8TfWGPmY/dEFQpqdkcBR7xD3CvIhPekDzWBqMSmOA9\npkC1vC/w/k5HCZr6qv7kaMO2NJm0GLKDGQBwxmdTc3O0dLBLbf8V8Mlpj9Pxg8QH\n6e7mODauCPbNYLNLCNLlSsq6sqIDgvx4QZLLAGVA9IqcKzEppJ/kX/Nwd6VLbbEe\nzm+P9RJRPGNBjpUKvkI1KLVRADLAuFQN44S9IzHItEYEi7C8Qe6EdPl5scXJn3hq\noPW7YoGgaaj7G81yXovQUK4tTSxduCsO4HhiYAF3CfYsoZ+sXQ/MpNQWZK5Yiic3\n+z6MsNSE0SYeOX9E008/PPzKwR2ehgmO2U6Lhl5AAQKBgQD8zSXbx7dEhRXI6+uV\nT+Fiuke3FGBUu6zGbMn75KGFnHaOMVfARaJnmla8EJ01ZkGMRis4f53elywUSQyJ\nBa7xCP7/FshuM9J+5vfvGM7NRc6kcUUaJSlg52/DNgeZy2cijBy0iaPy7+uyRRcO\nWhyaj3h+bv11US0dyt7TB3jf1QKBgQD34sgICtQcdkt51l+iHmAhiW2U6s8kuFDa\n9TX8bp4YaPoC65IkfutI3saLcJpqLgBb67j/HxCb6gkFr+OeQQIGN+X7J4rTUdhB\nLEWAnjMV3ow23q8tO5xN+jnLV0UCiF8EZLg7EIzLQ0MozjB9gndp8xxoEKDqWEG5\nagP9p4dAAQKBgELiF+EU7sTfHQtid5qyXqQbOrwSVQY1/RkmUS4mqCFMawVlwpyp\nD7WvXME2+BDXtAHj0q2I/gCVKGFZjkp2SXmV8rkUkwStC0Tt4KzOeHBQxsI1AZ5Q\nNKlhse0Iz2v+J5Q5U6LkQ48TsN0icF4osyalTLDOtpoiVvhp4xgcAvvdAoGAOBXL\nhGZOz5HESfDC+n886NmbPZJTA8/gG2pXqKGui39U8cwy6Kb+vSIKcgosJdH6qtGO\nrcpti5lMKUk+itPSjW2gT08HDgD6mORXZV5l2JDd0JxZrjZKiyoOYX+BUa1hMjFH\nrbV05Zh2XYkpV3xpYENtLe51OhB17mmaNY3uAAECgYBydII40sWjpvybaRc40UXH\nWLKCDPfeeSyJqrybU80nbOKI6WfmO9sBtVS/qf+4z8aDHenOhmcpXPIFhEe34ARw\nanWSjSBLI1sdn88KwtLSd2dCytN+0VnJ1RNpzHPwDK4J5wZaIJV0AYvryfWazqn4\nazDWb8s8eRHw+9nAS2TALg==\n-----END PRIVATE KEY-----\n",
"client_email": "rag-datasets-reader-sa@autingo-159109.iam.gserviceaccount.com",
"client_id": "111296269982496507213",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/rag-datasets-reader-sa%40autingo-159109.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
+359
View File
@@ -0,0 +1,359 @@
[
{
"name": "Fecha",
"mode": "NULLABLE",
"type": "DATETIME",
"description": "",
"fields": []
},
{
"name": "numeroDocumento",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "NumeroLineas",
"mode": "NULLABLE",
"type": "INTEGER",
"description": "",
"fields": []
},
{
"name": "idCentro",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "idProducto",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "cantidad",
"mode": "NULLABLE",
"type": "BIGNUMERIC",
"description": "",
"fields": []
},
{
"name": "precioUnitario",
"mode": "NULLABLE",
"type": "BIGNUMERIC",
"description": "",
"fields": []
},
{
"name": "Base_imponible_linea",
"mode": "NULLABLE",
"type": "BIGNUMERIC",
"description": "",
"fields": []
},
{
"name": "Tipo_de_documento",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "semana",
"mode": "NULLABLE",
"type": "INTEGER",
"description": "",
"fields": []
},
{
"name": "ano",
"mode": "NULLABLE",
"type": "INTEGER",
"description": "",
"fields": []
},
{
"name": "ano_mas_uno",
"mode": "NULLABLE",
"type": "INTEGER",
"description": "",
"fields": []
},
{
"name": "Fecha_mas_un_ano",
"mode": "NULLABLE",
"type": "DATETIME",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__nav_id",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__id_centro",
"mode": "NULLABLE",
"type": "INTEGER",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__name",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Companies__name",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Zones__name",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Cluster_centros",
"mode": "NULLABLE",
"type": "INTEGER",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Jefes_de_centro_y_Rmos___co_51ce9994",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Jefes_de_centro_y_Rmos___co_c6afb8e8",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Jefes_de_centro_y_Rmos___co_a5423963",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Provincias_comunidades_y_su_b8d2c2a0",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Provincias_comunidades_y_su_cb74d1a1",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Centros___Centro_NavId__Provincias_comunidades_y_su_9fd8d3fc",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__id",
"mode": "NULLABLE",
"type": "INTEGER",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__nav_id",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__description",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__is_tecdoc",
"mode": "NULLABLE",
"type": "BOOLEAN",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Status_de_producto",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Is_Encargo",
"mode": "NULLABLE",
"type": "BOOLEAN",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__is_service",
"mode": "NULLABLE",
"type": "BOOLEAN",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__is_tpv",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Tipo___Navision",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Product_Groups__description",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Product_Categories__description",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo1",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo2",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo3",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo5",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo4",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo6",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo7",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo8",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo9",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idAtributo10",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idCategoria",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Producto___Nav__idGrupo",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Categoria_16_07_cgq___t_cb30d712",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Categoria_16_07_cgq___t_d83f396f",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
},
{
"name": "Productos___Producto_NavId__Categoria_16_07_cgq___t_5e9550ff",
"mode": "NULLABLE",
"type": "STRING",
"description": "",
"fields": []
}
]
+68
View File
@@ -0,0 +1,68 @@
from google.cloud import bigquery
from typing import Generator, List, Dict
import os
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "rag-datasets-reader-sa-key.json"
def consultar_bigquery_paginado(
sql_query: str,
page_size: int = 1000,
project_id: str | None = None
) -> Generator[List[Dict], None, None]:
"""
Ejecuta una consulta SQL de BigQuery y devuelve los resultados paginados.
Limpia el texto SQL automáticamente antes de ejecutarlo.
Args:
sql_query (str): Texto SQL (puede incluir espacios o punto y coma al final).
page_size (int): Número de filas por página.
project_id (str | None): ID del proyecto GCP (opcional si está configurado el entorno).
Yields:
list[dict]: Página de resultados (cada fila convertida a dict).
"""
# 🧹 Limpieza del texto SQL
cleaned_query = (
sql_query.strip() # elimina espacios y saltos al inicio y fin
.removeprefix("```sql").removeprefix("```").removesuffix("```") # limpia delimitadores markdown si los hay
.strip() # vuelve a limpiar por si quedaron espacios
)
# Quita el punto y coma final si lo tiene
if cleaned_query.endswith(";"):
cleaned_query = cleaned_query[:-1].strip()
# Inicializa el cliente
client = bigquery.Client(project=project_id)
# Ejecuta la consulta
query_job = client.query(cleaned_query)
# Devuelve resultados paginados
iterator = query_job.result(page_size=page_size)
for page in iterator.pages:
yield [dict(row) for row in page]
if __name__ == "__main__":
resultado = consultar_bigquery_paginado("""```sql
SELECT
Productos___Producto_NavId__description AS producto,
Centros___Centro_NavId__name AS region,
SUM(cantidad) AS total_vendido,
SUM(cantidad * precioUnitario) AS ingreso_total
FROM
`autingo-159109.rag_datasets.Objeto_Ventas`
WHERE
Fecha >= DATE_SUB(CURRENT_DATE(), INTERVAL 3 MONTH)
GROUP BY
producto, region
ORDER BY
total_vendido DESC;
```""")
for pagina in resultado:
for fila in pagina:
print(fila)
print("----- Fin de página -----")
+32
View File
@@ -0,0 +1,32 @@
from decimal import Decimal
from datetime import datetime, date, time
def convertir_decimales(obj):
if isinstance(obj, list):
return [convertir_decimales(x) for x in obj]
elif isinstance(obj, dict):
return {k: convertir_decimales(v) for k, v in obj.items()}
elif isinstance(obj, Decimal):
return float(obj)
else:
return obj
def convertir_fechas(obj):
"""
Convierte objetos datetime, date y time a su representación ISO 8601.
"""
if isinstance(obj, list):
return [convertir_fechas(x) for x in obj]
elif isinstance(obj, dict):
return {k: convertir_fechas(v) for k, v in obj.items()}
elif isinstance(obj, (datetime, date, time)):
return obj.isoformat()
else:
return obj
def limpiar_datos_para_json(data):
data = convertir_decimales(data)
data = convertir_fechas(data)
return data
Generated
+198
View File
@@ -280,6 +280,7 @@ version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "agno" },
{ name = "google-cloud-bigquery" },
{ name = "icecream" },
{ name = "marimo" },
{ name = "openai" },
@@ -292,6 +293,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "agno", specifier = ">=2.1.1" },
{ name = "google-cloud-bigquery", specifier = ">=3.38.0" },
{ name = "icecream", specifier = ">=2.1.8" },
{ name = "marimo", specifier = ">=0.16.5" },
{ name = "openai", specifier = ">=2.1.0" },
@@ -519,6 +521,112 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" },
]
[[package]]
name = "google-api-core"
version = "2.25.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "google-auth" },
{ name = "googleapis-common-protos" },
{ name = "proto-plus" },
{ name = "protobuf" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/09/cd/63f1557235c2440fe0577acdbc32577c5c002684c58c7f4d770a92366a24/google_api_core-2.25.2.tar.gz", hash = "sha256:1c63aa6af0d0d5e37966f157a77f9396d820fba59f9e43e9415bc3dc5baff300", size = 166266, upload-time = "2025-10-03T00:07:34.778Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/d8/894716a5423933f5c8d2d5f04b16f052a515f78e815dab0c2c6f1fd105dc/google_api_core-2.25.2-py3-none-any.whl", hash = "sha256:e9a8f62d363dc8424a8497f4c2a47d6bcda6c16514c935629c257ab5d10210e7", size = 162489, upload-time = "2025-10-03T00:07:32.924Z" },
]
[package.optional-dependencies]
grpc = [
{ name = "grpcio" },
{ name = "grpcio-status" },
]
[[package]]
name = "google-auth"
version = "2.41.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cachetools" },
{ name = "pyasn1-modules" },
{ name = "rsa" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/af/5129ce5b2f9688d2fa49b463e544972a7c82b0fdb50980dafee92e121d9f/google_auth-2.41.1.tar.gz", hash = "sha256:b76b7b1f9e61f0cb7e88870d14f6a94aeef248959ef6992670efee37709cbfd2", size = 292284, upload-time = "2025-09-30T22:51:26.363Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/a4/7319a2a8add4cc352be9e3efeff5e2aacee917c85ca2fa1647e29089983c/google_auth-2.41.1-py2.py3-none-any.whl", hash = "sha256:754843be95575b9a19c604a848a41be03f7f2afd8c019f716dc1f51ee41c639d", size = 221302, upload-time = "2025-09-30T22:51:24.212Z" },
]
[[package]]
name = "google-cloud-bigquery"
version = "3.38.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "google-api-core", extra = ["grpc"] },
{ name = "google-auth" },
{ name = "google-cloud-core" },
{ name = "google-resumable-media" },
{ name = "packaging" },
{ name = "python-dateutil" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/07/b2/a17e40afcf9487e3d17db5e36728ffe75c8d5671c46f419d7b6528a5728a/google_cloud_bigquery-3.38.0.tar.gz", hash = "sha256:8afcb7116f5eac849097a344eb8bfda78b7cfaae128e60e019193dd483873520", size = 503666, upload-time = "2025-09-17T20:33:33.47Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/39/3c/c8cada9ec282b29232ed9aed5a0b5cca6cf5367cb2ffa8ad0d2583d743f1/google_cloud_bigquery-3.38.0-py3-none-any.whl", hash = "sha256:e06e93ff7b245b239945ef59cb59616057598d369edac457ebf292bd61984da6", size = 259257, upload-time = "2025-09-17T20:33:31.404Z" },
]
[[package]]
name = "google-cloud-core"
version = "2.4.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "google-api-core" },
{ name = "google-auth" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861, upload-time = "2025-03-10T21:05:38.948Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348, upload-time = "2025-03-10T21:05:37.785Z" },
]
[[package]]
name = "google-crc32c"
version = "1.7.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467, upload-time = "2025-03-26T14:36:06.909Z" },
{ url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309, upload-time = "2025-03-26T15:06:15.318Z" },
{ url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133, upload-time = "2025-03-26T14:41:34.388Z" },
{ url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773, upload-time = "2025-03-26T14:41:35.19Z" },
{ url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475, upload-time = "2025-03-26T14:29:11.771Z" },
{ url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243, upload-time = "2025-03-26T14:41:35.975Z" },
{ url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870, upload-time = "2025-03-26T14:41:37.08Z" },
]
[[package]]
name = "google-resumable-media"
version = "2.7.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "google-crc32c" },
]
sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099, upload-time = "2024-08-07T22:20:38.555Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251, upload-time = "2024-08-07T22:20:36.409Z" },
]
[[package]]
name = "googleapis-common-protos"
version = "1.70.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" },
]
[[package]]
name = "graphviz"
version = "0.21"
@@ -564,6 +672,51 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/b1/9ff6578d789a89812ff21e4e0f80ffae20a65d5dd84e7a17873fe3b365be/griffe-1.14.0-py3-none-any.whl", hash = "sha256:0e9d52832cccf0f7188cfe585ba962d2674b241c01916d780925df34873bceb0", size = 144439, upload-time = "2025-09-05T15:02:27.511Z" },
]
[[package]]
name = "grpcio"
version = "1.75.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9d/f7/8963848164c7604efb3a3e6ee457fdb3a469653e19002bd24742473254f8/grpcio-1.75.1.tar.gz", hash = "sha256:3e81d89ece99b9ace23a6916880baca613c03a799925afb2857887efa8b1b3d2", size = 12731327, upload-time = "2025-09-26T09:03:36.887Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/74/bac4ab9f7722164afdf263ae31ba97b8174c667153510322a5eba4194c32/grpcio-1.75.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:3bed22e750d91d53d9e31e0af35a7b0b51367e974e14a4ff229db5b207647884", size = 5672779, upload-time = "2025-09-26T09:02:19.11Z" },
{ url = "https://files.pythonhosted.org/packages/a6/52/d0483cfa667cddaa294e3ab88fd2c2a6e9dc1a1928c0e5911e2e54bd5b50/grpcio-1.75.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5b8f381eadcd6ecaa143a21e9e80a26424c76a0a9b3d546febe6648f3a36a5ac", size = 11470623, upload-time = "2025-09-26T09:02:22.117Z" },
{ url = "https://files.pythonhosted.org/packages/cf/e4/d1954dce2972e32384db6a30273275e8c8ea5a44b80347f9055589333b3f/grpcio-1.75.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5bf4001d3293e3414d0cf99ff9b1139106e57c3a66dfff0c5f60b2a6286ec133", size = 6248838, upload-time = "2025-09-26T09:02:26.426Z" },
{ url = "https://files.pythonhosted.org/packages/06/43/073363bf63826ba8077c335d797a8d026f129dc0912b69c42feaf8f0cd26/grpcio-1.75.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f82ff474103e26351dacfe8d50214e7c9322960d8d07ba7fa1d05ff981c8b2d", size = 6922663, upload-time = "2025-09-26T09:02:28.724Z" },
{ url = "https://files.pythonhosted.org/packages/c2/6f/076ac0df6c359117676cacfa8a377e2abcecec6a6599a15a672d331f6680/grpcio-1.75.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ee119f4f88d9f75414217823d21d75bfe0e6ed40135b0cbbfc6376bc9f7757d", size = 6436149, upload-time = "2025-09-26T09:02:30.971Z" },
{ url = "https://files.pythonhosted.org/packages/6b/27/1d08824f1d573fcb1fa35ede40d6020e68a04391709939e1c6f4193b445f/grpcio-1.75.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:664eecc3abe6d916fa6cf8dd6b778e62fb264a70f3430a3180995bf2da935446", size = 7067989, upload-time = "2025-09-26T09:02:33.233Z" },
{ url = "https://files.pythonhosted.org/packages/c6/98/98594cf97b8713feb06a8cb04eeef60b4757e3e2fb91aa0d9161da769843/grpcio-1.75.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c32193fa08b2fbebf08fe08e84f8a0aad32d87c3ad42999c65e9449871b1c66e", size = 8010717, upload-time = "2025-09-26T09:02:36.011Z" },
{ url = "https://files.pythonhosted.org/packages/8c/7e/bb80b1bba03c12158f9254762cdf5cced4a9bc2e8ed51ed335915a5a06ef/grpcio-1.75.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5cebe13088b9254f6e615bcf1da9131d46cfa4e88039454aca9cb65f639bd3bc", size = 7463822, upload-time = "2025-09-26T09:02:38.26Z" },
{ url = "https://files.pythonhosted.org/packages/23/1c/1ea57fdc06927eb5640f6750c697f596f26183573069189eeaf6ef86ba2d/grpcio-1.75.1-cp313-cp313-win32.whl", hash = "sha256:4b4c678e7ed50f8ae8b8dbad15a865ee73ce12668b6aaf411bf3258b5bc3f970", size = 3938490, upload-time = "2025-09-26T09:02:40.268Z" },
{ url = "https://files.pythonhosted.org/packages/4b/24/fbb8ff1ccadfbf78ad2401c41aceaf02b0d782c084530d8871ddd69a2d49/grpcio-1.75.1-cp313-cp313-win_amd64.whl", hash = "sha256:5573f51e3f296a1bcf71e7a690c092845fb223072120f4bdb7a5b48e111def66", size = 4642538, upload-time = "2025-09-26T09:02:42.519Z" },
{ url = "https://files.pythonhosted.org/packages/f2/1b/9a0a5cecd24302b9fdbcd55d15ed6267e5f3d5b898ff9ac8cbe17ee76129/grpcio-1.75.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:c05da79068dd96723793bffc8d0e64c45f316248417515f28d22204d9dae51c7", size = 5673319, upload-time = "2025-09-26T09:02:44.742Z" },
{ url = "https://files.pythonhosted.org/packages/c6/ec/9d6959429a83fbf5df8549c591a8a52bb313976f6646b79852c4884e3225/grpcio-1.75.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06373a94fd16ec287116a825161dca179a0402d0c60674ceeec8c9fba344fe66", size = 11480347, upload-time = "2025-09-26T09:02:47.539Z" },
{ url = "https://files.pythonhosted.org/packages/09/7a/26da709e42c4565c3d7bf999a9569da96243ce34a8271a968dee810a7cf1/grpcio-1.75.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4484f4b7287bdaa7a5b3980f3c7224c3c622669405d20f69549f5fb956ad0421", size = 6254706, upload-time = "2025-09-26T09:02:50.4Z" },
{ url = "https://files.pythonhosted.org/packages/f1/08/dcb26a319d3725f199c97e671d904d84ee5680de57d74c566a991cfab632/grpcio-1.75.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2720c239c1180eee69f7883c1d4c83fc1a495a2535b5fa322887c70bf02b16e8", size = 6922501, upload-time = "2025-09-26T09:02:52.711Z" },
{ url = "https://files.pythonhosted.org/packages/78/66/044d412c98408a5e23cb348845979a2d17a2e2b6c3c34c1ec91b920f49d0/grpcio-1.75.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:07a554fa31c668cf0e7a188678ceeca3cb8fead29bbe455352e712ec33ca701c", size = 6437492, upload-time = "2025-09-26T09:02:55.542Z" },
{ url = "https://files.pythonhosted.org/packages/4e/9d/5e3e362815152aa1afd8b26ea613effa005962f9da0eec6e0e4527e7a7d1/grpcio-1.75.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3e71a2105210366bfc398eef7f57a664df99194f3520edb88b9c3a7e46ee0d64", size = 7081061, upload-time = "2025-09-26T09:02:58.261Z" },
{ url = "https://files.pythonhosted.org/packages/1e/1a/46615682a19e100f46e31ddba9ebc297c5a5ab9ddb47b35443ffadb8776c/grpcio-1.75.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8679aa8a5b67976776d3c6b0521e99d1c34db8a312a12bcfd78a7085cb9b604e", size = 8010849, upload-time = "2025-09-26T09:03:00.548Z" },
{ url = "https://files.pythonhosted.org/packages/67/8e/3204b94ac30b0f675ab1c06540ab5578660dc8b690db71854d3116f20d00/grpcio-1.75.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aad1c774f4ebf0696a7f148a56d39a3432550612597331792528895258966dc0", size = 7464478, upload-time = "2025-09-26T09:03:03.096Z" },
{ url = "https://files.pythonhosted.org/packages/b7/97/2d90652b213863b2cf466d9c1260ca7e7b67a16780431b3eb1d0420e3d5b/grpcio-1.75.1-cp314-cp314-win32.whl", hash = "sha256:62ce42d9994446b307649cb2a23335fa8e927f7ab2cbf5fcb844d6acb4d85f9c", size = 4012672, upload-time = "2025-09-26T09:03:05.477Z" },
{ url = "https://files.pythonhosted.org/packages/f9/df/e2e6e9fc1c985cd1a59e6996a05647c720fe8a03b92f5ec2d60d366c531e/grpcio-1.75.1-cp314-cp314-win_amd64.whl", hash = "sha256:f86e92275710bea3000cb79feca1762dc0ad3b27830dd1a74e82ab321d4ee464", size = 4772475, upload-time = "2025-09-26T09:03:07.661Z" },
]
[[package]]
name = "grpcio-status"
version = "1.75.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "googleapis-common-protos" },
{ name = "grpcio" },
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/74/5b/1ce0e3eedcdc08b4739b3da5836f31142ec8bee1a9ae0ad8dc0dc39a14bf/grpcio_status-1.75.1.tar.gz", hash = "sha256:8162afa21833a2085c91089cc395ad880fac1378a1d60233d976649ed724cbf8", size = 13671, upload-time = "2025-09-26T09:13:16.412Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d8/ad/6f414bb0b36eee20d93af6907256f208ffcda992ae6d3d7b6a778afe31e6/grpcio_status-1.75.1-py3-none-any.whl", hash = "sha256:f681b301be26dcf7abf5c765d4a22e4098765e1a65cbdfa3efca384edf8e4e3c", size = 14428, upload-time = "2025-09-26T09:12:55.516Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
@@ -1282,6 +1435,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" },
]
[[package]]
name = "proto-plus"
version = "1.26.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" },
]
[[package]]
name = "protobuf"
version = "6.32.1"
@@ -1321,6 +1486,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/49/a6cfc94a9c483b1fa401fbcb23aca7892f60c7269c5ffa2ac408364f80dc/psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2", size = 2569060, upload-time = "2025-01-04T20:09:15.28Z" },
]
[[package]]
name = "pyasn1"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" },
]
[[package]]
name = "pyasn1-modules"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
]
[[package]]
name = "pycparser"
version = "2.23"
@@ -1736,6 +1922,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" },
]
[[package]]
name = "rsa"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyasn1" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
]
[[package]]
name = "ruamel-yaml"
version = "0.18.15"