---
name: build_eda_notebook
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def build_eda_notebook(db_path: str, table: str, notebook_path: str, run_models: bool = False, run_llm: bool = False) -> dict"
description: "Genera un notebook Jupyter de EDA (nbformat v4) para una tabla DuckDB usando el grupo eda. Escribe el .ipynb a disco listo para abrir/ejecutar; no ejecuta el notebook. dict-no-throw."
tags: [eda, notebook, jupyter, datascience, duckdb, profiling]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [json, os]
params:
  - name: db_path
    desc: "Ruta al archivo DuckDB que contiene la tabla a perfilar. Se referencia dentro del notebook, no se abre en esta funcion."
  - name: table
    desc: "Nombre de la tabla DuckDB a perfilar."
  - name: notebook_path
    desc: "Ruta de salida del .ipynb. El directorio padre se crea si no existe."
  - name: run_models
    desc: "Si True, añade celda con prof['models'] (PCA explained_variance_ratio, kmeans best_k, outliers n_outliers) y pasa run_models=True a profile_table dentro del notebook. Default False."
  - name: run_llm
    desc: "Si True, añade celda que llama eda_llm_insights(prof) para insights generados por LLM. Default False."
output: "dict. En exito {status:'ok', notebook_path:str, n_cells:int}. En error {status:'error', error:str}."
tested: true
tests: ["genera notebook ok", "notebook es json nbformat valido", "run_models añade celda de modelos", "run_llm añade celda de insights", "sin flags no añade celdas opcionales", "crea directorio padre"]
test_file_path: "python/functions/datascience/build_eda_notebook_test.py"
file_path: "python/functions/datascience/build_eda_notebook.py"
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from datascience.build_eda_notebook import build_eda_notebook

r = build_eda_notebook(
    db_path="/home/enmanuel/data/ventas.duckdb",
    table="cubo_ventas",
    notebook_path="/tmp/eda_demo.ipynb",
    run_models=True,
    run_llm=False,
)
# {'status': 'ok', 'notebook_path': '/tmp/eda_demo.ipynb', 'n_cells': 10}
# Luego se abre/ejecuta en Jupyter; este paso solo escribe el .ipynb.
```

## Cuando usarla

Cuando quieras entregar un EDA como **notebook ejecutable** (no un report estatico):
perfilar una tabla DuckDB con el grupo `eda` y dejar un `.ipynb` listo. El notebook
se lanza despues en Jupyter colaborativo con las funciones del grupo `notebook`
(`jupyter_discover` / `jupyter_exec` / `jupyter_write`) y el usuario lo ve ejecutarse
en vivo. Es la base de la entrega "analysis EDA".

## Gotchas

- **Impura**: escribe un archivo `.ipynb` a `notebook_path` (crea el directorio padre).
- **NO ejecuta el notebook**: solo emite las celdas. La ejecucion la hace Jupyter despues.
- Las celdas asumen que `python/functions` del registry esta accesible desde el kernel:
  el startup `00_fn_registry.py` del analysis lo expone, o como fallback la primera celda
  inserta `~/fn_registry/python/functions` en `sys.path`. Si el repo no esta ahi y el
  kernel no lo expone, las celdas de import fallaran al ejecutarse (no al generar).
- `profile_table` se invoca con `write_report=False` dentro del notebook: no toca disco
  para reports, el perfil vive en la variable `prof`.
- `run_llm=True` emite una celda que llama `eda_llm_insights`, que requiere token OAuth
  de Claude disponible para el kernel; sin el, esa celda fallara al ejecutarse.
- dict-no-throw: cualquier fallo de escritura se devuelve como `{status:'error', error}`,
  no se propaga excepcion.
