.json`, run
`bun scripts/validate-menu.ts` against it, rebuild the demo
(`bun web-apps/demo/build.ts`), and stop. The agent does not send the menu,
schedule a review, or follow up.
---
Previous: [8. Personalise](08-personalise.md) ·
See it done: [Worked example](../menu/worked-example.md)
# Exchange lists
Loaded by steps 5, 7 ·
Owns every portion definition on this site ·
Never restate a value from here on a method page
The menu is built in **portions** , not grams. A
portion is a fixed quantity of macronutrient; the foods listed under it are
interchangeable because they deliver that same quantity.
This is what makes the framework work. When the client swaps grilled chicken for
lentils, the arithmetic behind their menu does not move.
## The six groups
Every food the menu uses belongs to exactly one group. The per-portion values
below are the ones step 5 divides by, and they are rounded — real foods vary,
and the rounding is deliberate so the client is counting spoons, not decimals.
| Group | Carb (g) | Protein (g) | Fat (g) | kcal |
|---|---|---|---|---|
| Starch | 15 | 3 | 1 | 80 |
| Protein | 0 | 7 | 1 | 35 |
| Fat | 0 | 0 | 5 | 45 |
| Vegetable | 5 | 2 | 0 | 25 |
| Fruit | 15 | 0 | 0 | 60 |
| Dairy | 12 | 8 | 3 | 100 |
!!! note "Two things about this table"
**The protein portion is lean.** 0–1 g of fat: fish, chicken breast, tofu,
white cheese. Fattier proteins are counted as protein *plus* fat — an egg
is 1 protein + 1 fat, 30 g of hard cheese is 1 protein + 1 fat. This keeps
the fat budget visible and spendable on olive oil and tahini rather than
hidden inside the protein.
**The kcal column is the conventional rounded figure**, not the exact
4/4/9 arithmetic of the macro columns — it reads about 3% low across a full
day's portions. That is expected and harmless, because
[step 5](../method/05-portion-exchanges.md) reconciles portion counts
against **macro grams**, never against the kcal column.
!!! note "7 g is small on purpose"
7 g of protein is one egg, not one chicken breast. It is the smallest unit
that composes cleanly across meat, dairy and legumes, which means a meal
target of [30–40 g of protein](menopause.md#protein-per-meal) lands on a
whole number of portions — 4 to 6 — rather than on a fraction.
The client never sees the number 7. They see. The portion is the agent's accounting
unit; food is the client's.
### Legumes count twice
Cooked legumes are the one food that appears in two groups, because half a cup
delivers both a starch portion and a protein portion. Count both. This is not a
bonus — it is why works as a main course and why
step 5's arithmetic does not break when a vegetarian pattern pushes protein onto
plants.
## Starch — 1 portion
| Food | Portion | Notes |
|---|---|---|
| Wholemeal bread | 1 slice, 30 g | |
| Pita, wholemeal | ½ small | |
| Cooked brown rice | ½ cup, 90 g | |
| Cooked wholemeal pasta | ½ cup, 80 g | |
| Cooked quinoa | ½ cup | Also ½ protein portion |
| Cooked buckwheat | ½ cup | Also ½ protein portion |
| Rolled oats | 3 tbsp dry, 30 g | |
| Sweet potato | 100 g, one small | |
| Potato | 100 g, one small | |
| Cooked legumes | ½ cup | **Also 1 protein portion** |
| Brown rice cakes | 2 | |
| Corn | ½ cup kernels | |
| Wholemeal couscous | ½ cup cooked | |
Refined versions of these — white bread, white rice, instant noodles — are not
listed and are not used. See [Food quality](food-quality.md).
## Protein — 1 portion
| Food | Portion | Notes |
|---|---|---|
| Fish | 30 g cooked | |
| Chicken or turkey | 30 g cooked | |
| Lean beef | 30 g cooked | Limit — see [Food quality](food-quality.md) |
| Egg | 1 whole | Carries ~5 g fat — count **1 protein + 1 fat** |
| Cottage cheese 5% | 50 g | |
| White cheese ≤5% | 50 g | |
| Tofu | 60 g | Phytoestrogen source — see [Menopause](menopause.md) |
| Edamame | ½ cup shelled | Phytoestrogen source |
| Cooked legumes | ½ cup | **Also 1 starch portion** |
| Hard cheese ≤9% | 30 g | Carries fat — count 1 protein + 1 fat |
| Greek yoghurt 3% | 100 g | |
| Sardines, tinned | 30 g | Calcium source — see [Supplements](supplements.md#calcium) |
| Protein powder | ⅓ scoop | Only if already used — see [Supplements](supplements.md) |
## Fat — 1 portion
| Food | Portion |
|---|---|
| Olive oil | 1 tsp |
| | Avocado oil |
| 1 tsp | |
| Avocado | 30 g, about ⅙ of a large one |
| | Raw tahini paste |
| 2 tsp | |
| Prepared tahini | 1 tbsp |
| | Almonds |
| 6 | |
| Walnuts | 3 halves |
| | Pumpkin seeds |
| 1 tbsp | |
| Ground flaxseed | 1 tbsp |
| | Peanut or almond butter |
| 2 tsp | |
| Hummus | 2 tbsp |
| | Olives |
| 8 | |
| Pesto | 2 tsp |
Only fats from [the approved list](food-quality.md#fats) appear here. Canola,
sunflower, corn and soybean oils and margarine are not portions of anything —
they are not used.
## Vegetable — 1 portion
100 g raw, or ½ cup cooked. Practically: a fist of salad, half a cucumber, a
medium tomato, a cup of leaves, half a cup of cooked broccoli.
Vegetables are the one group the client is told they may exceed. The count in
the menu is a **minimum**, not a budget, and the menu says so. Starchy
vegetables — potato, sweet potato, corn, peas — are not in this group; they are
starch.
Cruciferous vegetables are
called out separately in [Menopause](menopause.md#cruciferous) and should appear
in at least one option per day where that protocol applies.
## Fruit — 1 portion
| Food | Portion |
|---|---|
| Apple, pear | 1 medium |
| Berries | 1 cup |
| Orange, clementines | 1 large or 2 small |
| Banana | ½ medium |
| Grapefruit | ½ |
| Kiwi | 2 |
| Melon, watermelon | 1 cup diced |
| Grapes | 12 |
Two fruit portions a day, **tart fruits preferred** — apple, berries, citrus,
kiwi, grapefruit over banana, grapes and melon. Dried fruit is not a fruit
portion in this system; it sits with
[added sugars](food-quality.md#hidden-sugars).
## Dairy — 1 portion
| Food | Portion |
| --- | --- |
| Milk | 1 cup, 200 ml |
| Yoghurt, plain | 1 cup, 200 g |
| Kefir | 1 cup |
| Labneh | 100 g |
| Unsweetened soy milk | 1 cup — phytoestrogen source |
Sweetened yoghurts and dairy desserts are not dairy portions. Unsweetened
almond, oat and rice milks are close to macronutrient-free and are not counted
at all — they are treated as a drink, though oat milk carries enough starch that
a cup or more should be counted as ½ a starch portion.
## Foods with no portion
Some things the client eats are not in the system, and the menu should say so
rather than leave the client guessing.
- **Free**: water, herbal tea, green tea, black coffee, lemon, vinegar, herbs
and spices, garlic, raw leafy greens beyond the vegetable count.
- **Counted as its parts**: any composed dish. Shakshuka is 2 protein (eggs) +
2 vegetable + 1–2 fat. Break it down; do not estimate it whole.
- **Not in this system at all**: alcohol, sweetened drinks, confectionery,
fried food, processed meat. These have no portion because the menu does not
allocate any. Where a client's intake shows them, that is handled in
[step 8](../method/08-personalise.md), not by inventing an exchange.
# Food quality
Loaded by steps 7, 8 ·
Owns the fats, sugars and sweeteners lists ·
Applies to every option in every slot
The portion system says *how much*. This page says *which*. An option that hits
its portion counts using foods from the wrong side of these lists is a failed
option, not a compromise.
## Real food first
The menu is built on — food recognisable as
the thing it came from, cooked from components rather than opened. In practice
that means a bias toward plants: vegetables, fruit, whole grains, legumes, nuts
and seeds carry most of the volume, with animal protein present but not central.
Three things follow, and they are enforceable rather than aspirational:
1. **No ultra-processed items appear as options.** Anything whose ingredient
list carries added sugar, preservatives, emulsifiers, colourings or flavour
enhancers is out. This removes most packaged breakfast cereals, most
"protein" bars, flavoured yoghurts, processed meat and instant meals.
2. **Variety is a requirement, not a nicety.** The four options in a slot should
not be four arrangements of the same three ingredients. Across a week the
menu should reach a broad spread of plants — the micronutrient coverage that
makes a supplement conversation unnecessary comes from this and nothing else.
3. **Whole beats refined, every time.** Where the
[exchange lists](exchange-lists.md) name a grain, it is the wholemeal form.
There is no white-bread portion.
## Fats
Fat quality is where this menu departs most sharply from a generic plan, so the
lists are absolute rather than a preference ordering.
=== "Use"
- Olive oil
- Avocado oil
- Grapeseed oil
- Coconut oil
- Pumpkin seed oil
- Hemp oil
- Whole-food fats: avocado, olives, nuts, seeds, tahini
=== "Avoid"
- Canola / rapeseed oil
- Sunflower oil
- Corn oil
- Soybean oil
- Margarine
The avoid list is the practical problem, not the use list: those five oils are
what almost all Israeli restaurant, bakery and packaged food is made with. The
menu cannot control that, and should not pretend to. What it controls is what
the client buys and cooks with at home, and that is where the instruction is
aimed. Where `nut_meals_out` is high, say this explicitly in *Notes for you*
rather than writing options the client will eat out of the house anyway.
Frying is not a cooking method this menu uses. Baked, grilled, steamed, roasted,
raw.
## Hidden sugars
Added sugar is a whole-health problem, and the reason it needs a list is that
most of it does not arrive labelled "sugar". Everything below is an added sugar
for this menu's purposes:
| | |
|---|---|
| **Plain sugars** | sugar, brown sugar, demerara, dextrose, glucose, fructose |
| | **Syrups** |
| agave, cane syrup, maple, malt extract, glucose-fructose syrup, silan | |
| **Concentrated fruit** | dates, date paste, dried fruit, fruit juice concentrate |
| | **Malt** |
| malt, malted barley extract | |
| **Sugar alcohols** | anything ending *-tol* — sorbitol, maltitol, xylitol, isomalt |
!!! note "Why dates and silan are on this list"
They read as natural, and in a different framing they would be. Here they
are concentrated free sugar with a fibre content too low to change the
insulin response, and they are the single most common way an otherwise
careful Israeli diet carries a large added-sugar load. They are not
forbidden foods — they are simply not ingredients the menu builds with.
Sugar alcohols are on the list for a different reason: they are a common
trigger for bloating and wind. Where the systems review shows either, see
[Fibre & the gut](fibre-and-gut.md).
### Sweeteners
Where something must be sweetened: **stevia**, **monk fruit**, **erythritol**.
Nothing else. Erythritol is a sugar alcohol and is the exception to the rule
above — it is tolerated far better than the others, but drop it too if the
client reports bloating.
## Fibre
25–30 g a day, from food. This has its own page because it interacts with the
digestive answers in section 3 — see [Fibre & the gut](fibre-and-gut.md).
## Alcohol
The menu allocates no portions to alcohol. Where `health_alcohol_units` shows
regular intake, the *Notes for you* section states the position once, without
elaboration: alcohol is not part of the plan, and what the client does outside
the plan is theirs. The agent does not calculate an allowance, negotiate a
number, or moralise. If intake reaches a level that is a health matter in its
own right, that is a [red flag](red-flags.md), not a menu adjustment.
## The check step 9 runs
Before a menu ships, every named food in every option is checked against this
page:
- [ ] No refined grain
- [ ] No oil from the avoid list
- [ ] No added sugar under any of its names
- [ ] No sweetener other than stevia, monk fruit or erythritol
- [ ] No processed meat
- [ ] No fried item
- [ ] No ultra-processed packaged item
# Fibre & the gut
Loaded by step 7 ·
Reads sys_digestive_*, sys_bowel_* ·
Owns the fibre target and the ramp rule
**25–30 g of fibre a day, from food.** Not from a supplement, even where the
client already takes one — a fibre supplement in `health_supplements` is
recorded and left alone, not built around.
Fibre is doing three jobs at once here: it blunts the insulin response to the
starch portions, it feeds the microbiome, and it is most of what makes a
portion-controlled menu feel like enough food. A menu that hits its macros and
misses its fibre will be abandoned inside two weeks for reasons the client will
describe as willpower.
## Where it comes from
Hitting 25–30 g is not difficult once the menu is built from whole foods, but it
does not happen by accident either. Rough contributions per portion:
| Group | Fibre per portion |
| --- | --- |
| Legumes | 6–8 g |
| Vegetables | 2–3 g |
| Fruit (whole, with skin) | 2–4 g |
| Wholegrain starch | 2–3 g |
| Nuts, seeds, ground flaxseed | 2–3 g |
Five vegetable portions, two fruit, three wholegrain starch and one legume
portion reaches roughly 28 g without trying. Take out the legumes and it drops
to about 21, which is why legumes appear in most builds regardless of the
protein arithmetic.
## Fermented foods
Include a fermented food most days: yoghurt with live cultures, kefir,
sauerkraut, pickled vegetables in brine rather than vinegar, miso, tempeh
.
These pair with the fibre rather than substituting for it — the fibre feeds what
the fermented food delivers. Where `health_supplements` already includes
probiotics, the food still goes in the menu; the supplement is the client's
business.
## The ramp rule
!!! danger "Do not put a low-fibre eater on 30 g of fibre tomorrow"
The result is bloating, wind and abdominal discomfort, the client concludes
the menu makes them feel worse, and they are right.
Read the current intake from `nut_ffq` — the frequency of legumes, wholegrains,
vegetables and fruit — and from `nut_typical_day`.
| Current intake looks like | Start at | Then |
|---|---|---|
| Legumes and wholegrains most days | 25–30 g | Hold |
| Some vegetables, refined grains, legumes weekly or less | ~20 g | +5 g a week to target |
| Little of any of it | ~15 g | +5 g every two weeks to target |
In practice this is done by holding back legume and wholegrain portions in weeks
one and two, replacing them with lower-fibre members of the same group, and
saying so in *Notes for you*. The portion counts do not change — the foods
filling them do. Water intake matters more during a ramp, not less; see
[Hydration](hydration-and-behaviour.md).
## Reading the digestive answers
Section 3 asks about digestion in eleven separate yes/no fields. They change the
menu in specific ways.
| Field | `"Yes"` means |
|---|---|
| `sys_digestive_bloating` | Ramp fibre slowly. Cook cruciferous rather than serving raw. Drop sugar alcohols, including erythritol. Consider holding legumes to one portion a day at first. |
| | `sys_digestive_wind` |
| As bloating. Legumes soaked and well cooked; rinsed tinned legumes are better tolerated than home-cooked dried. | |
| `sys_digestive_constipation` | Fibre to the top of the range, water to the top of [its range](hydration-and-behaviour.md), ground flaxseed daily. Do not add a laxative or suggest one. |
| | `sys_digestive_diarrhoea` |
| Ramp slowly and stay at the low end. If persistent, this is a [red flag](red-flags.md) before it is a menu question. | |
| `sys_digestive_reflux` | Smaller evening meal, finish eating earlier in the window, keep the evening meal low in fat. The no-starch dinner already helps. |
| | `sys_digestive_nausea` |
| Smaller, more frequent meals. Do not stretch the gaps to 5 h. | |
| `sys_digestive_appetite` | Read alongside `nut_appetite`. Low appetite argues for calorie-denser options within the same portion counts. |
| | `sys_digestive_portions` |
| Reinforce [the 80% rule](hydration-and-behaviour.md#at-the-table) and the pace guidance. Handle gently — see [Scope & limits](../project/scope-and-limits.md). | |
| `sys_digestive_pain` | Not a menu adjustment. Note it and refer if persistent. |
| | `sys_digestive_vomiting` |
| [Red flag](red-flags.md). | |
| `sys_laxatives` = Daily/Weekly | Note it. Do not build around it, do not tell the client to stop. Refer. |
`sys_gi_urgent` — blood in stool, black stools, unexplained vomiting,
unintentional weight loss, difficulty swallowing — is a **stop condition** and
nothing on this page applies. See [Red flags](red-flags.md).
# Menopause
Loaded by steps 4, 8 ·
Triggered by sys_menses_status ·
Owns the elevated protein target
This protocol applies when `sys_menses_status` is **`"Perimenopausal"`** or
**`"Post-menopausal"`**.
It does not apply on age alone. A 52-year-old who answered `"Cycling
regularly"` gets the standard build; a 44-year-old who answered
`"Perimenopausal"` gets this one. The client's answer governs.
!!! warning "Pregnant and postpartum are not this"
`"Pregnant"` and `"Postpartum"` are [stop conditions](red-flags.md), not
protocol variants. Do not read them as adjacent cases and adapt.
## Anabolic resistance
The change that matters for a menu is that muscle becomes harder to build and
easier to lose. The same protein intake that maintained muscle before produces
less of a response after — the tissue is less sensitive to the signal, so the
signal has to be stronger.
Two consequences, and they are both about protein:
### Protein per kilogram
**1.5–1.8 g of protein per kg of body weight per day**, against the 1.2–1.6 g/kg
this menu uses otherwise.
Use body weight in kg — `meas_weight_kg`, or the self-reported fallback. Where
BMI is over 30, use an adjusted weight rather than actual: `ideal + 0.4 ×
(actual − ideal)`, taking ideal as the weight at BMI 25. Otherwise the target
lands somewhere no one is going to eat.
Pick within the range by training load:
| `act_sessions_per_week` | Target |
| --- | --- |
| 0–1 | 1.5 g/kg |
| 2–3 | 1.6 g/kg |
| 4–5 | 1.7 g/kg |
| 6+ | 1.8 g/kg |
This target **overrides the percentage split** in
[step 4](../method/04-macro-split.md). Protein is set from body weight first;
carbohydrate and fat divide what is left.
### Protein per meal
**30–40 g in each main meal** — not 90 g at dinner and a token amount at
breakfast. Distribution matters as much as the total here, because each meal is
a separate stimulus and one below the threshold is largely wasted.
In [portions](exchange-lists.md), 30–40 g is **4–6 protein portions** per main
meal. Snacks carry 1–2.
This is the constraint most likely to make a breakfast option fail. A slice of
bread with tahini and a coffee will not reach 30 g of protein; eggs, cottage
cheese, Greek yoghurt or a legume-based breakfast will. Where the client's
current intake (`nut_typical_day`) shows a carbohydrate-only breakfast, this is
the single biggest change the menu makes, and *Notes for you* should say so
plainly.
## Phytoestrogens
Plant compounds that provide some support as the body's own oestrogen
production declines. Aim for a source in the menu daily.
| Source | Portion | Counts as |
|---|---|---|
| Tofu | 60 g | 1 protein |
| Edamame | ½ cup shelled | 1 protein |
| Tempeh | 50 g | 1 protein |
| Unsweetened soy milk | 1 cup | 1 dairy |
| Ground flaxseed | **2 tbsp daily** | 2 fat |
Ground flaxseed is the easiest to place — two tablespoons stirred into yoghurt,
oats or a salad, every day. It must be **ground**; whole seed passes through
largely intact. It also carries fibre, which is doing separate work; see
[Fibre & the gut](fibre-and-gut.md).
Soy is the one to check before using. Where the client has a thyroid disorder in
`health_conditions`, keep soy to one portion a day and separate it from any
thyroid medication by several hours — and say in *Notes for you* that the timing
is a question for their doctor. That is the boundary: the menu can space foods
out, it cannot advise on medication.
## Cruciferous
Broccoli, cauliflower, cabbage, kale, Brussels sprouts, kohlrabi
.
At least one vegetable portion a day from this family, and it should appear in
one of the options for at least two slots so the client can actually get it.
Where the systems review shows bloating or wind (`sys_digestive_bloating`,
`sys_digestive_wind` = `"Yes"`), cruciferous vegetables are a common
contributor. Do not drop them — cook them rather than serving them raw, start at
one portion, and note it. See [Fibre & the gut](fibre-and-gut.md).
## What else changes, and what does not
**Changes**
- Protein target and its distribution, as above
- A daily phytoestrogen source
- Cruciferous vegetables specified rather than left to chance
- Calcium becomes worth counting — see [Supplements](supplements.md#calcium)
**Does not change**
- The energy calculation. There is no menopause multiplier in
[step 3](../method/03-energy-target.md); the equation already takes age.
- The meal schedule.
- The dinner-without-starch rule.
## Out of scope
Hormone therapy, and whether the client should be on it. Vitex, evening primrose
and maca — [Supplements](supplements.md) covers why these are recognised but not
recommended. Symptom management beyond the food itself: hot flushes, sleep,
mood. `sys_menses_symptoms` is read so the menu does not make things worse, not
so it can treat them.
# Hydration & eating behaviour
Loaded by step 6 ·
Owns the drinking schedule and the table rules ·
Appears in every menu, verbatim
Two sections of the client menu come from this page: **Drinking** and **At the
table**. They are the same in every menu, which is why they live here rather
than being composed per client.
They are not filler. Most of what makes a portion-controlled menu tolerable —
feeling full, digesting comfortably, not finishing a plate in six minutes — is
here rather than in the portion counts.
## Water
**11–15 cups a day** (roughly 2.5–3.5 L). Water, or green tea, or herbal tea.
Where to sit in the range:
- Toward 15 with high training volume, hot weather, high fibre, or a
constipation answer
- Toward 11 with low activity and a small body size
- Above 15 is not better and is not recommended
Read `nut_water` for the starting point. `"Under 1 L"` or `"No idea"` means the
change is large, and it needs the same graduated approach as fibre — it will
otherwise mean a week of getting up at night, and the client will stop.
## The drinking schedule
This is the part clients find surprising, so the menu states it as a schedule
rather than a principle.
| When | What |
|---|---|
| On waking, before anything else | 1–2 cups of water, on an empty stomach |
| 20–30 minutes before each meal | 2 cups |
| **During the meal** | **Nothing, or a few sips at most** |
| From ~1½ hours after the meal | Resume normally |
The gap around meals is deliberate: drinking with food dilutes what the
digestive system is doing and, in practice, is how a meal gets washed down
rather than chewed. The morning glass is separate — it is rehydration after a
night, and it comes well before the first meal, which does not arrive for
[another 90–120 minutes](../method/06-meal-schedule.md#waking).
## Caffeine
Read `nut_caffeine` and `nut_caffeine_latest`.
- Not on waking. Coffee belongs after the first meal, not before it.
- Nothing caffeinated after mid-afternoon where `life_sleep_quality` is 3 or
below, or `life_sleep_continuity` shows broken sleep.
- Where `nut_beverage_sugar` is `"Yes"`, the sweetener rules in
[Food quality](food-quality.md#sweeteners) apply to coffee too.
The menu does not tell the client to quit caffeine. Where intake is high — more
than four a day — it notes the interaction with sleep once and leaves it.
## At the table
Eight rules, and they go in the menu as a list. They are the whole of what this
menu says about *how* to eat.
1. **Eat without screens.** Attention on the food — its taste, its texture.
2. **Put the fork down between bites.** The single most effective one, and the
easiest to check.
3. **Chew thoroughly, and unhurried.** Digestion starts in the mouth, and what
is not done there is done less well further down.
4. **Do not eat stressed.** A meal eaten tense is digested badly. Better to wait
five minutes than to eat in that state.
5. **Eat slowly.** Satiety signalling runs about twenty minutes behind the
stomach. Eating faster than that means eating past full before knowing it.
6. **Stop at about 80% full.** Not stuffed, not still hungry — comfortable, and
able to imagine eating more without wanting to.
7. **Sit down.** Standing at the counter is not a meal, and it does not register
as one.
8. **One meal at a time.** No eating while cooking the next thing.
!!! note "Where the client's answers say to go carefully"
Rules 5, 6 and 8 touch on eating behaviour, and for some clients that is
sensitive ground. Where `nut_emotional_eating` is `"Often"`, or
`nut_disordered_history` is `"Prefer not to say"`, keep this list to rules
1, 2, 3, 4 and 7 — the mechanical ones — and drop the fullness and pace
rules entirely. Where `nut_disordered_history` is `"Yes"`, the menu is not
being written at all; see [Scope & limits](../project/scope-and-limits.md).
## Why these are not optional extras
Rules 2, 5 and 6 are the mechanism by which the portion counts actually work. A
client eating at speed will finish the portioned plate and still feel hungry,
conclude the portions are too small, and add to them. The same plate eaten
slowly registers as enough. The arithmetic in steps 3 to 5 assumes this page is
being followed; without it the menu is a calorie target being enforced by
willpower, which is a different and much worse plan.
# Supplements
Loaded by step 9 ·
Reads health_supplements, health_supplements_detail ·
Never recommends
!!! danger "This page is for recognising, not prescribing"
The agent does not recommend supplements, does not set doses, and does not
tell a client to start or stop one. That is a clinician's call and this menu
is not clinical.
What this page is for: the client has told you in `health_supplements` what
they already take. The menu must not contradict it, must not duplicate it,
and must not silently undermine it. That requires knowing what the things
are.
## What the menu actually does with this
Three things, and nothing else:
1. **Avoids contradiction.** A client on iron is told, in *Notes for you*, not
to take it with the coffee or the dairy portion — a timing note, not a dosing
one.
2. **Avoids duplication.** A client already taking a fibre supplement or protein
powder does not need the menu to solve the same problem twice, and protein
powder that is already in use can be counted as
[a protein portion](exchange-lists.md).
3. **Notes where food covers it.** Where the menu already delivers what a
supplement is for, that is worth saying — it is the client's decision what to
do about it.
Everything else goes in one line in *Notes for you*: which supplements were
noted, and that any change to them is a conversation with their doctor or
dietitian.
## What the things are
Reference only. Presence here is not endorsement.
### Calcium
1000–1200 mg a day, from **diverse sources** rather than one. This is the one
the menu can genuinely influence, and it matters most where
[the menopause protocol](menopause.md) applies.
Food sources worth placing: dairy portions (~250 mg each), tahini
( — very high), sardines with bones, almonds,
tofu set with calcium, leafy greens, fortified plant milks. Two dairy portions
plus tahini daily gets most of the way there.
### Vitamin D
Fat-soluble, so absorption is materially better taken **with a fat portion** —
alongside olive oil, avocado or nuts rather than on an empty stomach. That is a
timing note the menu can make. The dose is not.
Often paired with **K2** and **magnesium**, which is a common protocol and not
one this menu has an opinion on.
### Omega-3
1–2 capsules daily is the usual form. Food sources — oily fish, walnuts, ground
flaxseed, hemp — are in the [exchange lists](exchange-lists.md) and the menu
should place them regardless of whether a capsule is also being taken.
### Magnesium
Frequently taken for sleep and cramps. Food sources: pumpkin seeds, almonds,
dark leafy greens, legumes, buckwheat.
### Vitamin B complex / B12
B12 is the one to be alert to. Where `nut_pattern` is `"Vegan"` — or
`"Vegetarian"` with very low dairy and egg frequency in `nut_ffq` — B12 cannot
be covered by this menu, and *Notes for you* should say that plainly and point
at their doctor. That is a statement of a limitation, which is in scope; a dose
is not.
### Iron
Absorption is helped by vitamin C in the same meal and hindered by tea, coffee,
calcium and dairy. The menu can and should separate them by an hour or two.
Where the client is menstruating with `sys_menses_flow` of `"Heavy"` or `"Very
heavy"`, that is a [referral](red-flags.md), not a menu adjustment.
### Collagen
10–12 g daily is the usual amount. Contributes protein, but as an incomplete
protein it is not counted toward
[the protein portions](exchange-lists.md) or the per-meal target.
### Creatine
Commonly used, well studied for strength and increasingly for older women.
Requires adequate water — relevant given
[the hydration target](hydration-and-behaviour.md). No menu adjustment.
### NAC, curcumin
Recognise, note, no menu implication.
### Sports supplements — BCAA, protein powder
Protein powder is [a protein portion](exchange-lists.md) at ⅓ scoop and can be
used in options where the client already has it. BCAAs are redundant against a
menu hitting its protein target, which is worth one sentence at most.
### Evening primrose, maca, vitex (chaste tree)
Taken for menopausal and cycle symptoms. Vitex in particular is
hormonally active and interacts with hormonal contraception and HRT.
**Recognise and note. Never suggest.** Where a client is already taking vitex
and is also on hormonal contraception or HRT, that is a note to raise it with
their doctor — nothing more.
## The line, restated
| | |
|---|---|
| **In scope** | "Take your vitamin D with the meal that has the fat portion." |
| **In scope** | "This menu cannot cover B12 on a vegan pattern — worth raising with your doctor." |
| **In scope** | "Your iron is best kept away from the coffee." |
| **Out of scope** | "You should take magnesium." |
| **Out of scope** | "500 mg twice a day." |
| **Out of scope** | "You can stop the collagen, the menu covers it." |
# Red flags
Loaded by step 2 ·
Owns every stop condition ·
Effect a referral note replaces the menu
A red flag is a field value that means **no menu is written**. Not a caveated
menu, not a conservative menu, not a menu with a warning at the top. A referral
note instead.
The list is exhaustive. A concern not on it is not a stop condition — note it
and proceed.
## Stop conditions
### Medical clearance and consent
| Trigger | Condition |
|---|---|
| `consent_accuracy` | absent or not `"Yes"` |
| | `consent_data_processing` |
| absent or not `"Yes"` | |
| `consent_clearance_confirm` | not `"Yes"`, **and** any PAR-Q trigger below fired |
PAR-Q triggers: `parq_1_heart` = `"Yes"`, `parq_3_dizziness` = `"Yes"`,
`parq_4_condition` = `"Yes"`, `parq_5_medication` = `"Yes"`, `parq_6_msk` =
`"Yes"`, `parq_2a_breathlessness` ≠ `"No"`, `parq_2b_heart_rate` ≠ `"No"`.
These are **conditional stops** — the referral note says what would unblock
them.
### Conditions requiring a registered dietitian
| Trigger | Condition |
|---|---|
| `health_conditions` | contains `"Type 1 diabetes"` |
| `health_diabetes_mgmt` | contains `"Insulin"` |
| `health_conditions` | contains `"Kidney disease"` |
| `health_conditions` | contains `"Liver disease"` |
| `health_conditions` | contains `"Cancer (current or past)"` — **and** treatment is current or recent per `health_practitioners_detail` |
| `nut_medical_diet` | `"Yes"` |
Type 2 diabetes and pre-diabetes are **not** stops unless insulin-managed. Build,
and state in the menu that carbohydrate targets need their clinician's review.
### Eating disorder
| Trigger | Condition |
| --- | --- |
| `nut_disordered_history` | `"Yes"` |
`"Prefer not to say"` is not a stop. It triggers the numberless adaptation in
[Scope & limits](../project/scope-and-limits.md).
### Pregnancy and postpartum
| Trigger | Condition |
|---|---|
| `sys_menses_status` | `"Pregnant"` or `"Postpartum"` |
| | `health_births` |
| describes a birth within the last 6 months | |
| `sys_pregnancy_plans` | `"Yes"` — **not a stop**, but see below |
Planning a pregnancy is not a stop. It is a note: folate, iron and B12 are worth
raising with their doctor before conception.
### Gastrointestinal
| Trigger | Condition |
| --- | --- |
| `sys_gi_urgent` | any value other than `"None of these"` |
Blood in stool, black or tarry stools, unexplained vomiting, unintentional
weight loss, difficulty swallowing. Any one of these. This is the most important
entry on the page, because these are the answers a client is most likely to have
given without thinking they matter.
| Trigger | Condition |
| --- | --- |
| `sys_digestive_vomiting` | `"Yes"` |
| `sys_digestive_diarrhoea` | `"Yes"` **and** `sys_digestive_pain` = `"Yes"` |
### Unintentional weight loss
Where `nut_typical_day` or any free-text field describes unexplained weight
loss, stop — regardless of whether `sys_gi_urgent` was ticked.
### Cardiovascular, found in the measurements
| Trigger | Condition |
| --- | --- |
| `meas_bp_systolic` | ≥ 180 |
| `meas_bp_diastolic` | ≥ 110 |
| `sys_cardio` | contains `"Chest pain"` or `"Breathlessness at rest"` |
A high reading here is a same-week medical matter and takes precedence over
everything else on the page. Systolic 140–179 or diastolic 90–109 is **not** a
stop — build, and note it.
### Under 18
| Trigger | Condition |
| --- | --- |
| `client_dob` | age under 18 at the date of writing |
Growth changes the energy calculation and the whole basis of the portion system.
### Severe allergy without detail
Where `health_allergies` or `nut_intolerances` describes anaphylaxis or a
severe reaction without naming the food clearly enough to exclude it reliably,
stop. This is a **conditional stop** — the note asks for the specific food.
## Not stops
Listed because they look like they might be:
- High blood pressure, high cholesterol, thyroid disorder, osteoporosis,
arthritis, asthma — diet-relevant, not stops
- Type 2 or pre-diabetes, not insulin-managed
- Anxiety, depression — note; do not build around
- Perimenopause, post-menopause — [a protocol](menopause.md), not a stop
- Coeliac, IBS, lactose intolerance — constraints
- Currently seeing a dietitian (`health_practitioners`) — **not** a stop, but
the menu says it should be shared with them
- Any single digestive symptom other than vomiting
- Heavy alcohol intake — note, refer, still build
- Smoking
## The referral note
What replaces the menu. Same file path, same naming, English, and short — a
page at most.
```markdown
# Referral — [name]
Date: YYYY-MM-DD
## What stopped this
[One or two plain sentences. The finding, not the field ID. No diagnosis,
no speculation about cause.]
## What to do now
[Who to see, and how soon. Specific: "your GP, this week".]
## Why no menu was written
[One sentence. That this needs a professional the agent is not, and that
a menu written now could get in the way.]
## What would let this continue
[Only for conditional stops — the consent, the clearance, the missing
detail. Omit the section entirely otherwise.]
```
Four rules for writing it:
1. **Name the finding, not the field.** "You mentioned blood in your stool", not
"`sys_gi_urgent` was non-empty".
2. **Do not diagnose or speculate.** Not what it might be, not that it is
probably nothing, not that it is serious. The referral is the whole message.
3. **Give a real timeframe.** "Soon" is not one. The GI and blood-pressure flags
are this-week matters; a consent gap is not.
4. **Do not attach any of the menu.** Not the energy target, not the portion
counts, not "some general guidance in the meantime". If the pipeline stopped,
it stopped.
## Precedence
Where more than one fires, the note covers **all** of them, ordered most urgent
first: GI and cardiovascular, then the dietitian referrals, then consent. A
client who needs a doctor this week and has also not signed a consent form needs
to read about the doctor first.
# Menu style guide
Loaded by step 9 ·
Owns the client-facing register and vocabulary, in both languages ·
Applies to the menu and the referral note
The method pages decide what the menu contains. [The schema](schema.md)
decides its shape. This page decides how it reads — in English and in Hebrew,
because every client-facing string in
[a `localizedText`](schema.md#localizedtext) is both.
## Write both languages together
Not an English draft followed by a translation pass. Write an option's name,
then its Hebrew name, then move to the next food item — so neither language
quietly becomes the source of truth the other is translated *from*. A phrase
that is awkward to say in Hebrew is often a sign the English was already too
clever; writing them side by side catches that immediately, where a translation
pass at the end would not.
## Register
Second person, warm, direct. The client is an adult being handed a plan, not a
patient and not a project. In Hebrew this means choosing and holding a gender
throughout — see below.
- Write "eat", not "consume" — לאכול, not
לצרוך.
- Write "breakfast", not "the first meal of the day" —
ארוחת בוקר, not
הארוחה הראשונה ביום.
- No exclamation marks. No emoji. No encouragement that is not information —
"you've got this" tells the client nothing they can act on, in either
language.
- No hedging. "Finish dinner by 20:00", not "you might want to try to finish
dinner by around 20:00 if you can".
### Hebrew gender
Hebrew's second person is gendered — there is no neutral form. Read
`client_gender` and hold it for the whole file:
| `client_gender` | Address |
|---|---|
| `"Female"` | את — feminine throughout |
| `"Male"` | אתה — masculine throughout |
| `"other"` or absent | Impersonal constructions: מומלץ, כדאי, אפשר. Avoid direct address rather than guessing |
A file that opens את יכולה and drifts to
אתה יכול partway through reads as written for someone
else. Check every verb and every adjective before a menu ships — the schema
cannot catch this, because both forms are grammatically valid Hebrew.
### Plainness is a requirement, in both languages
The reader may not be fluent in either, and they are reading instructions, not
prose.
| Instead of | Write |
|---|---|
| "Aim to incorporate a source of protein" | "Have one of these at every meal" |
| "A legume serving" | "Half a cup of cooked lentils" |
| "Optimise your hydration" | "Drink two cups of water" |
| "Nutrient-dense whole foods" | Name the foods |
| "Approximately 30–40 grams" | "About 30–40 grams" |
Short sentences. One instruction per line. No idiom, no metaphor, no wordplay —
the first things to fail in translation, so avoided in both directions rather
than smoothed over afterward.
## Vocabulary
Use one name for one thing, every time, in both languages. A menu that calls
the same thing two names is a menu the client has to decode.
### Structure
| English | Hebrew | Not |
|---|---|---|
| Menu | תפריט | Meal plan, diet, programme |
| Portion | מנה · plural מנות | Serving, exchange, unit |
| Meal | ארוחה | Feeding, eating occasion |
| Breakfast | ארוחת בוקר | Meal 1 |
| Lunch | ארוחת צהריים | Meal 2 |
| Dinner | ארוחת ערב | Meal 3 |
| Snack | ארוחת ביניים | Mini-meal, nibble |
| Option | אפשרות | Choice, alternative, variant |
ארוחת ביניים, not נשנוש — a
snack is a scheduled meal in this menu, and the surrounding text should say so
in both languages: "the 11:00 snack" /
ארוחת הביניים של השעה 11:00, not "if you get peckish".
### Food groups
| English | Hebrew |
|---|---|
| Starch | פחמימה |
| Protein | חלבון |
| Fat | שומן |
| Vegetables | ירקות |
| Fruit | פרי · plural פירות |
| Dairy | מוצרי חלב |
Exactly as [the exchange lists](../reference/exchange-lists.md) name them in
both languages. Not "carbs" for starch, not "veg" for vegetables, not
פחמימות טובות for anything. The client is counting
portions of six named groups, and the names have to match what they are
counting against.
### Measures
| English | Hebrew |
|---|---|
| Cup | כוס |
| Half a cup | חצי כוס |
| Tablespoon | כף |
| Teaspoon | כפית |
| Slice | פרוסה |
| Gram | גרם |
| Handful | חופן |
Spell fractions out in prose in both languages — "half a cup" /
חצי כוס, not `½ cup`. Grams for meat, fish and cheese;
household measures for everything else, because those are the things a client
already owns in either kitchen.
## Writing a food item and an option
Every food in the menu is one [`foodItem`](schema.md#fooditem): a `food` name,
a `qty`, and the portions it delivers — each a `localizedText`, written
together per food, not per language.
```json
{
"food": { "en": "cooked lentils", "he": "עדשים מבושלות" },
"qty": { "en": "½ cup", "he": "חצי כוס" }
}
```
An [`option`](schema.md#option) is a short bilingual name plus its list of
items:
```json
{ "en": "Rice and lentils", "he": "אורז ועדשים" }
```
Four rules, unchanged by the format:
1. **Lead with a name.** "Rice and lentils" / אורז
ועדשים — so the client can refer to it and remember it, in whichever
language they're reading.
2. **Every quantity measurable.** Not "some", not "a portion of", not "a
handful of vegetables" where a count exists — in either language.
3. **List items in plate order** — the starch, then the protein, then the
vegetables, then the fat. Consistent order across every option makes them
comparable at a glance, and the order does not change between languages.
4. **One food per item.** A composed dish is broken into its parts — see
[the exchange lists](../reference/exchange-lists.md#foods-with-no-portion)
— not folded into one `food` string with two quantities in it.
## Tone in the notes
`notes` — what a client sees as "the things we adjusted for you" — is the only
part of the menu that explains itself, and it is where tone goes wrong in
either language.
- **State the change, then the reason.** "Breakfast moves from 11:00 to 08:15
and gains protein — that is what holds your energy to lunch." /
ארוחת הבוקר עוברת מ-11:00 ל-08:15 ומקבלת יותר חלבון — זה מה
שמחזיק את האנרגיה עד הצהריים.
- **Never comment on the client's body**, their weight, their shape or their
eating history. Not critically, and not approvingly either.
- **Never moralise about food.** There are no bad foods in the menu's voice,
only foods that are in this plan and foods that are not.
- **Say a limitation plainly.** "This menu cannot cover B12 on a vegan pattern
— worth raising with your doctor" is the right shape in both languages. Do
not soften it into vagueness in either.
- **One sentence per note.** A note that runs to a paragraph is doing a
reference page's job.
## What never appears in the menu
- **Field IDs.** Not once, in either language.
- **The agent's arithmetic.** No kcal, no grams of macronutrient, no g/kg, no
activity factor. `dailyTarget` exists in the file for
[validation](schema.md#the-arithmetic-the-schema-cant-express); it is not
rendered. The client sees portions and food.
- **A weight, BMI or body-fat figure** where `meas_weight_aware` or
`meas_blind_weigh` said not to.
- **A promised outcome.** No predicted weight, no timeline, no "you will".
- **A diagnosis, or any interpretation of a test.**
## The disclaimer
Fixed text, both languages, at the schema's `disclaimer` key, pinned by
`const` so it cannot be edited and still validate:
```json
"disclaimer": {
"en": "This menu was built from what you filled in on the questionnaire. It is not nutritional therapy, not a medical diagnosis, and not a substitute for advice from a doctor or a registered dietitian. If you have a medical condition, take medication, or something does not feel right, talk to them before you start.",
"he": "התפריט הזה נבנה על סמך מה שמילאת בשאלון. הוא אינו טיפול תזונתי, לא אבחנה רפואית, ולא תחליף לייעוץ עם רופא או דיאטניתית קלינית. אם יש לך מצב רפואי, את/ה נוט/לת תרופה, או שמשהו לא מרגיש בסדר — כדאי להתייעץ לפני שמתחילים."
}
```
The Hebrew keeps the את/ה form regardless of the
client's gender — it is the one string in the file that is not addressed
personally, so it does not follow the gender rule above.
# JSON schema
Loaded by step 9 ·
File menus/schema/menu.schema.json ·
Authoritative for a menu's structure
The blank a menu is written into. Not a template to copy and fill in by hand —
a [JSON Schema](https://json-schema.org/) (2020-12) that
[`scripts/validate-menu.ts`](#the-arithmetic-the-schema-cant-express) checks
every `menus/*.json` file against before it counts as finished.
## Why a schema and not a template
A Markdown template is a suggestion — nothing stops a menu drifting from it one
edit at a time. A schema is a gate: a file that doesn't match it doesn't
validate, and `bun scripts/validate-menu.ts menus/*.json` says exactly why.
Three rules that used to be enforced by careful reading are now enforced
structurally:
- **The disclaimer can't be edited.** Its `localizedText` is declared with
`const`, so a shortened, reworded or removed disclaimer fails to validate.
- **A menu can't be missing its Hebrew.** Every client-facing string is a
`localizedText` — `{ "en": "...", "he": "..." }` — and both keys are
required, non-empty.
- **Dinner can't carry starch or fruit.** The schema's `dinner` slot fixes
`portions.starch` and `portions.fruit` to `0` via an `if`/`then`.
## The shapes
### `localizedText`
```json
{ "en": "cooked lentils", "he": "עדשים מבושלות" }
```
Every piece of client-facing text in the file — food names, quantities, option
names, notes, the disclaimer — is one of these. Nothing is optional; a menu
that only has the English half is not a finished menu.
### `portionCounts`
```json
{ "starch": 2, "protein": 2, "fat": 2, "veg": 1, "fruit": 0, "dairy": 1 }
```
All six [exchange groups](../reference/exchange-lists.md), always present, zero
where a group is unused. This is the shape of `dailyPortions`, of every slot's
`portions`, and of every food item's own `portions`.
### `foodItem`
```json
{
"food": { "en": "cooked lentils", "he": "עדשים מבושלות" },
"qty": { "en": "½ cup", "he": "חצי כוס" },
"portions": { "starch": 1, "protein": 1, "fat": 0, "veg": 0, "fruit": 0, "dairy": 0 }
}
```
One food, at one quantity, contributing to one or more portion groups. A
legume item's `portions` carries both `starch: 1` and `protein: 1` in the same
object — see
[step 5](../method/05-portion-exchanges.md#legumes-are-counted-twice-and-that-is-not-a-rounding-error).
### `option`
```json
{
"name": { "en": "Rice and lentils", "he": "אורז ועדשים" },
"items": [ /* foodItem, foodItem, ... */ ]
}
```
One of a slot's 3–4 interchangeable choices. Its `items` must sum, group by
group, to exactly its slot's `portions` — see below.
### `slot`
```json
{
"id": "lunch",
"time": "13:00–13:30",
"label": { "en": "Lunch", "he": "ארוחת צהריים" },
"portions": { "starch": 4, "protein": 3, "fat": 2, "veg": 3, "fruit": 0, "dairy": 0 },
"options": [ /* 3-4 option objects */ ]
}
```
`slots` is a fixed-length array of five, in a fixed order —
`breakfast, snack1, lunch, snack2, dinner` — each `id` pinned to its position
by the schema, and `dinner` additionally constrained as above.
### The top level
`type` discriminates a `menu` from a `referral` — see
[the output contract](../project/output-contract.md#required-structure) for
what each top-level key is for. A file is exactly one or the other; the schema
is a `oneOf` over the two shapes.
### `referral`
```json
{
"type": "referral",
"client": { "name": "…", "slug": "…" },
"date": "2026-08-23",
"whatStopped": { "en": "…", "he": "…" },
"whatToDoNow": { "en": "…", "he": "…" },
"whyNoMenu": { "en": "…", "he": "…" },
"whatWouldContinue": { "en": "…", "he": "…" }
}
```
Where [step 2](../method/02-safety-screen.md) stops the run — see
[the referral note](../reference/red-flags.md#the-referral-note). No `slots`,
no `dailyPortions`: a referral has none of a menu's structure, only these four
required fields plus `whatWouldContinue`, which exists only for a
**conditional** stop — omit the property entirely for an unconditional one,
rather than setting it to an empty string.
## The arithmetic the schema can't express
JSON Schema has no arithmetic — it cannot sum a list of numbers and compare the
total to another number, which is most of what makes a menu correct.
[`scripts/validate-menu.ts`](https://github.com/) compiles the schema with
[ajv](https://ajv.js.org/) for the structural half, then runs the checks that
need real arithmetic, using the same exchange values as
[the reference page](../reference/exchange-lists.md):
- **Every option's items sum to its slot's `portions`**, group by group — the
check from [step 9](../method/09-assemble-and-check.md#arithmetic), run on
every option rather than by hand.
- **The five slots' `portions` sum to `dailyPortions`.**
- **`dailyPortions`, converted through the exchange values, reconciles with
`dailyTarget`** within the tolerance from
[step 5](../method/05-portion-exchanges.md#6-reconcile) — ±5 g protein,
±10 g carbohydrate, ±5 g fat.
- **Each main meal (`breakfast`, `lunch`, `dinner`) delivers 30–40 g of
protein** — [step 4](../method/04-macro-split.md#per-meal-protein).
```bash
bun scripts/validate-menu.ts menus/*.json
```
A file that fails either half — the structural schema or the arithmetic — is
not finished, whichever step of [the method](../method/01-read-the-intake.md)
produced it.
## What the schema does not check
It cannot know a client's exclusions — a peanut allergy, a disliked food — so
it cannot catch one appearing inside an option. That check is
[step 9](../method/09-assemble-and-check.md)'s, done by searching the finished
file for every excluded food by name. It also cannot judge whether four
options in a slot are meaningfully different from one another, or whether a
translation reads naturally rather than just being present. Those stay human
judgement calls.
## Rendering
This schema describes data, not a page. What a client actually sees is built
by [`web-apps/demo/build.ts`](../../web-apps/demo/) from this file — see its
own `README.md` for how a `menu` and a `referral` are each rendered, and how
the Hebrew half is chosen for display.
# Worked example
Client persona A, mock/answers-noa-barlev.json ·
Output menus/noa-barlev-2026-08-23.json ·
Run on 2026-08-23
One complete run of [the pipeline](../project/how-it-works.md), from a real
answers file to a finished menu, with the numbers shown at every step.
The answers are in `mock/answers-noa-barlev.json` and the menu is in
`menus/noa-barlev-2026-08-23.json`, validated against
[the schema](schema.md) by `scripts/validate-menu.ts`. Both are in the
repository; this page is the reasoning between them.
## Step 1 — the digest
```
Who 52 y · female · Tel Aviv · product manager · seated most of the day
Screening PAR-Q: 4_condition Yes, 5_medication Yes (thyroid) — clearance
confirmed. Conditions: high cholesterol, thyroid disorder.
GI urgent: none. Disordered eating: No. Menses: Perimenopausal.
Consents: all Yes.
Body 164 cm · 78 kg (measured) · BMI 29.0 · waist 92 · BP 132/84
Activity Active · 3 sessions/wk · ~45 min · sedentary day · seated job
Goal Lose body fat + Build muscle + Feel better + Improve health markers
Eating First food 11:00 · last meal 21:30 · 4 occasions · water 1–2 L ·
caffeine 4/day incl. on waking · FFQ: legumes Occasionally,
plant proteins Never, pastries Daily, processed meat few×/wk
Limits No pattern · lactose (milk only; yoghurt/labneh/hard cheese fine) ·
no fish except tuna and salmon · no coriander, no aubergine ·
cooking confidence 3 · 4 meals out/wk · kids eat differently
Symptoms Bloating · wind · constipation · reflux · afternoon crash ·
sleep 6–7 h, quality 3, wakes tired · stress 7
Gaps No budget given · no body-fat measurement · no waist follow-up
```
## Step 2 — the screen
Two PAR-Q triggers fired, so `consent_clearance_confirm` was required — and it
is `"Yes"`. Every stop condition checked; none fired.
`sys_gi_urgent` is `"None of these"`, `nut_medical_diet` is `"No"`,
`nut_disordered_history` is `"No"`, diabetes is absent, BP is 132/84 which is
below the 180/110 stop. **Clear.**
Carried forward: perimenopausal → step 4. Thyroid → soy limit. Bloating, wind,
constipation, reflux → step 7. Lactose, fish, coriander, aubergine → step 8.
High cholesterol → no special framing. BP noted.
## Step 3 — energy
```
BMR = (10×78) + (6.25×164) − (5×52) − 161 = 1384 → 1380
TDEE = 1380 × 1.55 = 2139
(1.55: 3 sessions/wk; 45 min pulls neither way; sedentary day
does not override the training band)
Goal = "Lose body fat" + "Build muscle" → deficit wins → −15% → 1818
Floor = max(1380 × 1.1, 1400) = 1518 — not binding
TARGET = 1800 kcal/day
```
## Step 4 — macros
Perimenopausal, so the [menopause protocol](../reference/menopause.md) sets
protein from body weight: 3 sessions/week → 1.6 g/kg, plus 0.1 for the deficit
→ **1.7 g/kg**. BMI 29.0 is under 30, so actual weight is used.
```
Protein 78 × 1.7 = 133 g → 532 kcal → 29.6% ✓ inside 20–30%
Fat 27.5% of 1800 = 495 kcal → 55 g ✓ inside 25–30%
Carb 1800 − 532 − 495 = 773 kcal → 193 g → 42.9% ✓ at or above 35%
Per main meal: 30–40 g protein
```
## Step 5 — portions
```
Fixed veg 7 · fruit 2 · dairy 2
→ carb 89, protein 30, fat 6
Remaining carb 104 · protein 103 · fat 49
starch = round(104/15) = 7 → carb 105, protein 21, fat 7
protein = round((103−21)/7)
= round(82/7) = 12 → protein 84, fat 12
fat = round((49−7−12)/5)
= round(30/5) = 6 → fat 30
Totals carb 194 (+1) · protein 135 (+2) · fat 55 (0) ✓ all inside tolerance
```
**7 starch · 12 protein · 6 fat · 7 vegetable · 2 fruit · 2 dairy**
## Step 6 — the schedule
Wakes 06:30, so the first meal is 08:00–08:30. Trains in the evening straight
from work, so snack 2 sits before the session and dinner after it.
| Slot | Time | Starch | Protein | Fat | Veg | Fruit | Dairy | Protein g | kcal |
|---|---|---|---|---|---|---|---|---|---|
| Breakfast | 08:00–08:30 | 2 | 2 | 2 | 1 | — | 1 | **30** | 445 |
| Snack 1 | 11:00 | — | 2 | 1 | — | 1 | — | 14 | 175 |
| Lunch | 13:00–13:30 | 4 | 3 | 2 | 3 | — | — | **39** | 590 |
| Snack 2 | 16:30 | 1 | 1 | — | — | 1 | 1 | 18 | 275 |
| Dinner | 18:30–19:00 | **0** | 4 | 1 | 3 | — | — | **34** | 260 |
| **Total** | | **7** | **12** | **6** | **7** | **2** | **2** | 135 | 1745 |
Lunch is the largest slot at 34%; dinner is the smallest main at 15%, which also
suits the reflux. The fat allocation is deliberately light at dinner and heavier
at breakfast, where the two tablespoons of ground flaxseed have to fit.
The lunch-to-dinner gap is 5½ hours — over the ordinary 3–5 h rule, and covered
by [the evening-training exception](../method/06-meal-schedule.md#training-in-the-evening).
Snack 2 at 16:30 sits in the second half of it.
## Steps 7–8 — food
Four options per slot. The constraints that actually shaped them:
| Constraint | Effect |
|---|---|
| Lactose — milk only | No milk to drink anywhere. Dairy portions are yoghurt, kefir and labneh |
| No fish except tuna and salmon | Those two only; the exception in the free text is what made salmon available at all |
| No coriander, no aubergine | Absent from every option |
| Thyroid | Soy not needed — the phytoestrogen requirement is met by flaxseed, so no soy limit had to be spent |
| Perimenopausal | Cruciferous in lunch options 2 and 4 and dinner option 1; 2 tbsp ground flaxseed daily, split across breakfast and snack 1 |
| Bloating, wind, low legume baseline | The fibre ramp — see below |
| Constipation | Flaxseed daily, water to the top of the range |
| Reflux | Dinner light, low fat, finished by 19:00 |
| Cooking confidence 3 | One involved option per main slot; the rest assemble |
| 4 meals out/week | Lunch option 3 is orderable anywhere |
| Kids eat differently | Lunch options 1 and 4 are a variation on one pot |
### The fibre finding
A representative day of this menu delivers roughly 45 g of fibre — well above
the 25–30 g target, because a build that is wholegrain throughout with seven
vegetable portions simply gets there.
Against `nut_ffq` showing legumes `"Occasionally"` and plant proteins `"Never"`,
and bloating and wind both `"Yes"`, that is too much too fast. So
[the ramp](../reference/fibre-and-gut.md#the-ramp-rule) binds, and it binds
*inside* the portion counts rather than against them: half a cup of tinned
rinsed legumes rather than a cup, cruciferous cooked rather than raw, rice and
sweet potato preferred over oats for the first fortnight. That is written into
the menu as a first-weeks instruction with a date to revisit, not as a permanent
feature.
This is the case that made [step 7](../method/07-plate-and-combinations.md)
grow a section on overshooting the target. The first draft only handled coming
in under it.
## Step 9 — the check
`bun scripts/validate-menu.ts menus/noa-barlev-2026-08-23.json` counts every
option's items back against its slot mechanically — the same check a human
count-back does, run on every option rather than a sample. The first draft of
this file failed it once: lunch option 1 came to 1 fat portion against a slot
of 2. Adding a teaspoon of olive oil fixed it. Nineteen of twenty options were
right; the twentieth read perfectly well and was wrong, which is exactly why
this check is mechanical rather than a read-through — and why it is now the
validator's job rather than a human's.
The searches for excluded foods, in both languages, returned only the `notes`
mentions — milk named in order to say it is out, coriander named in order to
say to order without it. The `dinner` slot's `portions.starch` and
`portions.fruit` are both `0`, which the schema would refuse to validate
otherwise.
Final reconciliation, exactly as `scripts/validate-menu.ts` computes it from
`dailyPortions`:
```
carb 194 g (target 193) · protein 135 g (target 133) · fat 55 g (target 55)
slot counts sum exactly to 7 · 12 · 6 · 7 · 2 · 2
main meals at 30, 39 and 34 g protein
```
### Both languages, one file
`client.name` is `"נעה בר-לב"` — her name exactly as she gave it in
`client_full_name`, in the script she wrote it in. That field is data, not
authored copy, so it is not translated — see
[the output contract](../project/output-contract.md#language). Every other
string in the file — every food name, every quantity, every note, the
disclaimer — is a `localizedText`, written in English and Hebrew together, per
[the style guide](style-guide.md#write-both-languages-together).
Nothing about the arithmetic changes with the language. The portion counts, the
schedule and the exclusions are the same numbers regardless of which half of a
`localizedText` is being read.
## What the menu says that the arithmetic does not
The two changes the client will actually notice are not in any of the numbers
above: **breakfast moves from 11:00 to 08:15 and acquires 30 g of protein**, and
**dinner moves from 21:30 to 18:30 and loses its starch**. Both are in `notes`,
stated as changes with the reason attached, because a client who is told what
changed and why will follow it and a client handed a table will not.
## The other two personas
`mock/answers-tomer-adler.json` is the opposite build — vegan, 34, five
sessions a week, muscle gain. It exercises the surplus branch, the dairy group
dropping to zero and the rebuild from step 5, and the B12 limitation note.
`mock/answers-yael-stern.json` is a **stop**. Three independent flags fire: a
`sys_gi_urgent` tick for blood in the stool, insulin-managed type 2 diabetes,
and a prescribed therapeutic diet. The correct output is
`menus/yael-stern-2026-08-23.json` with `type: "referral"` — no `slots`, no
`dailyPortions`, just the four bilingual fields
[the schema](schema.md#referral) defines for a stop — ordered GI first. It is
in the repository as the case that proves step 2 is not decorative.