feat(eda): poblar head_rows real en el capitulo OVERVIEW (df.head)

El capitulo OVERVIEW del motor AutomaticEDA mostraba "df.head no disponible"
porque ninguna fase de calculo poblaba las primeras filas crudas de la tabla.

- build_eda_render_ctx: nuevo bloque que muestrea SELECT * LIMIT head_n
  (param nuevo head_n=10) y lo expone en ctx["head_rows"] como lista de
  dicts fila. Estilo dict-no-throw: si la query falla, se omite la clave.
- profile_table: puebla prof["head_rows"] reusando _sample_rows (SELECT de
  las columnas LIMIT 10) tras recalcular el type_breakdown. Asi el report
  JSON sidecar tambien lo lleva y el capitulo lo recoge via profile aunque
  no se construya el ctx.
- overview.py: la nota del DataTable de df.head ahora indica el total de
  filas del dataset cuando se conoce ("primeras 10 filas de 891"). Bump
  CHAPTER_VERSION 1.0.0 -> 1.1.0.
- overview_test.py (nuevo): golden (head via profile y via ctx, render PDF
  + PPTX muestran las filas reales, placeholder ausente), edge (sin
  head_rows degrada a nota honesta sin romper, None/vacio devuelven None).

Verificado end-to-end con titanic: render_automatic_eda emite PDF + PPTX con
df.head visible (Braund/Cumings/Heikkinen + columnas) y sin el placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 17:56:24 +02:00
parent c6d9bc26da
commit b1d205203a
4 changed files with 239 additions and 7 deletions
@@ -536,6 +536,21 @@ def profile_table(
type_breakdown[it] += 1
prof["type_breakdown"] = type_breakdown
# 8.1) Primeras filas crudas (df.head) para el capitulo OVERVIEW del motor
# AutomaticEDA: una muestra SELECT col1,col2,... LIMIT 10 alineada por fila.
# Se reusa _sample_rows (mismo lector read-only). Estilo dict-no-throw: si
# falla, head_rows queda None y el capitulo degrada a su nota honesta. El
# capitulo lo recoge via profile["head_rows"]; build_eda_render_ctx ademas
# lo replica en ctx["head_rows"] cuando se construye el contexto de render.
try:
head_names = [c.get("name") for c in cols if c.get("name")]
head_rows = _sample_rows(_q, table, head_names, 10)
prof["head_rows"] = [
dict(r) for r in head_rows if isinstance(r, dict)
] or None
except Exception: # noqa: BLE001
prof["head_rows"] = None
# 8.5) Matriz de correlacion/asociacion sobre una muestra de filas
# alineadas. Elige la metrica por par de tipos (Pearson/Spearman,
# Cramer's V/Theil's U, correlation ratio, MI) via association_matrix.