Week 5 of 16

Build: Add History Logging

Add timestamp-based history to your Prompt Vault — without breaking anything that already works. This is how real features get built.

Day 24 60 minutes Build

Day 24 of 80

Today's Goal

Every time a prompt is generated, log it. You want to know: when did it happen, what shot was requested, which platform, what did Claude say, did it get saved. All of this goes into history.json — a running record you can browse any time.

You're adding this feature to the project from Day 23. The goal is to add it cleanly: two new functions in helpers.py, one new menu option in vault.py, and one new call inside generate_prompt(). Everything else stays unchanged.

What Gets Added Where

Adding to helpers.py

Open helpers.py and add the following. First, add the datetime import at the top of the file (after the existing import json):

helpers.py — top of file (additions) Python
import json
from datetime import datetime   # ADD THIS LINE

PROMPTS_FILE = "prompts.json"
HISTORY_FILE = "history.json"  # ADD THIS LINE
VALID_PLATFORMS = ["Kling", "Runway", "Veo"]

from datetime import datetime: Python's datetime module contains many time-related classes. You specifically want the datetime class inside it. This is the standard pattern: from module import ClassName.

HISTORY_FILE = "history.json": Same pattern as PROMPTS_FILE — define the filename once at the top so it's easy to change later.

Now add the three new functions at the bottom of helpers.py:

helpers.py — new functions Python
def load_history():
    """Load generation history. Returns empty list if no history yet."""
    try:
        with open(HISTORY_FILE, "r") as f:
            return json.load(f)
    except FileNotFoundError:
        return []


def log_generation(shot, platform, prompt, saved):
    """Append one generation event to the history file."""
    history = load_history()
    history.append({
        "timestamp": datetime.now().isoformat(),
        "shot": shot,
        "platform": platform,
        "prompt": prompt[:80],
        "saved": saved,
    })
    with open(HISTORY_FILE, "w") as f:
        json.dump(history, f, indent=2)


def show_history():
    """Print the 10 most recent generation events, newest first."""
    history = load_history()
    if not history:
        print("  No history yet.\n")
        return
    recent = list(reversed(history[-10:]))
    print(f"\n--- Recent History (last {len(recent)}) ---\n")
    for entry in recent:
        date, time = entry["timestamp"].split("T")
        time = time[:5]
        status = "saved" if entry["saved"] else "skipped"
        print(f"  [{date} {time}] [{entry['platform']}] {entry['shot']}")
        print(f"    {entry['prompt'][:60]}... ({status})\n")

load_history() (lines 1–7): Identical pattern to load_prompts(). The try/except means the first time you run the app (before any history exists), it silently returns an empty list instead of crashing.

log_generation() (lines 10–20): Takes four arguments — the shot description, platform, the full generated prompt, and whether the user saved it (a boolean: True or False).

Line 13 — datetime.now().isoformat(): datetime.now() returns the current date and time. .isoformat() converts it to a string in the standard format: "2026-04-06T14:32:05.123456". ISO format is sortable as a string, which makes it perfect for logs.

Line 16 — prompt[:80]: Truncate the prompt to 80 characters for storage. The full prompt can be very long; you only need enough to recognize it in the history view.

Line 17 — "saved": saved: Stores the boolean directly. In JSON this becomes true or false (lowercase).

show_history() (lines 23–35): Shows the 10 most recent entries in reverse order (newest first).

Line 29 — history[-10:]: Slice the last 10 items. Then reversed() flips them so the most recent appears at the top. Wrap in list() because reversed() returns an iterator, not a list.

Lines 31–32 — splitting the timestamp: "2026-04-06T14:32:05".split("T") returns ["2026-04-06", "14:32:05"]. You unpack that into date and time. Then time[:5] trims it to "14:32" — no seconds needed in the display.

Line 33 — inline conditional: "saved" if entry["saved"] else "skipped" is a ternary expression — Python's one-line if/else. It reads like English.

Updating vault.py

Two changes in vault.py. First, update the import line:

vault.py — updated import line Python
from helpers import load_prompts, save_prompts, validate_platform, log_generation, show_history

Just add log_generation and show_history to the existing import list. Python imports exactly what you name — nothing more.

Second, call log_generation() at the end of generate_prompt(), right after the save decision:

vault.py — end of generate_prompt() Python
    save_it = input("  Save this prompt? (y/n): ").strip().lower()
    did_save = False
    if save_it == "y":
        prompts = load_prompts()
        prompts.append({"shot": shot, "platform": platform, "prompt": prompt_text})
        save_prompts(prompts)
        print("  Saved.\n")
        did_save = True
    log_generation(shot, platform, prompt_text, did_save)

Lines 2 and 8: did_save is a boolean flag that starts as False and becomes True only if the user confirmed saving. This is cleaner than checking save_it == "y" again inside the log call.

Line 9: log_generation() is called regardless of whether the user saved. You want a record of every generation, not just the ones that got saved. That's what makes the history useful — you can look back at prompts you skipped.

Third, add "View history" to the menu in main():

vault.py — updated main() menu Python
def main():
    print("\n=== DVP Prompt Vault ===\n")
    while True:
        print("  1. View saved prompts")
        print("  2. Generate new prompt")
        print("  3. View history")
        print("  4. Quit")
        choice = input("\n  Choose: ").strip()
        if choice == "1":
            show_prompts()
        elif choice == "2":
            generate_prompt()
        elif choice == "3":
            show_history()
        elif choice == "4":
            print("  Bye.\n")
            break
        else:
            print("  Type 1, 2, 3, or 4.\n")

Only two real additions: print(" 3. View history"), the elif choice == "3": show_history() branch, and bumping Quit to option 4. The structure of the while loop is identical to before — you're extending it, not rewriting it.

This is what "adding a feature without breaking what works" looks like. You touched three lines in vault.py and added three functions to helpers.py. Everything that existed before still runs exactly as it did.

What the Output Looks Like

vault.py — history view
--- Recent History (last 3) ---

  [2026-04-06 14:45] [Kling] Aerial drone shot over golf course
    Cinematic wide-angle drone footage soaring over an emerald golf... (saved)

  [2026-04-06 14:38] [Runway] Slow-motion club impact
    Ultra slow-motion capture of club face striking ball at moment... (skipped)

  [2026-04-06 14:21] [Veo] Tracking shot following the ball
    Cinematic tracking shot locked onto the golf ball from the tee... (saved)
Also Check: history.json Directly

After running the vault and generating a few prompts, open history.json in VS Code. You'll see a JSON array where each object has timestamp, shot, platform, prompt, and saved. This is raw data — not just for display. Next week's Flask app could serve this data to a browser as an API response with two lines of code.

This Is the Real Pattern

90% of professional software development is exactly what you did today: add a feature to existing code without breaking what already works. You didn't rewrite the vault. You didn't touch show_prompts(). You added functions, extended the import line, called a new function in one place, and added one menu option. The existing tests (your manual testing from Day 23) still pass. That discipline — add, don't break — is the thing senior developers mean when they say "clean code."

End of Day Checklist

Tomorrow — Day 25: Week 5 Review & Milestone Check

Tomorrow is a reflection day. You'll review everything you built this week, do a milestone check against four specific skills, and get a preview of Week 6 where you turn this command-line vault into a web app using Flask. No new code — just making sure everything landed before you move forward.