Sync: EGG LIMIT hard rule + weekly cuisine rotation + scripts
- SKILL.md: add egg-limit hard rule (max 4/meal, one egg-meal/day), cuisine-rotation section - scripts/pick_cuisine.py: seeded weekly world-cuisine picker (stable per week, no recent repeats) - scripts/verify_eggs.py: regression guard for the egg cap - README.md: full project documentation - .gitignore: exclude cuisine-rotation.json + python caches (live data stays local)
This commit is contained in:
80
SKILL.md
80
SKILL.md
@ -45,6 +45,7 @@ Schema:
|
||||
## Meal suggestion rules
|
||||
- Suggest **two meals** per day (e.g. lunch + dinner) using only items currently in the inventory.
|
||||
- **Maximize protein** per meal; lead with a protein source.
|
||||
- **EGG LIMIT (hard rule):** Eggs are capped at **4 per meal** and **only ONE of the two daily meals may contain eggs** — the other meal MUST be egg-free. Never suggest more than 4 eggs in a single meal, and never put eggs in both meals. If a meal needs more protein than 4 eggs supply, close the gap with other proteins (lentils, beans, halloumi, cheese, tinned fish, whey) or a whey shake — do NOT add a 5th+ egg. This overrides the "lead with eggs" preference below.
|
||||
- **Avoid red meat for PURCHASES** (beef, lamb, pork, venison, bacon, gammon) — the
|
||||
weekly shopping list must NEVER suggest red meat to buy.
|
||||
- **If red meat is already in the inventory, it stays usable** in daily meal
|
||||
@ -53,6 +54,7 @@ Schema:
|
||||
- Preferred proteins (lead with these): eggs, chicken, turkey, fish, prawns, tofu,
|
||||
tempeh, lentils, beans, chickpeas, Greek yogurt, cottage cheese, quark, skim milk,
|
||||
whey, tinned tuna/salmon, edamame, halloumi.
|
||||
(Eggs are subject to the EGG LIMIT hard rule above: max 4 per meal, only one meal/day may include them.)
|
||||
- **Protein target:** ~**190–200 g/day** (user ~95–100 kg × 2 g/kg — burns recovery +
|
||||
preserving lean mass while losing ~15 kg fat). Aim each meal at **~90–100 g protein**
|
||||
and **STATE the per-meal and daily protein totals** in the email. If 2 meals can't
|
||||
@ -103,8 +105,35 @@ header, the two meal cards, and a footer note
|
||||
("Reply to tell me what you used and I'll update the inventory").
|
||||
Use the scaffold in `references/email-template.md`.
|
||||
|
||||
## Weekly cuisine rotation
|
||||
To keep shops and meals varied over time, each week's list is themed around a
|
||||
**randomly chosen world cuisine**. This is driven by
|
||||
`scripts/pick_cuisine.py` (same skill dir).
|
||||
|
||||
- Run `python3 scripts/pick_cuisine.py` at the **start of the weekly job**. It
|
||||
picks a cuisine seeded by the week's Monday, so it is **stable within a week**
|
||||
(re-runs/retries never flip the cuisine mid-week) but **varies week to week**
|
||||
and avoids the last 4 cuisines (no back-to-back repeats). It prints JSON with
|
||||
`cuisine` and `theme_items`, and persists the choice to `cuisine-rotation.json`.
|
||||
- The chosen cuisine must **theme the entire weekly basket**: lead the protein,
|
||||
veg, and 1–2 condiment/flavour buys with that cuisine's `theme_items`, so the
|
||||
shop introduces varied ingredients. Cuisines are all Lidl-achievable on ~£30,
|
||||
high-protein, and **red-meat-free by design**.
|
||||
- Do NOT abandon the core rules for the theme: still hit the protein target,
|
||||
still cover healing/brain/eyes/46yo micronutrients where possible, still
|
||||
default to Lidl, still never buy red meat, still keep the £30 budget. Theme
|
||||
items that are just flavour (spices, sauces, lime, herbs) are the optional
|
||||
"interest" buys — keep them low-price and behind protein + veg + staples.
|
||||
- Persist the active cuisine so the **daily meal job can read `cuisine-rotation.json`
|
||||
and on-theme meals from the same week's buys** (e.g. build a Korean bowl, a
|
||||
Mexican bowl, etc., from stock). The daily job should still respect the STRICT
|
||||
inventory rule — only cook with what's actually in inventory.json.
|
||||
- The rotation file keeps a short history; no manual tracking needed.
|
||||
|
||||
## Weekly shopping list (Mondays)
|
||||
- Budget **£30** total. Use realistic UK supermarket prices (Tesco/Asda/Sainsbury's).
|
||||
- **Pick this week's cuisine first** (see "Weekly cuisine rotation" above) and
|
||||
theme the basket around it — varied ingredients keep meals interesting.
|
||||
- Prioritize protein, then veg/fruit, then staples. Show item, est. price, and a
|
||||
running total that lands in range.
|
||||
- **APPLY the research** (`references/food-health.md`): build the basket so it covers
|
||||
@ -201,11 +230,54 @@ garlic, soy, etc.) and the optional "third hit" snack are NOT logged. Units matc
|
||||
6) Confirm what was removed. The user can also say "remove all of <day>" to drop both
|
||||
meals, or name a specific item to override.
|
||||
|
||||
**Item-level overrides & partial consumption (apply BEFORE step 3 above):**
|
||||
- If the user corrects a quantity for one item (e.g. "only used 100g halloumi, not
|
||||
200g"), use their stated qty instead of the logged qty for that item.
|
||||
- If the user says they only used PART of an item and the rest remains (e.g. "still
|
||||
got half a lettuce", "there's half a head of broccoli left"), do NOT remove the whole
|
||||
inventory entry — keep/restore the remaining portion (re-add at e.g. 0.5 count, or
|
||||
leave prior-qty minus what was used). Never zero out an item the user says they still
|
||||
have. Log the restored amount back into `inventory.json` and re-sort.
|
||||
|
||||
**Reconcile other meal-history entries AFTER removal (prevents stale future plans):**
|
||||
The daily job only writes TODAY's two meals at 09:00 — it never pre-plans future days.
|
||||
But `meal-history.json` may contain future-dated entries (pre-seeded, or written by a
|
||||
re-run). After any removal that deletes an inventory item (hits ~0), scan EVERY entry
|
||||
in `meal-history.json` — including future-dated ones inside the 7-day window — for
|
||||
`items` that reference the now-absent inventory name. For each such entry:
|
||||
- Flag it to the user: "<Day> <meal> ('<name>') still lists <gone item> but it's no
|
||||
longer in inventory."
|
||||
- Offer to rewrite that entry using ONLY in-stock items (substitute the missing item
|
||||
for an in-stock alternative, or drop it). Do NOT silently leave a plan that can't be
|
||||
cooked.
|
||||
This is the only guard against "Thursday's plan used chicken, but I ate the chicken
|
||||
today" — the design is correct; the stale future row is the anomaly, so fix the row,
|
||||
not the design. See `references/reconcile-history.md` for the scan snippet.
|
||||
|
||||
## On-demand (chat)
|
||||
If the user lists ingredients in chat, add them to the inventory and optionally
|
||||
give an immediate 2-meal suggestion. If they ask "what should I eat today?", read
|
||||
the inventory and suggest now (and offer to email it).
|
||||
|
||||
## Pitfalls (learned the hard way)
|
||||
- **"Regenerate / resend today's meals" means GENERATE FRESH, not replay.** When the
|
||||
user asks to regenerate a day's meals or re-send the email, do NOT pull the two meals
|
||||
back out of `meal-history.json` and re-format them. Re-read `inventory.json`, build
|
||||
two new meals from current stock (honouring the egg limit, STRICT inventory rule, etc.),
|
||||
then update today's `meal-history.json` entries to match what you actually send.
|
||||
The history is a log; it is not the source of truth for a regeneration request.
|
||||
- **Editing a cron job's prompt: edit jobs.json AND push via the `cronjob update`
|
||||
action.** The scheduler may hold jobs in memory, so a bare file edit is not enough —
|
||||
the running daemon won't see it. After writing the new prompt string, call
|
||||
`cronjob update job_id=<id> prompt="..."` so the in-memory job reloads. Verify with
|
||||
`cronjob list` that the preview reflects the change. (Pushing to a non-running daemon
|
||||
is harmless; not pushing is the silent-failure trap.)
|
||||
- **The egg rule is a HARD cap, not a preference.** The daily job leaned on eggs as the
|
||||
"preferred protein" and produced 8 eggs across two meals (6 + 4). Cured by (a) the
|
||||
EGG LIMIT prose rule above, (b) `diet.egg_rules` in inventory.json, and (c) running
|
||||
`scripts/verify_eggs.py` after any history/inventory change or before a resend.
|
||||
Always run that probe before sending a regenerated day.
|
||||
|
||||
## Automation
|
||||
Two cron jobs (created via the `cronjob` tool), both loading this skill:
|
||||
- Daily 09:00 — `0 9 * * *` — 2 meal suggestions.
|
||||
@ -214,6 +286,14 @@ Two cron jobs (created via the `cronjob` tool), both loading this skill:
|
||||
Delivery is `local` because the email itself is the deliverable; check
|
||||
`cronjob action=list` / logs if a send ever seems missing.
|
||||
|
||||
## Scripts (in this skill dir)
|
||||
- `scripts/pick_cuisine.py` — picks the week's random world cuisine (stable per week,
|
||||
varies weekly, no recent repeats); persists to `cuisine-rotation.json`. Used by the
|
||||
Monday weekly job (see "Weekly cuisine rotation").
|
||||
- `scripts/verify_eggs.py` — regression probe for the EGG LIMIT. Run after any change to
|
||||
`meal-history.json`, `inventory.json`, or the daily job, and before sending a
|
||||
regenerated day's email. Exits non-zero on violation. See "Pitfalls".
|
||||
|
||||
## References
|
||||
- `references/protein-sources.md` — allowed proteins + example pairings.
|
||||
- `references/email-template.md` — HTML email scaffold.
|
||||
|
||||
Reference in New Issue
Block a user