Generated: 2026-08-24 · Branch: backend · Scope: web/ (Next.js app), supabase/ (Postgres schema), ingestion/ (offline pipeline)
SheraTutor is a bilingual (Bangla/English) AI study platform for Bangladeshi SSC/HSC students, currently scoped to NCTB Physics. Students upload handwritten answer scripts, an AI pipeline OCRs and grades them against official board rubrics, and the app turns the results into weakness tracking, personalized study plans, AI-generated practice papers, and a Socratic AI tutor chat — all delivered in a "খাতা" (exam-notebook) themed UI.
Stack: Next.js 16 (App Router, React 19) · Supabase (Postgres 17 + pgvector + pgmq + Auth + Storage) · Genkit 1.41 (Google's AI orchestration framework) · NVIDIA NIM (primary LLM/embedding provider, OpenAI-compatible) · Tailwind v4 + shadcn/ui (radix-nova) · TypeScript.
flowchart LR
subgraph Client["Browser"]
UI[Next.js App Router\nServer + Client Components]
end
subgraph Edge["Next.js Server (proxy.ts)"]
MW[Session refresh middleware]
SA[Server Actions]
API[API Routes]
end
subgraph AI["Genkit AI Layer (web/src/ai)"]
L1[Layer 1: transcribe.ts\nVLM OCR]
L2[Layer 2: retrieve-grounding.ts\nHybrid RAG]
L3[Layer 3+4: evaluate-rubric.ts\nRubric-grounded grading]
L5[generate-question-paper.ts]
L6[tutor-chat.ts\nSocratic tutor + safety filter]
end
subgraph Providers["External AI Providers"]
NIM[NVIDIA NIM\nLlama vision/reasoning + embeddings]
Fireworks["Fireworks (configured, unused by default)"]
Ollama["Ollama (local dev)"]
end
subgraph DB["Supabase Postgres"]
Tables[(23 tables\nRLS-protected)]
Queue[[pgmq: grading_queue]]
Vec[(pgvector HNSW\nchunk_embeddings)]
end
subgraph Worker["Background Grading"]
Cron[External scheduler] --> Internal[POST /api/internal/process-grading-queue\nsecret-header auth]
Internal --> GradeFlow[grade-submission.ts\nservice-role client]
end
UI --> MW --> SA & API
SA --> Tables
API --> Tables
API --> Queue
GradeFlow --> L1 & L2 & L3
L5 --> L2
L1 & L3 & L5 & L6 --> NIM
L2 --> Vec
Queue --> GradeFlow
Tables --> Vec
Four-layer grading pipeline (the architectural core of the product):
- Transcribe (
transcribe.ts) — VLM reads the handwritten page image, produces a verbatim transcript (explicitly forbidden from auto-correcting student errors), flags uncertain spans. - Retrieve grounding (
retrieve-grounding.ts) — embeds the question, hybrid-searches (pgvectorHNSW + Postgres full-text, combined via RRF) the curriculum chunk store scoped to the right chapter/language, pulls parent stimulus text for CQ sub-questions. - Evaluate against rubric (
evaluate-rubric.ts) — combines the official rubric JSON + grounded curriculum text + verbatim transcript (+ raw page images when available) and asks the LLM to score against the rubric, citing which rule each mark deduction comes from, and to flag if the photo contradicts the transcript. - Persist with provenance (
grade-submission.tsorchestrates 1–3 per question) — everygrading_resultsrow records model name/version, prompt version, rubric version, and pipeline version for auditability and offline eval.
Grading itself runs asynchronously via a Postgres queue (pgmq): submitting a script enqueues a job; a secret-header-protected internal route drains up to 5 jobs at a time with retry/backoff (MAX_ATTEMPTS=3) and idempotent re-processing.
23 tables, all with Row Level Security enabled. Extensions: pgvector (embeddings), pgmq (queue), pg_trgm/full-text search.
| Group | Tables |
|---|---|
| Identity & tenancy | institutions, profiles, student_profiles, teacher_profiles |
| Curriculum content | subjects, chapters, curriculum_versions, curriculum_chunks, chunk_embeddings, ingestion_jobs |
| Assessment content | rubrics, question_papers, questions |
| Student submissions & grading | exam_submissions, submission_pages, grading_results, grading_corrections |
| Progress & planning | weakness_logs, study_plans |
| Tutor | tutor_chat_sessions, tutor_chat_messages |
| Quality/eval | golden_set_items, golden_set_human_grades, golden_set_model_runs |
| Ops | audit_log, waitlist_signups |
profiles.role:STUDENT | TEACHER | INST_ADMIN | GOVT_ADMINinstitutions.type:COACHING | SCHOOL | GOVT_BOARD;subscription_tier:TRIAL | BASIC | PREMIUM | ENTERPRISEstudent_profiles: board (DHAKA…MADRASAH…TECHNICAL),exam_type(SSC|HSC),academic_group(SCIENCE|HUMANITIES|BUSINESS_STUDIES),overall_momentum_score, minor/guardian-consent fields (PDPA compliance),training_data_opt_incurriculum_chunks:chunk_type(theory|worked_example|cq_stimulus|cq_subquestion|table), self-referencingparent_chunk_id(sub-questions point back to their stimulus), generatedfts_doc tsvectorcolumn,official_rubric_rules jsonbchunk_embeddings:vectorcolumn +model_name/model_version(supports swapping embedding models without losing history)questions: supports bothMCQandCQ(question_type), CQ usesstimulus_bn/en+sub_questions_json, MCQ usesmcq_options_json/mcq_correct_optionexam_submissions.status:QUEUED → OCR_PROCESSING → EVALUATING → COMPLETED|FAILED;idempotency_key(unique) prevents duplicate submission processingsubmission_pages:question_id(nullable — per-page question mapping),ocr_uncertain_spans jsonb,student_flagged_mismatch(student can dispute OCR)grading_results: full provenance columns (model_name,model_version,prompt_version,pipeline_version,rubric_version_id, token/cost tracking),transcript_mismatch_detectedrubrics: versioned viaversion+superseded_byself-reference (never mutated in place, always superseded)tutor_chat_sessions.mode:rubric(tied to a specific graded question/step) orgeneral(open subject chat);tutor_chat_messages.safety_categoryfor self-harm pre-filter logginggolden_set_*: a human-graded benchmark set (transcription + scoring) used to evaluate model accuracy offline — currently empty (0 rows), infrastructure only
erDiagram
institutions ||--o{ teacher_profiles : employs
institutions ||--o{ question_papers : owns
institutions ||--o{ exam_submissions : scopes
institutions ||--o{ submission_pages : scopes
institutions ||--o{ grading_results : scopes
profiles ||--o| student_profiles : "is-a"
profiles ||--o| teacher_profiles : "is-a"
profiles ||--o{ rubrics : created_by
profiles ||--o{ question_papers : created_by
profiles ||--o{ grading_corrections : "corrected by"
profiles ||--o{ audit_log : actor
subjects ||--o{ chapters : has
subjects ||--o{ curriculum_versions : has
subjects ||--o{ question_papers : "for subject"
subjects ||--o{ ingestion_jobs : "sourced for"
chapters ||--o{ rubrics : "graded via"
chapters ||--o{ curriculum_chunks : contains
chapters ||--o{ questions : "belongs to"
chapters ||--o{ weakness_logs : "tracked per"
curriculum_versions ||--o{ curriculum_chunks : versions
curriculum_chunks ||--o{ chunk_embeddings : embedded_as
curriculum_chunks ||--o{ curriculum_chunks : "parent (stimulus)"
rubrics ||--o{ questions : scores
rubrics ||--o{ grading_results : "version used"
rubrics ||--o{ rubrics : superseded_by
question_papers ||--o{ questions : contains
question_papers ||--o{ exam_submissions : "attempted as"
questions ||--o{ submission_pages : "mapped to"
questions ||--o{ grading_results : scored
questions ||--o{ golden_set_items : benchmarked
questions ||--o{ tutor_chat_sessions : "discussed in"
student_profiles ||--o{ exam_submissions : submits
student_profiles ||--o{ weakness_logs : accumulates
student_profiles ||--o{ study_plans : follows
student_profiles ||--o{ tutor_chat_sessions : chats
exam_submissions ||--o{ submission_pages : "made of"
exam_submissions ||--o{ grading_results : produces
exam_submissions ||--o{ tutor_chat_sessions : "context for"
grading_results ||--o{ grading_corrections : "corrected by teacher"
golden_set_items ||--o{ golden_set_human_grades : "graded by examiner"
golden_set_items ||--o{ golden_set_model_runs : "scored by model"
tutor_chat_sessions ||--o{ tutor_chat_messages : contains
Chronological build-up: extensions → core (identity) → curriculum → exams → progress → RLS policies → hybrid retrieval RPC function → waitlist → storage buckets → provider pivot (Google GenAI → NIM/Fireworks, per genkit.ts comments) → golden set → embed-model default fix → curriculum enrichment (chunk types, section metadata) → tutor chat tables → student-writable study plans → submission↔question mapping → transcription-mismatch safeguard columns → grading queue (pgmq) → paper-generator RLS → grading-queue grants fix → grading/transcript-mismatch column → submission-pages write RLS → CQ schema update (stimulus/sub-questions/MCQ support).
This sequence tells a clear story: the schema started as a generic exam-grading platform and was iteratively hardened for real handwritten-answer grading at scale — provenance tracking, transcript-mismatch detection, async queueing, and CQ (Creative Question, the actual Bangladesh board format) support were all added after the initial core schema.
| File | Purpose | Notable logic |
|---|---|---|
auth.ts |
Email/password sign-up & sign-in, Google OAuth, sign-out | Uses useActionState form-state pattern; OAuth redirects through /auth/callback |
generate-paper.ts |
Generate an AI practice paper and persist it | Zod-validates input → calls generateQuestionPaperFlow → writes question_papers + one rubrics row + one questions row per generated question, building criteria_json from CQ sub-parts or a single MCQ rule → rolls back (deletes paper) if any insert fails |
onboarding.ts |
Complete student profile after signup | PDPA compliance gate: computes age from DOB; if under 18, guardian phone + acknowledged consent are hard-required before the profile can be created |
profile.ts |
Update student profile / opt-in preferences | Only stamps training_data_opt_in_at when the opt-in value actually changes |
study-plan.ts |
Generate/update a 14-day study plan | Deterministic, non-AI scheduler: pulls top 8 weakness_logs (or falls back to first 8 chapters), spreads each across a 14-day cycle with frequency 1–4 proportional to weakness score; togglePlanTask mutates a completed_tasks_json map |
waitlist.ts |
Public landing-page waitlist signup | BD phone regex validation; minor/guardian-consent enforced both here and via a DB check constraint (defense in depth); handles unique-phone conflicts gracefully |
genkit.ts— central config; registersnim(NVIDIA NIM, default, OpenAI-compatible),fireworks(configured but unused by default),ollama(local dev).MODELS = { vision, reasoning, fast }all default to NIM Llama models. Two custom embedders (ollamaEmbedder,nimEmbedder);nimEmbedder(1024-dim, Matryoshka-truncated) is the active one. ExportsPIPELINE_VERSION/PROMPT_VERSIONfor grading provenance.schemas/rubric.ts—RubricEvaluationSchemaforces the model to cite which rubric rule backs each deduction (cited_rubric_rule), emit agrounding_confidence(routes low-confidence grades to human review), and flagtranscript_mismatch_detected.schemas/transcription.ts—TranscriptionSchemacaptures verbatim text, LaTeX equations, diagram descriptions, detected language, confidence, and uncertain spans — explicitly designed to stop the VLM from silently "fixing" student mistakes.flows/transcribe.ts— Layer 1 OCR; vision model prompt explicitly forbids auto-correcting arithmetic/unit errors.flows/retrieve-grounding.ts— Layer 2 RAG; hybrid dense+FTS search via Postgres RPCmatch_curriculum_chunks; resolves parent stimulus for CQ sub-questions; returnsgroundingConfidence.flows/evaluate-rubric.ts— Layers 3+4; combines rubric + grounding + transcript (+ page images when question-region mapping exists); uses vision model when images are supplied (to cross-check photo vs transcript), else reasoning model; temperature 0.2.flows/generate-question-paper.ts— Builds a large hardcoded NCTB-Physics-specific Bangla prompt; question counts derived from paper type/marks (CQ = marks/10 questions × 4 sub-parts ক/খ/গ/ঘ; MCQ = 5–25 one-mark items; MIXED = 70/30 split). Bypasses Genkit'sai.generate, calls the raw OpenAI SDK against NIM directly withresponse_format: json_object, then manually repairs/parses the JSON before Zod validation.flows/grade-submission.ts— Orchestrates all four layers for one submission, using the service-role client (background job only, idempotent — aCOMPLETEDsubmission is a safe no-op on redelivery); builds per-question transcripts from mapped pages.flows/tutor-chat.ts—preFilterSafety(): regex-based self-harm pre-filter (English + Bangla patterns) that short-circuits to a fixed Bangla safe-escalation message referencing the Kaan Pete Roi helpline, explicitly documented as "a floor, not a ceiling" (no real moderation API);buildTutorPrompt()forrubricvsgeneralmodes, enforces Bangla-only replies with LaTeX math delimiters, no opening greetings; post-processing normalizes LaTeX delimiters and strips greetings the model didn't obey.
| Route | Method | Auth | Purpose |
|---|---|---|---|
/api/tutor-chat |
POST | Session + student profile | 50 messages/day rate limit (Asia/Dhaka midnight); resolves/creates chat session (rubric sessions keyed by submission+question+step; general sessions per chapter); safety pre-filter with audit logging; SSE streaming or JSON response |
/api/tutor-chat/sessions |
GET | Session | Lists general sessions, or looks up one rubric session by submission/question/step |
/api/tutor-chat/sessions/[sessionId] |
GET | Session (RLS-scoped) | Returns session + ordered messages |
/api/submissions |
POST | Session + student profile | Idempotent submission creation (dedupe by idempotencyKey); inserts exam_submissions + submission_pages; enqueues grading via enqueue_grading_job RPC (pgmq wrapper) |
/api/submissions/[id]/pages/[pageId]/flag |
POST | Session (RLS-scoped) | Student flags "this isn't what I wrote" on a page; logs audit_log entry |
/api/internal/process-grading-queue |
POST | x-worker-secret header |
Drains up to 5 pgmq jobs (120s visibility), runs gradeSubmissionFlow, retries up to 3 attempts before marking FAILED |
/auth/callback |
GET | — | OAuth/email code exchange; redirects to /dashboard or /onboarding depending on profile existence |
SSR-cookie-based auth (@supabase/ssr). proxy.ts (Next 16's renamed middleware.ts) refreshes the session cookie on every non-static route via updateSession. Server Components/Route Handlers each call supabase.auth.getUser() and redirect/401 if absent. RLS is the primary authorization boundary; a separate service-role client (bypasses RLS) is reserved strictly for background grading jobs, the internal queue worker, and cross-tenant audit logging.
| Route | Rendering | What it does |
|---|---|---|
/ |
Client | Landing page: live student-count social proof, hero, waitlist form, "how it works," value props |
/login, /signup |
Client | Email/password + Google OAuth forms (still on legacy inline-style CSS, not yet migrated to shadcn) |
/onboarding |
Client | Board/exam-type/group/target-year form; conditionally shows guardian-consent block for minors |
/dashboard |
Server | Aggregates profile, submissions, subjects, weakness logs, active study plan; computes average score, letter grade, momentum/readiness score, percentile estimate, today's tasks |
/dashboard/achievements |
Server | XP/level/badge gamification computed from real submission data (with fallback placeholders) |
/dashboard/board-simulator |
Server | Renders one question paper in full exam-simulation mode |
/dashboard/mistake-analysis |
Server | Weakness logs ranked by score, recoverable-marks estimate |
/dashboard/practice + /practice/generate + /practice/[id] |
Server | Practice paper list, AI paper-generation form, single paper viewer (KaTeX-rendered, printable) |
/dashboard/profile |
Server | Editable profile/preferences settings |
/dashboard/study-plan |
Server | Active 14-day plan + weakness-based recommendations (currently assumes "today" = day 1, not yet offset from start_date) |
/dashboard/submissions + /submissions/[id] |
Server | Submission history list + detailed per-question rubric breakdown |
/dashboard/tutor |
Server | Subject/chapter picker + full tutor chat session UI |
/dashboard/upload |
Server | Answer-sheet page upload flow with per-page question mapping |
- Page-body client components (
components/pages/*.tsx) — one per dashboard route, separating server data-fetching from client interactivity (DashboardPageClient,AchievementsPageClient,ExamsPageClient(shared by practice + simulator),MistakesPageClient,PlannerPageClient,QuestionPaperViewerClient,SettingsPageClient,SubmissionDetailClient,SubmissionsPageClient). Header.tsx— profile menu, Supabase Realtime subscription onexam_submissionsupdates for grading-complete notifications, language/theme toggles.Sidebar.tsx— main nav (desktop fixed + mobile drawer);dashboard-nav.tsxappears to be an unused/legacy duplicate.tutor-chat-panel.tsx— shared chat UI (markdown + KaTeX rendering viareact-markdown/remark-math/rehype-katex), reused by both the standalone tutor page and the inline "explain simply" sheet.explain-simply-button.tsx— opens a per-rubric-step tutor chat with quick-reply chips ("explain simply," "real-life example," "where was my mistake").upload-form.tsx,waitlist-form.tsx,khata-preview.tsx(decorative landing illustration),ScoreRing.tsx,BarChart.tsx(hand-rolled SVG charts),page-transcription-card.tsx(OCR confidence badge + dispute button).- Theming duplication:
theme-provider.tsx/theme-toggle.tsxwrapnext-themes, but a separate customThemeContext.tsx(manual localStorage +.darkclass toggling) is whatClientShell/Header/SettingsPageClientactually use — two parallel theme mechanisms coexist.
LanguageContext.tsx— bn/en toggle, SSR-safe (defaults to'bn', hydrates fromlocalStoragepost-mount),t(key, fallback)lookup againstdata/translations.ts.ThemeContext.tsx— independent dark/light implementation (see duplication note above).
Tailwind v4 (@import "tailwindcss") + shadcn/ui (components.json: style radix-nova, base color neutral, icons lucide) + tw-animate-css. globals.css defines a "খাতা" (exam-notebook) design system — light theme is ruled paper, dark theme is blackboard/chalk, bottle-green primary, with "examiner red" reserved narrowly for marks/critical-gap indicators. Layered on top of an older plain-CSS/inline-style layer (styles.css, pages.css, layout-fixes.css) still visible in /login, /signup, and parts of the landing page — a visible seam from an earlier design iteration that hasn't been fully migrated.
types/index.ts— shared UI types (NavItem,SubjectProgress,AchievementItem,TaskItem,MistakePattern).data/translations.ts— ~450 lines of bn/en copy keyed by dotted namespace, the single source for all UI text.data/mockData.ts— static placeholder data, largely superseded by live Supabase queries but still used as fallback/demo content in a few components.
- Bilingual landing page & waitlist — public marketing page with live signup counter and a guardian-consent-aware waitlist form (minors require acknowledged parental consent, enforced both client- and DB-side).
- Auth & onboarding — email/password + Google sign-in; onboarding collects board/exam type/subject group/target year, and legally gates minors behind guardian consent before a profile is created.
- Answer-sheet upload & AI grading — students photograph/upload handwritten answer pages, optionally mapping pages to specific questions; submissions are queued and graded asynchronously through a four-layer AI pipeline (verbatim OCR → curriculum-grounded retrieval → rubric-cited scoring → provenance-tracked persistence), with idempotent retry and a student-facing "this isn't what I wrote" dispute mechanism for OCR errors.
- Rubric-based scoring with citations — every mark deduction is traceable to a specific official rubric rule and a specific retrieved curriculum passage, with a confidence score that can route uncertain grades to human review, and automatic detection of transcript/photo mismatches.
- AI practice-paper generation — students pick chapters/paper type/difficulty/marks and get a freshly generated NCTB-format paper (CQ with ক/খ/গ/ঘ sub-parts, MCQ, or mixed), auto-paired with a matching rubric for later grading.
- Board simulator — full exam-mode presentation of a question paper (KaTeX-rendered, printable) for realistic timed practice.
- AI Socratic tutor chat — bilingual (Bangla-enforced) chat available both as a standalone page and inline per rubric-deduction ("explain simply," "real-life example," "where was my mistake"); rate-limited to 50 messages/day; includes a hardcoded self-harm safety pre-filter that redirects to a crisis helpline before any AI response.
- Weakness tracking & study planning — per-chapter weakness scores accrue from grading results; a deterministic (non-AI) algorithm turns the top weaknesses into a 14-day study schedule with completable daily tasks.
- Progress dashboard — average score, letter grade, momentum/readiness score, percentile estimate, and "today's tasks" summary.
- Mistake analysis — surfaces recurring weak chapters and estimates recoverable marks.
- Gamification — XP, levels, and unlockable badges computed from real submission history.
- Submission history & detail — per-submission rubric breakdown by criterion, OCR confidence indicators per page.
- Multi-tenant/role foundation (not yet exposed in UI) — schema supports institutions, teacher accounts, and teacher grade-correction overrides (
grading_corrections), suggesting a planned B2B/coaching-center rollout beyond the current B2C student flow. - Offline evaluation infrastructure — a "golden set" of human-graded exam scripts (schema present, currently empty) for measuring model transcription/grading accuracy against ground truth, plus a
scripts/eval-golden-set.tsCLI. - Curriculum ingestion pipeline (
ingestion/, Python, separate from the web app) — OCRs and chunks official NCTB textbook PDFs (Physics, Chemistry, Math, English, in both Bangla and English editions) into thecurriculum_chunks/chunk_embeddingstables that ground both grading and tutoring.
- Provider migration in progress: the codebase pivoted away from Google GenAI to NVIDIA NIM (free tier) as the default LLM/embedding provider, with Fireworks configured as a paid fallback not yet wired as default — visible in extensive comments in
genkit.ts. - Two parallel theming systems (
next-themes-basedtheme-provider.tsxvs. the actually-used customThemeContext.tsx) and two CSS eras (legacy plain CSS on auth/landing pages vs. Tailwind v4/shadcn on the dashboard) indicate an in-progress UI migration rather than a finished design system. - Compliance-first data model: PDPA-oriented guardian-consent fields, training-data opt-in tracking, and full grading provenance (model/prompt/rubric/pipeline versions on every score) suggest the team is building toward regulatory and academic-integrity scrutiny, not just an MVP demo.
- Reliability patterns already in place: idempotency keys on submissions, a retry-capped async queue with dead-lettering to
FAILED, and audit logging for safety-sensitive and dispute-relevant actions — more production-hardened than a typical hackathon-stage app. - Known gaps flagged in the code itself: study-plan "today" is hardcoded to day 1 instead of computing a real offset from
start_date; the tutor's safety pre-filter is explicitly documented as "a floor, not a ceiling" (regex-only, no real moderation API); the golden-set evaluation tables exist but are unpopulated.