Back to Projects
apps
Next.jsFastAPISQLAlchemyPydanticPostgreSQLSQLiteLLMsDockeruv
ExamPrep
Turn course materials into grounded practice: MCQ, short answer, and structured items—with timed exams, safe in-exam payloads, and rubric-aware grading behind a FastAPI + Next.js stack.
Case Study
Problem
Students and self-learners often practice with generic question banks that don’t match their actual syllabus, or they spend hours turning notes into quiz items by hand. At the same time, AI-generated practice can feel repetitive (same idea, different wording) or ungrounded if it isn’t tied to the materials they uploaded.
This project asks: Can we turn your course materials into varied, trustworthy practice—MCQ, short answer, and structured items—with timed exams, immediate feedback, and grading that respects what each question is testing?
Approach
Product flow
Users create courses, upload PDF / DOCX / TXT, and generate question banks with control over composition (mixed, MCQ-only, scenario-style, etc.) and difficulty. They take timed exams tied to a document or a specific bank; the API returns sanitized exam payloads (no leaked answers mid-exam), then graded results after submit.
Technical architecture
FastAPI backend with clear separation between HTTP layer and services (documents, chunking, generation, exams, grading, analytics). SQLAlchemy + Pydantic for persistence and validation; designed so SQLite works locally and PostgreSQL drops in via configuration for deployment. LLM abstraction so generation and rubric-style grading can swap providers (including a mock path for local/CI work). Frontend (Next.js) talks to the API via a single configurable base URL for local vs production.
Quality & deduplication
To avoid “same question, new punctuation,” generation and exam assembly use a hybrid dedupe strategy: lexical overlap plus semantic similarity, with an extra cross-check on borderline pairs—so banks and exam draws stay diverse without manually auditing every stem.
Operations
Backend packaged with uv and lockfiles; containerized API for cloud deploys; environment-driven CORS, auth, and limits so the same codebase can run dev → staging → prod.
Results
• End-to-end workflow from upload → generated banks → exam session → results and explanations.
• Objective grading (e.g. MCQ) with structured feedback for written items via rubric-aware logic.
• “Learn more”-style depth on results so practice doubles as study time, not only a score.
• Deduped question generation and exam sampling so repeated practice feels like coverage, not repetition.
• Deployable stack: API suitable for Render (or similar), SPA with environment-based API URL for production.
Outcome-focused: capabilities shipped and reliability goals (grounded generation, safe exam payloads, consistent deploys)—no usage metrics claimed where they aren’t available yet.
Learnings
Grounding beats glitter: The product only feels useful if questions and feedback stay anchored to uploaded content; the LLM layer has to be constrained by excerpts and schemas, not left to freewheel.
Dedupe is a product feature, not an ML detail—users feel “repetition” immediately; combining fast lexical checks with semantic similarity (and borderline review) was the right tradeoff for quality vs latency/cost.
Exam UX is security + pedagogy: Stripping sensitive fields from live exam responses and delivering rich feedback after grading requires deliberate API design, not a single “return everything” payload.
Monorepo deploys teach painful lessons: Same app, two origins (SPA vs API) means CORS, NEXT_PUBLIC_* URLs, and secrets must be explicit in every environment—defaults that work on localhost break silently in production.
Lockfiles and containers reduced “works on my machine” friction when moving from local uv to hosted Postgres and Docker builds.