Student Grade Calculator

import json

import os

import statistics

from datetime import datetime

from pathlib import Path


# ============================================================

# CONFIGURATION

# ============================================================


DATA_FILE    = "students_data.json"

PASSING_MARK = 40       # minimum marks to pass a subject

MAX_MARKS    = 100      # maximum marks per subject


# ============================================================

# GRADING SYSTEM

# ============================================================


def get_grade(marks, max_marks=100):

    """Return letter grade, grade point, and remarks."""

    pct = (marks / max_marks) * 100


    if pct >= 90:

        return "A+", 10.0, "Outstanding"

    elif pct >= 80:

        return "A",  9.0,  "Excellent"

    elif pct >= 70:

        return "B+", 8.0,  "Very Good"

    elif pct >= 60:

        return "B",  7.0,  "Good"

    elif pct >= 50:

        return "C+", 6.0,  "Above Average"

    elif pct >= 40:

        return "C",  5.0,  "Average"

    elif pct >= 35:

        return "D",  4.0,  "Pass (Marginal)"

    else:

        return "F",  0.0,  "Fail"



def get_class(percentage):

    """Return division/class based on overall percentage."""

    if percentage >= 75:

        return "First Class with Distinction"

    elif percentage >= 60:

        return "First Class"

    elif percentage >= 50:

        return "Second Class"

    elif percentage >= 40:

        return "Pass Class"

    else:

        return "Fail"



def calculate_gpa(subjects):

    """Calculate weighted GPA from a list of subject dicts."""

    total_points  = 0

    total_credits = 0

    for sub in subjects:

        _, gp, _ = get_grade(sub["marks_obtained"], sub["max_marks"])

        credits   = sub.get("credits", 1)

        total_points  += gp * credits

        total_credits += credits

    if total_credits == 0:

        return 0.0

    return round(total_points / total_credits, 2)



# ============================================================

# LOAD & SAVE DATA

# ============================================================


def load_data():

    if Path(DATA_FILE).exists():

        try:

            with open(DATA_FILE, "r") as f:

                return json.load(f)

        except:

            pass

    return {"students": []}



def save_data(data):

    with open(DATA_FILE, "w") as f:

        json.dump(data, f, indent=2)



# ============================================================

# ADD STUDENT

# ============================================================


def add_student(data):

    print("\n" + "="*50)

    print("  ADD NEW STUDENT")

    print("="*50)


    name     = input("  Student Name  : ").strip()

    roll_no  = input("  Roll Number   : ").strip()

    cls      = input("  Class/Semester: ").strip()

    section  = input("  Section       : ").strip() or "A"


    if not name or not roll_no:

        print("  Name and Roll Number are required.")

        return


    # Check duplicate roll number

    if any(s["roll_no"] == roll_no for s in data["students"]):

        print(f"  Roll number {roll_no} already exists.")

        return


    # Input subjects

    subjects = []

    print(f"\n  Enter subjects and marks (type 'done' to finish)")

    print(f"  Format: Subject Name | Marks Obtained | Max Marks | Credits")

    print(f"  Example: Mathematics | 85 | 100 | 4\n")


    while True:

        entry = input("  > ").strip()

        if entry.lower() == "done":

            if not subjects:

                print("  At least one subject is required.")

                continue

            break


        parts = [p.strip() for p in entry.split("|")]

        if len(parts) < 2:

            print("  Format: Subject | Marks | Max Marks (opt) | Credits (opt)")

            continue


        sub_name = parts[0]

        try:

            marks    = float(parts[1])

            max_m    = float(parts[2]) if len(parts) > 2 and parts[2] else MAX_MARKS

            credits  = float(parts[3]) if len(parts) > 3 and parts[3] else 1


            if marks < 0 or marks > max_m:

                print(f"  Invalid marks. Must be between 0 and {max_m}")

                continue


            grade, gp, remarks = get_grade(marks, max_m)

            subjects.append({

                "name":          sub_name,

                "marks_obtained": marks,

                "max_marks":     max_m,

                "credits":       credits,

                "grade":         grade,

                "grade_point":   gp,

                "remarks":       remarks,

                "passed":        marks >= (max_m * PASSING_MARK / MAX_MARKS)

            })

            print(f"  Added: {sub_name} — {marks}/{max_m}  Grade: {grade}  ({remarks})")


        except ValueError:

            print("  Invalid marks value.")

            continue


    # Calculate overall stats

    total_obtained = sum(s["marks_obtained"] for s in subjects)

    total_max      = sum(s["max_marks"]      for s in subjects)

    percentage     = round((total_obtained / total_max) * 100, 2) if total_max else 0

    gpa            = calculate_gpa(subjects)

    passed_all     = all(s["passed"] for s in subjects)

    division       = get_class(percentage)

    failed_subs    = [s["name"] for s in subjects if not s["passed"]]


    student = {

        "id":             len(data["students"]) + 1,

        "name":           name,

        "roll_no":        roll_no,

        "class":          cls,

        "section":        section,

        "subjects":       subjects,

        "total_obtained": total_obtained,

        "total_max":      total_max,

        "percentage":     percentage,

        "gpa":            gpa,

        "division":       division,

        "passed":         passed_all,

        "failed_subjects": failed_subs,

        "added_at":       datetime.now().strftime("%d-%m-%Y %H:%M:%S")

    }


    data["students"].append(student)

    save_data(data)


    print(f"\n  Student saved! Quick summary:")

    print(f"  Percentage : {percentage}%")

    print(f"  GPA        : {gpa}")

    print(f"  Result     : {'PASS' if passed_all else 'FAIL'}")

    print(f"  Division   : {division}")

    if failed_subs:

        print(f"  Failed in  : {', '.join(failed_subs)}")



# ============================================================

# PRINT REPORT CARD

# ============================================================


def print_report_card(student):

    w = 58

    print("\n" + "=" * w)

    print(f"  {'REPORT CARD':^{w-4}}")

    print("=" * w)

    print(f"  Name        : {student['name']}")

    print(f"  Roll No     : {student['roll_no']}")

    print(f"  Class       : {student['class']}  Section: {student['section']}")

    print(f"  Date        : {student['added_at'][:10]}")

    print("-" * w)

    print(f"  {'SUBJECT':<20} {'MAX':>5} {'OBT':>5} {'%':>6}  "

          f"{'GR':>3}  {'GP':>4}  REMARKS")

    print("-" * w)


    for sub in student["subjects"]:

        pct   = round((sub["marks_obtained"] / sub["max_marks"]) * 100, 1)

        pass_marker = " " if sub["passed"] else "*"

        print(f"  {sub['name']:<20} {sub['max_marks']:>5.0f} "

              f"{sub['marks_obtained']:>5.0f} {pct:>5.1f}%  "

              f"{sub['grade']:>3}  {sub['grade_point']:>4.1f}  "

              f"{sub['remarks']}{pass_marker}")


    print("-" * w)

    overall_pct = round((student["total_obtained"] / student["total_max"]) * 100, 1)

    print(f"  {'TOTAL':<20} {student['total_max']:>5.0f} "

          f"{student['total_obtained']:>5.0f} {overall_pct:>5.1f}%")

    print("=" * w)

    print(f"  GPA         : {student['gpa']} / 10.0")

    print(f"  Percentage  : {student['percentage']}%")

    print(f"  Division    : {student['division']}")

    result_str = "PASS" if student["passed"] else "FAIL"

    print(f"  Result      : {result_str}")

    if student.get("failed_subjects"):

        print(f"  Failed in   : {', '.join(student['failed_subjects'])}")

    print("=" * w)


    # Grade scale legend

    print(f"\n  Grade Scale: A+(90+)=10 | A(80+)=9 | B+(70+)=8 | "

          f"B(60+)=7 | C+(50+)=6 | C(40+)=5 | D(35+)=4 | F=0")

    print(f"  * = Failed subject (below passing marks)\n")



# ============================================================

# SEARCH STUDENT

# ============================================================


def search_student(data):

    query = input("\n  Search by name or roll number: ").strip().lower()

    results = [

        s for s in data["students"]

        if query in s["name"].lower() or query in s["roll_no"].lower()

    ]


    if not results:

        print(f"  No students found for: '{query}'")

        return None


    if len(results) == 1:

        return results[0]


    print(f"\n  Found {len(results)} students:")

    for i, s in enumerate(results, 1):

        print(f"  [{i}] {s['roll_no']}  {s['name']}  "

              f"Class: {s['class']}  {s['percentage']}%")

    try:

        idx = int(input("\n  Select number: ").strip()) - 1

        if 0 <= idx < len(results):

            return results[idx]

    except ValueError:

        pass

    return None



# ============================================================

# CLASS REPORT (ALL STUDENTS)

# ============================================================


def class_report(data):

    students = data["students"]

    if not students:

        print("\n  No students added yet.")

        return


    print("\n" + "="*70)

    print(f"  CLASS REPORT  ({len(students)} students)")

    print("="*70)

    print(f"  {'ROLL':<10} {'NAME':<22} {'%':>6}  "

          f"{'GPA':>5}  {'DIV':<30}  RESULT")

    print("  " + "-"*65)


    sorted_students = sorted(students,

                             key=lambda x: x["percentage"],

                             reverse=True)


    for rank, s in enumerate(sorted_students, 1):

        result = "PASS" if s["passed"] else "FAIL"

        print(f"  {s['roll_no']:<10} {s['name']:<22} "

              f"{s['percentage']:>5.1f}%  {s['gpa']:>5.2f}  "

              f"{s['division']:<30}  {result}")


    # Class statistics

    percentages = [s["percentage"] for s in students]

    gpas        = [s["gpa"]        for s in students]

    passed      = sum(1 for s in students if s["passed"])

    failed      = len(students) - passed


    print("  " + "-"*65)

    print(f"\n  CLASS STATISTICS")

    print(f"  Total Students : {len(students)}")

    print(f"  Passed         : {passed}  "

          f"({passed/len(students)*100:.1f}%)")

    print(f"  Failed         : {failed}  "

          f"({failed/len(students)*100:.1f}%)")

    print(f"  Highest %      : {max(percentages):.2f}%")

    print(f"  Lowest  %      : {min(percentages):.2f}%")

    print(f"  Average %      : {statistics.mean(percentages):.2f}%")

    print(f"  Median  %      : {statistics.median(percentages):.2f}%")

    print(f"  Average GPA    : {statistics.mean(gpas):.2f}")


    # Topper

    topper = max(students, key=lambda x: x["percentage"])

    print(f"\n  Class Topper   : {topper['name']} "

          f"({topper['roll_no']})  — {topper['percentage']}%")


    # Grade distribution

    grade_dist = {}

    for s in students:

        g, _, _ = get_grade(s["percentage"])

        grade_dist[g] = grade_dist.get(g, 0) + 1


    print(f"\n  Grade Distribution:")

    for grade in ["A+", "A", "B+", "B", "C+", "C", "D", "F"]:

        count = grade_dist.get(grade, 0)

        bar   = "█" * count

        print(f"  {grade:>3}: {count:>3}  {bar}")


    print("="*70)



# ============================================================

# SUBJECT-WISE ANALYSIS

# ============================================================


def subject_analysis(data):

    students = data["students"]

    if not students:

        print("\n  No data yet.")

        return


    # Collect all subjects

    sub_map = {}

    for s in students:

        for sub in s["subjects"]:

            name = sub["name"]

            if name not in sub_map:

                sub_map[name] = []

            sub_map[name].append(sub["marks_obtained"])


    if not sub_map:

        return


    print("\n" + "="*60)

    print("  SUBJECT-WISE ANALYSIS")

    print("="*60)

    print(f"  {'SUBJECT':<22} {'AVG':>6}  {'HIGH':>6}  "

          f"{'LOW':>6}  {'PASS%':>6}  STUDENTS")

    print("  " + "-"*56)


    for sub_name, marks_list in sorted(sub_map.items()):

        avg      = statistics.mean(marks_list)

        high     = max(marks_list)

        low      = min(marks_list)

        pass_pct = sum(1 for m in marks_list

                       if m >= PASSING_MARK) / len(marks_list) * 100

        print(f"  {sub_name:<22} {avg:>6.1f}  {high:>6.1f}  "

              f"{low:>6.1f}  {pass_pct:>5.1f}%  {len(marks_list)}")


    print("="*60)



# ============================================================

# DELETE STUDENT

# ============================================================


def delete_student(data):

    student = search_student(data)

    if not student:

        return

    confirm = input(f"\n  Delete {student['name']} ({student['roll_no']})? "

                    f"(yes/no): ").strip().lower()

    if confirm == "yes":

        data["students"] = [s for s in data["students"]

                            if s["roll_no"] != student["roll_no"]]

        save_data(data)

        print("  Student deleted.")

    else:

        print("  Cancelled.")



# ============================================================

# MAIN MENU

# ============================================================


def print_menu(data):

    count = len(data["students"])

    print("\n" + "-"*48)

    print(f"  STUDENT GRADE CALCULATOR  [{count} students]")

    print("-"*48)

    print("  1. Add new student with marks")

    print("  2. View student report card")

    print("  3. Search student")

    print("  4. Class report (all students)")

    print("  5. Subject-wise analysis")

    print("  6. Delete a student")

    print("  0. Exit")

    print("-"*48)



def main():

    print("\n" + "="*55)

    print("     STUDENT GRADE CALCULATOR")

    print("="*55)

    print(f"\n  Passing Marks : {PASSING_MARK}/{MAX_MARKS}")

    print(f"  Grading       : A+(90+) to F(<35)")

    print(f"  GPA Scale     : 0 - 10")


    data = load_data()

    if data["students"]:

        print(f"\n  Loaded {len(data['students'])} student(s) from {DATA_FILE}")


    while True:

        print_menu(data)

        choice = input("  > ").strip()


        if choice == "1":

            add_student(data)


        elif choice == "2":

            if not data["students"]:

                print("\n  No students added yet.")

                continue

            student = search_student(data)

            if student:

                print_report_card(student)


        elif choice == "3":

            if not data["students"]:

                print("\n  No students added yet.")

                continue

            student = search_student(data)

            if student:

                print(f"\n  Found: {student['name']}  |  "

                      f"Roll: {student['roll_no']}  |  "

                      f"Class: {student['class']}")

                print(f"  Percentage: {student['percentage']}%  |  "

                      f"GPA: {student['gpa']}  |  "

                      f"Result: {'PASS' if student['passed'] else 'FAIL'}")

                show = input("\n  Show full report card? (y/n): ").strip().lower()

                if show == "y":

                    print_report_card(student)


        elif choice == "4":

            class_report(data)


        elif choice == "5":

            subject_analysis(data)


        elif choice == "6":

            delete_student(data)


        elif choice == "0":

            print("\n  Goodbye!\n")

            break


        else:

            print("  Invalid choice.")



# ============================================================

# RUN

# ============================================================


if __name__ == "__main__":

    main()

No comments: