Module · Exams & Marks
Schedule exams, enter marks per subject, generate report cards. The most domain-specific module — get the data model right and the UI follows.
Exams are the module parents care about most. Get the data model right and the rest is straightforward. Plan ~10 hours.
By the end:
- Branch admin schedules an exam (Half-Yearly, Annual, Unit Test) for a grade
- Each exam has multiple subject papers with max marks and a date
- Teachers enter marks for their assigned section's students
- Parents see report cards per exam
- A PDF download for printable report cards (optional stretch)
1. The Three Tables
The exams.status field is important: parents should not see marks until the school officially publishes them. The select policy on exam_marks for parents must check exams.status = 'published'.
2. Schedule an Exam
/admin/exams/new. Branch admin fills in:
- Exam name (e.g., "Half-Yearly 2026")
- Grade (drop-down: all grades that have students)
- Start date, end date
- Then a repeating subject row block: subject, date, max marks, passing marks
The form is one Zod schema with a nested array:
React Hook Form's useFieldArray handles the repeating subject rows. Save creates one exams row and N exam_subjects rows in a single transaction.
For atomicity, use a Postgres RPC:
Client:
3. Enter Marks (Teacher View)
/teacher/exams/[examSubjectId]/marks. Shows a list of students with a marks input next to each name.
The pattern is the same as attendance marking — local state, batch upsert. But two extras:
- Inline validation: if marks > max_marks, red border + "Cannot exceed 100" message.
- Live pass/fail indicator: row turns red if marks < passing_marks. Teacher sees who's failing as they type.
- "Absent" checkbox clears the marks field and marks
absent = true.
marks_obtained is numeric(5, 2) — supports 99.50 and similar decimals. Use Number(value).toFixed(2) server-side to avoid floating point junk.
4. Report Card (Parent View)
/parent/[childId]/report-card/[examId]. Reads from a view:
Set RLS on the view to match the underlying exam_marks policy. Parents only see rows where exams.status = 'published'.
The UI renders the rows as a table with totals, percentage, and overall result.
5. Publishing Marks
A branch admin can transition exams.status from results_pending → published. Only when published do parents see them.
/admin/exams/[id]/publish shows:
- Summary: 23 of 26 students have full marks entered, 3 missing
- "Publish results" button (disabled if anyone is missing marks)
- After publish: send a circular to all parents (next module) automatically? Optional.
6. PDF Report Card (Stretch)
If you want printable report cards:
- Use
@react-pdf/renderer(works in Next.js) - Build a
<ReportCardPDF student={...} marks={...} />component - Render it server-side, return as a download
This is a half-day's work — entirely skip for v1.
7. What to Verify
- Branch admin creates an exam with 5 subjects → one
examsrow + 5exam_subjectsrows visible - Teacher enters marks for 26 students → all saved correctly
- Parent cannot see marks until
exams.status = 'published' - After publish, parent sees the report card; another parent does NOT see their child's
- Super admin sees all exams across all branches; branch admin sees only own branch
- Re-saving marks (correcting a typo) works idempotently
What's Next
You now have attendance and exams. Next module: Payments — by far the most security-sensitive. Razorpay integration, webhook verification, receipts. Get this right.