---
name: eda_llm_insights
kind: function
lang: py
domain: datascience
version: "1.0.0"
purity: impure
signature: "def eda_llm_insights(profile: dict, model: str = \"claude-haiku-4-5-20251001\") -> dict"
description: "Capa LLM interpretativa del grupo eda. Toma un TableProfile YA CALCULADO (el dict de profile_table) y, con UNA sola llamada al LLM, genera el bloque 'llm': resumen de la tabla, significado de una fila, diccionario de datos, deteccion de PII (RGPD), sugerencias de limpieza y analisis sugeridos. Clave de coste/privacidad: NO envia filas crudas al LLM, solo el perfil AGREGADO (nombres, tipos, % nulos, distinct, top valores agregados de categoricas, stats de numericas, pares de correlacion fuertes). Reusa ask_llm del grupo claude-direct (API directa con token OAuth de Claude). Impura, dict-no-throw."
tags: [eda, llm, claude-direct, datascience, profiling, pii, data-dictionary]
params:
  - name: profile
    desc: "TableProfile ya calculado (el dict que devuelve profile_table()['profile']). Se espera {table, n_rows, columns:[{name, inferred_type, semantic_type, null_pct, distinct_count, numeric:{min,max,mean,p50,...}, categorical:{top:[{value,count,pct}], mode,...}}], correlations:{strong:[{a,b,method,value}]} | None}. Solo se le envia al LLM un resumen agregado; nunca filas crudas."
  - name: model
    desc: "id del modelo Anthropic a usar. Default 'claude-haiku-4-5-20251001' (haiku, coste bajo). Para mayor calidad interpretativa, pasar p.ej. 'claude-opus-4-8'."
output: "dict dict-no-throw. En exito: {status:'ok', llm:{summary:str, row_meaning:str, dictionary:[{column,description,business_meaning,unit}], pii:[{column,kind,severity}], cleaning:[str], analyses:[str]}}. Las claves que el LLM omita se rellenan con defaults vacios. En error (sin lanzar): {status:'error', error:str}."
uses_functions: [ask_llm_py_core]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: []
tested: true
tests: ["test_build_prompt_includes_table_and_columns", "test_build_prompt_includes_numeric_stats_and_top_values", "test_build_prompt_handles_empty_profile", "test_parse_llm_json_plain", "test_parse_llm_json_with_fences", "test_parse_llm_json_with_surrounding_text", "test_parse_llm_json_nested_braces_in_strings", "test_parse_llm_json_raises_without_object", "test_eda_llm_insights_ok_with_monkeypatched_llm", "test_eda_llm_insights_fills_missing_keys", "test_eda_llm_insights_error_on_empty_profile", "test_eda_llm_insights_error_on_empty_llm_response", "test_eda_llm_insights_error_on_unparseable_llm_response"]
test_file_path: "python/functions/datascience/eda_llm_insights_test.py"
file_path: "python/functions/datascience/eda_llm_insights.py"
---

## Ejemplo

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

from pipelines.profile_table import profile_table
from datascience import eda_llm_insights

# 1) Perfila la tabla (calculo agregado, sin LLM).
r = profile_table("data/ventas.duckdb", "ventas", write_report=False)
profile = r["profile"]

# 2) Interpreta el perfil con UNA llamada al LLM (solo el perfil agregado viaja).
out = eda_llm_insights(profile)                       # haiku por defecto
# out = eda_llm_insights(profile, model="claude-opus-4-8")  # mas calidad

if out["status"] == "ok":
    llm = out["llm"]
    print(llm["summary"])         # que es la tabla, 2-3 frases
    print(llm["row_meaning"])     # que representa una fila
    for d in llm["dictionary"]:   # diccionario de datos por columna
        print(d["column"], "->", d["description"], f"({d['unit']})")
    for p in llm["pii"]:          # datos personales/sensibles RGPD
        print("PII:", p["column"], p["kind"], p["severity"])
    print(llm["cleaning"])        # sugerencias de limpieza
    print(llm["analyses"])        # analisis sugeridos + hipotesis
else:
    print("error:", out["error"])
```

## Cuando usarla

Cuando necesites entender SEMANTICAMENTE una tabla ya perfilada: generar un
diccionario de datos legible, detectar PII/datos sensibles RGPD, recibir
sugerencias de limpieza y una lista de analisis/hipotesis a explorar. Es el
paso interpretativo que sigue a `profile_table`: este calcula las metricas, y
`eda_llm_insights` las traduce a lenguaje de negocio. El resultado encaja en la
clave `llm` del TableProfile (la que `render_eda_markdown` renderiza en la
seccion "Analisis LLM").

## Gotchas

- **Impura: hace 1 llamada de red al LLM.** No es determinista ni gratis.
- **Requiere token OAuth de Claude** en `~/.claude/.credentials.json` (via
  `ask_llm` / grupo `claude-direct`). Sin token, devuelve `{status:'error'}`.
- **NO envia filas crudas al LLM**, solo el perfil AGREGADO (nombres, tipos,
  % nulos, distinct, top valores ya agregados, stats numericas, correlaciones
  fuertes). Privacidad y coste minimos por diseno — pero requiere que el
  `profile` venga ya calculado por `profile_table`.
- **Modelo `haiku` por defecto** para coste bajo; sube a `claude-opus-4-8` si
  necesitas interpretacion mas fina (mas caro y lento).
- El LLM puede omitir claves: las que falten se rellenan con defaults vacios
  (`""` o `[]`), nunca lanza por shape incompleto.
- El parseo tolera `\`\`\`json` fences y texto alrededor del objeto, pero si el
  modelo no devuelve ningun objeto JSON, retorna `{status:'error'}`.
