feat(eda): wiring AutomaticEDA — build_eda_render_ctx + pipeline render_automatic_eda + profile_table(emit_automatic)
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>
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
"""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)}
|
||||
Reference in New Issue
Block a user