Tech With Galvan
Back to Articles
Development7/21/2025 • 3 min read

Student Grade Calculator using Python and Streamlit

Calculate grades with Python and Streamlit — weighted categories, letter grades, GPA scale, and a what-if final exam calculator.

Galvan
Galvan

Founder & Creator

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

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 considered logic must include the Final once it has a score; check the condition includes scores[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 <= 0 means 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.