Personal messages for everyone who owes a fee this week.
/build/project-fee-reminders with the starter and the rubric already in it.From a list of students with fees and due dates, write a personal reminder for everyone who has not paid and is due within a week (overdue included), earliest first, and total what is still to come in.
Tuition teachers, shops and clubs send these by hand every month. You'll use str.format, dates and timedelta, filtering and sorting: the pieces of every reminder system.
You can work with dates and turn one template into many personal messages correctly.
Each step is one function in fee_reminders.py. In the workspace, press Check next to a step: it runs your file and tells you exactly what is still wrong. Step 1 is free; the rest are part of Pro or your 7-day free trial.
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 fee_reminders.py. The TODO parts are yours to fill.
"""
Fee reminders.
Writes one personal reminder for every student whose fee is due in the
next few days, from a single message template. Fill in one function per
step, press Run, then press "Check" on that step.
"""
from datetime import date
TODAY = date(2026, 11, 3)
TEMPLATE = "Hi {name}, your fee of ₹{fee} is due on {due}. Thank you!"
# name, monthly fee (₹), due date, already paid?
STUDENTS = [
("Aarav", 2500, date(2026, 11, 5), False),
("Diya", 3000, date(2026, 11, 5), True),
("Kabir", 2500, date(2026, 11, 7), False),
("Meera", 3500, date(2026, 11, 4), False),
("Rohan", 2500, date(2026, 11, 20), False),
("Sana", 3000, date(2026, 11, 2), False),
]
def fill(template: str, name: str, fee: int, due: date) -> str:
# TODO: fill {name}, {fee} and {due} in the template with .format().
# The fee gets a comma (2,500): f"{fee:,}". The date reads 05 Nov:
# due.strftime("%d %b").
raise NotImplementedError("step 1, fill(): one personal message")
def days_left(due: date, today: date) -> int:
# TODO: how many days from today until due. Subtracting two dates gives a
# timedelta; .days is the number. Overdue gives a negative number.
raise NotImplementedError("step 2, days_left(): days until the due date")
def needs_reminder(student: tuple, today: date, within: int = 7) -> bool:
# TODO: student is (name, fee, due, paid). True when the fee is not paid
# and is due within `within` days, overdue included.
raise NotImplementedError("step 3, needs_reminder(): who should get a message")
def reminders(students: list[tuple], template: str, today: date) -> list[str]:
# TODO: one filled message per student who needs a reminder, the
# earliest due date first.
raise NotImplementedError("step 4, reminders(): all the messages, earliest first")
def to_collect(students: list[tuple], today: date) -> int:
# TODO: the total fee of everyone who needs a reminder.
raise NotImplementedError("step 5, to_collect(): money still to come in")
def main() -> None:
print(fill(TEMPLATE, "Test", 1000, TODAY))
print(f"Sana's fee was due {-days_left(STUDENTS[5][2], TODAY)} day(s) ago")
print(f"Diya needs a reminder: {needs_reminder(STUDENTS[1], TODAY)}")
msgs = reminders(STUDENTS, TEMPLATE, TODAY)
total = to_collect(STUDENTS, TODAY)
# TODO: print every message with "→ " in front, then
# "4 reminders, ₹11,500 to collect".
raise NotImplementedError("step 6, main(): print the reminders")
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.")
fee_reminders.py and a README.md holding all 5 rubric items as a checklist.pyrun.in/u/<handle>/w/project-fee-reminders.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.
A published PyRun workspace URL (pyrun.in/u/<handle>/w/project-fee-reminders) works as the public URL too.