viewof mode = Inputs.radio(
["Category averages", "Individual exams and quizzes"],
{value: "Category averages", label: "Enter grades as"}
)Grade Calculator
Estimate your current course grade
This tool estimates your current course grade from the grading scheme in the syllabus. Your mastery grade comes from your exams, the work graded for correctness. Completing the formative work adds a bonus: up to 12 points for homework. The bonus applies only once your mastery grade is at least 60%, and your final grade is capped at 100%.
Pick how you would like to enter your grades, then fill in the fields. This is an estimate to help you plan; your official grades are in Canvas.
html`<p class="gc-hint">${mode === "Category averages"
? "Enter your current average in each category."
: "Enter the individual scores you have so far and leave the rest blank. If you enter all five exams, your lowest is dropped automatically."}</p>`viewof entry = {
const pct = (label, value) => Inputs.number([0, 100], {label, value, step: 0.1});
const hw = pct("Homework completion (%)", 100);
if (mode === "Category averages") {
return Inputs.form({
examAvg: pct("Exam average (%)", 85),
hw
});
}
return Inputs.form({
e1: pct("Exam 1 (%)"),
e2: pct("Exam 2 (%)"),
e3: pct("Exam 3 (%)"),
e4: pct("Exam 4 (%)"),
e5: pct("Final Exam (%)"),
hw
});
}html`<div class="grade-card">
<div class="gc-row"><span>Mastery grade</span><span class="gc-val">${fmt(result.mastery)}</span></div>
<div class="gc-row"><span>Formative bonus</span><span class="gc-val">${result.gateMet && result.bonus > 0 ? "+" : ""}${fmt(result.bonus)}</span></div>
<div class="gc-row gc-total"><span>Course grade</span><span class="gc-val">${fmt(result.course)}</span></div>
<div class="gc-badge">
<span class="gc-letter">${result.letter}</span>
<span class="gc-gpa">${Number.isFinite(result.gpa) ? result.gpa.toFixed(2) + " GPA" : ""}</span>
</div>
<div class="gc-detail">Exam average ${fmt(result.examAvg)}</div>
${result.note ? html`<div class="gc-note">${result.note}</div>` : ""}
</div>`result = {
const g = entry;
let examAvg = NaN;
if (mode === "Category averages") {
examAvg = g.examAvg;
} else {
const exams = finite([g.e1, g.e2, g.e3, g.e4, g.e5]).map(clamp);
let used = exams;
if (exams.length === 5) used = exams.slice().sort((a, b) => a - b).slice(1); // drop lowest of five
examAvg = used.length ? used.reduce((a, b) => a + b, 0) / used.length : NaN;
}
// Mastery grade: exams 144 points (144 total),
// renormalized over whatever is entered.
const parts = [];
if (Number.isFinite(examAvg)) parts.push([144, clamp(examAvg)]);
const wsum = parts.reduce((s, p) => s + p[0], 0);
const mastery = wsum > 0 ? parts.reduce((s, p) => s + p[0] * p[1], 0) / wsum : NaN;
// Formative bonus: homework up to 12 points,
// as a percentage of the 144-point mastery base, gated on mastery >= 60%.
const hw = Number.isFinite(g.hw) ? clamp(g.hw) : 0;
const rawBonus = (12 * hw) / 144;
const maxBonus = (12) * 100 / 144;
const gateMet = Number.isFinite(mastery) && mastery >= 60;
const bonus = gateMet ? rawBonus : 0;
const course = Number.isFinite(mastery) ? Math.min(100, mastery + bonus) : NaN;
const lg = Number.isFinite(course) ? letterAndGpa(course) : {letter: "\u2014", gpa: NaN};
let note = "";
if (!Number.isFinite(mastery)) {
note = "Enter at least one exam score to see your grade.";
} else if (!gateMet) {
note = `Your mastery grade is below 60%, so the formative bonus does not apply yet. Once your mastery grade reaches 60%, your current homework completion would add ${rawBonus.toFixed(1)}% (up to ${maxBonus.toFixed(1)}% at full completion), so focus on the exams.`;
} else if (course >= 100) {
note = "You have reached the 100% cap; more formative work will not raise your grade further.";
}
return {examAvg, mastery, rawBonus, bonus, course, gateMet, note, ...lg};
}letterAndGpa = function (score) {
const bands = [
[97, "A+", 4.00],
[93, "A", 4.00],
[90, "A-", 3.75],
[87, "B+", 3.25],
[83, "B", 3.00],
[80, "B-", 2.75],
[77, "C+", 2.25],
[73, "C", 2.00],
[70, "C-", 1.75],
[67, "D+", 1.25],
[63, "D", 1.00],
[60, "D-", 0.75],
[0, "F", 0.00]
];
for (const [lo, letter, gpa] of bands) if (score >= lo) return {letter, gpa};
return {letter: "F", gpa: 0};
}