--- name: build_eda_render_ctx kind: function lang: py domain: datascience version: "1.0.0" purity: impure signature: "def build_eda_render_ctx(db_path: str, table: str, profile: dict, backend: str = 'duckdb', sample: int = 5000, base_ctx: dict = None) -> dict" description: "Constructor del `ctx` de datos crudos del motor AutomaticEDA. Dado un db_path+table (DuckDB o Postgres) y el TableProfile AGREGADO ya calculado por profile_table, produce el dict ctx que los renderers (render_automatic_eda_pdf/_pptx -> build_document(profile, ctx)) pasan a los capitulos que necesitan DATOS CRUDOS no presentes en el perfil agregado: modelos (project_clusters_2d en vivo), timeseries, geospatial y agregacion (groupby/pivot push-down). NO trae tablas enteras a RAM: muestrea con LIMIT sample y delega el push-down de la serie en extract_timeseries_raw. Construye el lector read-only query_fn(sql)->dict igual que profile_table (closure sobre duckdb_query_readonly / pg_query). Estilo dict-no-throw del grupo eda: NUNCA lanza; si una pieza falla, degrada esa clave a ausente/[] y sigue. Devuelve el ctx dict directamente (NO un wrapper {status,...}); se pasa tal cual como meta={'ctx': }. Claves de datos que produce: raw_numeric (muestra cruda alineada por fila), timeseries_raw (fechas+series), geo_points (lats/lons) y db_path+table para el push-down de agregacion. Respeta base_ctx: parte de una copia y solo AÑADE las claves de datos; las de presentacion (dataset_name, source_origin, ...) no se pisan." tags: [eda, datascience, automatic-eda, render, ctx, extraction, read-only, duckdb, postgres, python] uses_functions: [detect_time_column_py_datascience, extract_timeseries_raw_py_datascience, detect_latlon_columns_py_datascience, duckdb_query_readonly_py_infra, pg_query_py_infra] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [] params: - name: db_path desc: "ruta al archivo DuckDB, o DSN PostgreSQL si backend='postgres'. Se guarda tal cual en ctx['db_path'] (el capitulo agregacion lo usa para el groupby/pivot push-down via DuckDB) y se inyecta en el closure query_fn. No se valida aqui: si la base no existe, las queries devuelven status error y las claves de datos se omiten." - name: table desc: "nombre de la tabla. Se escapa con comillas dobles en las queries (raw_numeric y timeseries) y se guarda en ctx['table']." - name: profile desc: "TableProfile AGREGADO producido por profile_table. Solo se lee su clave `columns` (lista de ColumnProfile dict con name / inferred_type / numeric.{min,max} / semantic_type). Lectura defensiva: si no es dict o no tiene columns, se trata como []. NO se traen las filas crudas de aqui — se muestrean de la base." - name: backend desc: "'duckdb' (default) o 'postgres'. Selecciona el lector read-only del registry (duckdb_query_readonly / pg_query). Cualquier otro valor devuelve el base_ctx tal cual, SIN añadir claves de datos (ni siquiera db_path/table)." - name: sample desc: "maximo de filas a muestrear (clausula LIMIT) tanto para raw_numeric (una sola query SELECT de las numericas) como para timeseries_raw (max_rows de extract_timeseries_raw). Default 5000. Acota memoria y tiempo de render." - name: base_ctx desc: "dict opcional con claves de PRESENTACION ya preparadas (dataset_name, source_origin, ...). Se parte de una copia y NO se pisan sus claves; solo se añaden las de datos. Default None -> {}." output: "El dict `ctx` directamente (NO un wrapper {status,...}); se pasa tal cual como meta={'ctx': } a render_automatic_eda_pdf/pptx. Nunca lanza. Para backends validos contiene SIEMPRE db_path + table, y opcionalmente: raw_numeric {col:[float|None,...]} (muestra cruda alineada por fila; omitida si no hay numericas o falla la query), timeseries_raw {time_col, t:[iso...], series:{col:[float|None,...]}} (solo si hay columna temporal + numericas y trae filas), geo_points {lats:[...], lons:[...]} (solo si se detecta par lat/lon y ambas estan en raw_numeric). Ante fallo global devuelve al menos {**base_ctx, 'db_path': db_path, 'table': table}. Backend desconocido -> base_ctx tal cual sin claves de datos." tested: true tests: ["test_db_path_y_table_en_ctx", "test_raw_numeric_con_columnas_numericas", "test_timeseries_raw_con_fecha", "test_geo_points_con_latlon", "test_sin_fecha_no_hay_timeseries", "test_base_ctx_preservado"] test_file_path: "python/functions/datascience/build_eda_render_ctx_test.py" file_path: "python/functions/datascience/build_eda_render_ctx.py" --- ## Ejemplo ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from datascience import build_eda_render_ctx, render_automatic_eda_pdf from datascience import profile_table # opcional: para obtener el TableProfile # 1) Perfil agregado de la tabla (push-down, sin RAM). prof = profile_table("data/ventas.duckdb", "ventas_geo", write_report=False)["profile"] # 2) ctx de datos crudos para los capitulos (muestrea con LIMIT, no carga todo). ctx = build_eda_render_ctx( "data/ventas.duckdb", "ventas_geo", prof, backend="duckdb", sample=5000, base_ctx={"dataset_name": "Ventas con geolocalizacion"}, ) # ctx == { # "dataset_name": "Ventas con geolocalizacion", # preservado del base_ctx # "db_path": "data/ventas.duckdb", "table": "ventas_geo", # "raw_numeric": {"ventas": [1200.5, ...], "lat": [40.41, ...], "lon": [-3.70, ...]}, # "timeseries_raw": {"time_col": "fecha", "t": ["2024-01-01", ...], "series": {...}}, # "geo_points": {"lats": [40.41, ...], "lons": [-3.70, ...]}, # } # 3) Se entrega tal cual a los renderers via meta={"ctx": ctx}. render_automatic_eda_pdf(prof, "reports/eda.pdf", meta={"ctx": ctx}) ``` ## Cuando usarla Justo antes de renderizar un AutomaticEDA (PDF o PPTX), cuando ya tienes el TableProfile AGREGADO de `profile_table` pero los capitulos de modelos, timeseries, geospatial y agregacion necesitan DATOS CRUDOS que el perfil agregado no lleva (la muestra numerica alineada por fila, la serie cronologica, el par lat/lon, y el db_path/table para el push-down del groupby/pivot). Es el puente entre el perfil agregado y `build_document(profile, ctx)`: una sola llamada produce el `ctx` completo muestreando con `LIMIT` en vez de cargar la tabla entera en memoria. ## Gotchas - **Impura**: lee de la base de datos a traves de `query_fn` (closure sobre `duckdb_query_readonly` / `pg_query`). No abre conexiones fuera de esos wrappers del registry. Estilo dict-no-throw del grupo `eda`: NUNCA lanza; ante cualquier fallo (query, deteccion, render de una clave) degrada esa clave a ausente/`[]` y sigue. Ante un fallo global devuelve al menos `{**base_ctx, "db_path": db_path, "table": table}`. - **`error_type` en el frontmatter es `error_go_core` por convencion del registry** (toda funcion impura debe declararlo y el indexer lo exige), pero el codigo NO lanza esa excepcion: degrada al ctx parcial. Es metadata, no comportamiento. - **Devuelve el ctx dict directamente, NO un wrapper `{status,...}`**: a diferencia de `extract_timeseries_raw` / `profile_table`, esta funcion es el ultimo eslabon antes del render y su salida se pasa tal cual como `meta={"ctx": }`. No envuelvas su retorno. - **Backend desconocido**: con un `backend` que no sea `duckdb` ni `postgres` devuelve el `base_ctx` tal cual, SIN claves de datos (ni siquiera `db_path`/`table`). Comprueba el backend antes si dependes de esas claves. - **Alineacion por fila de `raw_numeric`**: `raw_numeric[col]` tiene una entrada por fila muestreada (un valor no convertible a float queda como `None`, no se descarta la fila) porque `project_clusters_2d` descarta filas listwise: todas las columnas deben tener la MISMA longitud. `geo_points` se construye desde `raw_numeric` para heredar esa alineacion. - **`geo_points` exige lat/lon en `raw_numeric`**: el par lat/lon solo se adjunta si ambas columnas se detectaron (nombre+rango) Y figuran en `raw_numeric` (es decir, son numericas en el perfil). Si la tabla guarda lat/lon como texto no promovido a numeric, no apareceran; el capitulo geospatial sabe degradar. - **`timeseries_raw` depende del orden del backend**: hereda el `ORDER BY "time_col"` de `extract_timeseries_raw`. Si la columna temporal esta guardada como texto no ordenable lexicograficamente (p.ej. `DD/MM/YYYY`), el orden no sera el cronologico real — normaliza la columna a date/timestamp antes. - **`LIMIT sample`**: con tablas grandes obtienes el primer tramo (raw_numeric por orden fisico, timeseries por orden cronologico), no un muestreo uniforme. Sube `sample` si necesitas mas cobertura. - **No loguear los datos crudos**: `raw_numeric` / `timeseries_raw` / `geo_points` pueden contener datos sensibles. En trazas usa solo conteos y nombres de columna, no el ctx completo.