ajiinx/markify-backend

Python

0

1 commits

updated Sep 22, 2026

See the code

README

Markify — Automated Answer Sheet Grading

In active development

What it does

Markify is an end-to-end automated grading system for scanned student answer sheets. You upload a marking scheme and a student's scanned PDF; Markify extracts, segments, and grades each answer against the official rubric using a locally-hosted Qwen/Qwen2.5-VL-72B-Instruct-AWQ model served through vLLM.

Current Features

  • PDF → Markdown extraction (PyMuPDF fast path)
  • vLLM multimodal OCR fallback for scanned / image-only PDFs (1288px longest-edge constraint applied globally before dispatch)
  • LLM-based segmentation of student answer sheets into per-question sections
  • Model-answer ingestion and structured storage (MongoDB)
  • Automated per-question grading with marks and evaluation feedback
  • Smart JSON safety net — regex fallback parser + Pydantic invariants (awarded_marks ≤ max_marks, sum == total_score)
  • React.js UI for all API endpoints
  • MongoDB document lifecycle

Architecture

React Frontend  (port 5173)
       │
       ▼  HTTP
FastAPI Backend  (port 8080)          ← main.py  [single process]
       │
       ├── PyMuPDF           (CPU)    ← PDF text extraction
       ├── pdf_to_images     (CPU)    ← PDF → 1288px PIL images
       │
       └── HTTP → vLLM Server  (port 8000)
                  └── Qwen/Qwen2.5-VL-72B-Instruct-AWQ  (GPU VRAM)
                      ├── /v1/chat/completions  (text + vision)
                      └── /v1/models            (health check)

The legacy model_server.py / model_client.py two-process routing layer has been fully retired. All inference (OCR, segmentation, grading) routes directly to the single vLLM server.

API Endpoints

MethodPathDescription
GET/healthvLLM server status + loaded model
POST/markdownUpload marking-scheme PDF → parsed questions + MongoDB save
POST/scannedUpload student answer PDF → OCR → segment → grade
GET/get_latest_modelList the 5 most recent model-answer documents

Directory Structure

.
├── backend
│   ├── db.py                    # MongoDB lifecycle helpers
│   ├── llm_grading.py           # Per-question LLM grading
│   ├── llm_marking_scheme.py    # LLM marking-scheme extraction
│   ├── llm_segmentation.py      # LLM-based document segmentation
│   ├── main.py                  # FastAPI app  ← entry point
│   ├── model_client.py          # [RETIRED] legacy model server client
│   ├── model_server.py          # [RETIRED] legacy OlmOCREngine process
│   ├── olmocr_grading
│   │   ├── config.py            # Rendering / preprocessing config
│   │   ├── image_utils.py       # Resize, deskew, denoise helpers
│   │   ├── pdf_utils.py         # PDF → PIL image rendering
│   │   └── ...
│   ├── requirements.txt
│   ├── segmentation.py          # Regex fallback segmenter
│   └── vlm_client.py            # vLLM OpenAI-compatible HTTP client  ← NEW
└── frontend
    └── ...

Prerequisites

RequirementVersionNotes
Python≥ 3.10
CUDA GPU≥ 24 GB VRAMFor the 72B AWQ model; A100/H100 recommended
MongoDBany recentRunning locally on default port 27017
Node.js / Bunany recentFor the React frontend

Environment Variables

All variables are optional (sane defaults shown):

VariableDefaultDescription
VLLM_BASE_URLhttp://localhost:8000/v1vLLM server endpoint
VLLM_MODELQwen/Qwen2.5-VL-72B-Instruct-AWQModel name on the vLLM server
VLLM_MAX_TOKENS3072Max tokens for text-only calls
VLLM_TIMEOUT600HTTP timeout (seconds) for inference calls
MONGODB_URImongodb://localhost:27017MongoDB connection string
MONGODB_DBautoassessDatabase name
MONGODB_SCANNED_COLLECTIONscanned_documentsCollection for student sheets
MONGODB_MODEL_ANSWERS_COLLECTIONmodel_answersCollection for marking schemes

Setup & Running

1. Start MongoDB

# macOS (Homebrew)
brew services start mongodb-community

# Ubuntu / WSL
sudo systemctl start mongod

2. Start the vLLM Server

Install vLLM (if not already):

pip install vllm

Serve the model (requires a CUDA GPU with ≥ 24 GB VRAM):

vllm serve Qwen/Qwen2.5-VL-72B-Instruct-AWQ \
  --host 0.0.0.0 \
  --port 8000 \
  --max-model-len 32768 \
  --trust-remote-code

Wait until you see INFO: Application startup complete. in the vLLM logs before starting the backend.

Tip: For multi-GPU setups, add --tensor-parallel-size <N> to distribute the model across N GPUs.

3. Set Up the Backend

cd backend

# Create and activate a virtual environment (uv recommended)
uv venv

# macOS / Linux
source .venv/bin/activate

# Windows
.venv\Scripts\activate

# Install dependencies
uv pip install -r requirements.txt

4. Run the Backend

# Development (auto-reload on file changes)
uv run uvicorn main:app --host 0.0.0.0 --port 8080 --reload

# Production
uv run uvicorn main:app --host 0.0.0.0 --port 8080 --workers 2

The FastAPI interactive docs will be available at:

5. Run the Frontend

cd frontend

# Using npm
npm install && npm run dev

# Using bun
bun install && bun run dev

The React app will be available at http://localhost:5173.

Quick Health Check

# Check vLLM server + model status
curl http://localhost:8080/health
# Expected: {"status":"ok","model":"Qwen/Qwen2.5-VL-72B-Instruct-AWQ",...}

Usage Example

# 1. Upload a marking scheme
curl -X POST http://localhost:8080/markdown \
  -F "myFile=@marking_scheme.pdf" \
  | python -m json.tool

# 2. Grade a student answer sheet (using the model_answer_id from step 1)
curl -X POST http://localhost:8080/scanned \
  -F "myFile=@student_answer.pdf" \
  -F "model_answer_id=<document_id_from_step_1>" \
  | python -m json.tool

Upgrading from the Legacy Architecture

If you were previously running the two-process setup:

# OLD (no longer needed — retire these)
uv run uvicorn model_server:app --host 127.0.0.1 --port 8001
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload

# NEW — just three processes
mongod                          # MongoDB
vllm serve Qwen/...             # vLLM  (port 8000)
uvicorn main:app --port 8080    # FastAPI  (port 8080)

model_server.py and model_client.py are kept for reference but are no longer imported or executed.

ajiinx/markify-backend

Python

0

1 commits

updated Sep 22, 2026

See the code

README

Markify — Automated Answer Sheet Grading

In active development

What it does

Markify is an end-to-end automated grading system for scanned student answer sheets. You upload a marking scheme and a student's scanned PDF; Markify extracts, segments, and grades each answer against the official rubric using a locally-hosted Qwen/Qwen2.5-VL-72B-Instruct-AWQ model served through vLLM.

Current Features

  • PDF → Markdown extraction (PyMuPDF fast path)
  • vLLM multimodal OCR fallback for scanned / image-only PDFs (1288px longest-edge constraint applied globally before dispatch)
  • LLM-based segmentation of student answer sheets into per-question sections
  • Model-answer ingestion and structured storage (MongoDB)
  • Automated per-question grading with marks and evaluation feedback
  • Smart JSON safety net — regex fallback parser + Pydantic invariants (awarded_marks ≤ max_marks, sum == total_score)
  • React.js UI for all API endpoints
  • MongoDB document lifecycle

Architecture

React Frontend  (port 5173)
       │
       ▼  HTTP
FastAPI Backend  (port 8080)          ← main.py  [single process]
       │
       ├── PyMuPDF           (CPU)    ← PDF text extraction
       ├── pdf_to_images     (CPU)    ← PDF → 1288px PIL images
       │
       └── HTTP → vLLM Server  (port 8000)
                  └── Qwen/Qwen2.5-VL-72B-Instruct-AWQ  (GPU VRAM)
                      ├── /v1/chat/completions  (text + vision)
                      └── /v1/models            (health check)

The legacy model_server.py / model_client.py two-process routing layer has been fully retired. All inference (OCR, segmentation, grading) routes directly to the single vLLM server.

API Endpoints

MethodPathDescription
GET/healthvLLM server status + loaded model
POST/markdownUpload marking-scheme PDF → parsed questions + MongoDB save
POST/scannedUpload student answer PDF → OCR → segment → grade
GET/get_latest_modelList the 5 most recent model-answer documents

Directory Structure

.
├── backend
│   ├── db.py                    # MongoDB lifecycle helpers
│   ├── llm_grading.py           # Per-question LLM grading
│   ├── llm_marking_scheme.py    # LLM marking-scheme extraction
│   ├── llm_segmentation.py      # LLM-based document segmentation
│   ├── main.py                  # FastAPI app  ← entry point
│   ├── model_client.py          # [RETIRED] legacy model server client
│   ├── model_server.py          # [RETIRED] legacy OlmOCREngine process
│   ├── olmocr_grading
│   │   ├── config.py            # Rendering / preprocessing config
│   │   ├── image_utils.py       # Resize, deskew, denoise helpers
│   │   ├── pdf_utils.py         # PDF → PIL image rendering
│   │   └── ...
│   ├── requirements.txt
│   ├── segmentation.py          # Regex fallback segmenter
│   └── vlm_client.py            # vLLM OpenAI-compatible HTTP client  ← NEW
└── frontend
    └── ...

Prerequisites

RequirementVersionNotes
Python≥ 3.10
CUDA GPU≥ 24 GB VRAMFor the 72B AWQ model; A100/H100 recommended
MongoDBany recentRunning locally on default port 27017
Node.js / Bunany recentFor the React frontend

Environment Variables

All variables are optional (sane defaults shown):

VariableDefaultDescription
VLLM_BASE_URLhttp://localhost:8000/v1vLLM server endpoint
VLLM_MODELQwen/Qwen2.5-VL-72B-Instruct-AWQModel name on the vLLM server
VLLM_MAX_TOKENS3072Max tokens for text-only calls
VLLM_TIMEOUT600HTTP timeout (seconds) for inference calls
MONGODB_URImongodb://localhost:27017MongoDB connection string
MONGODB_DBautoassessDatabase name
MONGODB_SCANNED_COLLECTIONscanned_documentsCollection for student sheets
MONGODB_MODEL_ANSWERS_COLLECTIONmodel_answersCollection for marking schemes

Setup & Running

1. Start MongoDB

# macOS (Homebrew)
brew services start mongodb-community

# Ubuntu / WSL
sudo systemctl start mongod

2. Start the vLLM Server

Install vLLM (if not already):

pip install vllm

Serve the model (requires a CUDA GPU with ≥ 24 GB VRAM):

vllm serve Qwen/Qwen2.5-VL-72B-Instruct-AWQ \
  --host 0.0.0.0 \
  --port 8000 \
  --max-model-len 32768 \
  --trust-remote-code

Wait until you see INFO: Application startup complete. in the vLLM logs before starting the backend.

Tip: For multi-GPU setups, add --tensor-parallel-size <N> to distribute the model across N GPUs.

3. Set Up the Backend

cd backend

# Create and activate a virtual environment (uv recommended)
uv venv

# macOS / Linux
source .venv/bin/activate

# Windows
.venv\Scripts\activate

# Install dependencies
uv pip install -r requirements.txt

4. Run the Backend

# Development (auto-reload on file changes)
uv run uvicorn main:app --host 0.0.0.0 --port 8080 --reload

# Production
uv run uvicorn main:app --host 0.0.0.0 --port 8080 --workers 2

The FastAPI interactive docs will be available at:

5. Run the Frontend

cd frontend

# Using npm
npm install && npm run dev

# Using bun
bun install && bun run dev

The React app will be available at http://localhost:5173.

Quick Health Check

# Check vLLM server + model status
curl http://localhost:8080/health
# Expected: {"status":"ok","model":"Qwen/Qwen2.5-VL-72B-Instruct-AWQ",...}

Usage Example

# 1. Upload a marking scheme
curl -X POST http://localhost:8080/markdown \
  -F "myFile=@marking_scheme.pdf" \
  | python -m json.tool

# 2. Grade a student answer sheet (using the model_answer_id from step 1)
curl -X POST http://localhost:8080/scanned \
  -F "myFile=@student_answer.pdf" \
  -F "model_answer_id=<document_id_from_step_1>" \
  | python -m json.tool

Upgrading from the Legacy Architecture

If you were previously running the two-process setup:

# OLD (no longer needed — retire these)
uv run uvicorn model_server:app --host 127.0.0.1 --port 8001
uv run uvicorn main:app --host 0.0.0.0 --port 8000 --reload

# NEW — just three processes
mongod                          # MongoDB
vllm serve Qwen/...             # vLLM  (port 8000)
uvicorn main:app --port 8080    # FastAPI  (port 8080)

model_server.py and model_client.py are kept for reference but are no longer imported or executed.