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; earned and possible are null
  • dropped - flagged by the IC gradebook (tl-curriculum-flags element contains "Dropped")
  • possible === 0 - zero-weight assignments
  • hypothetical assignments 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:

  1. Permanent score overrides (PE_ key): replace the assignment's earned/possible with the pinned values.
  2. Permanent additions (PA_ key): append new assignments to the category.
  3. What-if additions (WI_ adds): append hypothetical assignments.
  4. What-if drops (WI_ drops): remove matched assignments entirely.
  5. What-if edits (WI_ edits): override earned/possible for matched assignments.
  6. Letter grade resolution: convert letter-graded assignments to numeric.
  7. Synthetic assignment injection (rolling mode): add rolled-up past-quarter totals.
  8. Category average calculation (standard or SEBI).
  9. 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.