# Meal Suggestion — Kitchen Inventory + High-Protein Meals An automated, research-grounded meal system for a single user (jp): a recovering burns victim on a **high-protein diet (~165–175 g/day)** who is also losing fat, and who wants to **avoid buying red meat**. It does two things on a schedule: 1. **Daily meal suggestions** — two meals (lunch + dinner) built only from what is actually in the kitchen inventory, emailed every day at 09:00. 2. **Weekly shopping list** — a £30 Lidl basket, themed around a randomly chosen world cuisine that week, emailed Mondays at 08:00. Sender: `hpm6@txt3.com` (the agent's Gmail, via `smtp.gmail.com:587`). Recipient: `jp@txt3.com`. > General guidance, **not medical advice** — defer to the user's clinical / > dietitian team; keep hydrated on a high-protein intake. --- ## Table of contents - [How it works](#how-it-works) - [Repository layout](#repository-layout) - [Core rules](#core-rules) - [Egg limit (hard rule)](#egg-limit-hard-rule) - [Weekly cuisine rotation](#weekly-cuisine-rotation) - [Inventory & meal history](#inventory--meal-history) - [Email format](#email-format) - [Automation (cron)](#automation-cron) - [Scripts](#scripts) - [Where to shop](#where-to-shop-eastbourne) - [On-demand usage](#on-demand-usage) - [Testing & regression guards](#testing--regression-guards) - [Git & sync](#git--sync) --- ## How it works The system is driven by a **skill** (`SKILL.md`) plus two cron jobs that load it. On each run the agent: - Reads the kitchen **`inventory.json`** (current stock). - **Daily:** searches the web for easy, tasty, high-protein budget recipes for inspiration, then builds two meals **strictly from in-stock items**, records them to `meal-history.json`, and emails them. - **Weekly:** picks a cuisine, builds a £30 Lidl basket themed around it, adds a per-store price comparison and a health-coverage note, and emails it. All generated meals obey the **strict inventory rule**: no ingredient may appear that is not already in `inventory.json`. Recipe ideas are only used as inspiration; if a recipe needs something not in stock, it is substituted with an in-stock item or dropped. --- ## Repository layout ``` meal-suggestion/ ├── SKILL.md # The operative spec: rules, format, automation ├── README.md # This document ├── .gitignore # Excludes live data (inventory/meal-history/cuisine-rotation) ├── inventory.example.json # Schema example for inventory.json ├── meal-history.example.json # Schema example for meal-history.json ├── references/ # Knowledge base the agent reads when generating │ ├── email-template.md # HTML email scaffold (inline CSS, mobile-friendly) │ ├── food-health.md # Healing + 46yo health + easy/tasty meal research │ ├── protein-sources.md # Allowed proteins + example pairings │ ├── protein-targets.md # Protein math (1.7 g/kg) │ ├── meal-history.md # 7-day logger spec + reconcile-from-inventory logic │ └── eastbourne-shops.md # Shop price table (Lidl preferred) ├── scripts/ │ ├── pick_cuisine.py # Picks this week's world cuisine (seeded, varied) │ └── verify_eggs.py # Regression guard for the EGG LIMIT hard rule └── (live, git-ignored) ├── inventory.json # CURRENT stock — drifts as the user eats/buys ├── meal-history.json # 7-day rolling log of suggested meals └── cuisine-rotation.json # Current + recent weekly cuisine picks ``` The live skill directory is `/home/jp/.hermes/skills/meal-suggestion/`; this repo is a synced mirror under `~/IdeaProjects/meal-suggestion/`. The two stay in step (see [Git & sync](#git--sync)). --- ## Core rules - **High-protein target:** ~165–175 g/day (≈1.7 g/kg at ~95–100 kg, for burns recovery + lean-mass preservation during ~15 kg fat loss). Aim ~90–100 g per meal and **state the per-meal and daily protein totals** in the email. - **Weight-loss framing:** mild calorie deficit via bulking with veg and going easy on chips/bread/oil — **not** by cutting protein. - **Red meat:** already in stock is *usable* (use it up, deprioritised), but the shopping list must **never suggest buying red meat** (beef, lamb, pork, venison, bacon, gammon). - **Strict inventory rule:** both daily meals are built ONLY from items in `inventory.json`. Nothing invented, assumed, or added. - **Tasty & varied:** the agent does a 1–2 query web search for easy, high-protein budget meals and draws on real recipes so meals aren't the same plate repeated. Prefers no-cook / one-pan / 10–15 min methods. - **Health coverage:** meals/items favour wound-healing nutrients (Vit C, Zinc, Vit A, Copper, Vit K, iron+VitC) and 46-year-old health (Vit D, B12, Magnesium, Omega-3) plus mental + visual acuity (leafy greens, eggs/choline, berries, walnuts, olive oil, lutein/zeaxanthin, omega-3 DHA). Noted as gentle "health boost" lines, not medical advice. --- ## Egg limit (hard rule) Eggs were previously overloaded (a day suggested 8 eggs across two egg-bearing meals). This is now a **hard cap**, enforced three ways: 1. **In SKILL.md:** *max 4 eggs per meal; only ONE of the two daily meals may contain eggs — the other must be egg-free.* If a meal needs more protein than 4 eggs supply, close the gap with lentils/beans/halloumi/cheese/tinned fish/ whey — never a 5th+ egg. This overrides the "lead with eggs" preference. 2. **In inventory.json:** `diet.egg_rules` carries `max_per_meal: 4` and `max_meals_per_day_with_eggs: 1`; the Eggs item note states the cap. Every run reads the limit from the data itself. 3. **In scripts/verify_eggs.py:** a regression guard that checks the persisted history against `diet.egg_rules` and exits non-zero on violation. Run it after any change to inventory/meal-history or the daily job, and before sending a regenerated day's email. --- ## Weekly cuisine rotation To keep shops and meals varied over time, each week's list is themed around a **randomly chosen world cuisine**, driven by `scripts/pick_cuisine.py`. - **Stable within a week:** the pick is seeded by the week's Monday ISO date, so re-runs / retries of the Monday job never flip the cuisine mid-week. - **Varied over time:** the last 4 chosen cuisines are excluded from the pool (no back-to-back repeats). - **Persisted:** writes the choice to `cuisine-rotation.json` (`week_monday`, `cuisine`, `theme_items`) so the **daily job can read it** and cook on-theme meals from the same week's buys. - **Cuisine pool (all Lidl-achievable on ~£30, high-protein, red-meat-free by design):** Mexican/Tex-Mex, Greek/Mediterranean, Indian/South Asian, Thai, East Asian/Japanese, Middle Eastern/Levantine, Italian, Korean. - **Themes without breaking rules:** the cuisine leads protein/veg/condiment buys with its `theme_items`, but still respects £30 budget, protein-first, healing/ 46yo coverage, Lidl-default, and never-buy-red-meat. Pure flavour buys (spices/sauces/lime/herbs) are the optional low-price "interest" items. The weekly email subject/header shows `Cuisine: `. --- ## Inventory & meal history **`inventory.json`** — current stock. Schema: ```json { "updated": "YYYY-MM-DD", "diet": { "goal": "high-protein", "avoid_purchase": ["red meat"], "egg_rules": { "max_per_meal": 4, "max_meals_per_day_with_eggs": 1, "note": "..." }, "age_years": 46, "bodyweight_kg": "95-100", "protein_per_kg": 2, "protein_target_g_per_day": "190-200", "weight_loss_goal_kg": 15 }, "items": [ { "name": "Eggs", "qty": 10, "unit": "count", "category": "protein", "notes": "Hard cap: max 4 eggs per meal; only ONE of the two daily meals may use eggs." } ] } ``` - `category` ∈ `protein, veg, fruit, dairy, grain, tinned, frozen, condiment, other`. - **You own the inventory.** When the user says they *used/consumed* ingredients, decrement/remove them (via the `patch` tool) and bump `updated`. When they *bought* items, add/increase them. The daily email only *proposes* meals — it does **not** auto-consume. **`meal-history.json`** — rolling 7-day log. The daily job **UPSERTS** today's two entries (replaces same `date`+`meal`, so re-runs don't duplicate) and **prunes** entries older than 7 days. Each entry: ```json { "date": "YYYY-MM-DD", "weekday": "Thursday", "meal": "lunch", "name": "Halloumi & Red Lentil Power Bowl (egg-free)", "items": [{ "name": "Halloumi", "qty": 100, "unit": "g" }] } ``` Only substantive food items are logged — pure condiments and the optional "third hit" snack are not. This log is what the on-demand inventory-removal feature reconciles against. --- ## Email format Sent via `python3 /home/jp/.hermes/scripts/meal/send_meal_email.py` (reads SMTP creds from `~/.hermes/.env`). Both HTML (inline CSS, mobile-friendly, dark-on-light) and a plain-text alternative are produced so the plan survives HTML-stripping clients. - **Daily:** header (date + `high-protein · ~165–175g/day · no red meat bought`), two meal cards (name, ingredients, 2–3 step method, protein total, short "health boost" note), footer (`Reply to tell me what you used and I'll update the inventory`). - **Weekly:** shopping table (item, est. price, running total ≤ £30, Total row), header with `Cuisine: `, a "health coverage" note (healing + brain + eyes + 46yo + cuisine theme), a "Same basket — where else?" per-store comparison table, and a "Same basket — where else?" plain-text mirror. --- ## Automation (cron) Two cron jobs (created via the `cronjob` tool), both loading the `meal-suggestion` skill: | Job | Schedule | Purpose | |-----|----------|---------| | Daily meal suggestions | `0 9 * * *` | 2 in-stock meal suggestions → email | | Weekly shopping list | `0 8 * * 1` | £30 cuisine-themed Lidl basket → email | Delivery is `local` (the email is the deliverable); check `cronjob action=list` / logs if a send seems missing. The weekly job runs `pick_cuisine.py` **first**, then themes the basket; the daily job reads `cuisine-rotation.json` and prefers on-theme cooking while still obeying the strict inventory rule and the egg cap. --- ## Scripts ### `scripts/pick_cuisine.py` Picks this week's cuisine. - `python3 scripts/pick_cuisine.py` — pick (or reuse this week's) and persist. - `python3 scripts/pick_cuisine.py --dry-run` — print only, do not write. - Prints JSON: `{"week_monday", "cuisine", "theme_items", "reused"}`. - Seeded by the week's Monday → stable within a week; excludes the last 4 cuisines → no back-to-back repeats. ### `scripts/verify_eggs.py` Regression guard for the EGG LIMIT contract. Reads `inventory.json` (`diet.egg_rules`) and `meal-history.json`, checks no meal exceeds `max_per_meal` and no day exceeds `max_meals_per_day_with_eggs`. Exits non-zero on violation. Run after any change to the egg logic or before sending a regenerated day. --- ## Where to shop (Eastbourne) User preference (confirmed): **Lidl is the preferred shop** — a weekly trip, so the extra distance is fine; best value and leaves budget for essential micronutrients and the occasional treat. Others (Tesco Express, Sainsbury's, Co-op, Londis) are comparison/fallback only. Grounded in Which? 2026 (93-item basket; Lidl ≈ £160.70 baseline): - Lidl ≈ 1.00x — cheapest, preferred - Tesco superstore w/ Clubcard ≈ 1.17x; Tesco Express ≈ 1.22x - Sainsbury's w/ Nectar ≈ 1.17x - Co-op ≈ 1.30x (membership = annual dividend) - Londis ≈ 1.25–1.35x (milk 2L £1.40 is a confirmed cheaper line) The weekly email shows a "Same basket — where else?" comparison (Lidl / Tesco Express Clubcard / Sainsbury's Nectar / Co-op member / Londis) as **estimates** from the Which? 2026 ratios + known local prices — no live web search. Default is a single Lidl shop; split only when a specific item's local price is *definitely* known lower. --- ## On-demand usage - **"What should I eat today?"** — read `inventory.json`, suggest now, offer to email. - **"Remove Wednesday's dinner from inventory"** — resolve the entry, decrement each logged item from `inventory.json` (remove if ~0), skip items not present, bump `updated`, confirm. Works per-item and partial-consumption overrides too. - **Adding stock** — list items in chat; they're added to `inventory.json` and an immediate 2-meal suggestion can follow. --- ## Testing & regression guards - `python3 scripts/verify_eggs.py` — asserts the EGG LIMIT holds across `meal-history.json` using the persisted `diet.egg_rules`. Should print `PASS: egg limit OK (max 4/meal, max 1 egg-meal/day)`. - The cuisine picker is deterministic per week (Monday-seeded) and was verified to produce no consecutive repeats across 8 simulated weeks. --- ## Git & sync This repo (`~/IdeaProjects/meal-suggestion/`) is the version-controlled mirror of the live skill (`/home/jp/.hermes/skills/meal-suggestion/`). - **Live data is git-ignored:** `inventory.json`, `meal-history.json`, `cuisine-rotation.json`, and `__pycache__/`. Only the spec, references, examples, and scripts are committed. - **Sync workflow:** copy the changed source files (currently `SKILL.md` and `scripts/`) from the live skill dir into this repo, then commit. Keep `.gitignore` excluding live data in both places. ```bash # from /home/jp/.hermes/skills/meal-suggestion cp SKILL.md /home/jp/IdeaProjects/meal-suggestion/SKILL.md cp scripts/pick_cuisine.py scripts/verify_eggs.py \ /home/jp/IdeaProjects/meal-suggestion/scripts/ # commit the mirror cd /home/jp/IdeaProjects/meal-suggestion git add -A && git commit -m "Sync: egg-limit hard rule + weekly cuisine rotation" git push ``` Version history of the mirror: - `72ce56e` Initial meal-suggestion skill (logic + template; live data git-ignored) - `c5f4fb1` Sync: food-health research, mackerel, easy/tasty + acuity, weekly health-coverage - `9e44876` Sync: strict inventory-only rule for daily meals - *(current)* Sync: EGG LIMIT hard rule + weekly cuisine rotation + scripts