On Your Own: A Fresh Brief

The Course
•
September 24, 2026
•
7 min read

The capstone asked you to read a finished program and extend it. That’s real practice, but it isn’t the whole skill. The whole skill is looking at a blank file and a description of what’s wanted, and getting from one to the other.

This page is that, at a small scale: a brief, requirements and sample output. There’s no finished program above it to follow.

The rules

Allowed: the hints, any lesson in this course, Python’s documentation, and an AI assistant used the way Day 1 describes — for an explanation or a hint, checked, and followed by doing it yourself.

Not allowed: pasting in someone else’s finished program, from anywhere. Not because it’s cheating — nobody is watching — but because it would measure the wrong thing. The point is to find out what you can do alone.

If it takes you a few sessions, that’s normal. Nothing here is timed.

The brief

You keep a log of your reading sessions: the date, the book, and how many minutes you read. You wrote it by hand, so a few lines are messy.

On your own

A reading log report

Write a program that reads reading.txt and prints a report.

Requirements

  1. Read the log from the file — the playground writes it for you.
  2. Each good line has three parts separated by |: a date, a book title, and whole minutes. Ignore spaces around the parts.
  3. Skip blank lines silently. Skip any other line that doesn’t have exactly three parts, or whose minutes aren’t a whole number — and count those.
  4. Report the total minutes for each book, most-read first, and the grand total.
  5. Report the day with the most reading in total, across all books.
  6. Report how many bad lines were skipped.

Sample output for the log below. Your layout can differ; your numbers shouldn’t.

Reading report
--------------
Dune          100 min
Piranesi       55 min
Total         155 min
Best day: 2026-03-01 (50 min)
Skipped 2 bad lines

An edge case to try. When it works, change LOG to an empty string and run again. Your program should still print a report — no books, a total of 0, and no best day — rather than crash.

reading_report.py Python
LOG = """2026-03-01 | Dune | 30
2026-03-01 | Piranesi | 20
2026-03-02 | Dune | 45

2026-03-03 | Piranesi | forty
2026-03-03 | Dune | 25
2026-03-04 | Piranesi
2026-03-04 | Piranesi | 35
"""

with open("reading.txt", "w") as file:
    file.write(LOG)

# Your program starts here: read reading.txt and print the report.
Hint 1: the idea

Before any code, decide the shape of your data. After reading, what do you want to be holding? A list of (day, book, minutes) records is a good answer. The two totals — per book, per day — are then the counting-with-a-dictionary pattern, run twice. Planning the data first is most of the design.

Hint 2: a first step

Write one small function first: parse_line(line), which returns (day, book, minutes) for a good line and None for a bad one. Test it on three lines before you use it on the file — a good one, "2026-03-04 | Piranesi", and one with forty in it. Everything else builds on that function.

One way to do it — open after you've finished

Compare this with yours after you have something working. It’s one reasonable design, not the answer: yours can differ in every line and still be good.

def parse_line(line):
    """Return (day, book, minutes) for a good line, or None for a bad one."""
    parts = [part.strip() for part in line.split("|")]
    if len(parts) != 3:
        return None
    day, book, minutes = parts
    try:
        return day, book, int(minutes)
    except ValueError:
        return None


def read_log(path):
    """Return the good sessions and a count of the bad lines."""
    sessions = []
    skipped = 0
    with open(path) as file:
        for line in file:
            if not line.strip():
                continue                      # blank lines aren't mistakes
            session = parse_line(line)
            if session is None:
                skipped += 1
            else:
                sessions.append(session)
    return sessions, skipped


def totals_by(sessions, position):
    """Add up minutes, grouped by the part at `position` (0 = day, 1 = book)."""
    totals = {}
    for session in sessions:
        key = session[position]
        totals[key] = totals.get(key, 0) + session[2]
    return totals


def report(sessions, skipped):
    books = totals_by(sessions, 1)
    days = totals_by(sessions, 0)
    lines = ["Reading report", "-" * 14]
    for book, minutes in sorted(books.items(), key=lambda pair: -pair[1]):
        lines.append(f"{book:<12}{minutes:>5} min")
    lines.append(f"{'Total':<12}{sum(books.values()):>5} min")
    if days:
        best = max(days, key=lambda day: days[day])
        lines.append(f"Best day: {best} ({days[best]} min)")
    else:
        lines.append("Best day: none")
    lines.append(f"Skipped {skipped} bad line{'s' if skipped != 1 else ''}")
    return "\n".join(lines)


sessions, skipped = read_log("reading.txt")
print(report(sessions, skipped))

Four small functions, each with one job and each testable alone: parsing a line, reading a file, totalling, formatting. report returns its text rather than printing it, so a test could check it. The empty-log case needed exactly one decision — the if days: — because max of nothing raises.

Judge your own work

A program that prints the right numbers is the start, not the finish. Go back to yours and answer these honestly — writing the answers down is better than thinking them.

  1. Did you choose your data on purpose? Could you say why you used the containers you used — and what would have been worse about another choice?
  2. Is the work split into pieces with one job each? Could you test your line parsing without a file, or your totals without printing?
  3. Did you handle the edge cases deliberately? Blank lines, malformed lines, an empty log: did you try each one, or just hope?
  4. Can you explain it? Write five sentences at the top of your file, as a docstring, describing how your program works to someone who hasn’t seen it. If a sentence is hard to write, that part of the program is worth another look.

None of these is about remembering syntax. They’re the things that separate someone who can follow Python from someone who can use it.

Your next project

The fastest way to keep what you’ve learned is to build something you actually want. Three briefs, each using only this course, each small enough to finish. No solutions — by now, you don’t need them.

A personal tracker. Pick something you do: workouts, spending, water, practice. Record entries through input() in a loop (add 2026-03-01 30, report, quit), save them to a JSON file so they survive between runs, and report weekly totals. It’s the habit tracker’s shape, built by you.

A CSV summary. Export a CSV from something you use — a bank, a fitness app, a spreadsheet. Write a program that reads it with csv.DictReader and reports the row count, the total and average of one column, and the five largest values. Make it fail with a clear message if the column you ask for doesn’t exist.

A file organiser with a preview mode. Sort the files in a folder into subfolders by extension: .jpg into images/, .pdf into documents/. Build it so that it only prints what it would do by default, and moves anything only after the user types yes. Try it on a folder of copies first — never on the real thing. Showing before acting is a habit worth more than any feature.

Stuck on one of these? That’s the hint system working in real life: work out what you’d need to know, find the lesson that teaches it, and go back.

Last updated on September 24, 2026

Was this article helpful?

Your response is saved on this device.