Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions models/report_correction_model/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Grading Automation Tools

This project provides a set of Python scripts to automate and standardize the process of creating grading reports for student assignments. The solution is divided into two main tools:

1. **Report Generator (`report_tool.py`)**: Creates a folder and file structure for grading in Markdown (`.md`) for each student from a customizable template.
2. **PDF Converter (`convert_to_pdf.py`)**: Converts the finalized Markdown grading reports to PDF, preserving formatting, tables, and mathematical formulas in LaTeX.

## ✨ Features

- **Batch Generation**: Create the grading structure for all students in a class at once.
- **Standardized Template**: Ensure consistency across all grades by using a template file (`template.md`).
- **Customizable**: Easily modify the `template.md` to adapt to different evaluation criteria.
- **High-Quality PDF Conversion**: Generate professional-looking PDFs by rendering Markdown with a style similar to GitHub.
- **LaTeX Support**: Write complex mathematical formulas in your reports using LaTeX syntax, which will be correctly rendered in the final PDF.

## 📂 Expected Directory Structure

For the scripts to work correctly, organize your folders as follows:

```
your_project/
├── submissions/ <-- Folder with each student's directory
│ ├── Example_1/
│ ├── Example_2/
│ └── Example_3/
├── report_tool.py <-- Generator script
├── convert_to_pdf.py <-- Converter script
└── template.md <-- Report template
```

- The main directory (e.g., `submissions/`) should contain a subfolder for each student. The name of the subfolder will be used as the student's name in the report.

## 🚀 Installation and Setup

### Prerequisites

- Python 3.7 or higher.

### Installation Steps

1. **Clone or download the files** to a directory on your computer.

2. **Create a virtual environment (recommended)**:

```bash
python -m venv venv
```

- On Windows, activate with: `.\venv\Scripts\activate`
- On macOS/Linux, activate with: `source venv/bin/activate`

3. **Install the necessary dependencies**:
The scripts use a few Python libraries to render Markdown and generate the PDF. Install them with the following command:

```bash
pip install "markdown-it-py<3.0.0" "mdit-py-plugins" pyppeteer
```

> **Note**: The first time the `convert_to_pdf.py` script is run, the `pyppeteer` library will automatically download a version of Chromium (a headless browser) to render the page. This may take a few minutes.

## 📝 Workflow and Usage

### Step 1: Generate the Grading Files

Run the `report_tool.py` script to create the `.md` files for each student.

**Basic Usage:**
Provide the activity name as the main argument. The script will look for student folders in the current directory (`.`).

```bash
# Example for an activity named "Lab01"
python report_tool.py Lab01
```

**Advanced Usage with Options:**
You can specify the student directory, the grader's name, and the report title.

```bash
python report_tool.py "Lab02_Circuits" -o "./submissions" -g "Prof. Ada Lovelace" --activity-title "Laboratory 02 Report"
```

After running, the following structure will be created inside each student's folder:

```
submissions/
└── John_Smith/
└── John_Smith_Lab01_correction/
├── John_Smith_Lab01_correction.md <-- The report to be filled out
└── src/ <-- Optional folder for corrected code
```

### Step 2: Fill in the Reports

Open each generated `.md` file and fill in the grades, comments, and feedback for each student.

### Step 3: Convert the Reports to PDF

Once the grades are finalized, run the `convert_to_pdf.py` script to generate the PDFs.

**Usage:**
Provide the directory where the `.md` reports are located. The script will recursively search for all `.md` files and convert them.

```bash
# Converts all reports inside the 'submissions' folder
python convert_to_pdf.py ./submissions
```

The script will create a `.pdf` file with the same name and in the same location as the original `.md` file.

## 🔧 Component Details

### `report_tool.py`

- **Purpose**: To automate the creation of the grading structure.
- **Command-Line Arguments**:
- `activity_name` (required): A short name for the activity, used for naming files.
- `-o, --output-dir`: The directory where the student folders are located (default: `.`).
- `-g, --grader`: The name of the grader to be inserted into the report.
- `-t, --activity-title`: The full title of the activity for the header. If omitted, it uses `activity_name`.
- `--template`: The path to the template file (default: `template.md`).

### `convert_to_pdf.py`

- **Purpose**: To convert Markdown files to PDF.
- **Technology**: Uses `pyppeteer` to control a headless browser that "prints" the HTML version of the Markdown to a PDF file.
- **Command-Line Arguments**:
- `base_dir` (optional): The base directory to recursively search for `.md` files (default: `.`).

### `template.md`

- **Purpose**: To serve as a model for all grading reports.
- **Syntax**: Uses the `${variable}` syntax from Python's `string.Template` for fields that will be replaced by `report_tool.py`, such as `${student_name}`, `${activity_title}`, etc..
- **Customization**: Feel free to edit this file, changing the evaluation criteria, weights, text, and the calculation formula to meet your needs. Just keep the placeholders that the script uses.
150 changes: 150 additions & 0 deletions models/report_correction_model/convert_to_pdf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import argparse
import asyncio
import sys
from pathlib import Path

from markdown_it import MarkdownIt
from mdit_py_plugins.front_matter import front_matter_plugin
from mdit_py_plugins.texmath import texmath_plugin

from pyppeteer import launch

# --- HTML TEMPLATE ---
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{title}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.8/dist/katex.min.css"
integrity="sha384-GvrOXuhMATgEsSwCs4smul74iXGOixntILdUW9XmUC6+HX0sLNAK3q71HotJqlAn"
crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
<style>
.markdown-body {{
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}}
@media (max-width: 767px) {{
.markdown-body {{
padding: 15px;
}}
}}
</style>
</head>
<body class="markdown-body">
{content}
</body>
</html>
"""

# --- HELPER FUNCTION ---
def log(msg: str, success: bool = True, indent: int = 0) -> None:
GREEN, RED, YELLOW, RESET = "\033[92m", "\033[91m", "\033[93m", "\033[0m"
prefix = f"{GREEN}✅{RESET}" if success else (f"{YELLOW}ℹ️{RESET}" if success is None else f"{RED}❌{RESET}")
print(f"{' ' * indent}{prefix} {msg}")

# --- CORE LOGIC ---
async def convert_file_to_pdf(md_path: Path, browser):
pdf_path = md_path.with_suffix(".pdf")
log(f"Processando '{md_path.name}'...", success=None, indent=1)

try:
# Plugin de matemática sem argumento extra
md_converter = (
MarkdownIt("gfm-like")
.use(front_matter_plugin)
.use(texmath_plugin) # ✅ sem argumento
)

markdown_text = md_path.read_text(encoding="utf-8")
html_content = md_converter.render(markdown_text)
final_html = HTML_TEMPLATE.format(title=md_path.stem, content=html_content)

page = await browser.newPage()
await page.setContent(final_html)
await page.pdf({
"path": str(pdf_path),
"format": "A4",
"printBackground": True,
"margin": {"top": "2.5cm", "right": "2.5cm", "bottom": "2.5cm", "left": "2.5cm"},
})
await page.close()

log(f"Convertido com sucesso para '{pdf_path.name}'", success=True, indent=2)
return True

except Exception as e:
log(f"Falha na conversão de '{md_path.name}'", success=False, indent=2)
log(f"Erro: {e}", success=False, indent=3)
return False

async def main_converter(base_dir: Path):
print("\n" + "="*50)
print("🚀 Iniciando Conversão de Markdown para PDF")
print(f"📂 Diretório de Busca: {base_dir.resolve()}")
print("="*50 + "\n")

markdown_files = list(base_dir.rglob("*.md"))
if not markdown_files:
log("Nenhum arquivo Markdown (.md) foi encontrado para conversão.", success=None)
print("\n✨ Processo concluído!")
return

log(f"Encontrados {len(markdown_files)} arquivos Markdown para processar.")
log("Iniciando o navegador headless (pode demorar na primeira vez)...", success=None)

browser = None
try:
browser = await launch(headless=True, args=['--no-sandbox'])

success_count = 0
fail_count = 0

tasks = [convert_file_to_pdf(md_path, browser) for md_path in markdown_files]
results = await asyncio.gather(*tasks)

for result in results:
if result:
success_count += 1
else:
fail_count += 1

except Exception as e:
log("Ocorreu um erro ao iniciar o navegador ou processar os arquivos.", success=False)
log(f"Erro: {e}", success=False)

finally:
if browser:
await browser.close()
log("Navegador headless fechado.", success=None)

print("\n" + "="*50)
print("✨ Processo de conversão concluído!")
log(f"Sucessos: {success_count}", success=True)
log(f"Falhas: {fail_count}", success=False)
print("="*50)


# --- SCRIPT ENTRY POINT ---
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Ferramenta para converter arquivos Markdown (.md) em PDF usando Pyppeteer.",
formatter_class=argparse.HelpFormatter
)
parser.add_argument(
"base_dir",
type=Path,
nargs='?',
default=Path("."),
help="Diretório base para buscar arquivos .md recursivamente. Padrão: diretório atual."
)
args = parser.parse_args()

if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

asyncio.run(main_converter(args.base_dir))
Loading