Upload a question paper and an answer sheet. Get back a graded score, a per-question breakdown, skill tags, five analytics charts, and an AI-written feedback memo. End to end, about 90 seconds for a 10-question paper.
The format does not matter. PDFs, Word docs, photos of handwritten pages, plain text, any combination. The system OCRs what it needs to OCR, parses what it can parse, and feeds everything to GPT-4o vision to do both the reading and the marking in one pass.
The interesting problem here is not "can an LLM grade a paper". It's can an LLM grade a paper reliably enough that the feedback is actually useful? That comes down to three things:
- Constraining the model's output to a strict JSON schema so the feedback is structured, comparable, and chartable
- Sending images directly to a multimodal model instead of OCR'ing first and stacking error sources
- Wrapping every API call in retries with exponential backoff so transient failures stop looking like app crashes
That's the whole engineering problem. The rest is plumbing.
That's it. Supported file types:
|
|
Most "AI grades your paper" projects break the moment you hand them a real student's handwriting or a question with a diagram. Here's why this one doesn't:
|
Instead of running OCR first and then sending text to GPT, this sends the raw image directly to GPT-4o vision. The model sees the diagram, the handwriting, the equations, all of it at once. The grading prompt is locked to a strict JSON schema. Every skill tag must be evidenced by the question or answer. No invented categories. Max 3 skills per question. The model is being graded on its grading. |
Every OpenAI call goes through a def _retry(fn, *, attempts=4,
base_delay=1.0, max_delay=6.0):
# exponential backoff with jitter
# retries on httpx errors, timeouts,
# rate limits, transient 5xx
...Five percent failure rate becomes effectively zero. |
The most useful chart is not the percentage. It's the skill mastery bar.
Every question gets tagged with the skills it tested. Kinematics. Free-body diagrams. Unit conversion. Stoichiometry. The chart aggregates marks across all questions in each skill bucket, so you see your mastery percentage per skill across the whole paper.
Patterns emerge that the percentage hides. A student scoring 75% overall might be sitting at:
90% mastery on Concept Application 40% mastery on Unit Conversion
The mistakes aren't physics mistakes. They're arithmetic mistakes at the last step. The fix isn't "study more physics", it's "slow down on the last step."
You cannot see that from a single percentage at the top of a paper. You need the skill breakdown. That's what this chart exists for.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser UI β
β (Jinja templates, vanilla JS, drag-drop upload) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Flask app (app.py) β
β β
β βββββββββββββββ ββββββββββββββββ ββββββββββββββββββ β
β β File Intake ββββΆβ OCR + Parse ββββΆβ Multimodal β β
β β PDF/DOCX/ β β PyPDF2, β β Grading via β β
β β IMG/TXT β β python-docx, β β GPT-4o Vision β β
β βββββββββββββββ β Pillow β ββββββββββ¬ββββββββ β
β ββββββββββββββββ β β
β βΌ β
β ββββββββββββββββββββββββββββ β
β β Strict JSON schema β β
β β + retry with backoff β β
β ββββββββββββ¬ββββββββββββββββ β
β βΌ β
β βββββββββββββββββββ βββββββββββββββββββ β
β β Matplotlib β β SQLite β β
β β 5 charts (Agg) β β users + β β
β β β β attempt history β β
β βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Layer | What it is | Why this and not something else |
|---|---|---|
| Flask | Web framework | Tiny, no magic, perfect for a 1-file app |
| SQLite | User accounts + every attempt ever submitted | Zero setup, persists between sessions, file-based |
| GPT-4o (vision) | Does both the OCR AND the grading | One model = one source of error, not two |
| PyPDF2 + python-docx | Parses PDFs and Word docs | The boring infra that lets "any format" actually mean any format |
| Pillow | Preprocesses images before OCR (downscale β grayscale β contrast) | Cuts payload size and improves OCR accuracy in one step |
| Matplotlib (Agg) | Renders charts headlessly | Server-side rendering, no browser needed |
| Werkzeug | Password hashing + session cookies | Boring, secure, ships with Flask |
| Render | Hosting | Free tier, supports background workers, just works |
|
|
|
git clone https://github.com/RicKanjilal/testwallah.in.git
cd testwallah.in
pip install -r requirements.txtYou'll need three environment variables:
export OPENAI_API_KEY="sk-..."
export OPENAI_PROJECT_ID="proj_..."
export APP_SECRET="any-random-string-for-sessions"Then:
python app.pyOpens on http://localhost:8000. SQLite database is created on first run. Sign up with any email, upload a paper, get graded.
LLM grading is shockingly good when you constrain the output format. The difference between "grade this paper" and "grade this paper and return strictly this JSON schema with these exact keys and these exact constraints" is the difference between unusable and production-ready.
Multimodal models change everything for OCR-heavy projects. I was originally going to run Tesseract for OCR, then send the text to GPT-4o for grading. Two error sources stacked. Just sending the image directly to GPT-4o cuts Tesseract entirely and the model handles diagrams + handwriting in one shot.
Retries matter more than you think. The OpenAI API fails maybe 5% of the time on long requests. A single failure in production looks like the whole app is broken. Wrapping every LLM call in _retry() with exponential backoff turned a flaky app into a reliable one for the cost of about 30 lines of code.
Most of the work in any "AI app" is the boring parts. Auth, sessions, DB schema, file uploads, error handling, deploying to Render. The GPT-4o call is 5% of the codebase. The other 95% is making sure people can actually use it without it crashing.
- Compare two attempts side by side ("am I getting better at unit conversion?")
- Weak-areas dashboard aggregating skill mastery across all attempts
- Export full report as a single printable PDF
- Teacher account type that pulls all attempts from a class
- Batch grading. Drop in 30 papers, get 30 reports
- Offline mode using a local LLM so it works without an API key
AGPL-3.0. If you fork this and run it as a service, you have to share your modified source code.
I picked this license on purpose. I do not want someone wrapping this in a paywall and charging students for it without contributing back.
Built by Ric Kanjilal Β· Grade 10 Β· Don Bosco School, Liluah Β· Kolkata