--- name: render_paper_pdf kind: function lang: py domain: datascience version: "1.0.0" purity: impure signature: "def render_paper_pdf(paper_dir: str) -> dict" description: "Convierte un paper académico IMRaD escrito en Markdown (papers//paper.md, con frontmatter YAML opcional title/authors/date/abstract + cuerpo) en un PDF papers//out/paper.pdf. REUTILIZA el paginador de flujo del paquete automatic_eda (el mismo motor del PDF móvil A5 de los informes EDA): no reimplementa paginación ni toca matplotlib. Cada sección IMRaD (encabezado de nivel 1, p.ej. # Introduction, # Methods) se mapea a un Chapter que empieza en página nueva; el motor parsea por sí mismo headings, listas, tablas pipe, párrafos y **negrita** dentro del texto. Como el motor NO entiende la sintaxis de imagen Markdown ![alt](src), esta función detecta esas líneas y las parte en bloques Image separados, resolviendo el src relativo a base_dir y base_dir/figures/. La portada (si hay título) lista autores y fecha (DD/MM/AAAA si parseable) más el abstract. dict-no-throw: nunca lanza, devuelve {status, pdf_path, n_pages, note}." tags: [papers, pdf, academic, render, report, imrad, mobile, automatic-eda, markdown, no-cut, matplotlib, datascience, python] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [os, re, datetime, yaml, "datascience.automatic_eda"] params: - name: paper_dir desc: "ruta al directorio del paper (papers//, del que se lee paper.md) O directamente la ruta a un archivo paper.md (cualquier ruta terminada en .md). El directorio base para resolver figuras y escribir el PDF es el dirname del paper.md. Si el paper.md no existe (incluida una ruta totalmente inexistente) devuelve status='error' sin crash." output: "dict (nunca lanza): {status: 'ok'|'error', pdf_path: str|None, n_pages: int, note: str}. En éxito status='ok', pdf_path es la ruta del PDF escrito (/out/paper.pdf) y n_pages el total de páginas. En error status='error', pdf_path=None, n_pages=0 y note explica la causa (paper.md no encontrado, fallo del motor, o excepción inesperada)." tested: true tests: ["test_golden_genera_pdf_con_portada_y_secciones", "test_edge_sin_frontmatter_ni_figuras", "test_edge_path_inexistente_no_revienta", "test_edge_figura_inexistente_degrada", "test_acepta_ruta_directa_al_md"] test_file_path: "python/functions/datascience/render_paper_pdf_test.py" file_path: "python/functions/datascience/render_paper_pdf.py" --- ## Ejemplo ```python from datascience import render_paper_pdf # Estructura del paper: # papers/zz-demo/paper.md (frontmatter YAML + cuerpo IMRaD) # papers/zz-demo/figures/fig1.png (figuras referenciadas con ![alt](figures/fig1.png)) # # paper.md: # --- # title: A Minimal IMRaD Paper # authors: [Ada Lovelace, Alan Turing] # date: 2026-06-30 # abstract: Demostramos que el motor pagina un paper sin cortar nada. # --- # # Introduction # Texto con **negrita** y una lista: # - Punto uno. # ![Figura 1](figures/fig1.png) # # Methods # | Métrica | Valor | # | --- | --- | # | Precisión | 0.91 | res = render_paper_pdf("papers/zz-demo") print(res["status"], res["n_pages"], res["pdf_path"]) # -> ok 3 papers/zz-demo/out/paper.pdf # También acepta la ruta directa al .md: render_paper_pdf("papers/zz-demo/paper.md") ``` ## Cuando usarla Cuando tengas un paper académico (o cualquier documento IMRaD) escrito en Markdown y quieras un **PDF móvil A5 listo para leer**, sin montar LaTeX ni configurar un pipeline de pandoc. Úsala después de redactar `paper.md` con su frontmatter (título, autores, fecha, abstract) y secciones de nivel 1; obtienes `out/paper.pdf` con portada, una página nueva por sección IMRaD, tablas que se parten repitiendo la cabecera y figuras escaladas para caber enteras — garantía de no-corte heredada del motor `automatic_eda`. Es la capa de presentación PDF del grupo `papers`. ## Gotchas - **Impura**: escribe `out/paper.pdf` (y crea el directorio `out/`) junto al `paper.md`. Necesita **matplotlib** instalado en el venv (lo usa el motor `automatic_eda.render_pdf` con backend headless `Agg`; corre en agentes/CI sin display). `pyyaml` es opcional: si falta, el frontmatter se parsea con un parser line-based `clave: valor` degradado. - **Reutiliza el motor `automatic_eda.render_pdf`**: NO reimplementa paginación ni toca matplotlib. `render_pdf` no tiene ID propio en el registry (es parte del paquete de soporte `automatic_eda`), por eso `uses_functions` queda vacío; la dependencia real es ese motor del paquete. - **Nunca lanza** (dict-no-throw): `paper.md` inexistente → `{status:"error", pdf_path:None, note:"paper.md no encontrado: ..."}`; cualquier excepción inesperada → `{status:"error", note:"fallo: ..."}`. Frontmatter ausente o incompleto degrada limpio (sin portada, el cuerpo entero se pagina). - **Figuras relativas a `figures/`**: el `src` de `![alt](src)` se resuelve probando `/` y `/figures/`; usa el primero que exista. Si ninguno existe, el motor **degrada** dibujando "(imagen no encontrada: ...)" — el PDF se genera igual, no crashea. Las URLs `http(s)` se dejan como texto Markdown, no se descargan. - **Solo imágenes en línea propia**: el motor `_place_markdown` NO entiende `![alt](src)`; esta función solo convierte a `Image` las líneas cuyo único contenido es la imagen. Una imagen embebida a mitad de un párrafo se quedaría como texto crudo. - **A5 portrait mobile-first**: el formato (tamaño de página, tipografía, pie `Capítulo · vX.Y.Z`) lo fija el motor EDA y no es configurable desde aquí.