Relax protein to 1.7 g/kg; add rational-portion guard + weekly protein floor
- diet.protein_per_kg 2 -> 1.7 g/kg (evidence ceiling for trained/resistance-adapted adults) daily target 190-200 -> 165-175 g/day, ~80-90 g/meal - weekly non-whey protein floor 1330 -> 1190 g (170 g/day x 7), still excl whey - SKILL.md: RATIONAL PORTIONS hard rule (per-item max_per_meal caps, no single-ingredient overload, honest shortfall reporting); WEEKLY PROTEIN FLOOR (>=3 sources, none >50%) - inventory.json: per-item max_per_meal caps + rational_portions defaults table - scripts/verify_portions.py: regression probe for overload/cap violations - README + references synced to 1.7 g/kg; .idea/.agentbridge gitignored
This commit is contained in:
86
SKILL.md
86
SKILL.md
@ -46,6 +46,35 @@ Schema:
|
||||
- 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.
|
||||
- **MEAL VARIETY (rule):** the two daily meals (lunch + dinner) should use **disjoint
|
||||
ingredient sets** — avoid repeating the same items across both meals. Build each meal
|
||||
from a different subset of the inventory so the day isn't the same plate twice. Only
|
||||
reuse an ingredient if stock genuinely forces it, and keep any reuse to the minimum.
|
||||
This often means leading one meal with a non-red-meat protein (lentils/beans/eggs/whey/
|
||||
cheese) and the other with a different protein (e.g. in-stock red meat being used up,
|
||||
or eggs if the first meal was egg-free). Honour this alongside the EGG LIMIT and the
|
||||
STRICT inventory rule.
|
||||
Do NOT inflate a single ingredient's quantity to an unrealistic amount just to avoid
|
||||
overlap (e.g. 2 cans of baked beans in one meal). If a fully-disjoint meal can't reach
|
||||
the protein target, close the gap with a whey shake / extra eggs (third hit) rather than
|
||||
unrealistic quantities.
|
||||
|
||||
- **RATIONAL PORTIONS (hard rule):** every meal must be a *realistic, balanced, interesting*
|
||||
plate — NOT overloaded with one ingredient. Each protein item has a `max_per_meal` cap in
|
||||
`inventory.json` (expressed in that item's own unit, e.g. Eggs 4, Cumberland sausages 6,
|
||||
Cheese 0.5 "small block" ≈ 60 g, Lamb mince 200 g, Lentils 150 g, Whey 50 g, Baked beans 1 can).
|
||||
The daily job MUST NOT exceed a cap in any single meal. If an item has no `max_per_meal`,
|
||||
fall back to `diet.rational_portions.default_caps` (keyword match) or `unit_fallback`
|
||||
(count→6, g→200, can→1). `scripts/verify_portions.py` converts caps and logged amounts to
|
||||
grams so a cap stored as "small block" is compared correctly against e.g. cheese logged in grams.
|
||||
OVERLOAD test (what makes a meal "silly"): ONE protein at >=70% of its cap AND supplying
|
||||
>=85% of that meal's protein — e.g. a bowl of 10 sausages. A balanced plate where the lead
|
||||
protein is capped but other proteins + veg contribute is NOT overload (a normal 4-egg meal
|
||||
passes). So spread across 3+ different proteins (plus veg, grain, dairy) so the plate is varied.
|
||||
**If capped + balanced meals still can't reach ~165 g/day with current stock, DO NOT fake it**
|
||||
— produce the best balanced meal you can, state the realistic total, and list the top 2–3
|
||||
things to buy (eggs, whey, chicken, tuna) to close the gap. Never suggest "10 sausages" or any
|
||||
single-ingredient overload. This rule overrides "maximize protein" — balance and realism beat a number.
|
||||
- **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
|
||||
@ -55,10 +84,11 @@ Schema:
|
||||
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**
|
||||
- **Protein target:** ~**165–175 g/day** (user ~95–100 kg × 1.7 g/kg — evidence ceiling for
|
||||
trained/resistance-adapted adults; burns recovery + preserving lean mass while losing ~15 kg
|
||||
fat). Aim each meal at **~80–90 g protein**
|
||||
and **STATE the per-meal and daily protein totals** in the email. If 2 meals can't
|
||||
reach ~190 g with current stock, say so and suggest a third hit (whey shake, extra
|
||||
reach ~165 g with current stock, say so and suggest a third hit (whey shake, extra
|
||||
eggs, Greek yogurt) to close the gap.
|
||||
- **Weight-loss framing:** keep meals calorie-moderate — bulk with veg, go easy on oven
|
||||
chips, white bread, and oil; lead with lean protein. Fat loss comes from a mild calorie
|
||||
@ -110,6 +140,15 @@ 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).
|
||||
|
||||
**Theme start rule (IMPORTANT):** the cuisine is picked by the **Monday weekly job**
|
||||
and it themes that week's **shop basket**, but it applies to **MEALS from TUESDAY
|
||||
onward**. Monday's daily meal suggestion is cooked **UNTHEMED** from whatever is
|
||||
currently in stock (the shop items aren't in inventory yet). From Tuesday the daily
|
||||
meal job cooks **on-theme**. If `cuisine-rotation.json` has no `current` cuisine set
|
||||
(e.g. cleared between weeks, or the rest of a week with no active theme), meals are
|
||||
unthemed. This keeps a fresh shop's new ingredients aligned with themed meals without
|
||||
forcing a theme onto the day the shop is only proposed.
|
||||
|
||||
- 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**
|
||||
@ -125,12 +164,23 @@ To keep shops and meals varied over time, each week's list is themed around a
|
||||
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.
|
||||
and cook on-theme meals from the same week's buys (TUESDAY–SUNDAY only; Monday is
|
||||
unthemed)** (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)
|
||||
- **WEEKLY PROTEIN FLOOR (hard rule):** the non-whey protein bought in the Monday shop MUST be
|
||||
enough to cover the week. Required = 170 g/day × 7 = **1190 g** of **non-whey** protein
|
||||
(whey is excluded — it is a third-hit top-up, not a meal base). Estimate the non-whey protein
|
||||
ALREADY in `inventory.json` (sum each protein/cheese/dairy item's qty × its per-g from
|
||||
`references/protein-sources.md`). The shop's bought non-whey protein must total at least
|
||||
`MAX(0, 1190 − in_stock)` g. **SPREAD it across ≥3 distinct protein sources** — no single
|
||||
source may exceed ~50% of the bought total (never "20 eggs only", never one tub). If £30 can't
|
||||
reach the floor, get as close as budget allows and STATE the shortfall + what to buy next.
|
||||
Record `bought_protein_g_excl_whey` and `stock_protein_g_excl_whey` on `last-shop.json` and
|
||||
show both in the email so weekly coverage is visible.
|
||||
- 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.
|
||||
@ -300,6 +350,20 @@ Note: this is entirely on-demand — the weekly job itself never writes inventor
|
||||
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.
|
||||
- **Don't auto-resend after a stock update.** When the user reports actual consumption or you
|
||||
correct `inventory.json` mid-day, update the inventory and STOP — do NOT volunteer a fresh
|
||||
meal email. The next scheduled 09:00 cron picks up the new stock automatically. Only
|
||||
regenerate + resend when the user explicitly asks (usually because they flagged a
|
||||
meal-QUALITY issue, e.g. unrealistic portions — "2 cans of baked beans is silly, be
|
||||
realistic"). They said plainly "I will wait for the cron job to run." Distinguish: a
|
||||
consumption report = update stock, no email; a meal-quality complaint = regenerate fresh.
|
||||
- **Portion probe scope + threshold (avoid false positives).** `verify_portions.py` defaults to
|
||||
TODAY only — run it that way before a send. Its OVERLOAD flag requires BOTH >=70% of one
|
||||
protein's cap AND >=85% of that meal's protein; a capped lead protein with real supporting
|
||||
proteins (e.g. a normal 4-egg meal) must NOT trip it. If you widen the threshold later you'll
|
||||
start flagging legitimate balanced plates — keep the >=85% dominance condition. `--all` is an
|
||||
audit of history, not a send gate; historical over-cap rows (old halloumi quantities) are
|
||||
already-sent and only fixed on explicit request.
|
||||
|
||||
## Automation
|
||||
Two cron jobs (created via the `cronjob` tool), both loading this skill:
|
||||
@ -316,6 +380,16 @@ Delivery is `local` because the email itself is the deliverable; check
|
||||
- `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".
|
||||
- `scripts/verify_portions.py` — regression probe for RATIONAL PORTIONS. By DEFAULT checks
|
||||
only TODAY's entries in `meal-history.json` (the meals the daily job just wrote, before
|
||||
sending). It flags (a) any protein used above its `max_per_meal` (or fallback) cap, and
|
||||
(b) single-ingredient OVERLOAD — one protein at >=70% of its cap that ALSO supplies >=85%
|
||||
of the meal's protein (a "pile of one thing", e.g. 10 sausages). Caps are stored in each
|
||||
item's own unit (e.g. Cheese 0.5 "small block"); the probe converts to grams so it can
|
||||
compare against logged grams (e.g. 40 g cheese). Run it alongside `verify_eggs.py` BEFORE
|
||||
sending a regenerated day. Pass `--all` to audit the full 7-day window (this WILL flag
|
||||
ALREADY-SENT historical rows like old 100–200 g halloumi — audit only, NOT a send gate;
|
||||
only fix history on explicit user request). Exits non-zero on violation.
|
||||
|
||||
## References
|
||||
- `references/protein-sources.md` — allowed proteins + example pairings.
|
||||
|
||||
Reference in New Issue
Block a user