hermes d4c5a8f658 Add on-demand meal-log health/calorie analyzer + git-sync README trap pitfall
- scripts/analyze_history.py: read-only per-meal/per-day kcal+protein+micronutrient review
  of meal-history.json, unit-aware via references/nutrition-db.json (g_per_unit conversion so
  veg logged in g vs count both resolve correctly)
- references/nutrition-db.json: estimate per-item nutrition DB (ESTIMATE-grade; condiments/oil
  not logged => ~+80-120 kcal/meal real)
- SKILL.md: 'Health / calorie review (on-demand)' section; 'Git-sync README trap' pitfall
  (live README may be a stub while repo README is the real doc -- safe-sync procedure);
  scripts + references pointers
2026-08-29 08:11:16 +01:00

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 (~165175 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

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


Core rules

  • High-protein target: ~165175 g/day (≈1.7 g/kg at ~95100 kg, for burns recovery + lean-mass preservation during ~15 kg fat loss). Aim ~90100 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 12 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 / 1015 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: <name>.


Inventory & meal history

inventory.json — current stock. Schema:

{
  "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." }
  ]
}
  • categoryprotein, 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:

{ "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 · ~165175g/day · no red meat bought), two meal cards (name, ingredients, 23 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: <name>, 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.251.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.
# 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
Description
No description provided
Readme 163 KiB
Languages
Python 100%