Totals, percentages, grades and the class topper from a dict of marks.
/build/project-marks-report-card with the starter and the rubric already in it.Take the marks of five students in five subjects and print a report card: total, percentage, grade, pass/fail (every subject needs 33) and the class topper, in a neatly aligned table.
This is the first program most teachers ask for, and it still catches people: the boundary at exactly 90, a student who has a good total but fails one subject, and a table that lines up. Six small functions, each tested on its own.
You can break a problem into small functions, get if/elif boundaries right and format output with f-strings.
Each step is one function in report_card.py. In the workspace, press Check next to a step: it runs your file and tells you exactly what is still wrong.
Build against this exact list — it is the spec your published project is judged on.
Copy this into a new file — or open it in a workspace as report_card.py. The TODO parts are yours to fill.
"""
Marks report card.
Turns a dict of marks into a printed report card: total, percentage,
grade, pass/fail and the class topper. Fill in one function per step,
press Run, then press "Check step" in the Checkpoints panel.
"""
MARKS = {
"Aarav": {"Maths": 88, "Physics": 76, "Chemistry": 91, "English": 67, "CS": 95},
"Diya": {"Maths": 95, "Physics": 89, "Chemistry": 84, "English": 92, "CS": 98},
"Kabir": {"Maths": 45, "Physics": 38, "Chemistry": 29, "English": 58, "CS": 61},
"Meera": {"Maths": 72, "Physics": 81, "Chemistry": 77, "English": 85, "CS": 70},
"Rohan": {"Maths": 33, "Physics": 41, "Chemistry": 36, "English": 49, "CS": 52},
}
PASS_MARK = 33 # per subject, out of 100
def total(marks: dict[str, int]) -> int:
# TODO: add up every subject's marks. Hint: marks.values()
raise NotImplementedError("step 1, total(): add up the marks")
def percentage(marks: dict[str, int]) -> float:
# TODO: total / maximum possible * 100, rounded to 2 decimals.
# Every subject is out of 100, so the maximum is len(marks) * 100.
raise NotImplementedError("step 2, percentage(): percentage to 2 decimals")
def grade(pct: float) -> str:
# TODO: 90+ "A+", 75+ "A", 60+ "B", 45+ "C", 33+ "D", else "F"
raise NotImplementedError("step 3, grade(): turn a percentage into a grade")
def passed(marks: dict[str, int]) -> bool:
# TODO: True only if EVERY subject is at least PASS_MARK.
raise NotImplementedError("step 4, passed(): every subject >= PASS_MARK")
def topper(all_marks: dict[str, dict[str, int]]) -> tuple[str, float]:
# TODO: return (name, percentage) of the student with the highest %.
raise NotImplementedError("step 5, topper(): highest percentage in the class")
def report_line(name: str, marks: dict[str, int]) -> str:
# Given: one aligned row of the table, built from your four functions.
result = "PASS" if passed(marks) else "FAIL"
pct = percentage(marks)
return f"{name:<8}{total(marks):>5}{pct:>8.2f}% {grade(pct):<3}{result}"
def main() -> None:
print(f"{'Name':<8}{'Total':>5}{'Pct':>9} {'Gr':<3}Result")
for name, marks in MARKS.items():
print(report_line(name, marks))
name, pct = topper(MARKS)
print(f"Topper: {name} with {pct:.2f}%")
# TODO: print the class average of everyone's percentage, 2 decimals:
# "Class average: 68.08%"
raise NotImplementedError("step 6, main(): print the class average")
if __name__ == "__main__":
try:
main()
except NotImplementedError as todo:
# A fresh starter is SUPPOSED to stop here. Say which step is next
# instead of printing a traceback that looks like a bug.
print(f"Not built yet: {todo}")
print("Write that function, then press Run again. Each step you finish moves this message forward.")
Try every step first. This version passes all 6 checks; yours can look different and still pass.
"""
Marks report card.
Turns a dict of marks into a printed report card: total, percentage,
grade, pass/fail and the class topper. Run it and read the table.
"""
MARKS = {
"Aarav": {"Maths": 88, "Physics": 76, "Chemistry": 91, "English": 67, "CS": 95},
"Diya": {"Maths": 95, "Physics": 89, "Chemistry": 84, "English": 92, "CS": 98},
"Kabir": {"Maths": 45, "Physics": 38, "Chemistry": 29, "English": 58, "CS": 61},
"Meera": {"Maths": 72, "Physics": 81, "Chemistry": 77, "English": 85, "CS": 70},
"Rohan": {"Maths": 33, "Physics": 41, "Chemistry": 36, "English": 49, "CS": 52},
}
PASS_MARK = 33 # per subject, out of 100
def total(marks: dict[str, int]) -> int:
return sum(marks.values())
def percentage(marks: dict[str, int]) -> float:
return round(total(marks) / (len(marks) * 100) * 100, 2)
def grade(pct: float) -> str:
if pct >= 90:
return "A+"
if pct >= 75:
return "A"
if pct >= 60:
return "B"
if pct >= 45:
return "C"
if pct >= 33:
return "D"
return "F"
def passed(marks: dict[str, int]) -> bool:
return all(m >= PASS_MARK for m in marks.values())
def topper(all_marks: dict[str, dict[str, int]]) -> tuple[str, float]:
name = max(all_marks, key=lambda n: percentage(all_marks[n]))
return name, percentage(all_marks[name])
def report_line(name: str, marks: dict[str, int]) -> str:
result = "PASS" if passed(marks) else "FAIL"
pct = percentage(marks)
return f"{name:<8}{total(marks):>5}{pct:>8.2f}% {grade(pct):<3}{result}"
def main() -> None:
print(f"{'Name':<8}{'Total':>5}{'Pct':>9} {'Gr':<3}Result")
for name, marks in MARKS.items():
print(report_line(name, marks))
name, pct = topper(MARKS)
print(f"Topper: {name} with {pct:.2f}%")
average = sum(percentage(m) for m in MARKS.values()) / len(MARKS)
print(f"Class average: {average:.2f}%")
if __name__ == "__main__":
main()
report_card.py and a README.md holding all 5 rubric items as a checklist.pyrun.in/u/<handle>/w/project-marks-report-card.pyrun.in/u/<handle> portfolio page.Your workspace saves to this browser only. Sign in to keep it across devices and to publish it.
Flip the workspace to Public and paste its URL below, or push the file to a GitHub gist or repo. Paste the code inline too if you want the optional AI review to comment on specific lines.
A published PyRun workspace URL (pyrun.in/u/<handle>/w/project-marks-report-card) works as the public URL too.