--- name: render_automatic_eda_markdown kind: function lang: py domain: datascience version: "1.0.0" purity: impure signature: "def render_automatic_eda_markdown(chapters_or_profile, out_path: str, meta: dict = None) -> dict" description: "Renderiza un documento AutomaticEDA por CAPÍTULOS (modelo de bloques independiente del formato) en un único MARKDOWN autocontenido pensado para PEGAR A UN LLM. Acepta una lista de capítulos del modelo o directamente un TableProfile del grupo eda (construye los capítulos canónicos con build_document). Prioriza TEXTO + DATOS sobre lo visual: las tablas se vuelcan como tablas markdown con TODAS las filas (sin paginar — no hay páginas que cortar), una figura matplotlib se reduce a su caption más la tabla de datos subyacente (Desde/Hasta/Frecuencia de las barras del histograma) porque un LLM no ve la imagen, y los marcadores de glosario se eliminan conservando el **negrita**. Lleva cabecera (# título), bloque de metadatos en blockquote e índice numerado con anclas GitHub. Espejo de render_automatic_eda_pdf/render_automatic_eda_pptx pero SIN manifest (KISS, el markdown es un único artefacto de texto). dict-no-throw: nunca lanza, devuelve {path, n_chars, chapters, note}; en error fatal path es None y note explica la causa. Flag opcional meta['embed_figures'] exporta PNGs junto al .md (off por defecto)." tags: [eda, markdown, render, report, llm, automatic-eda, chapters, versioned, no-cut, text, datascience, python] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [os, re, matplotlib, "datascience.automatic_eda"] params: - name: chapters_or_profile desc: "una lista de capítulos del modelo AutomaticEDA (dataclasses Chapter o dicts {id,title,version,blocks}) O un TableProfile dict del grupo eda. Si es un TableProfile, los capítulos canónicos se construyen con build_document(profile, meta['ctx']). Bloques soportados: heading, markdown, kv_table, data_table, figure, image, caption, note, group, glossary_entry. Lectura defensiva: lo no reconocido se degrada a Note, nunca lanza." - name: out_path desc: "ruta del archivo .md de salida. Los directorios padre se crean si faltan. Directorio no escribible → {path:None, note:} sin lanzar." - name: meta desc: "dict opcional. Claves: title (título del documento), ctx (dict con dataset_name→Dataset, source_origin→Fuente, storage→Almacenamiento, n_rows/n_cols→Dimensiones; también lo consumen los builders de capítulo cuando se da un profile), generated_at (timestamp; si falta se genera ISO UTC), embed_figures (True para exportar PNGs _figN.png junto al .md; por defecto False y el markdown queda autocontenido)." output: "dict (nunca lanza): {path: str|None, n_chars: int, chapters: list[{id,version}], note: str}. En error fatal (p.ej. directorio no escribible) path es None y note explica la causa. Un documento sin capítulos aplicables produce un markdown mínimo válido con 'documento vacío' y chapters=[]." tested: true tests: ["test_golden_bloques_sinteticos_serializa_todo_a_markdown", "test_edge_documento_vacio_no_revienta", "test_profile_path_construye_capitulos_y_escribe"] test_file_path: "python/functions/datascience/render_automatic_eda_markdown_test.py" file_path: "python/functions/datascience/render_automatic_eda_markdown.py" --- ## Ejemplo ```python from datascience import render_automatic_eda_markdown # Desde un TableProfile del grupo eda (mismo modelo que los renderers PDF/PPTX). 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, "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}]}}, ], } res = render_automatic_eda_markdown( profile, "reports/ventas_aeda.md", {"title": "EDA — ventas", "ctx": {"dataset_name": "Ventas", "source_origin": "ERP export", "n_rows": 1000, "n_cols": 2}}) print(res["path"], res["n_chars"], res["chapters"]) # -> reports/ventas_aeda.md 4123 [{'id':'portada','version':'1.0.0'}, ...] ``` ## Cuando usarla Cuando quieras **pegar el EDA a un LLM** (ChatGPT, Claude, ...) o tenerlo en texto plano versionable: mismo documento por capítulos que el PDF/PPTX, pero serializado a Markdown sin binarios. Úsala como tercera salida junto a `render_automatic_eda_pdf` (móvil) y `render_automatic_eda_pptx` (compartir) desde el MISMO modelo de capítulos. A diferencia de esas dos, no hay páginas ni slides: todas las filas de cada tabla se vuelcan (nada se corta) y cada figura se reduce a su caption + la tabla de datos subyacente, que es lo que un LLM puede leer. Para añadir capítulos al documento, ver `docs/capabilities/automatic_eda.md`. ## Gotchas - **Impura**: escribe el `.md` en `out_path` (crea los directorios padre). Con `meta['embed_figures']=True` además exporta un PNG `_figN.png` por figura junto al `.md`; por defecto NO exporta nada y el markdown queda autocontenido. - **Nunca lanza** (dict-no-throw): un bloque que falle se degrada a una nota y se anota en `note`; el documento se escribe igual. Un profile/lista vacíos producen un markdown mínimo válido con `*(documento vacío …)*` y `chapters=[]`. - **Figuras = datos, no imagen**: un bloque `figure` se serializa como `*Figura: caption*` más, si la figura matplotlib trae barras (histograma / barras), una tabla `| Desde | Hasta | Frecuencia |` extraída de los `Rectangle` patches (máx 100 filas; el resto se trunca con `*… (N filas más)*`). Si no hay barras o algo falla, solo sale el caption. La figura se cierra (`plt.close`) tras leerla. - **Glosario vs negrita**: se eliminan SOLO los marcadores de glosario `[[term:key]]visible[[/term]]` (queda `visible`); el `**negrita**` markdown SE CONSERVA (es válido). No se usa `strip_inline_md` aquí porque ese también quita el bold. - **Anclas del índice**: el `## Índice` enlaza cada capítulo con un ancla estilo GitHub del encabezado `## N. Título` (minúsculas, espacios→`-`, sin signos). Si dos capítulos comparten título exacto sus anclas colisionan (caso raro; los capítulos canónicos tienen títulos únicos). - **Tablas**: las celdas escapan `|` (→ `\|`) y pliegan saltos de línea a `
` para no romper la columna. No hay reparto por ancho — un LLM no lo necesita.