feat(browser): auto-commit con 178 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
"""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)}
|
||||
Reference in New Issue
Block a user