9cdde4a341
Introduce la capa intermedia entre el contenido de un EDA y su formato de
salida. Un documento es una lista de capítulos versionados; cada capítulo es
un conjunto ordenado de bloques (heading, markdown, kv_table, data_table,
figure, image, caption, note) independientes del formato.
Núcleo (paquete de soporte python/functions/datascience/automatic_eda/):
- model.py: dataclasses de bloques + Chapter, normalizadores defensivos
(aceptan dataclass o dict, nunca lanzan), ENGINE_VERSION y el manifiesto
por capítulo (automatic_eda_manifest.json).
- text_layout.py: medición/wrapping por rejilla de caracteres compartida.
- chapters_registry.py: CHAPTER_ORDER pre-declarado + build_document con
auto-discovery de capítulos por convención (permite añadir capítulos en
paralelo sin editar el registro).
- render_pdf_impl.py: paginador A5 retrato móvil que MIDE cada bloque y nunca
corta: texto a líneas completas, tablas largas partidas por filas repitiendo
cabecera, figuras/imágenes escaladas para caber enteras. Pie versionado por
capítulo.
- render_pptx_impl.py: mismo principio sobre slides 16:9 (continúa en slide
"(cont.)"; tablas repiten cabecera; figuras exportadas a PNG escaladas).
- chapters/portada.py y chapters/overview.py: capítulos de referencia. Portada
con nombre, rótulo Automatic-EDA, fuente, almacenamiento (inferido de
source), fecha europea, filas×cols, descripción, granularidad y calidad con
criterios. Overview con df.head (placeholder honesto si falta head_rows),
diccionario de columnas (tipo/nulos/ejemplos) y describe numérico.
Funciones públicas del registry (grupo eda, dict-no-throw):
- render_automatic_eda_pdf / render_automatic_eda_pptx: aceptan capítulos o un
TableProfile (construyen los capítulos con build_document) y escriben el
manifiesto. Aditivas — no reemplazan render_eda_pdf.
Tests self-contained (sin DuckDB) para ambos renderers: golden (portada +
overview), partición de tablas largas repitiendo cabecera, no-corte de celdas
y markdown largos, profile None/{} válido de 1 página/slide, y error path en
directorio no escribible. 23 tests verdes (incluye los previos de
render_eda_pdf, intactos).
Dependencia nueva python-pptx>=1.0.2 declarada en python/pyproject.toml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.4 KiB
7.4 KiB
name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, params, output, tested, tests, test_file_path, file_path
| name | kind | lang | domain | version | purity | signature | description | tags | uses_functions | uses_types | returns | returns_optional | error_type | imports | params | output | tested | tests | test_file_path | file_path | |||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| render_automatic_eda_pdf | function | py | datascience | 1.0.0 | impure | def render_automatic_eda_pdf(chapters_or_profile, out_path: str, meta: dict = None) -> dict | Renderiza un documento AutomaticEDA por CAPÍTULOS (modelo de bloques independiente del formato) en un PDF A5 retrato pensado para LEER EN EL MÓVIL. Acepta una lista de capítulos del modelo o directamente un TableProfile del grupo eda (en cuyo caso construye los capítulos canónicos con build_document). El paginador MIDE cada bloque y NUNCA corta nada: el texto se envuelve a líneas completas, las tablas largas se parten por filas REPITIENDO la cabecera, figuras e imágenes se escalan para caber enteras. Cada capítulo empieza en página nueva con pie 'Capítulo · vX.Y.Z' y se escribe un manifiesto automatic_eda_manifest.json junto a la salida para seguimiento por capítulo. dict-no-throw: nunca lanza, devuelve {path, n_pages, chapters, manifest_path, note}. Motor matplotlib PdfPages. Aditivo: NO reemplaza render_eda_pdf. |
|
false | error_go_core |
|
|
dict (nunca lanza): {path: str|None, n_pages: int, chapters: list[{id,version,n_pages}], manifest_path: str|None, note: str}. En éxito path es la ruta escrita, n_pages el total de páginas, chapters el desglose por capítulo para el manifiesto. En error fatal path es None y note explica la causa. | true |
|
python/functions/datascience/render_automatic_eda_pdf_test.py | python/functions/datascience/render_automatic_eda_pdf.py |
Ejemplo
from datascience import render_automatic_eda_pdf
# Caso 1: directamente desde un TableProfile del grupo eda.
# profile = profile_table(db, "ventas", backend="duckdb")["profile"]
profile = {
"table": "ventas", "source": "/data/ventas.csv",
"n_rows": 1000, "n_cols": 2, "quality_score": 92.5,
"columns": [
{"name": "precio", "inferred_type": "numeric", "null_pct": 0.01,
"null_count": 10,
"numeric": {"mean": 42.5, "median": 40.0, "min": 1.0, "max": 100.0,
"std": 12.3}},
{"name": "categoria", "inferred_type": "categorical", "null_pct": 0.0,
"categorical": {"top": [{"value": "neumaticos", "count": 500},
{"value": "aceite", "count": 300}]}},
],
}
res = render_automatic_eda_pdf(
profile, "reports/ventas_aeda.pdf",
{"title": "EDA — ventas",
"ctx": {"dataset_name": "Ventas", "source_origin": "ERP export",
"description": "Líneas de venta del ERP.",
"granularity": "Cada fila es una línea de venta."}})
print(res["n_pages"], res["chapters"], res["manifest_path"])
# -> 3 [{'id':'portada','version':'1.0.0','n_pages':1},
# {'id':'overview','version':'1.0.0','n_pages':2}] reports/automatic_eda_manifest.json
# Caso 2: desde capítulos construidos a mano (modelo de bloques).
from datascience.automatic_eda.model import Chapter, Heading, DataTable
ch = Chapter(id="resumen", title="Resumen", version="1.0.0", blocks=[
Heading("Tabla", 1),
DataTable(header=["col", "valor"], rows=[["a", "1"], ["b", "2"]]),
])
render_automatic_eda_pdf([ch], "reports/manual.pdf")
Cuando usarla
Cuando quieras el PDF móvil del nuevo motor AutomaticEDA por capítulos (portada
- overview + los capítulos que existan): después de
profile_table(...), pásale elprofiley obtienes un PDF A5 retrato versionado por capítulo, con manifiesto. Úsala como capa de presentación PDF del grupoedacuando necesites garantía de no-corte (texto, tablas e imágenes nunca recortados) y versionado por capítulo para mejora continua. Es el reemplazo evolutivo derender_eda_pdf: comparte estética Tufte/móvil pero separa contenido (capítulos/bloques) de formato (renderer), de modo que el mismo documento se emite también como PPTX (render_automatic_eda_pptx). Para añadir un capítulo nuevo, verdocs/capabilities/automatic_eda.md.
Gotchas
- Impura: escribe el PDF en
out_path(crea los directorios padre) y, salvometa['write_manifest']=False, unautomatic_eda_manifest.jsonjunto a la salida. Backend headlessAggde matplotlib (corre en agentes/CI sin display). - Nunca lanza (dict-no-throw): un bloque o capítulo que falle se omite y se anota
en
note; el PDF se genera igual. Un profileNone/{}produce un PDF de 1 página válido.out_pathno escribible →{path: None, note: <causa>}. - No corta nada: el paginador mide cada bloque con una rejilla de caracteres (sobre-estima ligeramente, nunca afirma que algo cabe cuando se desbordaría). El texto se envuelve a líneas completas (sin cortar a media palabra), las tablas largas se parten por filas repitiendo la cabecera, las celdas con texto largo se envuelven dentro de su columna (la fila crece), y figuras/imágenes se escalan para caber enteras (nunca se recortan).
- Tablas muy anchas: con muchas columnas (>10) cada columna se estrecha y su texto se envuelve en varias líneas (sigue sin perderse). El reparto por columnas-en-grupos para tablas muy anchas es una mejora pendiente (ver capability page).
- head_rows / examples: el capítulo Overview muestra
df.headdesdectx['head_rows']/profile['head_rows']y ejemplos no-nulos desdecolumns[i]['examples']; si el profile no los trae (hoy no los trae), degrada con un placeholder honesto y deriva los ejemplos de los valores reales del perfil (top categóricos, min/median/max numéricos). Documentado en el contrato. - Registro en el package: el
## Ejemplousafrom datascience import render_automatic_eda_pdf(añadido al__init__.py); el test importa el módulo directo para no depender de ese registro. - Fechas en UI europeas: la portada formatea la fecha como
DD/MM/AAAA HH:mm.