--- 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.