Grade Calculation Engine
This document explains the exact algorithms IC Insight uses to calculate category averages and overall grades. These algorithms are implemented in three parallel locations: content.js (live overlay), dashboard.js (Dashboard cards), and details.js (Details View), and must be kept in sync.
Assignment Eligibility
An assignment is included in grade calculations only if it passes these checks:
function isGradedAssignment(a) {
return a.earned !== null && a.possible !== null
&& isFinite(a.earned) && isFinite(a.possible)
&& a.possible > 0;
}
function isGradedAndNotDropped(a) {
return isGradedAssignment(a) && !a.dropped;
}
Excluded assignments:
ungraded- no score yet;earnedandpossibleare nulldropped- flagged by the IC gradebook (tl-curriculum-flagselement contains "Dropped")possible === 0- zero-weight assignmentshypotheticalassignments that were what-if dropped
Multipliers
Each assignment may carry a multiplier property (a positive number). Multipliers are parsed exclusively from the text "Multiplier: X" within the assignment row in the IC DOM. When present, both earned and possible are scaled by the multiplier before aggregation:
function getMultiplier(a) {
return typeof a.multiplier === 'number' ? a.multiplier : 1;
}
effectiveEarned = Math.max(0, a.earned) * getMultiplier(a)
effectivePossible = Math.max(0, a.possible) * getMultiplier(a)
Math.max(0, ...) ensures negative scores are clamped to zero.
Letter Grade Resolution
Assignments graded with a US letter grade (A, A+, A-, B … D-, F; no E) are stored with letterGrade: "A+" and earned: null. Before grade calculation, they are resolved to numeric earned/possible:
pct = midpoint of the grade band = (lower_bound + upper_bound) / 2
For grade band "A" (93 ≤ pct < 97):
upper_bound = next higher grade's lower_bound = 97
lower_bound = 93
midpoint = (93 + 97) / 2 = 95
For grade band "A+" (pct ≥ 97):
upper_bound = 100 (hardcoded cap)
lower_bound = 97
midpoint = (97 + 100) / 2 = 98.5
The resolved numeric value is then scaled to match the category's average point value:
const avgPossible = average of possible values across all non-letter-graded,
graded, non-dropped assignments in the same category
assignment.earned = (pct / 100) * avgPossible
assignment.possible = avgPossible
If there are no numeric assignments in the category, avgPossible defaults to 100.
This ensures that a letter-graded "A" in a homework category where assignments are out of 10 points becomes 9.5/10 rather than 95/100.
Standard Category Average (Non-SEBI)
When SEBI is not enabled for a category, the category average is a point-weighted total across all eligible assignments:
total_earned = Σ max(0, a.earned) × getMultiplier(a)
total_possible = Σ max(0, a.possible) × getMultiplier(a)
category_avg = (total_earned / total_possible) × 100
This is equivalent to treating all assignment points as directly additive.
SEBI Category Average
When SEBI (System for Equalizing Balanced Instructions) is enabled for a category, each assignment's percentage is computed first and then multiplier-weighted-averaged:
pct_i = (a.earned / a.possible) × 100
category_avg = Σ (pct_i × getMultiplier(a)) / Σ getMultiplier(a)
SEBI makes every assignment equally important regardless of point value. A 10-point quiz and a 100-point test contribute equally to the category average.
Overall Grade Calculation
Weighted Gradebook
If any category has a non-null, non-zero weight property, IC Insight uses a category-weighted average:
categories_with_weight = [ c for c in categories if c.possible > 0 AND c.weight > 0 ]
wsum = Σ c.weight
overall = Σ (c.avg × c.weight) / wsum
Only categories that have at least one graded, non-dropped assignment (c._possible > 0) are included in the denominator.
Unweighted / Point-Based Gradebook
If no category has an explicit weight, IC Insight falls back to a direct points aggregation across all categories:
total_earned = Σ (across all categories) Σ (graded, non-dropped assignments) earned × multiplier
total_possible = Σ (across all categories) Σ (graded, non-dropped assignments) possible × multiplier
overall = (total_earned / total_possible) × 100
Rolling Mode: Synthetic Assignments
In rolling gradebook logic with multiple cached quarters, the overall grade is computed from the latest quarter only. To make this correct, past quarters' totals are injected as synthetic assignments into the latest quarter's matching categories:
For Q2 (latest), for each category C:
1. Find category C's data in Q1.
2. Sum Q1's C: total_earned_Q1, total_possible_Q1 (applying multipliers, skipping dropped).
3. Inject synthetic assignment: { name: "Q1", earned: total_earned_Q1, possible: total_possible_Q1 }
4. Now Q2's category C has: [Q1 synthetic, Q2 real assignments]
5. Category average is computed across all of them (points-based, SEBI if enabled).
For Q3 (latest with Q1 and Q2 cached), only Q2's totals are rolled up (not Q1 again - Q2 already contains Q1's rolled total). The pattern is always: add ONE synthetic representing the immediately previous quarter's cumulative totals.
Synthetic assignments are:
- Marked
synthetic: true - Never persisted to storage
- Not shown with action buttons (cannot be dropped/edited)
- Named after the quarter they represent (e.g.
"Q1")
Floating-Point Precision
When comparing a grade percentage against a grade band boundary, IC Insight uses a small epsilon (0.005) to handle floating-point precision:
for (const band of scale) {
if (pct >= band.min - EPSILON) return band;
}
This prevents a grade of exactly 93.000 from floating to 92.999999... and incorrectly rounding down to A-.
Application Order for Overrides
When computing a grade, modifiers are applied in this exact order:
- Permanent score overrides (
PE_key): replace the assignment's earned/possible with the pinned values. - Permanent additions (
PA_key): append new assignments to the category. - What-if additions (
WI_adds): append hypothetical assignments. - What-if drops (
WI_drops): remove matched assignments entirely. - What-if edits (
WI_edits): override earned/possible for matched assignments. - Letter grade resolution: convert letter-graded assignments to numeric.
- Synthetic assignment injection (rolling mode): add rolled-up past-quarter totals.
- Category average calculation (standard or SEBI).
- Overall grade calculation (weighted or point-based).
Important: When computing the cache snapshot, permanent overrides are intentionally skipped (passed as empty). The cache always stores original IC values. Overrides are re-applied dynamically at render time.