feat(eda): portada cap01 + zebra global y emphasis de render

Itera el capítulo PORTADA del AutomaticEDA y dos mejoras globales de los
renderers PDF/PPTX:

1. Zebra global (PDF): _place_kv_table ahora sombrea las filas pares igual que
   las DataTable, así toda tabla del documento queda rayada (no solo las
   DataTable). Mismo patrón coherente al partir/repetir cabecera.
2. Portada usa la descripción LLM rica (profile['llm']['summary']) cuando el
   perfil la tiene; se elimina del fallback derivado el texto ruido
   "active la interpretación LLM (run_llm)…". No fuerza llamadas LLM en el
   capítulo, solo consume profile['llm'] si está.
3. Se quita el bloque "Criterios de calidad" de la portada (PDF y PPTX);
   el score "Calidad" se mantiene.
4. "Resumen del análisis" (PDF): los valores se alinean al margen derecho via
   el nuevo KVTable.value_align="right".
5. Nombre del dataset en la portada PPTX más grande (44pt) y subrayado via los
   nuevos hints Heading.underline / Heading.size_pt (el PDF los ignora).

Bump CHAPTER_VERSION de portada 1.2.0 -> 1.3.0.

Verificado: suite 213 passed / 1 skipped (incl. aceptación de los 16 capítulos);
golden zebra = 185 filas zebra en 13 capítulos del PDF completo; portada con
run_llm sin "Criterios de calidad", con descripción LLM rica y valores a la
derecha; PPTX con nombre 44pt subrayado; edge sin LLM cae al fallback derivado
sin ruido; fn index sin error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 22:44:33 +02:00
parent ecc22d6d57
commit 80d10010f5
4 changed files with 77 additions and 26 deletions
@@ -26,7 +26,7 @@ from datetime import datetime, timezone
from .. import model
CHAPTER_VERSION = "1.2.0"
CHAPTER_VERSION = "1.3.0"
CHAPTER_ID = "portada"
CHAPTER_TITLE = "Portada"
@@ -35,12 +35,9 @@ CHAPTER_TITLE = "Portada"
# row represents) from it when the LLM layer ran (``run_llm``).
_LLM_KEY = "llm"
# Default human description of what the table quality score measures. Chapters
# can override it via ctx["quality_criteria"].
_DEFAULT_QUALITY_CRITERIA = (
"media de los scores por columna (0100): completitud (sin nulos/vacíos), "
"validez (tipo y rango coherentes) y consistencia (sin duplicados/constantes)."
)
# Font size (pt) for the dataset name on the PPTX cover slide — notably larger
# than the default H1 so the dataset name stands out (shown underlined too).
_PPTX_TITLE_PT = 44.0
def _storage_from_source(source: str) -> str:
@@ -120,7 +117,8 @@ def _summary_blocks(summary) -> list:
blocks = [model.Heading(text="Resumen del análisis", level=2)]
if rows:
blocks.append(model.KVTable(rows=rows))
# Values pinned to the right margin (numbers flush right, label left).
blocks.append(model.KVTable(rows=rows, value_align="right"))
if titles:
bullets = "\n".join(f"- {model._safe_str(t)}" for t in titles)
blocks.append(model.Markdown(
@@ -213,9 +211,7 @@ def _derive_description(profile: dict, ctx: dict) -> str:
score = profile.get("quality_score")
if score is not None:
parts.append(f"Calidad media estimada: {score}/100.")
parts.append(
"Resumen derivado del perfil; active la interpretación LLM (`run_llm`) "
"para una descripción de negocio más rica.")
parts.append("Resumen derivado del perfil.")
return " ".join(parts)
@@ -259,7 +255,6 @@ def build_portada(profile: dict, ctx: dict):
shape = f"{_fmt_int(n_rows)} filas × {_fmt_int(n_cols)} columnas"
score = profile.get("quality_score")
quality_criteria = ctx.get("quality_criteria") or _DEFAULT_QUALITY_CRITERIA
quality_value = "" if score is None else f"{score} / 100"
llm = _llm_block(profile, ctx)
@@ -282,8 +277,11 @@ def build_portada(profile: dict, ctx: dict):
# Title + dataset size shown together and BIG (Heading) at the top, kept on
# the same page (Group). The size is no longer buried in the metadata table.
# The dataset name is shown big and underlined on the PPTX cover slide
# (size_pt/underline are honoured by the PPTX renderer; the PDF ignores them).
cover = [
model.Heading(text=str(dataset_name), level=1),
model.Heading(text=str(dataset_name), level=1, underline=True,
size_pt=_PPTX_TITLE_PT),
model.Markdown(text="**Automatic-EDA** · informe exploratorio automático"),
model.Heading(text=shape, level=2),
]
@@ -295,7 +293,6 @@ def build_portada(profile: dict, ctx: dict):
("Almacenamiento", storage),
("Generado", when),
("Calidad", quality_value),
("Criterios de calidad", quality_criteria),
]),
model.Heading(text="Descripción", level=2),
model.Markdown(text=str(description)),