Family Menu Generator
Plans a week of protein-focused family dinners and emits a multi-page PDF: a stylized menu page followed by a categorized shopping list, recipe links, and kickoff notes.
Required First Steps (do these BEFORE planning meals)
These three steps are mandatory every time. Skipping any of them produces a worse menu.
1. Ask which day the week starts
ALWAYS ask the user which day the menu should start before doing anything else. A "week" is ambiguous — Monday-start vs. Sunday-start vs. "starting Thursday" all change the meal flow (e.g., where leftover days, pizza night, and restaurant night land).
Ask exactly this (or equivalent):
"Which day should this week's menu start on?"
Then ask the standing follow-ups (every time — these change the meal flow, not just flavor):
- "Any ingredients on hand to incorporate?"
- "Any dietary restrictions or cravings this week?"
- "Any nights this week you'll be working late?" — late nights should be paired with leftover/reheat meals or the restaurant night, never a 45+ minute cook.
- "Do you want any larger 'cook once, eat twice' meals built in for leftovers this week?" — if yes, plan at least one oversized roast/casserole/sheet-pan meal and explicitly schedule its leftover day 1–2 nights later (prefer placing the leftover night on a late-work night if the user named one).
If the user has not answered the start-day question, do NOT proceed to research or planning. For the other follow-ups, pick a sensible default only if they explicitly decline ("you choose"): assume no late nights and no oversized leftover meals unless told otherwise.
2. Search the web for seasonal menus and recipes
Use web search every time, even if you "know" what's in season. Seasonality, trends, and recipe links must come from current sources, not memory.
Required searches before drafting the menu:
- Seasonal produce/proteins for the current month and the user's region
- "[season] family dinner menu" or "[month] weekly meal plan" for inspiration
- High-protein recipes incorporating the seasonal items you found
- Restaurants near the user's zip code (default 20136, 25-mile radius) for restaurant night
Capture the actual recipe URLs as you go — you will need them for the recipes page in step 4.
3. Plan the week with the rules below
- 7 dinners, anchored on the user's chosen start day
- Protein variety: don't repeat the same protein more than twice
- Leftovers: schedule a leftover meal 1–2 days after any roast/casserole/large-batch meal, and label it explicitly ("Tuesday — Monday's pot roast"). If the user named late-work nights, prefer landing leftover/reheat nights on those dates.
- Cook-once-eat-twice meals: only build these in if the user said yes to the leftovers question. When yes, size the source meal for ~1.5–2× a normal dinner and call out the leftover plan in the recipe
notesand the context page. - Pizza Friday: every 2–3 weeks, mark a Friday as "Homemade Pizza Night 🍕"
- Restaurant night: ~once a week (typically Saturday), with a specific local pick from your search. Restaurant night is a strong fit for an unavoidable late-work night.
- Sunday or batch day: favor slow-cooker or roast meals that produce leftovers
Generating the PDF
The output PDF MUST contain, in order:
- The stylized menu page (random design) — exactly ONE 8.5" × 11" letter-size page, designed to be printed and stuck on the fridge. The seven dinners must always fit on this single sheet; the script enforces this.
- A categorized shopping list with checkboxes (may span multiple pages)
- Recipe links page (may span multiple pages)
- "Notes for the Week" context page (may span multiple pages)
Because the menu page is single-page printable, keep meal name, protein, and notes short — long strings are truncated to fit. If you have a lot of leftover/repurposing detail, put it on the context page, not the meal note.
Pass all four pieces of data to scripts/generate_menu.py:
from scripts.generate_menu import create_menu_pdf
menu_data = {
'title': 'Family Dinner Menu',
'subtitle': 'Week of Nov 10, 2026',
'meals': [
{'day': 'Monday', 'name': '...', 'protein': '...', 'prep_time': '...', 'notes': '...'},
# ... 7 entries, in the order the user's week runs
],
'shopping_list': {
'Produce': ['Brussels sprouts (1 lb)', 'Lemon', ...],
'Proteins': ['Chicken breast (2 lb)', ...],
'Dairy & Eggs': [...],
'Pantry': [...],
'Frozen': [...],
'Bakery': [...],
'Beverages': [...],
'Other': [...],
},
'recipes': [
{'day': 'Monday', 'name': 'Grilled Chicken...', 'url': 'https://...', 'notes': 'optional'},
# one entry per recipe-driven meal (skip leftover/restaurant days)
],
'context': "Free-form notes: what's seasonal this week, prep-ahead tips,\n"
"leftover repurposing plan, restaurant reservation reminders, etc.",
}
design = create_menu_pdf(menu_data, '/path/to/output.pdf')
Use only these shopping list categories (the script orders them and ignores empty ones): Produce, Proteins, Dairy & Eggs, Pantry, Frozen, Bakery, Beverages, Other.
recipes[].url becomes a clickable link in the PDF — verify the URLs from your web search before passing them in.
Common Mistakes
| Mistake | Fix |
|---|---|
| Generating the PDF with meals only, no shopping list / recipes / context | All three companion fields are required. The companion pages are not optional. |
| Skipping the "which day" question because the user said "this week" | "This week" is ambiguous. Ask for the start day. |
| Skipping the late-nights / leftover-meal questions | Both are required follow-ups. They shape which nights get leftovers and which get a slow roast. |
| Scheduling a 45-minute weeknight cook on a night the user said they'd be working late | Move that cook to an earlier/lighter night and put leftovers, restaurant night, or a sheet-pan meal on the late night. |
| Building in oversized "cook-once-eat-twice" meals when the user didn't ask for them | Only do this when the user opted in. Otherwise size meals normally and let one batch meal naturally feed one leftover night. |
| Skipping web search because you remember what's in season | Seasonality drifts and recipe links rot — always search. |
| Inventing recipe URLs or reusing prior ones | Use only URLs you obtained from the current web search. |
| Putting ingredients in custom categories ("For pizza", "For Tuesday") | Use the standard grocery categories listed above. |
| Putting leftover/restaurant nights in the recipes list | Recipes page is for cookable recipes only. |
| Long meal names / notes that overflow the menu page | Keep menu-page text short. Move detail to the context page. The menu page is one printable sheet. |
Red Flags — STOP and restart the workflow
- About to call
create_menu_pdfwithoutshopping_list,recipes, orcontext - About to plan meals before asking which day the week starts
- About to plan meals without asking about late-work nights or oversized leftover meals
- About to plan meals without running any web searches this turn
- About to write recipe URLs from memory instead of from search results
- Menu page rendered as more than one printed sheet (the script will assert — don't disable it)
Any of these means: stop, return to the Required First Steps, and do them in order.
Resources
scripts/generate_menu.py— PDF generator. Renders one stylized menu page (random theme from 9 designs) plus the three companion pages described above. The menu page is asserted to fit on a single 8.5" × 11" sheet.- Fonts are pulled from Google Fonts at build time on first run (jsDelivr's mirror of
google/fonts) and cached under~/.cache/family-menu-fonts/— setFAMILY_MENU_FONT_CACHEto override. Subsequent runs are offline. There is no bundled fonts directory; if a download ever fails, that font silently falls back to Helvetica.
User Defaults
- Zip code: 20136 (Centreville, VA) — used for restaurant search, 25-mile radius