← Back to course
Code Crew G6+ / Week 11 / Friday
5/6
Week 11 Β· Build Mission Log

Friday

Stats and the complete tool
// A real tool, one piece at a time
⏱ about 30 min

Friday: Stats and the complete tool

One stub is left. The flight director wants to know how the crew spends its time. How many build entries, test entries and ideas are there?

Rocket grins. "Counting with a dictionary," he says. "We did that in week 5 with tags. It is almost the same code."

Raven checks the plan on the whiteboard and ticks off four boxes. "What do you notice?" she asks. "When this one is done, every box is ticked. Then we test the whole tool from start to finish."

Nova dims her lights for a moment, like a theater before the curtain. "Interesting," she says. "The first full run of a program you built yourself is a special moment. Go slowly and watch everything."

Rocket types the last two functions.

The last two functions

count_tags returns a dictionary of tag counts. Type it below search_entries.

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

show_stats prints the total and each count. Type it below run_search.

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}")

In main, add the choice 4 branch below choice 3, and delete the stub lines completely. Here is the whole program, ready to compare with yours:

# mission_log.py
# Mission Log: keep dated notes about your missions.
# Entries are saved in mission_log.json, in the same folder.

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."""
    try:
        with open(LOG_FILE) as f:
            return json.load(f)
    except FileNotFoundError:
        return []


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()
    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()
    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()

The full test run

Delete mission_log.json, then run Mission Log twice. In the first run, add three entries and quit:

Mission Log ready. Entries loaded: 0

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 1
What happened? Built the launch clock
Tag (build, test, idea): build
Saved entry 1.

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 1
What happened? Tested the clock
Tag (build, test, idea): TEST
Saved entry 2.

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 1
What happened? Sound when it ends
Tag (build, test, idea): idea
Saved entry 3.

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 5
Log saved. See you next mission!

In the second run, the entries are loaded from the file. List, search, stats, a wrong choice, and quit. Your dates will be the day you run it:

Mission Log ready. Entries loaded: 3

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 2
1. 2026-04-12 [build] Built the launch clock
2. 2026-04-12 [test] Tested the clock
3. 2026-04-12 [idea] Sound when it ends

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 3
Search for: clock
1. 2026-04-12 [build] Built the launch clock
2. 2026-04-12 [test] Tested the clock
Matches found: 2

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: 9
Please type a number from 1 to 5.

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 5
Log saved. See you next mission!
In the second run, what does the first line say? Entries loaded: ____
After adding one more test entry, what would stats show for test?
PUT THE PARTS OF MISSION_LOG.PY IN ORDER, TOP TO BOTTOM
  • ?def main()
  • ?load_entries and save_entries
  • ?The helper functions, like make_entry and format_entry
  • ?if __name__ == "__main__": main()
  • ?Comments, imports and LOG_FILE
  • ?The feature functions, like add_entry and show_stats
WHY THIS EXERCISEEach function is defined before main runs, and main only starts at the very bottom.
At your computer
1. Type count_tags below search_entries, and show_stats below run_search.
2. In main, add the choice 4 branch and delete both stub lines.
3. Compare your whole file with the complete program, line by line.
4. Delete mission_log.json. Run the first test run exactly as shown, then the second.
5. Use Mission Log for real: add one entry about what you built this week.

Mission Log is complete, and you built every line of it. Next week you will test it, polish it, save a snapshot and demo it. Tomorrow is a Crew Quest.

← Thursday