Capstone: ad-copy reviewer
Why this matters
This is the shape of most useful AI tools. Plain code does everything that can be checked with rules, cheaply and the same way every time. A model handles the one step that needs judgment or writing. Agents, evals and content pipelines in Phase 1 are bigger versions of what you build here. And you'll check a model's output with your own code constantly.
- Break a real task into load, check, report and generate steps
- Write rule checks as small functions that return a list of problems
- Save results as a JSON report other tools can read
- Use one LLM call for the part rules can't do, and check what comes back
What you're building
Before ads go live, someone checks the copy. Is each headline short enough for the placement? Does it avoid phrases that get ads rejected or sound spammy? You're going to automate that review for a CSV export of headlines. Then you'll ask a model to rewrite the worst one.
The program has four steps:
- Load the headlines from
ads.csvinto a list of dictionaries. - Check each headline against two rules: at most 40 characters, and none of the banned phrases
"click here","guaranteed","act now"or"!!!". - Report: save every headline with its problems to
report.json. - Generate: ask the model for a better version of the headline with the most problems.
Each step is a small piece you already know. The skill you're practicing is putting them together, so each one does one job and hands clean data to the next.
For marketers: this is the same split you'd use with a junior copywriter and a style guide. The checklist catches the mechanical problems every time. The person is there for the rewrite. Here the checklist is code, and the rewrite comes from a model.
Step 1: load the data
csv.DictReader from the files lesson turns each row into a dictionary, keyed by the header row. Here it is on a different, smaller file:
import csv with open("subjects.csv", "w") as f: f.write("""subject,audience Last chance: spring sale ends tonight,customers Your March report is ready,partners """) with open("subjects.csv") as f: rows = list(csv.DictReader(f)) print(len(rows)) print(rows[0]) print(rows[1]["subject"])
list(...) collects the rows while the file is still open. After the with block ends, the file is closed, but rows keeps the data.
Step 2: rules as functions that return problems
A good shape for a checker is a function that takes one item and returns a list of problems. An empty list means it passed. That's easy to test, easy to count, and easy to add new rules to later.
A comprehension builds that list in one line. Lowercasing the text first makes the check ignore capitals, so "FREE" and "free" are both caught:
SPAMMY = ["free", "winner", "$$$"] def check_subject(subject): problems = [f"spammy word: {word}" for word in SPAMMY if word in subject.lower()] if len(subject) > 50: problems.append(f"too long ({len(subject)} characters)") return problems print(check_subject("Your March report is ready")) print(check_subject("FREE gift for our WINNER")) print(len(check_subject("FREE gift for our WINNER")))