Introduction
Every student asks two questions: “What’s my current grade?” and the more urgent, “What do I need on the final?” This app answers both — enter your scores with category weights, get your current grade, and the what-if mode solves for exactly the final-exam score needed to hit your target. It is the weighted-math sibling of the BMI calculator, with a genuinely useful algebra twist.
Features
- Weighted categories — homework, quizzes, midterm, final; weights must sum to 100%.
- Live grade — current weighted percentage and letter grade.
- What-if calculator — the final-exam score needed for a target grade.
- GPA conversion — your letter on the standard 4.0 scale.
- Visual breakdown — bar chart of each category’s contribution.
Prerequisites
- Python 3.8+ — from python.org.
- Streamlit:
pip install streamlit
Step 1: Create the Script
Save as grade_calculator.py:
import streamlit as st
st.set_page_config(page_title="Grade Calculator", page_icon="🎓")
st.title("🎓 Grade Calculator")
CATEGORIES = ["Homework", "Quizzes", "Midterm", "Final"]
DEFAULT_WEIGHTS = [30, 20, 20, 30]
scores, weights = {}, {}
cols = st.columns(len(CATEGORIES))
for i, cat in enumerate(CATEGORIES):
with cols[i]:
weights[cat] = st.number_input(f"{cat} weight %", 0, 100, DEFAULT_WEIGHTS[i], key=f"w{i}")
scores[cat] = st.number_input(f"{cat} score %", 0.0, 100.0, 85.0, key=f"s{i}")
total_weight = sum(weights.values())
if total_weight != 100:
st.warning(f"Weights sum to {total_weight}% — they should total 100%.")
# Current grade: only categories completed so far count proportionally
completed = {c: w for c, w in weights.items() if scores[c] > 0 or c != "Final"}
earned = sum(scores[c] * weights[c] for c in CATEGORIES if c != "Final" or scores[c] > 0)
considered = sum(weights[c] for c in CATEGORIES if c != "Final" or scores[c] > 0)
current = earned / considered if considered else 0
def letter(pct):
if pct >= 90: return "A"
if pct >= 80: return "B"
if pct >= 70: return "C"
if pct >= 60: return "D"
return "F"
def gpa_points(letter):
return {"A": 4.0, "B": 3.0, "C": 2.0, "D": 1.0}.get(letter, 0.0)
c1, c2, c3 = st.columns(3)
c1.metric("Current grade", f"{current:.1f}%", letter(current))
c2.metric("Letter", letter(current))
c3.metric("GPA points", f"{gpa_points(letter(current)):.1f}")
st.bar_chart({c: scores[c] * weights[c] / 100 for c in CATEGORIES})
st.divider()
st.subheader("🎯 What do I need on the final?")
target = st.selectbox("Target letter", ["A (90%)", "B (80%)", "C (70%)"])
target_pct = float(target.split("(")[1].strip("%)").strip())
w_final = weights["Final"] / 100
w_rest = 1 - w_final
needed = (target_pct - current * w_rest) / w_final if w_final else 0
if needed <= 0:
st.success(f"🎉 You're already there — even a 0 on the final keeps your {target[0]}!")
elif needed > 100:
st.error(f"Not reachable — you'd need {needed:.0f}% on the final.")
else:
st.info(f"Score at least **{needed:.1f}%** on the final to secure a {target[0]}.")
Step 2: Run the App
streamlit run grade_calculator.py
Enter your scores, set your syllabus weights, and check the what-if answer before finals week.
How It Works
The current grade is proportional weighting: each category contributes score × weight, divided by the total weight considered so far. That last part matters — if the final isn’t taken yet, its weight must be excluded from the denominator, or every grade reads unfairly low. The considered sum handles it by skipping the Final until a score exists.
The what-if formula is the same equation solved backwards. If final_grade = current×(1−w) + needed×w, then algebra gives needed = (target − current×(1−w)) / w — one line, no loops.
Letter grades are a threshold ladder, identical in shape to the BMI calculator’s categories, and the GPA lookup is a dictionary — the unit converter’s favorite data structure.
Common Errors & Fixes
- Current grade drops when entering the Final score — the
consideredlogic must include the Final once it has a score; check the condition includesscores[c] > 0. - Weights warning never clears — number_inputs step by 1; use 5s or make weights a single row of sliders summing visually to 100.
- What-if shows negative numbers weirdly —
needed <= 0means the target is already secured; that’s the success branch, not a bug. - Chart shows tiny bars — contributions are score×weight/100, maxing at the weight value; that’s correct — the chart shows contribution, not score.
Key Concepts
- Proportional weighting — divide by weight considered so far, not total.
- Solving backwards — same formula, different unknown.
- Threshold ladders — if/elif chains for grade boundaries.
- Dictionary lookups — letter → GPA points in one line.
What to Try Next
- Add drop-lowest logic per category (drop the minimum score before averaging).
- Support multiple courses with a selectbox and per-course CSV persistence, like the habit tracker.
- Add a grade simulator — sliders for possible final scores showing the outcome live.
- Export a transcript to Excel with the report generator.
FAQ
Why does my grade change when I enter the final score?
Because the Final’s weight joins the calculation — your earlier categories now matter proportionally less. That’s correct weighted grading, not a bug.
Can I use points instead of percentages?
Yes — convert points to percentages per category first (earned ÷ possible × 100); the weighting math is identical afterward.
Is the GPA conversion accurate?
It uses the common unweighted 4.0 scale. Schools vary (A+ = 4.3, plus/minus scales); adjust the dictionary to match your institution.