9c1b7dd0f3
Subsistema papers/: pieza de entrega + control de calidad. - render_paper_pdf_py_datascience (Python, impure, dominio datascience, grupo `papers`): convierte papers/<slug>/paper.md (frontmatter YAML + cuerpo IMRaD) en papers/<slug>/out/paper.pdf. Reutiliza el motor de paginación de flujo del paquete automatic_eda (matplotlib PdfPages, el mismo PDF móvil A5 de los informes EDA) — no reimplementa paginación ni toca matplotlib, y no añade dependencias. Cada sección IMRaD (# H1) → un Chapter en página nueva; portada desde el frontmatter (title/authors/date europea/abstract); detecta las imágenes Markdown  que el motor no entiende y las parte en bloques Image resueltos contra base_dir y base_dir/figures/. dict-no-throw estricto. 5 tests verdes (golden + edges: sin frontmatter, path inexistente, figura inexistente, ruta directa al .md). - .claude/agents/paper-reviewer: revisor académico adversarial read-only (gate anti paper-mill). Puntúa novedad/rigor/reproducibilidad/validez (0-5), intenta refutar cada claim contra la evidencia citada, detecta HARKing contra el preregistration.md, exige limitaciones declaradas y claims ≤ evidencia, y emite veredicto estructurado JSON (accept|major_revision|reject) con default conservador. Tools: Read, Grep, Glob, Bash (sin Edit/Write: solo juzga). Diseño completo: reports/0001-2026-06-30-papers-system-design.md (agente C). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
119 lines
3.3 KiB
Python
119 lines
3.3 KiB
Python
"""Tests para render_paper_pdf — DoD: golden + edges + error path.
|
|
|
|
Autocontenido y sin red: escribe papers Markdown sintéticos en directorios
|
|
temporales y verifica que el PDF se genera (estado, nº de páginas, archivo
|
|
no vacío) reutilizando el motor de paginación de ``automatic_eda``.
|
|
"""
|
|
|
|
import os
|
|
import tempfile
|
|
|
|
from datascience.render_paper_pdf import render_paper_pdf
|
|
|
|
|
|
_GOLDEN_PAPER = """---
|
|
title: A Minimal IMRaD Paper
|
|
authors:
|
|
- Ada Lovelace
|
|
- Alan Turing
|
|
date: 2026-06-30
|
|
abstract: >
|
|
Demostramos que el motor de paginación rinde un paper IMRaD completo en PDF
|
|
móvil sin cortar texto ni tablas.
|
|
---
|
|
|
|
# Introduction
|
|
|
|
Este es el cuerpo de la introducción con **texto en negrita** y una lista:
|
|
|
|
- Primer punto.
|
|
- Segundo punto.
|
|
|
|
# Methods
|
|
|
|
Resultados resumidos en una tabla pipe:
|
|
|
|
| Métrica | Valor |
|
|
| --- | --- |
|
|
| Precisión | 0.91 |
|
|
| Recall | 0.88 |
|
|
|
|
Texto final de la sección de métodos.
|
|
"""
|
|
|
|
|
|
def test_golden_genera_pdf_con_portada_y_secciones(tmp_path):
|
|
"""Golden: paper IMRaD con frontmatter + 2 secciones + tabla → PDF válido."""
|
|
paper_dir = tmp_path / "zz-demo"
|
|
paper_dir.mkdir()
|
|
(paper_dir / "paper.md").write_text(_GOLDEN_PAPER, encoding="utf-8")
|
|
|
|
res = render_paper_pdf(str(paper_dir))
|
|
|
|
assert res["status"] == "ok", res
|
|
assert res["n_pages"] >= 1
|
|
pdf_path = res["pdf_path"]
|
|
assert pdf_path is not None
|
|
assert os.path.exists(pdf_path)
|
|
assert os.path.getsize(pdf_path) > 0
|
|
|
|
|
|
def test_edge_sin_frontmatter_ni_figuras(tmp_path):
|
|
"""Edge 1: cuerpo plano sin frontmatter ni figuras → genera PDF igual."""
|
|
paper_dir = tmp_path / "plano"
|
|
paper_dir.mkdir()
|
|
(paper_dir / "paper.md").write_text(
|
|
"Solo un cuerpo plano, sin frontmatter ni encabezados de nivel 1.\n"
|
|
"Un par de líneas de texto corrido para que el motor lo pagine.\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
res = render_paper_pdf(str(paper_dir))
|
|
|
|
assert res["status"] == "ok", res
|
|
assert res["n_pages"] >= 1
|
|
assert os.path.exists(res["pdf_path"])
|
|
|
|
|
|
def test_edge_path_inexistente_no_revienta():
|
|
"""Edge 2: directorio inexistente → status error, sin crash, pdf_path None."""
|
|
res = render_paper_pdf("/tmp/no_existe_xyz_123")
|
|
|
|
assert res["status"] == "error"
|
|
assert res["pdf_path"] is None
|
|
assert res["n_pages"] == 0
|
|
assert "no encontrado" in (res["note"] or "")
|
|
|
|
|
|
def test_edge_figura_inexistente_degrada(tmp_path):
|
|
"""Edge 3: referencia a figura inexistente → el PDF se genera igual."""
|
|
paper_dir = tmp_path / "con-figura"
|
|
paper_dir.mkdir()
|
|
(paper_dir / "paper.md").write_text(
|
|
"---\n"
|
|
"title: Paper Con Figura Rota\n"
|
|
"---\n\n"
|
|
"# Results\n\n"
|
|
"Texto antes de la figura.\n\n"
|
|
"\n\n"
|
|
"Texto después de la figura.\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
res = render_paper_pdf(str(paper_dir))
|
|
|
|
assert res["status"] == "ok", res
|
|
assert res["n_pages"] >= 1
|
|
assert os.path.exists(res["pdf_path"])
|
|
|
|
|
|
def test_acepta_ruta_directa_al_md(tmp_path):
|
|
"""Acepta también la ruta directa a un paper.md (no solo el directorio)."""
|
|
md = tmp_path / "paper.md"
|
|
md.write_text("# Discussion\n\nCuerpo de la discusión.\n", encoding="utf-8")
|
|
|
|
res = render_paper_pdf(str(md))
|
|
|
|
assert res["status"] == "ok", res
|
|
assert os.path.exists(res["pdf_path"])
|