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.
A reading log report
Write a program that reads reading.txt and prints a report.
Requirements
- Read the log from the file — the playground writes it for you.
- Each good line has three parts separated by
|: a date, a book title, and whole minutes. Ignore spaces around the parts. - 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.
- Report the total minutes for each book, most-read first, and the grand total.
- Report the day with the most reading in total, across all books.
- 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 linesAn 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.
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.
- 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?
- Is the work split into pieces with one job each? Could you test your line parsing without a file, or your totals without printing?
- Did you handle the edge cases deliberately? Blank lines, malformed lines, an empty log: did you try each one, or just hope?
- 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.