Model
GradeCategory (weight, drop_lowest)
└── GradeItem (activity — quiz, assignment, manual; optional lesson link, dueDate, isPublished)
└── Grade (per-student grade)
└── Rubric (optional — criteria)
Calculation (CSV export)
The CSV export computes the weighted final grade as:
courseGrade = Σ (categoryAvg × categoryWeight) / Σ weightsUsed
categoryAvg = average(itemPercents after dropping the category's drop_lowest)
itemPercent = studentScore / itemMaxScore × 100
Items without a category are weighted by their own GradeItem.weight. Only published items count.
Roadmap: automatic late penalty based on
submittedAt > dueDateand scheduled date-based release are not yet enforced.dueDateis stored and shown;isPublishedcontrols visibility.
Integrity flags
QuizAttempt persists tabSwitchCount, copyPasteCount, blurCount, total timeSpentSec, IP and user-agent. A 🛡️ ShieldAlert appears next to a quiz grade in the gradebook when the attempt is flagged — e.g. tab switches over the configured maximum, time over the limit, or copy/paste attempts while copy/paste is blocked. Average time per question is derived from timeSpentSec ÷ question count (not stored as a column). Tooltip shows the flag reason.
Export
GET /api/institution/gradebook/[courseId]/export returns CSV with student, email, each GradeItem as a column, and the weighted average. Compatible with Google Sheets, Excel.