Last active
June 1, 2026 12:14
-
-
Save Steven24K/589cf661039af4606feb223fd655598f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import csv | |
| import math | |
| def safe_float(x): | |
| try: | |
| return float(x) | |
| except (ValueError, TypeError): | |
| return None | |
| def safe_div(x, y): | |
| if y == 0: return 0 | |
| return x / y | |
| class Student: | |
| def __init__(self, _id, _name, _assignments): | |
| self.id = _id | |
| self.name = _name | |
| self.assignments = _assignments | |
| def total(self): | |
| return len(self.assignments) | |
| def completed_list(self): | |
| return [a for a in self.assignments if a[1] is not None] | |
| def total_completed(self): | |
| return len(self.completed_list()) | |
| def avg_grade(self): | |
| grades = [a[1] for a in self.completed_list()] | |
| return round(safe_div(sum(grades), self.total_completed()), 2) | |
| def completion_percentage(self): | |
| if self.total() == 0: return 0 | |
| return math.floor((self.total_completed() / self.total()) * 100) | |
| def count_by_kind(self, kind): | |
| return len([a for a in self.completed_list() if a[0][1] == kind and "A1W3A1" not in a[0][0]]) | |
| def total_by_kind(self, kind): | |
| return len([a for a in self.assignments if a[0][1] == kind and "A1W3A1" not in a[0][0]]) | |
| def is_assessment_ready(self): | |
| # 1. Filter out the Flowchart (A1W3A1) from the required list | |
| required_assignments = [ | |
| a for a in self.assignments | |
| if a[0][1] == 'A' and "A1W3A1" not in a[0][0] | |
| ] | |
| # Check if all remaining [A] assignments are done | |
| all_a_done = all(a[1] is not None for a in required_assignments) | |
| # 2. Check Master project (M) | |
| master_done = self.count_by_kind('M') > 0 | |
| return all_a_done and master_done | |
| def show_detailed_grades(self): | |
| print(f"\n--- Detailed Grades for {self.name} ---") | |
| for (header, kind), grade in self.assignments: | |
| status = f"{grade:>5}" if grade is not None else " MISSING" | |
| # Flag the "OP" confusion in the detailed view | |
| label = f"[{kind}]" | |
| if "OP" in header: | |
| label = f"[{kind} - OOP?]" | |
| print(f"{label} {header[:45]:<45} : {status}") | |
| def __str__(self): | |
| status = "READY" if self.is_assessment_ready() else "NOT ELIGIBLE" | |
| return f""" | |
| === STUDENT SUMMARY === | |
| Name: {self.name} | |
| ID: {self.id} | |
| Status: {status} | |
| Progress: {self.completion_percentage()}% ({self.total_completed()}/{self.total()}) | |
| Avg Grade: {self.avg_grade()} | |
| ----------------------- | |
| Problems: {self.count_by_kind('P')}/{self.total_by_kind('P')} | |
| Assignments: {self.count_by_kind('A')}/{self.total_by_kind('A')} | |
| Master Task: {"[DONE]" if self.count_by_kind('M') > 0 else "[PENDING]"} | |
| (Note: Flowchart A1W3A1 is excluded from requirements) | |
| =======================""" | |
| def get_category(h): | |
| part = h.split(' - ')[0] | |
| if 'A' in part[-2:]: return 'A' | |
| if 'P' in part[-2:]: return 'P' | |
| if 'M' in part[-2:]: return 'M' | |
| return '?' | |
| def index_header(header): | |
| return [(h, get_category(h)) for h in header[2:]] | |
| students = [] | |
| try: | |
| csvfile = open('gradebook.csv', mode='r', encoding='utf-8') | |
| gradebook = csv.reader(csvfile, delimiter=',', quotechar='"') | |
| header = next(gradebook) | |
| header_cat = index_header(header) | |
| for row in gradebook: | |
| if not row: continue | |
| student_name = row[0] | |
| student_number = row[1] | |
| all_assignments = list(map(safe_float, row[2:])) | |
| students.append(Student(student_number, student_name, list(zip(header_cat, all_assignments)))) | |
| csvfile.close() | |
| except FileNotFoundError: | |
| print("Error: 'gradebook.csv' not found.") | |
| exit() | |
| students.sort(key=lambda s: s.completion_percentage(), reverse=True) | |
| while True: | |
| print(f"\n{'Student Name':<25} | {'ID':<10} | {'P-Done':<7} | {'A-Done':<7} | {'Ready?':<8} | {'Avg'}") | |
| print("-" * 85) | |
| for s in students: | |
| ready_status = "YES" if s.is_assessment_ready() else "NO" | |
| # Adjust P-count/A-count for the overview table display | |
| p_count = f"{s.count_by_kind('P')}/{s.total_by_kind('P')}" | |
| # Subtract 1 from total A because we skipped the flowchart | |
| a_count = f"{s.count_by_kind('A')}/{s.total_by_kind('A')}" | |
| print(f"{s.name[:25]:<25} | {s.id:<10} | {p_count:<7} | {a_count:<7} | {ready_status:<8} | {s.avg_grade():>5}") | |
| print(f"\n{len(students)} students loaded.\n") | |
| search = input("Enter Student Number for details (or 'exit'): ") | |
| if search.lower() == 'exit': break | |
| match = next((s for s in students if s.id == search), None) | |
| if match: | |
| print(match) | |
| match.show_detailed_grades() | |
| input("Press enter to continue") | |
| else: | |
| print("Student not found.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment