Safe Paws Project — Round 5/5
I once had a dog named Lassie. Not the breed, not the pedigree. Just pure love.
This one's for her.
Let's build the report.
We already know how to load data and hold it in dictionaries. adopted_dict has keys that come from paws_dict. When we reference an animal by ID, we can cross-reference it instantly in both dictionaries. The ID does the work.
We declare adopted_dict as a global — same as paws_dict:
adopted_dict = {}
And we extend load_from_txt() to populate it:
def load_from_txt():
try:
with open("safe_paws.txt", "r", encoding="utf-8") as f:
for line in f:
row = line.strip().split(";")
paws_dict[int(row[0])] = [row[1], row[2], row[3], row[4], int(row[5]), row[6]]
except FileNotFoundError:
pass
try:
with open("adopted_paws.txt", "r", encoding="utf-8") as f:
for line in f:
row = line.strip().split(";")
adopted_dict[int(row[0])] = [row[1], row[2], row[3]]
except FileNotFoundError:
pass
One function, two files, two dictionaries. Both ready before the menu starts.
A side note worth keeping in mind: in paw_adopted(), after we append to adopted_paws.txt, we call load_from_txt() — which reloads both dictionaries. So adopted_dict is always current after an adoption. In the future, with more complex flows, this kind of detail can make the difference between data that's in sync and data that isn't.
Now — the report. Adoptions first, sorted chronologically. Dictionaries don't have a built-in sort order we can rely on, so we sort explicitly.
def get_date(item):
return item[1][2]
sorted_items = sorted(adopted_dict.items(), key=get_date)
sorted_adopted = dict(sorted_items)
adopted_dict.items() returns all key-value pairs from the dictionary — in our case (id, [name, contact, date]). sorted() needs to know what to sort by, and key=get_date tells it: for each pair, call get_date and use the result as the sort value. get_date receives the pair and returns item[1][2] — the date, at index 2. sorted() orders everything from oldest to newest. dict() converts the sorted result back into a dictionary. Same principle as at the Calories Tracker — YYYY-MM-DD sorts correctly as a string.
The full report function — sorted adoptions first, available stock below:
def activity_report():
def get_date(item):
return item[1][2]
sorted_items = sorted(adopted_dict.items(), key=get_date)
sorted_adopted = dict(sorted_items)
report_date = datetime.date.today().strftime("%Y-%m-%d")
filename = f"activity_report_{report_date}.txt"
with open(filename, "w", encoding="utf-8") as f:
f.write("=" * 20 + "\n")
f.write("Adopted paws\n")
f.write("=" * 20 + "\n")
for key in sorted_adopted:
f.write(f"{sorted_adopted[key][2]} - {paws_dict[key][0]}, adopted by {sorted_adopted[key][0]} ({sorted_adopted[key][1]})\n")
f.write("=" * 20 + "\n")
f.write("Available paws\n")
f.write("=" * 20 + "\n")
for key in paws_dict:
if paws_dict[key][5] == "available":
f.write(f"{paws_dict[key][0]}, a {paws_dict[key][4]} years old, {paws_dict[key][2]} sized {paws_dict[key][1]}.\n")
print(f"Report saved: {filename}")
The first loop iterates over sorted_adopted — chronological order. For each adoption, it pulls the animal's name from paws_dict using the same ID. That's the link. The ID that seemed like a small decision in Round 1 is doing real work here.
The second loop goes through paws_dict and filters only "available" animals. No adopted animals in the stock section.
The output file for a shelter with a few adoptions and available animals:
====================
Adopted paws
====================
2025-03-10 - Cleo, adopted by Jon Snow (jon@thewall.com)
2025-03-15 - Buddy, adopted by Arya Stark (arya@winterfell.com)
2025-04-01 - Ella, adopted by Tyrion Lannister (tyrion@casterly.com)
====================
Available paws
====================
Lassie, a 4 years old, medium sized dog.
Whiskers, a 2 years old, small sized cat.
Rex, a 6 years old, big sized dog.
...
Into the menu:
while True:
print("\nSafe Paws Menu")
option = input("1-Add paw\n2-Adv search\n3-Edit paw health\n4-Register adoption\n5-Activity report\nq-Quit\nChoose your option: ")
if option == "1":
add_paw()
print("Paw added successfully!")
elif option == "2":
if len(adv_search()) > 0:
print_adv_search()
else:
print("No data match your search.")
elif option == "3":
edit_health()
elif option == "4":
paw_adopted()
elif option == "5":
activity_report()
elif option == "q":
break
else:
print("Invalid option!")
In fewer rounds than the Calories Tracker, we built something more complex. Two files, two dictionaries, five functions, one report that ties everything together.
Take a moment with that.
What we have is functional, well-structured, and ready to use. But the potential doesn't stop here. The next article points to where this can go — no solutions, just direction. You have the baggage. You have the examples. The fort is built.
We'll leave the flag for you to raise.