763e06c127
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
195 lines
7.0 KiB
Python
195 lines
7.0 KiB
Python
"""Genera un notebook Jupyter de EDA (nbformat v4) para una tabla DuckDB.
|
|
|
|
Construye un .ipynb listo para abrir/ejecutar que perfila una tabla con el
|
|
grupo `eda` del registry (profile_table + render_eda_markdown + run_eda_models +
|
|
eda_llm_insights). La funcion NO ejecuta el notebook: solo escribe el archivo
|
|
con las celdas. Es la base de la entrega "analysis EDA" que luego se lanza en el
|
|
navegador colaborativo con las funciones del grupo `notebook`.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
|
|
|
|
def _code_cell(source: str) -> dict:
|
|
"""Construye una celda de codigo nbformat v4."""
|
|
return {
|
|
"cell_type": "code",
|
|
"source": source,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"execution_count": None,
|
|
}
|
|
|
|
|
|
def _markdown_cell(source: str) -> dict:
|
|
"""Construye una celda markdown nbformat v4."""
|
|
return {"cell_type": "markdown", "source": source, "metadata": {}}
|
|
|
|
|
|
def build_eda_notebook(
|
|
db_path: str,
|
|
table: str,
|
|
notebook_path: str,
|
|
run_models: bool = False,
|
|
run_llm: bool = False,
|
|
) -> dict:
|
|
"""Genera un notebook Jupyter de EDA para una tabla DuckDB.
|
|
|
|
Construye un dict nbformat v4 (a mano, sin depender de la libreria nbformat)
|
|
con celdas que perfilan la tabla usando el grupo `eda` del registry, lo
|
|
serializa como JSON a disco y devuelve un resumen. NO ejecuta el notebook.
|
|
|
|
Args:
|
|
db_path: ruta al archivo DuckDB que contiene la tabla a perfilar.
|
|
table: nombre de la tabla a perfilar dentro de la DuckDB.
|
|
notebook_path: ruta de salida del .ipynb. El directorio padre se crea
|
|
si no existe.
|
|
run_models: si True, añade una celda que muestra prof["models"]
|
|
(PCA explained_variance_ratio, kmeans best_k, outliers n_outliers).
|
|
Tambien pasa run_models=True a profile_table dentro del notebook.
|
|
run_llm: si True, añade una celda que llama eda_llm_insights(prof) para
|
|
obtener insights generados por LLM.
|
|
|
|
Returns:
|
|
dict. En exito: {status:'ok', notebook_path: str, n_cells: int}.
|
|
En error (sin lanzar): {status:'error', error: str}.
|
|
"""
|
|
try:
|
|
cells = []
|
|
|
|
# 1) Titulo.
|
|
cells.append(
|
|
_markdown_cell(
|
|
f"# EDA — {table}\nGenerado por el grupo `eda` del registry."
|
|
)
|
|
)
|
|
|
|
# 2) Setup: sys.path + import de profile_table.
|
|
cells.append(
|
|
_code_cell(
|
|
"import sys, os\n"
|
|
"# El kernel startup del analysis (00_fn_registry.py) ya suele\n"
|
|
"# exponer python/functions en sys.path. Como fallback asumimos\n"
|
|
"# el repo en ~/fn_registry.\n"
|
|
'_fns = os.path.join(os.path.expanduser("~"), "fn_registry", "python", "functions")\n'
|
|
"if _fns not in sys.path:\n"
|
|
" sys.path.insert(0, _fns)\n"
|
|
"from pipelines.profile_table import profile_table"
|
|
)
|
|
)
|
|
|
|
# 3) Perfilar la tabla.
|
|
cells.append(
|
|
_code_cell(
|
|
f"r = profile_table({db_path!r}, {table!r}, run_models={run_models}, write_report=False)\n"
|
|
'prof = r["profile"]\n'
|
|
'prof["n_rows"], prof["n_cols"], prof["quality_score"]'
|
|
)
|
|
)
|
|
|
|
# 4) Report markdown renderizado.
|
|
cells.append(
|
|
_code_cell(
|
|
"from datascience import render_eda_markdown\n"
|
|
"from IPython.display import Markdown, display\n"
|
|
"display(Markdown(render_eda_markdown(prof)))"
|
|
)
|
|
)
|
|
|
|
# 5) Tabla de columnas con pandas.
|
|
cells.append(
|
|
_code_cell(
|
|
"import pandas as pd\n"
|
|
"pd.DataFrame([\n"
|
|
" {k: c.get(k) for k in (\n"
|
|
' "name", "inferred_type", "semantic_type", "null_pct",\n'
|
|
' "distinct_count", "unique_pct", "quality_score",\n'
|
|
" )}\n"
|
|
' for c in prof["columns"]\n'
|
|
"])"
|
|
)
|
|
)
|
|
|
|
# 6) Correlaciones fuertes.
|
|
cells.append(
|
|
_code_cell(
|
|
'corr = prof.get("correlations")\n'
|
|
'pd.DataFrame(corr["strong"]) if corr and corr.get("strong") else "sin correlaciones fuertes"'
|
|
)
|
|
)
|
|
|
|
# 7) Modelos (solo si run_models).
|
|
if run_models:
|
|
cells.append(
|
|
_markdown_cell("## Modelos no supervisados")
|
|
)
|
|
cells.append(
|
|
_code_cell(
|
|
'models = prof.get("models") or {}\n'
|
|
'pca = models.get("pca") or {}\n'
|
|
'kmeans = models.get("kmeans") or {}\n'
|
|
'outliers = models.get("outliers") or {}\n'
|
|
"{\n"
|
|
' "pca_explained_variance_ratio": pca.get("explained_variance_ratio"),\n'
|
|
' "kmeans_best_k": kmeans.get("best_k"),\n'
|
|
' "outliers_n_outliers": outliers.get("n_outliers"),\n'
|
|
"}"
|
|
)
|
|
)
|
|
|
|
# 8) Insights LLM (solo si run_llm).
|
|
if run_llm:
|
|
cells.append(_markdown_cell("## Insights (LLM)"))
|
|
cells.append(
|
|
_code_cell(
|
|
"from datascience import eda_llm_insights\n"
|
|
"ins = eda_llm_insights(prof)\n"
|
|
"ins"
|
|
)
|
|
)
|
|
|
|
# 9) Notas finales.
|
|
cells.append(
|
|
_markdown_cell(
|
|
"## Notas\n\n"
|
|
"- Este notebook fue generado por `build_eda_notebook` del grupo `eda`.\n"
|
|
"- Ejecuta las celdas en orden. La primera celda de codigo asume que\n"
|
|
" python/functions del registry esta en `sys.path` (kernel startup\n"
|
|
" del analysis o `~/fn_registry`).\n"
|
|
"- `profile_table` se llama con `write_report=False`: no escribe reports\n"
|
|
" a disco, todo el perfil vive en la variable `prof`.\n"
|
|
"- Para regenerar con modelos o insights LLM, vuelve a llamar a\n"
|
|
" `build_eda_notebook(..., run_models=True, run_llm=True)`."
|
|
)
|
|
)
|
|
|
|
notebook = {
|
|
"cells": cells,
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3",
|
|
},
|
|
"language_info": {"name": "python"},
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5,
|
|
}
|
|
|
|
parent = os.path.dirname(os.path.abspath(notebook_path))
|
|
if parent:
|
|
os.makedirs(parent, exist_ok=True)
|
|
|
|
with open(notebook_path, "w", encoding="utf-8") as f:
|
|
json.dump(notebook, f, indent=1)
|
|
|
|
return {
|
|
"status": "ok",
|
|
"notebook_path": notebook_path,
|
|
"n_cells": len(cells),
|
|
}
|
|
except Exception as exc: # noqa: BLE001 - dict-no-throw
|
|
return {"status": "error", "error": str(exc)}
|