Capstone: Grade Report

Capstone: Grade Report

Write a function that reads a class roster, validates it, and writes a grade-distribution report.

generate_grade_report(in_path, out_path)

in_path contains one "name,score" pair per line (no header). For each line:

  1. Parse the score as an int. If it's below 0 or above 100, raise ValueError(f"invalid score for {name}: {score}").
  2. Classify it into a letter grade using the same rule as Grade Letter: 90+"A", 80-89"B", 70-79"C", 60-69"D", below 60"F".

Once every line is processed, build a report: one line per letter grade, in the order A, B, C, D, F, formatted "LETTER:COUNT", joined with "\n" — including letters with a count of 0. Write that report to out_path, and also return it.

Example

Given in_path containing:

Ada,95
Alan,82
Grace,71
Kay,55
Lee,40

generate_grade_report(in_path, out_path) returns (and writes to out_path):

A:1
B:1
C:1
D:0
F:2

Walkthrough

Thinking it through

This problem chains together four separate skills from earlier in the course, in sequence. Breaking it into those stages is the key to not getting overwhelmed:

def generate_grade_report(in_path, out_path):
    with open(in_path) as f:
        lines = f.read().splitlines()

    counts = {"A": 0, "B": 0, "C": 0, "D": 0, "F": 0}
    for line in lines:
        name, score_text = line.split(",")
        score = int(score_text)
        if score < 0 or score > 100:
            raise ValueError(f"invalid score for {name}: {score}")
        letter = grade_letter(score)
        counts[letter] += 1

    report = "\n".join(f"{letter}:{counts[letter]}" for letter in ["A", "B", "C", "D", "F"])

    with open(out_path, "w") as f:
        f.write(report)

    return report

Stage 1 — read (File I/O): f.read().splitlines() gets one string per line, the same technique as Count CSV Rows.

Stage 2 — validate (Exceptions): for each line, parse the score and check its range before doing anything else with it — raising immediately with a message that names the offending student, the same guard-clause pattern as Validate Age. Note the validation happens inside the loop, per line — a single bad score should stop the whole report immediately (an exception propagating out of the function), not just get skipped.

Stage 3 — classify and count (Conditionals + Dictionaries): grade_letter reuses the exact if/elif chain from the Grade Letter problem. counts[letter] += 1 is the accumulator pattern from Word Frequency, except every possible key already exists in counts from the start (initialized to 0), so there's no need for .get() here — every letter returned by grade_letter is guaranteed to already be a key.

Stage 4 — format and write (File I/O again): the report needs a fixed letter order (A, B, C, D, F), not whatever order a dict happens to iterate in — that's why the join loops over a literal list of letters and looks each one up in counts, rather than iterating counts.items() directly. The same report string is both written to out_path and returned, satisfying both what the file should contain and what the function itself hands back to its caller.

main.py
Console
Press Run to execute your code, or Submit to test and complete this problem.