An AI-assisted grading workbench for open-ended exam questions. It combines a Gemini-powered rubric grader with calibration, evaluation, and mistake-analysis tooling, exposed through three surfaces:
- Python CLI — batch grading and evaluation over benchmark datasets.
- FastAPI backend — HTTP API consumed by the frontend and by scripts.
- React (Vite) frontend — end-to-end UI: question intake → rubric generation/revision → submission grading → evaluation.
app/ FastAPI backend (routes, schemas, services)
src/grading_tool/ Core library
grading/ Orchestrator, prompt builder, rubric grader/generator/reviser,
response parser, mistake analyzer, survey reviewer,
question-type router
evaluation/ Agreement, metrics, calibration, error analysis, reports
models/ Gemini client wrapper
schemas/ Pydantic schemas for benchmark + results
cli/ `grade` and `evaluate` entry points
configs/ base.yaml, prompts.yaml, scoring.yaml
data/
benchmarks/ cs302 midterm1, midterm2, final (fall 2025), synthesis, econ
outputs/ runs/, reports/, calibration/
frontend/ React 19 + Vite + TypeScript UI
scripts/ run_calibration.py and helper data scripts
tests/ pytest suite + reference run/eval fixtures
docs/ API.md, DATA_FORMATS.md, DEVELOPMENT.MD,
GETTING_STARTED.MD, ARCHITECTURE_DIAGRAM.md, REPO_NOTES.md
More details in FOLDER_STRUCTURE.md
python -m venv .venv
source .venv/bin/activate
pip install -e .pyproject.toml already pins FastAPI, Uvicorn, Pydantic, pandas, PyYAML, tqdm, pytest, python-dotenv, streamlit, and google-generativeai — no extra install needed for the CLI or API.
Create .env at the repo root:
API_KEY=your_key_here
MODEL=your_chosen_model_here.env is loaded automatically via python-dotenv.
Grade a benchmark:
python -m src.grading_tool.cli.grade \
--benchmark_dir data/benchmarks/cs302_final_fall2025 \
--output_path data/outputs/runs/final_prompt_v3.json \
--run_name final_prompt_v3 \
--prompt_name prompt_v3Evaluate the run against professor grades:
python -m src.grading_tool.cli.evaluate \
--run_path data/outputs/runs/final_prompt_v3.json \
--professor_grade_path data/benchmarks/cs302_final_fall2025/final_professor_grade.json \
--output_path data/outputs/reports/final_prompt_v3_eval.jsonUseful grading flags:
--question_ids q7 q8— restrict to specific questions--limit_students N/--limit_questions N— smoke-test slices--model_name gemini-2.5-pro— override the Gemini model--prompt_name prompt_v1|prompt_v2|prompt_v3— pick a strategy fromconfigs/prompts.yaml--manifest_path …/benchmark_manifest.json— non-default manifest location
scripts/run_calibration.py runs the iterative grade → diagnose → revise-rubric loop without booting the API:
python scripts/run_calibration.py \
--benchmark data/benchmarks/cs302_midterm1_fall2025 \
--question_id q3 \
--max_rounds 5 \
--output data/outputs/calibration/q3.jsonuvicorn app.main:app --reload --port 8000Implemented endpoints (see app/routes/):
Health / metadata
GET /api/healthGET /api/runsGET /api/evaluation/health
Grading
POST /api/grade— score a single answerPOST /api/grade-batch— score many answersPOST /api/survey-submissions— bulk survey/review passPOST /api/mistake-stats— aggregate mistake patternsPOST /api/generate-rubric— draft a rubric from a question + reference solutionPOST /api/revise-rubric— apply manual rubric editsPOST /api/revise-rubric-llm— let the LLM revise a rubric from feedback
Evaluation
POST /api/evaluation/run— compare AI grades to professor ground truthPOST /api/evaluation/calibrate— multi-round rubric calibration loop
The backend wires into the same src/grading_tool core, so API and CLI share the rubric grader, prompt builder, and evaluation metrics.
CORS allows local Vite (5173, 5174, 4173), LAN/private IP ranges on those ports, and the deployed Vercel frontends (grading-tool-beige.vercel.app, grading-tool-ruby.vercel.app).
cd frontend
npm install
npm run devStack: React 19, React Router 7, TypeScript, Vite 8, with jspdf + jspdf-autotable for report export.
Point the UI at a backend with:
VITE_API_BASE_URL=http://localhost:8000Pages (frontend/src/pages/):
HomePage.tsx— landing + demo data loaderQuestionIntakePage.tsx— paste/upload questions, generate and refine rubricsSubmissionGradingPage.tsx— run grading on student submissionsEvaluationPage.tsx— compare against ground truth, view calibration results
State is persisted in localStorage (saved questions, rubrics, grading results) so the three pages compose into one workflow.
configs/prompts.yaml defines the grading prompt strategies:
prompt_v1— strict, rubric-onlyprompt_v2— balanced, reference-awareprompt_v3— current default for the CS302 benchmarks
configs/base.yaml and configs/scoring.yaml hold pipeline defaults and scoring thresholds.
data/benchmarks/ contains the canonical exam sets used during development:
cs302_midterm1_fall2025cs302_midterm2_fall2025cs302_final_fall2025synthesis— small synthetic benchmark used in tests- Economics questions under
data/econ/
Per-benchmark layout, professor grade format, and run/report schemas are documented in docs/DATA_FORMATS.md.
Grading runs are written to data/outputs/runs/, evaluation reports to data/outputs/reports/, and calibration artifacts to data/outputs/calibration/. These folders will be created once the program finished running.
pytestCovers loader behavior (test_loader.py), aggregation (test_aggregation.py), evaluation metrics (test_evaluation_metrics.py), and the rubric grader response schema (test_rubric_grader_schema.py). Fixture runs/reports live alongside the tests.
docs/GETTING_STARTED.MD: end-to-end first rundocs/API.md: request/response shapes for every endpointdocs/DATA_FORMATS.md: benchmark, run, and report schemasdocs/DEVELOPMENT.MD: local dev workflowdocs/ARCHITECTURE_DIAGRAM.md: component relationshipsdocs/CALIBRATION.md: calibration logic design
