Mission Log passes every test and survives every trick Raven can think of. Rocket wants to add a new feature right away. Raven puts a hand on the desk.
"What do you notice about the version on your screen?" she asks. "It works perfectly. If the next change goes wrong, how do you get this exact version back?"
Rocket scratches his head. "I would have to undo a lot of typing," he says, "and hope I remember everything."
Nova projects a neat row of folders labeled v1, v2, v3. "Would you like a hint?" she asks. "Before a big change, save a copy with a version number. That copy is a snapshot. You never edit it, so it is always safe."
Rocket opens the File menu.
Here is the finished program with both fixes. Read it top to bottom and check the comments and docstrings. Every function says what it does.
# mission_log.py
# Mission Log: keep dated notes about your missions.
# Entries are saved in mission_log.json, in the same folder.
# Run this file to use the menu. test_mission_log.py checks the functions.
import json
from datetime import date
LOG_FILE = "mission_log.json"
def load_entries():
"""Read saved entries, or start an empty list on the first run.
Gives back None if the file is damaged, so nothing is overwritten.
"""
try:
with open(LOG_FILE) as f:
return json.load(f)
except FileNotFoundError:
return []
except json.JSONDecodeError:
return None
def save_entries(entries):
"""Write every entry to the JSON file."""
with open(LOG_FILE, "w") as f:
json.dump(entries, f, indent=2)
def make_entry(text, tag, day):
"""Build one entry as a dictionary."""
return {"date": day, "tag": tag, "text": text}
def format_entry(number, entry):
"""One entry as a line of text."""
return f"{number}. {entry['date']} [{entry['tag']}] {entry['text']}"
def search_entries(entries, word):
"""Entries whose text or tag contains word, ignoring capitals."""
word = word.lower()
found = []
for entry in entries:
if word in entry["text"].lower() or word in entry["tag"]:
found.append(entry)
return found
def count_tags(entries):
"""A dictionary of how many entries have each tag."""
counts = {}
for entry in entries:
tag = entry["tag"]
counts[tag] = counts.get(tag, 0) + 1
return counts
def add_entry(entries):
"""Ask for a new entry, add it to the list and save."""
text = input("What happened? ").strip()
if text == "":
print("Nothing typed, so nothing saved.")
return
tag = input("Tag (build, test, idea): ").strip().lower()
if tag == "":
tag = "note"
entries.append(make_entry(text, tag, date.today().isoformat()))
save_entries(entries)
print(f"Saved entry {len(entries)}.")
def show_entries(entries):
"""Print every entry with its number."""
if len(entries) == 0:
print("No entries yet.")
return
for number, entry in enumerate(entries, start=1):
print(format_entry(number, entry))
def run_search(entries):
"""Ask for a word and print the matching entries."""
word = input("Search for: ").strip()
if word == "":
print("Type a word to search for.")
return
found = search_entries(entries, word)
for number, entry in enumerate(found, start=1):
print(format_entry(number, entry))
print(f"Matches found: {len(found)}")
def show_stats(entries):
"""Print the number of entries and a count for each tag."""
print(f"Entries in total: {len(entries)}")
for tag, count in count_tags(entries).items():
print(f" {tag}: {count}")
def main():
"""Load the log, then show the menu until the user quits."""
entries = load_entries()
if entries is None:
print(f"{LOG_FILE} is damaged. Rename it, then run Mission Log again.")
return
print(f"Mission Log ready. Entries loaded: {len(entries)}")
while True:
print()
print("1 Add 2 List 3 Search 4 Stats 5 Quit")
choice = input("Choose 1 to 5: ").strip()
if choice == "1":
add_entry(entries)
elif choice == "2":
show_entries(entries)
elif choice == "3":
run_search(entries)
elif choice == "4":
show_stats(entries)
elif choice == "5":
print("Log saved. See you next mission!")
break
else:
print("Please type a number from 1 to 5.")
if __name__ == "__main__":
main() A final check run, with three entries already saved:
Mission Log ready. Entries loaded: 3 1 Add 2 List 3 Search 4 Stats 5 Quit Choose 1 to 5: 3 Search for: Type a word to search for. 1 Add 2 List 3 Search 4 Stats 5 Quit Choose 1 to 5: 3 Search for: sound 1. 2026-04-11 [idea] Add a sound when the clock ends Matches found: 1 1 Add 2 List 3 Search 4 Stats 5 Quit Choose 1 to 5: 4 Entries in total: 3 build: 1 test: 1 idea: 1 1 Add 2 List 3 Search 4 Stats 5 Quit Choose 1 to 5: 5 Log saved. See you next mission!
Save Copy As saves a copy but keeps your window on the original file, which is exactly what a snapshot needs.
Your tool is tested, documented and safely saved. Tomorrow you will demo it to a real user.