An AI-powered grading portal that turns scanned exam papers into rubric-scored, plagiarism-checked results — with a human in the loop.
Vision-LLM OCR pulls handwritten answers off PDFs, a LangGraph agent scores them against your rubric with Gemini, semantic similarity flags suspicious overlap, and anything below the auto-approve threshold lands in a TA review queue. A React dashboard ties it all together.
PDF scans ──▶ OCR (Qwen2-VL) ──▶ LangGraph agent ──▶ Review router ──▶ TA / auto-approved
│
├─ grade (Gemini 2.0 Flash, per-criterion)
├─ confidence (heuristic signals)
└─ plagiarism (bge-small embeddings)
The OCR pipeline does adaptive preprocessing and a two-pass spatial layout detection — it asks the vision model for answer-region bounding boxes first, so multi-question pages get cropped per question instead of being read as one blob.
The grading agent runs as a LangGraph state machine: validate → grade → confidence → plagiarism → route → result. Each criterion gets its own awarded score and justification. The router sorts results into three tiers:
| Tier | Confidence | Goes to |
|---|---|---|
auto_approved |
≥ 0.85 | Skipped — accepted as-is |
normal |
0.60 – 0.85 | TA review queue |
priority |
< 0.60, or plagiarism flag, or score 0 | Top of the TA queue |
- OCR — Qwen2-VL with adaptive preprocessing, auto-detect multi-question pages, batched SSE streaming
- AI grading — Gemini 2.0 Flash with per-criterion scores and natural-language justifications
- Plagiarism —
bge-small-en-v1.5cosine similarity, two severity levels (suspicious,high) - Cohort analytics — DBSCAN clustering surfaces consensus answers and outliers per question
- Review workflow — priority-sorted TA queue, single-key approve / override, full audit trail
- Live progress — Server-Sent Events for both batch OCR and batch grading
Backend FastAPI · LangGraph · Gemini 2.0 Flash · Qwen2-VL · sentence-transformers · scikit-learn · SQLite Frontend React 19 · Vite · Tailwind v4 · React Router
Prerequisites: Python 3.12, Node 18+, a Google API key with Gemini access. RAM: 8 GB if you use the 2B OCR model, 32 GB+ for the default 7B.
# Backend
cd backend
cp .env.example .env # set GOOGLE_API_KEY, QWEN_MODEL_SIZE=2b for laptops
pip install -r ../requirements.txt
python run_api_server.py # http://localhost:8000 (docs at /docs)
# Frontend (new terminal)
cd frontend
npm install
npm run dev # http://localhost:3000The OCR model loads in the background, so the server is reachable immediately — GET /health reports ocr_qwen_vl: not_loaded until it's ready.
GradeOPS/
├── backend/
│ └── gradeops/
│ ├── agents/ LangGraph nodes, prompts, state
│ ├── analytics/ DBSCAN cohort clustering
│ ├── api/ FastAPI routes
│ ├── batch/ Bulk processing + CSV/Excel export
│ ├── grading/ Rubric engine, schemas, confidence calculator
│ ├── llm/ Gemini client + grader adapter
│ ├── ocr/ Qwen-VL model, preprocessor, pipeline
│ ├── plagiarism/ Embedding cache + detector
│ └── storage/ SQLite repository
├── frontend/src/pages/ Dashboard · RubricBuilder · GradingStage · PlagiarismHub
└── frontend_design/ Original Stitch design references
backend/.env:
| Variable | Default | Notes |
|---|---|---|
GOOGLE_API_KEY |
— | Required for grading |
QWEN_MODEL_SIZE |
7b |
Use 2b on laptops |
GRADEOPS_DB |
gradeops.db |
SQLite file path |
GRADEOPS_MEDIA_DIR |
media/ |
Where extracted answer crops are saved |
FRONTEND_URL |
http://localhost:3000 |
CORS origin |
Full interactive docs at http://localhost:8000/docs. The endpoints you'll use most:
| Method | Endpoint | Purpose |
|---|---|---|
POST |
/api/grade |
Grade one submission |
POST |
/api/grade/batch/stream/start |
Start a streaming batch — SSE on /api/grade/batch/stream/{job_id} |
POST |
/api/ocr/extract/batch/start |
Start streaming OCR — SSE on /api/ocr/jobs/{id}/stream |
GET |
/api/review/queue |
TA queue, priority-sorted |
POST |
/api/review/{result_id}/decision |
Approve or override |
POST |
/api/analytics/cohort |
Cluster answers for a question |
GET |
/api/dashboard/stats |
Aggregate counts for the home page |
Rubric CRUD lives at /api/rubrics; plagiarism at /api/plagiarism/check and /api/plagiarism/exam/{exam_id}.
| Route | What it does |
|---|---|
/dashboard |
Bulk upload, exam stats, project list, activity feed |
/rubric |
Build a rubric with a live Gemini-graded preview |
/grading |
Split-pane TA review — Enter approve · O override · F flag |
/plagiarism |
Similarity clusters + side-by-side comparison |
cd backend && python run_all_tests.py # or: pytest tests/Suites cover the grading agent, confidence calculator, plagiarism detector, batch processor, LangGraph orchestration, API endpoints, and the OCR → grading integration path.
Done — OCR pipeline · LangGraph grading agent · three-tier routing · semantic plagiarism · cohort clustering · SQLite persistence · streaming batch jobs · TA review workflow · all four frontend pages.
Not yet — Authentication / multi-user · Docker / production deployment · email notifications · Postgres backend · frontend integration tests.
The OCR model is the only heavy component. Pick 2b in .env if your machine has under ~32 GB RAM — the 7B model will OOM.
| Model | CPU-only RAM | GPU VRAM |
|---|---|---|
Qwen2-VL-7B-Instruct (default) |
32 GB | 16 GB |
Qwen2-VL-2B-Instruct |
8 GB | 6 GB |