Before this lesson

Complete Python functions, exceptions and file reading. Understand grouped accumulation in dictionaries.

What you will be able to do

Combine parsing, validation, grouping and sorting into an explainable batch report.

Understand the mechanism

Define course and minutes columns, grouped totals and row-level errors. Separate parsing from reporting. The self-contained sample uses an in-memory stream that can later be replaced by an encoded file.

Use the CSV parser rather than splitting commas because quoted fields can contain delimiters. Validate headers and bounded integer minutes; rejected rows must not contribute to totals.

Accumulate by course and use a deterministic secondary sort key. Report source line numbers and state the partial-success policy instead of implying all rows were accepted.

Extend through explicit edge cases and define duplicate semantics before deduplicating. Identical rows can represent separate sessions rather than accidental copies.

Runnable example

Use Python 3.11 or later. Save lesson.py in a separate practice directory and run python3 lesson.py, or py lesson.py on Windows. Examples use only the standard library.

import csv
from io import StringIO

source = "course,minutes\nPython,25\nSQL,20\nPython,35\nJava,invalid\nSQL,-5\n"
reader = csv.DictReader(StringIO(source))
if reader.fieldnames != ["course", "minutes"]:
    raise ValueError("Expected course,minutes headers")
totals = {}
errors = []
for row in reader:
    course = (row.get("course") or "").strip()
    try:
        minutes = int(row.get("minutes") or "")
        if not course or not 0 <= minutes <= 240:
            raise ValueError("invalid row")
    except ValueError:
        errors.append(reader.line_num)
        continue
    totals[course] = totals.get(course, 0) + minutes
for course, minutes in sorted(totals.items(), key=lambda item: (-item[1], item[0])):
    print(f"{course}: {minutes}")
print("Rejected lines:", errors)

Trace and verify

Expect Python 60, SQL 20 and rejected lines five and six. Header-only input has no totals. Test quoted names and explicitly decide whether reordered headers are accepted.

Common mistakes and boundaries

This is not an unbounded upload service. The report summarizes records and does not claim duration alone measures learning quality.

Practice challenge

Extract summarize(stream), return totals and errors, then export and re-read a summary CSV.

Reference solution and reasoning

Keep output outside the calculation and use a CSV writer with explicit headers. Test valid, empty and invalid cases, extending diagnostics with reasons.

Further reading