Academic report automation toolkit built with Python. It helps generate structured university-style reports, validate deliverables, manage references, and create reproducible visual assets.
This public version is sanitized for portfolio use. It contains generic templates and sample data only.
Academic reports often require consistent formatting, metadata, figures, references, and PDF/DOCX delivery. This toolkit automates the repetitive parts while keeping the student's reasoning and final review as the most important part of the process.
- Markdown/YAML based report content.
- HTML and PDF generation helpers.
- Academic formatting templates.
- IEEE reference validation helpers.
- Report quality validation checks.
- Visual asset generation with Mermaid, Vega-Lite, ECharts and HTML screenshots (requires the optional Node toolchain — see Node toolchain).
- Sample report for local testing.
- Clean project structure prepared for public GitHub use.
There are two Markdown pipelines and they are not interchangeable.
| Canonical pipeline | Preview pipeline | |
|---|---|---|
| Entry point | tools/build_report_auto.py |
tools/build_report.py |
| Route | Markdown → build_latex_report.py → LaTeX → PDF |
Markdown → HTML → WeasyPrint PDF |
| Input | a report folder with report.yml, body.md, sources.bib |
a single Markdown file |
| Covers, bibliography, validation gates | yes | no |
| Use it for | every deliverable | a quick look at one Markdown file |
build_report_auto.py is canonical for anything you intend to submit or
ship. It is the pipeline the agent skill mandates
(skills/academic-report-flow/references/routing.md and production.md), and it is
the only one that renders BibTeX citations, institutional covers and the
validation gates.
build_report.py is a lightweight HTML/WeasyPrint preview of a single Markdown
file. It is useful when you want to see prose and tables in a browser without a
LaTeX toolchain. It does not read report.yml, does not resolve [@citation]
keys and does not build a bibliography; when it finds citation syntax it says
so in a warning banner instead of rendering it. The Quick Start below uses it
because it is the shortest path to a visible result, not because it is the
recommended way to produce a report.
The three Markdown backends read the same character sequence differently, and a
body.md written for one can be misread by another:
- LaTeX branch (
build_latex_report.py): a line matching^-{3,}$becomes a\newpage, i.e. a page break, anywhere in the document. - DOCX branch (
build_docx_report.py): the same^-{3,}$line inserts a page break, matching the LaTeX branch. - HTML branch (
build_report.py): a---on the first line opens a YAML front-matter block, which supplies the document metadata.
build_report.py treats --- as front-matter only when it opens the file and
the block reads as key: value pairs. A --- used as a page break — or any
--- further down the document — renders as an ordinary horizontal rule and is
never mistaken for metadata. The LaTeX and DOCX branches have no equivalent
guard, so a front-matter block written for the HTML branch will turn into page
breaks there. Keep a body.md bound to one backend.
- Python 3
- Markdown
- WeasyPrint
- python-docx
- BeautifulSoup
- Matplotlib / Altair / Plotly
- Mermaid CLI / Playwright / ECharts for optional visuals
academic-report-automation/
├── examples/
│ ├── ejemplo_informe_academico/ # complete report folder — copy this to start
│ │ ├── report.yml
│ │ ├── body.md
│ │ └── sources.bib
│ ├── placeholder.svg
│ └── sample_report.md # Markdown syntax snippet, not a report folder
├── templates/
│ ├── academic_format.yml # shared academic formatting rules
│ ├── ensayo_unl.css # stylesheet for the HTML preview branch
│ ├── ensayo_unl.md # sample UNL essay with YAML front-matter
│ ├── unl-report.tex # LaTeX template with the UNL cover
│ ├── plain-report.tex # LaTeX template without institutional furniture
│ └── chamba-overleaf.tex # Overleaf-compatible variant
├── tools/
│ ├── build_report_auto.py # canonical entry point (report folder -> PDF)
│ ├── build_latex_report.py # Markdown -> LaTeX -> PDF renderer
│ ├── build_report.py # HTML/WeasyPrint preview of one Markdown file
│ ├── report_config.py # CODE root / CONTENT root resolution
│ ├── output_router.py # publishes final PDFs under outputs/<subject>/
│ ├── source_library.py # local academic source library
│ ├── validate_report.py # deliverable validation gates
│ ├── validate_ieee_refs.py # IEEE reference checks
│ ├── visual_builder.py # Mermaid / Vega-Lite / ECharts / screenshots
│ ├── visual_pdf_auditor.py # rendered-PDF visual audit
│ └── test_*.py # pytest suite for the tools above
├── scripts/
│ ├── sync_skills.sh
│ └── install_hooks.sh
├── skills/ # agent skill definitions (source of truth)
├── tests/ # skill contract tests
├── assets/generated/
├── outputs/
├── requirements.txt
├── requirements-dev.txt
└── package.jsonA few tools in tools/ are one-off report builders kept for reference
(build_vm_contenedores_report.py, build_prompting_final_pdf.py,
build_mapa_conceptual_investigacion.py, restyle_docx_aa1.py). They are not
part of the general pipeline.
python3 -m venv .venv
source .venv/bin/activatepython3 -m pip install -r requirements.txtpython3 tools/build_report.py examples/sample_report.md --html outputs/sample_report.htmlpython3 tools/build_report.py examples/sample_report.md --pdf outputs/sample_report.pdfBoth commands use the preview pipeline. For an actual deliverable, use the canonical one against a report folder:
python3 tools/build_report_auto.py <report-folder>/
python3 tools/validate_report.py <report-folder>/examples/ejemplo_informe_academico/ is a complete, minimal report folder:
report.yml, body.md, sources.bib. Copy it into
$REPORT_CONTENT_ROOT/reports/, rename it, and edit — the relative paths inside
are written for that location.
It satisfies every validator rule with no exemption, and
tools/test_shipped_example.py keeps it that way, so the pattern you copy is
the pattern the validator enforces. Two things it demonstrates on purpose:
route:is declared rather than left to the academic default. The route decides which metadata is required; a project or business document that never declares one is validated as university coursework and asked for a teacher.- The final PDF goes to
outputs/<materia>/, never toreports/<trabajo>/outputs/.
A folder named with a leading _ is scratch work and is not copied to
outputs/<materia>/. That prefix is only about publication — use
publish_global: when you want to say so explicitly.
Only needed for tools/visual_builder.py. Everything above works without it.
npm installSee Node toolchain for what else it needs.
The suite needs pytest, which is not part of the runtime dependencies:
python3 -m pip install -r requirements-dev.txt
python3 -m pytest tools/ tests/skills/ holds the agent skill definitions and is the single source of truth
for them. The agent runtimes — ~/.claude/skills/ and
~/.config/opencode/skills/ — are mirrors. Edit here and sync outward; a file
edited directly in a runtime directory is overwritten on the next sync.
Sync by hand:
./scripts/sync_skills.sh # dry run — shows what would change
./scripts/sync_skills.sh --apply # write the changesThe sync is gated on the skill contract tests, so a skill that fails its own routing contract never reaches a runtime.
One skill, skills/academic-report-flow/, owns the whole document flow: intake,
research, plan, draft, approval, verify, format, generate, validate, review, deliver.
research-workflow and academic-visual-builder stay separate.
- Entry: a new request first creates its work folder
reports/<slug>/; thendoc_statusruns and itsnexttoken selects the one phase reference to load (an empty folder yieldsintake). The standalone full route runs only when the work-folder flow is unavailable. - Route: an academic assignment (subject, teacher, APE, AA, exercise, homework) records
route: academicwithout asking; the route is asked only when signals are absent or conflicting. - Identity: a student name the user saved as permanent counts as confirmed; otherwise it is a single-choice suggestion, never auto-filled. A title holding commentary gets a one-line confirmation.
- Guide: the teacher's guide, rubric and explanation are always requested, in one compact question batch, then the run stops.
- Three decisions: (1) intake data, (2) draft approval plus format (APE, AA or libre; PDF or DOCX) asked in one batch, (3) final review. Intake never asks formatting questions.
Flow diagram: docs/diagrams/report-flow.mmd
(rendered: report-flow.png; Spanish:
report-flow.es.png). Older diagrams in docs/diagrams/
(intake-flow*, new-report-flow.es.*) are historical.
Install the versioned hooks once per clone:
./scripts/install_hooks.shThis sets core.hooksPath to .githooks/, which is needed because .git/hooks/
is not versioned — the hooks travel with the repository, but each clone has to be
told where to look.
After that, post-merge, post-rewrite and post-checkout re-sync the runtimes
whenever the repository changes. All three are covered because a plain git pull
fires post-merge while git pull --rebase fires post-rewrite instead, and a
branch switch fires neither.
The hook fingerprints the tracked contents of skills/ and exits immediately when
nothing changed, so an ordinary pull costs nothing. When a sync fails it says so
and deliberately leaves the fingerprint unwritten, so the next pull retries rather
than silently skipping a pending update. Git ignores the exit code of post-*
hooks, so this can never block a pull.
The toolkit includes a visual builder for reproducible diagrams and charts.
python3 tools/visual_builder.py --helpSupported visual workflows include:
- Mermaid diagrams
- Vega-Lite charts
- ECharts visuals
- HTML screenshots through Playwright
- Generated asset validation
The visual builder is the only part of the toolkit that needs Node. A fresh
checkout ships no node_modules/, so nothing under visual_builder.py works
until you install it:
npm install # @mermaid-js/mermaid-cli, playwright, echartsSome subcommands also need a local Chrome/Chromium binary. visual_builder.py
looks for one under .cache/puppeteer/chrome/ in the code root and then in the
content root, and tells you to install it with:
npx @puppeteer/browsers install chrome@stable --path .cache/puppeteerRequirements per workflow:
| Workflow | Needs |
|---|---|
| Vega-Lite charts | Python only (vl-convert-python) |
| Asset validation | Python only |
| Mermaid diagrams | npm install + local Chrome |
| ECharts visuals | npm install |
| HTML screenshots | npm install + local Chrome |
echarts and html-shot generate a temporary .cjs and run it with Node. It is
written to .cache/visual-renders/ inside this repository, because Node
resolves require(...) by walking up from the script's own directory: a script
parked on the content tree could never reach node_modules/ here, whatever you
installed. Both commands now check the dependency before spawning Node and say
what to install if it is missing, instead of surfacing a module-resolution stack
trace.
For a browser, npx playwright install chromium matches the declared
devDependency. An existing @puppeteer/browsers install under .cache/puppeteer/
is still detected and takes precedence.
Two browser managers for one binary. @mermaid-js/mermaid-cli bundles
Puppeteer, while playwright manages its own Chromium. Both are declared. When
both are installed the Puppeteer build wins for every renderer, including
html-shot, which then drives a Puppeteer-managed Chrome through Playwright.
That usually works, but Playwright pins its builds to its own version, so the
pairing is not guaranteed across upgrades. Picking one manager is the proper fix.
This project demonstrates:
- Python scripting for automation.
- File organization and output routing.
- Template-based document generation.
- Validation-oriented thinking.
- Report rendering workflows.
- Reproducible academic visuals.
Automation should support understanding, structure, formatting and review. It should not replace the student's technical reasoning, source evaluation or final responsibility for the content.
Alejandro Padilla
- GitHub: AlejandroTatum
- LinkedIn: Alejandro Emanuel Padilla Espinoza