f3d427d9e4
Conecta el motor AutomaticEDA con los datos crudos para que los 4 capítulos
dependientes de ctx (modelos, timeseries, geospatial, agregacion) salgan
POBLADOS en vez de degradar a una nota.
- build_eda_render_ctx (datascience, impure, dict-no-throw): dado db_path+table
y el TableProfile agregado, construye el ctx con los datos crudos que el
perfil no incluye: raw_numeric {col:[float|None]} alineado por fila (modelos /
geospatial), timeseries_raw {time_col,t,series} vía extract_timeseries_raw,
geo_points {lats,lons} desde el par lat/lon detectado, y db_path/table para el
groupby/pivot push-down de agregacion. Muestrea con LIMIT (no trae la tabla
entera a RAM). Compone detect_time_column / extract_timeseries_raw /
detect_latlon_columns / duckdb_query_readonly (imports lazy para evitar ciclo).
- render_automatic_eda (pipeline): one-shot perfil -> ctx -> PDF + PPTX con los
11 capítulos poblados; devuelve rutas + manifest de versiones por capítulo.
- profile_table: flag aditivo emit_automatic=True emite el AutomaticEDA PDF+PPTX
además del flujo legacy (emit_pdf/render_eda_pdf intacto). Nuevas claves de
retorno aeda_pdf_path / aeda_pptx_path / aeda_manifest_path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
158 lines
6.9 KiB
Python
158 lines
6.9 KiB
Python
"""render_automatic_eda — EDA completo one-shot: perfil → ctx → PDF + PPTX.
|
|
|
|
Pipeline impuro del grupo de capacidad `eda`. Dada UNA tabla DuckDB (o
|
|
PostgreSQL), produce el informe AutomaticEDA COMPLETO en sus dos formatos a la
|
|
vez (PDF móvil A5 + PPTX 16:9) con los 11 capítulos POBLADOS, en una sola
|
|
llamada. Compone, sin reimplementar su lógica, cuatro funciones del registry:
|
|
|
|
- profile_table : perfila la tabla end-to-end (TableProfile agregado),
|
|
opcionalmente con modelos baratos y análisis de serie.
|
|
- build_eda_render_ctx : construye el `ctx` con los DATOS CRUDOS que el
|
|
TableProfile agregado no incluye (raw_numeric para
|
|
modelos/geo, timeseries_raw para series, geo_points
|
|
para el mapa, db_path/table para la agregación
|
|
push-down). Sin él, esos capítulos degradan.
|
|
- render_automatic_eda_pdf : renderiza el documento por capítulos a PDF.
|
|
- render_automatic_eda_pptx : renderiza el mismo documento a PPTX.
|
|
|
|
El TableProfile agregado basta para portada/overview/distribuciones/calidad/
|
|
correlación, pero los capítulos `modelos`, `timeseries`, `geospatial` y
|
|
`agregacion` necesitan datos crudos (los clusters proyectados sobre el PCA, la
|
|
serie valor-vs-tiempo, los puntos lat/lon, las filas para el groupby/pivot).
|
|
`build_eda_render_ctx` los muestrea (LIMIT + push-down, sin traer la tabla
|
|
entera a RAM) y los entrega en `ctx`; este pipeline los pasa como `meta['ctx']`
|
|
a ambos renderers para que el informe salga completo.
|
|
|
|
Estilo dict-no-throw del grupo `eda`: nunca lanza; captura cualquier error y
|
|
degrada a `{"status": "error", "error": str}`.
|
|
"""
|
|
|
|
import os
|
|
from datetime import datetime, timezone
|
|
|
|
from datascience import (
|
|
build_eda_render_ctx,
|
|
render_automatic_eda_pdf,
|
|
render_automatic_eda_pptx,
|
|
)
|
|
from pipelines.profile_table import profile_table
|
|
|
|
# Tokens de almacenamiento por backend (para la portada del informe).
|
|
_STORAGE = {"duckdb": "DuckDB", "postgres": "PostgreSQL"}
|
|
|
|
|
|
def render_automatic_eda(
|
|
db_path: str,
|
|
table: str,
|
|
backend: str = "duckdb",
|
|
sample: int = 5000,
|
|
run_models: bool = True,
|
|
run_series: bool = True,
|
|
run_llm: bool = False,
|
|
out_dir: str = "reports",
|
|
basename: str = None,
|
|
ctx_extra: dict = None,
|
|
) -> dict:
|
|
"""Perfila una tabla y emite el informe AutomaticEDA completo (PDF + PPTX).
|
|
|
|
Args:
|
|
db_path: ruta al archivo DuckDB, o DSN PostgreSQL si backend="postgres".
|
|
table: nombre de la tabla a perfilar.
|
|
backend: "duckdb" (default) o "postgres".
|
|
sample: máximo de filas/valores muestreados por columna para el perfil
|
|
y para los datos crudos del ctx (LIMIT). Default 5000.
|
|
run_models: si True (default) corre los modelos baratos
|
|
(PCA/KMeans/IsolationForest/normalidad). Necesario para que el
|
|
capítulo `modelos` pinte los clusters sobre el plano PCA.
|
|
run_series: si True (default) calcula el análisis de serie temporal por
|
|
columna numérica. Necesario para el análisis del capítulo
|
|
`timeseries` (la gráfica de evolución sale de los datos crudos del
|
|
ctx aunque run_series sea False).
|
|
run_llm: si True (default False) hace la interpretación LLM del perfil y
|
|
ACTIVA además la narrativa LLM de los capítulos modelos/geospatial/
|
|
agregacion (títulos de segmento, descripción de la zona, selección de
|
|
agregaciones). Con False esos capítulos usan su derivación
|
|
cuantitativa (siguen completos, sin llamadas de red).
|
|
out_dir: directorio de salida (se crea si no existe). Default "reports".
|
|
basename: nombre base de los archivos sin extensión. Default
|
|
"aeda_<table>_<timestamp>".
|
|
ctx_extra: dict opcional con claves de presentación/contexto extra que se
|
|
mezclan en el ctx (p.ej. dataset_name, description, source_origin).
|
|
No pisan las claves de datos calculadas por build_eda_render_ctx.
|
|
|
|
Returns:
|
|
dict (nunca lanza). En éxito::
|
|
|
|
{"status": "ok", "pdf_path": str, "pptx_path": str,
|
|
"manifest_path": str|None, "n_pages": int, "n_slides": int,
|
|
"pdf_note": str, "pptx_note": str, "profile": <TableProfile>}
|
|
|
|
En error: {"status": "error", "error": str}.
|
|
"""
|
|
try:
|
|
# 1) Perfil base + modelos/serie opcionales. No escribe report propio
|
|
# (write_report=False): este pipeline emite su propio par PDF/PPTX.
|
|
pres = profile_table(
|
|
db_path,
|
|
table,
|
|
backend=backend,
|
|
sample=sample,
|
|
run_models=run_models,
|
|
run_llm=run_llm,
|
|
run_series=run_series,
|
|
emit_pdf=False,
|
|
write_report=False,
|
|
)
|
|
if pres.get("status") != "ok":
|
|
return {"status": "error",
|
|
"error": f"profile_table falló: {pres.get('error')}"}
|
|
prof = pres.get("profile") or {}
|
|
|
|
# 2) Contexto de presentación + datos crudos para los 4 capítulos que los
|
|
# necesitan. Las claves de presentación van en base_ctx; build_eda_render_ctx
|
|
# añade raw_numeric / timeseries_raw / geo_points / db_path / table.
|
|
base_ctx = {
|
|
"dataset_name": table,
|
|
"source_origin": db_path,
|
|
"storage": _STORAGE.get(backend, backend),
|
|
}
|
|
if run_llm:
|
|
# Activa la narrativa LLM de los capítulos que la soportan.
|
|
base_ctx.update({
|
|
"run_cluster_llm": True,
|
|
"run_geo_llm": True,
|
|
"run_agg_llm": True,
|
|
})
|
|
if ctx_extra:
|
|
base_ctx.update(ctx_extra)
|
|
|
|
ctx = build_eda_render_ctx(
|
|
db_path, table, prof, backend=backend, sample=sample,
|
|
base_ctx=base_ctx,
|
|
)
|
|
|
|
# 3) Render a ambos formatos desde el MISMO documento por capítulos.
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
|
base = basename or f"aeda_{table}_{ts}"
|
|
pdf_path = os.path.join(out_dir, base + ".pdf")
|
|
pptx_path = os.path.join(out_dir, base + ".pptx")
|
|
meta = {"title": f"EDA — {table}", "ctx": ctx}
|
|
|
|
rpdf = render_automatic_eda_pdf(prof, pdf_path, meta) or {}
|
|
rpptx = render_automatic_eda_pptx(prof, pptx_path, meta) or {}
|
|
|
|
return {
|
|
"status": "ok",
|
|
"pdf_path": rpdf.get("path"),
|
|
"pptx_path": rpptx.get("path"),
|
|
"manifest_path": rpdf.get("manifest_path"),
|
|
"n_pages": rpdf.get("n_pages"),
|
|
"n_slides": rpptx.get("n_slides"),
|
|
"pdf_note": rpdf.get("note"),
|
|
"pptx_note": rpptx.get("note"),
|
|
"profile": prof,
|
|
}
|
|
except Exception as e: # noqa: BLE001 — dict-no-throw: degradar, nunca lanzar.
|
|
return {"status": "error", "error": str(e)}
|