# Dietitian Agent > How an agent turns a fitness intake questionnaire into a personalised weekly menu. # Dietitian Agent This site is the working manual for an agent that reads a completed fitness intake questionnaire and writes the client a personalised weekly menu. It is not a description of the agent. It is the agent's instructions: the procedure it follows, the arithmetic it does, and the standing nutritional knowledge it draws on. Everything the agent needs to build a menu correctly is on one of these pages, and nothing it needs is anywhere else. !!! info "What goes in, what comes out" **In** — one JSON object of answers to [the fitness questionnaire](https://github.com/), keyed by the stable field IDs defined in `survey/sections/*.json`. Roughly 180 fields across 11 sections, most of them optional. **Out** — one bilingual JSON menu, validated against [its schema](menu/schema.md), structured as a weekly framework: fixed portion counts per meal slot, with three or four interchangeable options for each slot. ## The short version
Steps 9  ·  Run in order  ·  Each step consumes the previous step's output
| # | Step | Produces | |---|------|----------| | 1 | [Read the intake](method/01-read-the-intake.md) | An intake digest — the ~30 fields out of 180 that change the menu | | 2 | [Safety screen](method/02-safety-screen.md) | Either a clearance to continue, or a stop-and-refer | | 3 | [Energy target](method/03-energy-target.md) | A daily kcal target | | 4 | [Macro split](method/04-macro-split.md) | Grams of protein, carbohydrate and fat per day | | 5 | [Portion exchanges](method/05-portion-exchanges.md) | Daily portion counts per food group | | 6 | [Meal schedule](method/06-meal-schedule.md) | Those portions distributed across 3 meals + 2 snacks, with clock times | | 7 | [Plate & combinations](method/07-plate-and-combinations.md) | Which foods fill each slot, and which pairings to prefer | | 8 | [Personalise](method/08-personalise.md) | The same plan with the client's restrictions, dislikes and logistics applied | | 9 | [Assemble & check](method/09-assemble-and-check.md) | The finished menu, validated against the checklist | ## How to read this site === "As the agent" Work the nine [Building a menu](method/01-read-the-intake.md) pages in order. Each one tells you what it needs from the step before, what to do, and what to hand on. Where a step needs a number — a portion size, a protein target, a list of fats to avoid — it links to the [Nutrition reference](reference/exchange-lists.md) page that owns that number rather than restating it. Follow the link. The reference pages are the single source of truth for every value on this site, and a method page that appears to contradict one is wrong. Before you start, read [Scope & limits](project/scope-and-limits.md). It is short and it is the page that governs all the others. === "As a dietitian reviewing it" The [Nutrition reference](reference/exchange-lists.md) section is where the clinical content lives — exchange definitions, macro ratios, the menopause protocol, the fats and sweeteners lists, the referral triggers. That is the section to argue with. The method pages are mechanical: given the reference values, they describe an arithmetic procedure with no clinical judgement of its own. === "As someone changing it" A number appears once. If you are about to type a portion size or a kcal-per-gram figure into a method page, it belongs on a reference page instead, with the method page linking to it. The client-facing wording is not written inline anywhere. It lives in [the style guide](menu/style-guide.md) and [the JSON schema](menu/schema.md). === "As a client" You are not the audience — the menu is. This site is the reasoning behind it. [The worked example](menu/worked-example.md) shows one complete menu produced from one complete set of answers, which is the closest thing here to what you would actually receive. ## Reading this site as an LLM Every page is also served as its original Markdown, at the page's own path with a `.md` extension: this page is at `/index.md`, step 3 is at `/method/03-energy-target.md`. (Those are deliberately not links — Zensical reads a link ending in `.md` as a reference to a page in the nav and warns that it does not exist.) [`/llms.txt`](/llms.txt) indexes every page in nav order, and [`/llms-full.txt`](/llms-full.txt) is the whole site in one file. !!! warning "Not medical advice" This site describes a general-population menu-building procedure. It is not clinical nutrition therapy, and the agent following it is not a dietitian. Several of the values here — the menopause protein range, the supplement list, the sweetener preferences — reflect one practitioner's approach and are not consensus guidelines. Anything the questionnaire flags as a medical matter is referred out rather than worked around; see [Red flags](reference/red-flags.md). # The pipeline
Audience anyone  ·  Read before the method pages  ·  Owns the load map
One questionnaire goes in, one menu comes out. In between are nine steps that each narrow the problem: from 180 raw answers, to the 30 that matter, to a number of calories, to grams, to portions, to meals, to food, to a page the client can follow. ```mermaid flowchart TD A["answers.json
~180 fields"] --> B["1. Read the intake
→ intake digest"] B --> C{"2. Safety screen"} C -->|red flag| STOP["Stop & refer
no menu is written"] C -->|clear| D["3. Energy target
→ kcal/day"] D --> E["4. Macro split
→ g protein / carb / fat"] E --> F["5. Portion exchanges
→ daily portion counts"] F --> G["6. Meal schedule
→ portions per slot + clock times"] G --> H["7. Plate & combinations
→ food choices per slot"] H --> I["8. Personalise
→ restrictions, dislikes, logistics"] I --> J["9. Assemble & check"] J --> K["menu.md
weekly framework"] ``` ## Why this order The order is not arbitrary — each step closes off decisions the next step would otherwise have to guess at. **Screening comes before arithmetic** because a red flag makes the arithmetic pointless. There is no version of "calculate the target first and check safety afterwards" that ends well: the effort is wasted at best, and at worst the agent has produced a document it should not have produced. **Energy before macros** because the macro split is a set of percentages, and percentages need something to be a percentage of. **Macros before portions** because the exchange system is a *representation* of a macro target, not an alternative to one. Portion counts are derived by dividing grams by the group's grams-per-portion — see [step 5](../method/05-portion-exchanges.md). **Portions before the schedule** because the daily count is what gets distributed. The schedule moves portions between slots; it never changes how many there are in a day. **Schedule before food** because the constraint that dinner carries no starch is a scheduling rule, and it determines which foods are even eligible for the evening slot. **Personalisation last** because it is a filter, not a design step. Every substitution it makes must preserve the portion count of the slot it touches — which is only checkable once the counts exist. ## What each step may and may not change This is the invariant that keeps the pipeline honest. A step that quietly revises an earlier step's output produces a menu whose numbers no longer mean anything. | Step | May change | Must preserve | |------|-----------|---------------| | 3. Energy | — | — | | 4. Macros | — | the kcal target (±2%) | | 5. Portions | — | the macro grams (±5 g protein, ±10 g carb, ±5 g fat) | | 6. Schedule | which slot a portion sits in | the daily portion counts, exactly | | 7. Plate | which food fills a portion | the portion counts per slot, exactly | | 8. Personalise | which food fills a portion | the portion counts per slot, exactly | | 9. Assemble | nothing | everything | If step 8 cannot make a substitution without breaking a count — the client cannot eat any of the eligible starches, say — it does not fudge the count. It goes back to step 5 and rebuilds the daily distribution with that group excluded, then re-runs 6 through 8. That is a loop, and it is expected to happen for restrictive patterns. ## The load map The agent does not hold this whole site in context. Each method page names the reference pages it needs; load those and no others. | Running step | Also load | |--------------|-----------| | 1. Read the intake | [Input contract](input-contract.md) | | 2. Safety screen | [Red flags](../reference/red-flags.md), [Scope & limits](scope-and-limits.md) | | 3. Energy target | — | | 4. Macro split | [Menopause](../reference/menopause.md) | | 5. Portion exchanges | [Exchange lists](../reference/exchange-lists.md) | | 6. Meal schedule | [Hydration & eating behaviour](../reference/hydration-and-behaviour.md) | | 7. Plate & combinations | [Exchange lists](../reference/exchange-lists.md), [Food quality](../reference/food-quality.md), [Fibre & the gut](../reference/fibre-and-gut.md) | | 8. Personalise | [Food quality](../reference/food-quality.md), [Menopause](../reference/menopause.md) | | 9. Assemble & check | [JSON schema](../menu/schema.md), [Style guide](../menu/style-guide.md), [Supplements](../reference/supplements.md) | [Scope & limits](scope-and-limits.md) is loaded at every step. It is short for that reason. ## Where the questionnaire itself lives The questions are not defined here. They are defined in `survey/sections/*.json` in this repository, in SurveyJS format, and that is the only source of truth for field IDs, answer types and conditional logic. This site consumes those field IDs; it does not get to invent them. See [the input contract](input-contract.md). # Input: the answers file
Loaded by step 1  ·  Source of truth survey/sections/*.json  ·  Never invent a field ID
The agent receives one JSON object: the SurveyJS response data for a completed questionnaire, keyed by field ID. ```json { "client_full_name": "…", "client_dob": "1974-03-09", "health_conditions": ["High cholesterol", "Thyroid disorder"], "nut_ffq": { "Fresh fruit": "Daily", "Red meat & offal": "Occasionally" }, "meas_weight": 78, "meas_weight__unit": "kg" } ``` ## What the shapes mean The value's JSON type follows from the question's SurveyJS `type`. Reading a value with the wrong expectation is the most common way to corrupt a menu silently, so the mapping is worth knowing. | SurveyJS `type` | JSON value | Example | |---|---|---| | `text`, `comment` | string | `"Two 20-minute sessions"` | | `text` with `inputType: "number"` | number | `78` | | `text` with `inputType: "date"` | `YYYY-MM-DD` string | `"1974-03-09"` | | `boolean` | the `valueTrue` / `valueFalse` string — **`"Yes"` / `"No"`, not `true` / `false`** | `"Yes"` | | `radiogroup`, `dropdown` | one choice string | `"2–3 L"` | | `checkbox` | array of choice strings | `["Wine", "Beer"]` | | `rating` | number within `rateMin`–`rateMax` | `4` | | `matrix` | object, row label → column label | `{ "Eggs": "Daily" }` | | `paneldynamic` | array of objects | `[{ "product_name": "…", "dosage": "…" }]` | | `expression` | number, computed by SurveyJS | `26.99` | | `file`, `signaturepad` | ignore — not menu input | | !!! danger "Booleans are strings" `"health_surgeries": "Yes"` is a string. Every `boolean` field in this questionnaire declares `valueTrue: "Yes"`, so a truthiness test on the raw value passes for `"No"` as well. Compare to `"Yes"` explicitly. ## Absence Most fields are optional, and an unanswered field is simply **absent from the object** — not `null`, not `""`. Three further rules: 1. **`clearInvisibleValues: "onHiddenContainer"`** is set on the survey. A conditional field whose trigger never fired is removed from the data, so its absence is meaningful: it means the question was never asked, not that it was skipped. 2. **Absent is not zero.** A missing `nut_caffeine` means unknown, not caffeine-free. Never substitute a default and then reason from it as though the client had said it. 3. **Absence is recorded, not resolved.** Where a missing field changes the menu, the menu says so — see the *Assumptions* section in [the JSON schema](../menu/schema.md). ## Units Three fields are paired with a `__unit` companion, and one pair is pre-converted for you by the survey's `calculatedValues`: | Value | Unit field | Pre-converted | |---|---|---| | `meas_height` | `meas_height__unit` (`cm` / `in`) | `meas_height_cm` | | `meas_weight` | `meas_weight__unit` (`kg` / `lb`) | `meas_weight_kg` | | `meas_weight_self` | `meas_weight_self__unit` | — | | `test_squat_est_1rm` | `test_squat_est_1rm__unit` | — | | `test_deadlift_est_1rm` | `test_deadlift_est_1rm__unit` | — | **Prefer `meas_height_cm` and `meas_weight_kg`.** They exist precisely so the agent never does unit conversion itself. If they are absent, fall back to the raw field plus its unit field, and convert with `1 in = 2.54 cm`, `1 lb = 0.453592 kg`. `meas_bmi` and `meas_whr` are `expression` fields — SurveyJS computes them. Do not recompute; if present, use them. ## The sections, and what step 1 wants from each Full field lists are in the JSON. This is the orientation. | Section | Fields | Menu-relevant? | |---|---|---| | 1. About you | `client_*` | **Yes** — age, gender, occupation, work activity | | 2. Health & medical history | `parq_*`, `health_*` | **Yes** — screening, conditions, supplements, allergies, alcohol, smoking | | 3. Systems review | `sys_*` | **Yes** — digestive symptoms, menstrual status, red flags | | 4. Injuries & movement | `inj_*` | Rarely — only via `nut_eating_limitation` | | 5. Activity & training history | `act_*` | **Yes** — activity factor | | 6. Goals & motivation | `goal_*` | **Yes** — direction of the energy adjustment, obstacles | | 7. Nutrition & hydration | `nut_*` | **Yes** — the richest section; pattern, FFQ, cooking, hydration | | 8. Sleep, stress & recovery | `life_*` | **Yes** — meal timing, shift work, energy crashes | | 9. Measurements & assessments | `meas_*`, `screen_*`, `test_*` | **Yes** — height, weight, BP, waist | | 10. Preferences & logistics | `pref_*` | Some — budget, travel, accessibility | | 11. Consent & sign-off | `consent_*`, `sign_*` | **Gate only** — see below | ## The consent gate Section 11 is not menu content, but it is a precondition. Before step 3 runs: - `consent_accuracy` must be `"Yes"` — otherwise the answers are not warranted accurate and nothing should be derived from them. - `consent_data_processing` must be `"Yes"`. - `consent_clearance_confirm` — where any PAR-Q trigger fired, this must be `"Yes"`. If a trigger fired and this is absent or `"No"`, that is a stop condition, not a caveat. See [step 2](../method/02-safety-screen.md). A missing consent is not something to work around, note as an assumption, or proceed past. It stops the run. ## Section 9 may be empty Section 9 is filled by the coach at a baseline appointment, so a questionnaire completed by the client alone will have no `meas_*` values at all. Height and weight are the only two the menu genuinely requires, and section 9 offers a self-reported fallback for weight in `meas_weight_self`. How to proceed when even that is missing is covered in [step 3](../method/03-energy-target.md#when-height-or-weight-is-missing). # Output: the menu
Loaded by step 9  ·  Format JSON, validated  ·  Shape weekly framework, options per slot
One file: `menus/-.json`. Structured data, validated against [the schema](../menu/schema.md) before it ships, and rendered by [the client demo](../../web-apps/demo/) rather than read as-is. ## Why JSON and not prose A hand-written document can drift from what it claims: an option that has quietly stopped matching its slot's portion count, a disclaimer that got trimmed, a food that snuck back into an option after the client excluded it. Every one of those was a real failure mode of the Markdown version this schema replaced. A schema turns most of that class of error into something that cannot be saved. [`menus/schema/menu.schema.json`](../menu/schema.md) requires every option's food items to be individually typed with their own portion counts, so [`scripts/validate-menu.ts`](../menu/schema.md#the-arithmetic-the-schema-cant-express) can add them up and refuse a file where they don't match the slot; it requires the disclaimer to equal an exact constant, so an edited one fails to validate; it requires every client-facing string to carry **both** an `en` and a `he` value, so a menu missing its Hebrew half is not a finished menu. None of that was enforceable when the output was Markdown. The rendering — the actual page a client reads — is a separate concern, owned by [the demo app](../../web-apps/demo/), not by this file. ## Why a framework and not seven named days A seven-day plan is a stack of seven guesses. It specifies Tuesday's dinner without knowing what the client has in the fridge on Tuesday, and the first time that guess is wrong the whole document reads as broken — which is how meal plans end up in a drawer. A framework specifies the *structure* — how many portions of what, in which slot, at roughly what time — and then offers three or four interchangeable options for each slot. The client picks. The structure holds regardless of which they pick, because every option in a slot carries the same portion counts. That is the whole design: **the slot owns the numbers, the options own the food.** It also survives contact with section 7 of the questionnaire. A client with two intolerances and four dislikes loses one option out of four in a couple of slots, rather than losing Tuesday. ## Required structure Every menu file has these top-level parts. [The schema](../menu/schema.md) is the authoritative definition; this is what each part is *for*. | Part | Purpose | |---|---| | `client`, `writtenDate`, `reviewWeeks` | Who this is for, and when to revisit it | | `dailyTarget` | The agent's working kcal/macro figures — not client-facing, kept so the arithmetic can be checked | | `dailyPortions` | The six-group daily total every slot's portions must sum to | | `slots` | The five meals, in fixed order, each with its portion counts and 3–4 options | | `drinking` | The hydration timing rules, as a list | | `atTheTable` | The mindful-eating rules, each flagged whether it survives the sensitivity filter | | `notes` | Personalisation the client would notice: intolerances honoured, symptoms accommodated, shift-work adaptation | | `assumptions` | Every place a missing answer was worked around — an empty array where there were none | | `disclaimer` | The scope disclaimer — schema-fixed, cannot be edited and still validate | A referral note — where [step 2](../method/02-safety-screen.md) stopped the run — is a different shape, `type: "referral"`, with no slots at all. See [the schema](../menu/schema.md#referral). ## Rules the output must satisfy 1. **Every option in a slot carries that slot's portion counts.** Not approximately, and not just as a total — every food item in the option carries its own `portions`, and `scripts/validate-menu.ts` adds them up. If the lunch slot is 2 starch / 4 protein / 2 fat / 3 vegetable, all four lunch options' items sum to exactly that. 2. **No starch or fruit in the dinner slot.** Enforced structurally: the schema's `dinner` slot definition fixes `portions.starch` and `portions.fruit` to the constant `0`. A menu with either does not validate. 3. **Portions are named in food terms as well as counted.** Every `foodItem` carries a `qty` — the actual quantity, e.g. `½ cup cooked brown rice` — not just its contribution to the six portion groups. 4. **No kcal figures in client-facing text**, unless `nut_tracked_before` is `"Currently tracking"`. `dailyTarget.kcal` exists in the file for the arithmetic; it is not rendered to the client. 5. **Nothing the client said they cannot or will not eat appears anywhere**, including inside a food's name. This is checked in [step 9](../method/09-assemble-and-check.md) — the schema cannot know a client's exclusions, so this check is manual. 6. **Every assumption is listed** in the `assumptions` array, in the client's own terms. An empty array is how a menu with no gaps says so. 7. **The disclaimer is present and unedited** — schema-enforced via `const`. 8. **Every string has both languages.** Enforced structurally: `localizedText` requires `en` and `he`, both non-empty. ## What the output is not - Not a shopping list. Useful, out of scope, and it would double the length. - Not a recipe book. Options name dishes and quantities, not methods. "Rice and lentils with baked chicken" is an option; how to cook it is not. - Not a tracking sheet. The menu does not ask the client to record anything. - Not a progress document. It contains no targets, no timeline, no weights. - Not itself the client-facing page. That's rendered by [the demo app](../../web-apps/demo/) from this data. ## Language Every client-facing string in the file is a `localizedText` object — `{ "en": "...", "he": "..." }` — written per [the menu style guide](../menu/style-guide.md) for English and its Hebrew counterpart. `client.name` is the one exception: it is the client's real name as given in `client_full_name`, in whatever script they wrote it, because it is data rather than authored copy. Keep both languages' prose plain. The reader is not necessarily a fluent speaker of either, and the menu is a set of instructions rather than an essay: - Short sentences. One instruction per line. - Concrete nouns over categories — "half a cup of cooked lentils", not "a legume serving". - Household measures the client already owns: cups, tablespoons, teaspoons, slices. Grams only for meat, fish and cheese. - No idiom, no metaphor, no wordplay — hardest to carry across two languages, so avoided in both. The full register and vocabulary rules, in both languages, are in [the menu style guide](../menu/style-guide.md). # Scope & limits This page outranks the rest of the site. Where a method page seems to permit something this page forbids, this page wins. !!! danger "The agent builds menus. It does not treat people." A menu is a general-population eating plan built from stated preferences, an energy target and a portion system. It is not nutrition therapy, it does not manage a disease, and it does not replace a consultation. The moment a client's answers make the menu a clinical instrument rather than a dietary one, the agent stops and refers. ## Never - **Diagnose.** Not from symptoms, not from the systems review, not from blood results. `sys_*` answers are referral triggers, never findings. - **Interpret a test.** Blood panels, DEXA, blood pressure readings, BMI. A number is recorded and, where it is a red flag, referred. It is not explained. - **Advise on medication.** Not dose, not timing, not whether to keep taking it, not how it interacts with food. This includes the obvious-sounding cases. - **Treat a diagnosed condition through the menu.** A client with type 1 diabetes, kidney disease, an active eating disorder or a prescribed therapeutic diet needs a registered dietitian, not this pipeline. - **Prescribe supplements.** [The supplements page](../reference/supplements.md) exists so the agent can *recognise* what a client already takes and avoid contradicting it. It is not a shopping list to hand over. - **Set an energy target below the floor.** See [step 3](../method/03-energy-target.md#floors). No goal, no client request and no rounding justifies crossing it. - **Invent an answer.** An absent field is unknown. Never fill it from context, from a plausible default, or from another field that "implies" it. - **Promise an outcome.** No weight predicted, no timeline, no "you will". - **Continue past a stop condition.** See [Red flags](../reference/red-flags.md). ## Where the boundary actually falls The hard cases are not the obvious ones. These are the calls that come up. | The answers show… | The agent… | |---|---| | High cholesterol, no other flag | Builds normally. Favours the fibre and fat-quality guidance that applies to everyone anyway. Does not frame it as treating cholesterol. | | Type 2 diabetes, diet-managed | Builds, with the fibre and meal-spacing rules applied. States plainly in the menu that carbohydrate targets must be reviewed by their treating clinician. | | Type 1 diabetes, or insulin-managed T2 | **Stops.** Carbohydrate counting against insulin dosing is clinical work. | | Prescribed therapeutic diet (`nut_medical_diet` = Yes) | **Stops.** The prescribing clinician owns the plan; a second one is a hazard, not a service. | | Eating-disorder history, `"Yes"` | **Stops.** Portion counting is contraindicated without specialist oversight. | | Eating-disorder history, `"Prefer not to say"` | Builds, but drops all numeric targets from the client-facing menu — portions described qualitatively. Notes the adaptation. | | Pregnant or postpartum | **Stops.** Energy and micronutrient needs are different and clinically supervised. | | Perimenopausal or post-menopausal | Builds, applying [the menopause protocol](../reference/menopause.md). This is in scope. | | Coeliac, declared as an intolerance | Builds gluten-free. A stated exclusion is a constraint, not a diagnosis to manage. | | A GI red flag (`sys_gi_urgent`) | **Stops.** | | Wants to lose weight, BMI already under 20 | Builds at maintenance, not a deficit, and says why. Does not argue with the goal beyond that. | | Asks a question the menu can't answer | Says so, and names who can. Does not approximate. | ## What "stop" means Stopping is a deliverable, not a failure. The agent produces a short referral note in place of the menu: what triggered the stop, which field it came from, what the client should do next, and — explicitly — that no menu was written and why. It does not write a partial menu, a caveated menu, or a menu "to be going on with". [Red flags](../reference/red-flags.md) specifies the format. Where a stop is *conditional* — a consent that could still be given, a clearance that could still be produced — the note says what would unblock it. ## Tone in the client-facing menu - Second person, warm, direct. Not clinical, not coy. - No moralising about food. There are no "bad" foods in the menu's voice, only foods that are in this plan and foods that are not. - No body-composition commentary. The menu never remarks on the client's weight, shape or eating history, even approvingly. - Numbers are portions and times, not calories. The kcal target is the agent's working figure; it is not printed in the client menu unless the client asked to track (`nut_tracked_before` = `"Currently tracking"`). The wording rules that realise all of this are in [the menu style guide](../menu/style-guide.md). # 1. Read the intake 180 fields go in. About 30 of them change the menu. This step finds those 30 and writes them down in one place, so that every later step reads from a short structured digest rather than re-reading raw JSON and reaching a slightly different conclusion each time. Write the digest even for a simple case. The steps that follow are specified against it, and half its value is that it makes the *missing* answers visible before any arithmetic depends on them. ## The digest Produce this as a working note. It is not client-facing and never ships. ```markdown ## Intake digest — [name], [date] ### Who age · gender · city · occupation · work activity · household cooking situation ### Screening → step 2 PAR-Q triggers fired · conditions · medications flag · allergies · GI urgent · disordered eating · menses status · consents ### Body → step 3 height cm · weight kg (measured | self-reported | missing) · BMI · waist ### Activity → step 3 currently active · sessions/week · session length · daily lifestyle · work activity ### Goal → step 3 primary goals · direction (deficit | maintenance | surplus) · obstacles · support wanted ### Protein modifiers → step 4 menses status · training load · age ### Current eating → steps 5–8 typical day · representative? · meals/day · first meal · last meal · weekend differs · FFQ standouts · water · caffeine · alcohol ### Constraints → step 8 pattern · intolerances · allergies · dislikes · medical diet · eating limitation · cooking confidence · who cooks · who shops · meals out/week · budget · household eating differently ### Symptoms → steps 6–8 digestive yes-answers · bowel · reflux · energy crash · sleep hours · sleep quality · stress · shift work · travel ### Gaps [every field this digest wanted and did not get] ``` ## Where each line comes from ### Who `client_dob` → age at today's date. `client_gender`. `client_address`. `client_occupation`. `client_work_activity`. `nut_who_cooks`, `nut_who_shops`, `client_children`. Age and gender both feed the energy equation directly. Where `client_gender` is `"other"` or absent, see [step 3](03-energy-target.md#when-gender-is-absent-or-other). ### Screening Copy every PAR-Q field verbatim — do not summarise them to "cleared". Then `health_conditions`, `health_diabetes_mgmt`, `parq_5_medication`, `health_allergies`, `nut_intolerances`, `sys_gi_urgent`, `nut_disordered_history`, `nut_medical_diet`, `sys_menses_status`, `consent_accuracy`, `consent_data_processing`, `consent_clearance_confirm`. This block exists so [step 2](02-safety-screen.md) can run against a short list rather than the whole file. Under-copying here is how a stop condition gets missed. ### Body `meas_height_cm` and `meas_weight_kg` if present. If not, in order: the raw `meas_height` / `meas_weight` with their `__unit` fields; then `meas_weight_self` with `meas_weight_self__unit`. **Record which source was used** — the digest line says `weight: 78 kg (self-reported)`, and that provenance ends up in the menu's *Assumptions*. Take `meas_bmi` and `meas_whr` as given where present. `meas_waist` is worth carrying for the notes even though nothing computes from it. !!! warning "`meas_weight_aware` governs what you may print" `"Yes, but I'd rather not say"`, `"No, and I'd rather not know"`, or `meas_blind_weigh` = `"I'd rather not be weighed at all"` all mean the same thing for the menu: **the weight may be used in the arithmetic and must never appear in the client-facing document.** Not in the assumptions, not in a note, nowhere. Carry the flag on the digest line so step 9 can check it. ### Activity `act_currently_active`, `act_sessions_per_week`, `act_session_length`, `act_types`, `act_daily_lifestyle`, `client_work_activity`. All six — the activity factor in step 3 is a judgement across them, not a lookup on one. ### Goal `goal_primary` is a checkbox, so it is a list and it will often contain goals that pull in opposite directions — `"Lose body fat"` and `"Build muscle"` together is the common case, not an edge case. Do not resolve it here. Record the list, and let [step 3](03-energy-target.md#reconciling-conflicting-goals) resolve it under its own rules. Also `goal_obstacles`, `goal_obstacles_plan`, `nut_support_wanted`. `nut_support_wanted` = `"None, training only"` is worth surfacing early: the client has said they do not want this. Build it, and say in *Notes for you* that it is there if wanted. ### Current eating `nut_typical_day` is the single most informative field in the questionnaire. Read it properly — meal times, meal sizes, what is missing, what the client notices about it. Then `nut_typical_representative`: where it is `"No, better than usual"`, the real baseline is worse than what is written and the menu is a larger change than it looks. From `nut_ffq`, extract only the **standouts** — anything `"Daily"` that the menu will reduce, anything `"Never"` or `"Occasionally"` that the menu will require. A client eating legumes `"Never"` is the fact that shapes the [fibre ramp](../reference/fibre-and-gut.md#the-ramp-rule); the other 28 rows are context. Then `nut_meals_per_day`, `nut_first_meal`, `nut_last_meal`, `nut_weekend_differs`, `nut_water`, `nut_caffeine`, `nut_caffeine_latest`, `nut_beverage_sugar`, `nut_sugary_drinks`, `health_alcohol_units` and its quantity fields. ### Constraints The block that does the most work in [step 8](08-personalise.md). `nut_pattern` (+ `nut_pattern_other`), `nut_intolerances`, `health_allergies`, `nut_dislikes`, `nut_medical_diet`, `nut_eating_limitation`, `nut_cooking_confidence`, `nut_who_cooks`, `nut_who_shops`, `nut_meals_out`, `nut_household_eating`, `pref_budget`, `pref_accessibility`. Copy free-text fields **whole**. `nut_dislikes` = "I don't eat fish except tuna, and no coriander" loses its exception when compressed to "dislikes fish", and that lost exception is a wrong menu. ### Symptoms Every `sys_digestive_*` that is `"Yes"`, plus `sys_bowel_frequency`, `sys_bowel_empty`, `sys_laxatives`, `life_energy_crash`, `life_energy_pattern`, `life_sleep_hours`, `life_sleep_quality`, `life_stress_level`, `life_shift_work` (+ `life_shift_pattern`), `life_travel`. Skip the `"No"` answers. Eleven digestive fields at `"No"` is one word: none. ### Gaps Every field above that was absent. Not a footnote — the list that determines which assumptions get made and which get printed. ## Do not - **Do not interpret.** The digest records what was said. It does not conclude that a client "probably" trains more than they wrote. - **Do not fill a gap.** A missing `act_sessions_per_week` goes in *Gaps*. Step 3 has a documented fallback; this step does not get to pre-empt it. - **Do not drop a free-text field because it looks like noise.** The exception that ruins a menu is nearly always in free text. - **Do not resolve contradictions.** `act_currently_active` = `"No"` alongside `act_sessions_per_week` = 4 gets recorded as the contradiction it is. Step 3 resolves it by taking the more conservative reading. --- Next: [2. Safety screen](02-safety-screen.md) # 2. Safety screen Run the whole of [Red flags](../reference/red-flags.md) against the digest. Every entry, in order, before any arithmetic. This step is short because the content is on the reference page. What lives here is how to run it. ## The procedure 1. **Check the consent gate first.** `consent_accuracy`, `consent_data_processing`, and — where any PAR-Q trigger fired — `consent_clearance_confirm`. Without these the answers are not warranted and nothing may be derived from them. 2. **Walk every stop condition on the red-flags page.** Do not sample. Do not skip the sections that "obviously don't apply" — the GI block in particular is checked on every run regardless of what else the client said. 3. **Collect all the flags that fired**, not just the first. 4. **If any fired**: write the referral note, in the format on the red-flags page, and stop. Steps 3 to 9 do not run. 5. **If none fired**: record the non-stop findings for step 8 and continue. ## Non-stop findings Plenty of things fire without stopping the run. They are carried forward, and each has a destination: | Finding | Goes to | |---|---| | Type 2 / pre-diabetes, not insulin-managed | Step 5 — carbohydrate distribution; a line in the menu that their clinician reviews the targets | | High blood pressure or cholesterol | Step 7 — the fat and fibre guidance already applies; no special framing | | Thyroid disorder | [Menopause](../reference/menopause.md) — soy limit and timing | | Perimenopause / post-menopause | Step 4 — [the elevated protein target](../reference/menopause.md) | | Digestive symptoms | Step 7 — [the fibre ramp](../reference/fibre-and-gut.md) | | Allergies and intolerances | Step 8 — exclusions | | `nut_disordered_history` = `"Prefer not to say"` | Step 9 — the numberless adaptation | | Heavy alcohol | Step 9 — one line in *Notes for you*, plus a referral note alongside the menu | | Already seeing a dietitian | Step 9 — the menu says to share it with them | | BP 140–179 / 90–109 | Step 9 — noted, with a suggestion to have it rechecked | | Blood tests uploaded | Ignored entirely — the agent does not open them | !!! danger "The blood test file" `health_bloods_file` may contain a real blood panel. Do not read it, do not reference it, do not let its presence change anything. Interpreting it is outside [scope](../project/scope-and-limits.md), and a menu that appears to respond to a blood result is making a clinical claim. ## Two failure modes **Stopping too readily.** Most chronic conditions in `health_conditions` are not stops — high cholesterol, thyroid disorder, arthritis and osteoporosis are diet-relevant conditions the general-population menu already suits. Referring every client with a diagnosis is not caution, it is uselessness, and it teaches the client that the honest answer was the wrong one. **Not stopping because the client seems fine.** The GI flags are the ones that get rationalised away, because a client who ticked `"Blood in your stool"` in a long checkbox list and then wrote an upbeat `nut_typical_day` does not read as someone in trouble. The tick is what counts. There is no version of this where the agent weighs the answer against a general impression. ## What a clearance looks like Append to the digest, then continue to step 3. ```markdown ### Screen: CLEAR Stops checked: all. Fired: none. Carried forward: perimenopausal → step 4; bloating + wind → step 7; lactose intolerance → step 8; BP 138/86 → note only. ``` ## What a stop looks like The referral note replaces the menu. Its format is on [the red-flags page](../reference/red-flags.md#the-referral-note), and the four rules there are the ones that get broken: name the finding not the field, do not speculate, give a real timeframe, attach none of the menu. Write it to the same file path the menu would have had. --- Previous: [1. Read the intake](01-read-the-intake.md) · Next: [3. Energy target](03-energy-target.md) # 3. Energy target Three multiplications and one adjustment. ``` BMR × activity factor = TDEE TDEE ± goal adjustment = target target, floored = the number ``` ## BMR — Mifflin-St Jeor Weight in kg, height in cm, age in years. === "Female" ``` BMR = (10 × weight) + (6.25 × height) − (5 × age) − 161 ``` === "Male" ``` BMR = (10 × weight) + (6.25 × height) − (5 × age) + 5 ``` Round to the nearest 10 kcal. The equation carries roughly ±10% error on an individual; carrying it to the unit implies a precision it does not have. ### When gender is absent or `"other"` Compute both and take the mean. Do not guess from the name, the occupation or anything else. Note it in the digest — it does not go in the client menu. ### When height or weight is missing | Have | Do | |---|---| | `meas_height_cm` + `meas_weight_kg` | Use them | | Height, no measured weight, but `meas_weight_self` | Use the self-report. Record the provenance; it goes in *Assumptions* | | Height, no weight at all | **Cannot proceed.** Ask for a weight, or for the client to accept a qualitative menu built with no energy target | | Weight, no height | **Cannot proceed.** Same | Do not substitute a population average for either. A menu built on an assumed height is a menu built on a number nobody supplied, and its portion counts are arbitrary while looking calculated. Where the client will not give a weight (`meas_weight_aware` = `"Yes, but I'd rather not say"` and no measurement), that is a legitimate answer, not an obstruction. Build the qualitative menu: the full structure, the schedule, the plate rules and the food options, with portions described in household measures and hand sizes instead of counts. Say in *Notes for you* that the plan is built on structure rather than numbers, and that this was their choice. ## Activity factor Judge across all six activity fields, not one. | Factor | Picture | |---|---| | **1.20** | Desk job, no training. `act_currently_active` = `"No"` and `act_daily_lifestyle` = sedentary | | **1.375** | 1–3 sessions/week, or an active day with no training | | **1.55** | 3–5 sessions/week, or 1–3 sessions plus a standing/moving job | | **1.725** | 6–7 sessions/week, or 4–5 sessions plus a physically demanding job | | **1.90** | Physically demanding job **and** near-daily training | Adjust within the band using `act_session_length` (under 30 min pulls down, over 75 min pushes up) and `act_types` (resistance and cardio push up; mind-body and flexibility alone do not). ### When the fields disagree `act_currently_active` = `"No"` alongside `act_sessions_per_week` = 4 is a contradiction the digest recorded rather than resolved. **Take the lower reading.** An overestimated factor produces a target above maintenance for someone who wanted a deficit, which is the failure that matters. ### When activity is missing entirely Fall back to `client_work_activity` alone: seated → 1.2, mixed → 1.375, on feet → 1.55, physically demanding → 1.725. This goes in *Assumptions*. ## Goal adjustment Read `goal_primary`, which is a list. | Direction | Adjustment | Triggered by | |---|---|---| | **Deficit** | −15% to −20% of TDEE | `"Lose weight"`, `"Lose body fat"`, `"Look better"` | | **Maintenance** | 0% | `"Health"`, `"General fitness"`, `"Feel better in my body"`, `"Improve health markers"`, `"Move without pain"`, `"Improve mobility and flexibility"`, `"Weight isn't what I'm here for"`, `"I'm not sure yet"` | | **Surplus** | +10% to +15% | `"Build muscle"`, `"Get stronger"`, `"Perform better in a sport"` — **only** when no deficit goal is also present | Within the deficit band: −15% as standard, −20% only where BMI is over 30 **and** the client is currently active. A larger deficit is not available at any BMI. ### Reconciling conflicting goals `"Lose body fat"` and `"Build muscle"` together is the most common pair in the questionnaire, and the answer is not to average them. **A deficit goal always wins over a surplus goal.** Build at −15%, and let the elevated protein target from [step 4](04-macro-split.md) do the muscle-retention work. Trying to do both at once produces a target near maintenance that achieves neither and looks, to the client, like nothing is happening. Where the deficit goal is `"Look better"` alone with no other weight goal, treat it as maintenance — it is not reliably a fat-loss request, and a client who wanted one has other ways to say so in this list. ### `"Return to training after a break"` Not a direction. It sets maintenance unless another goal establishes one, and it is a note for the coach rather than the menu. ## Floors !!! danger "The floor is not negotiable" The target never goes below the **higher** of: - **BMR × 1.1**, and - **1400 kcal** for women, **1600 kcal** for men Not for a stated goal, not at the client's request, not by rounding. Where the floor binds, the target is the floor, and *Notes for you* says the plan was built to a level that keeps the client fed rather than to the fastest possible rate of change. It says that without arithmetic and without apology. Where the floor binds by a wide margin — the calculated target is more than 200 kcal below it — the underlying picture is usually a small, sedentary client with an aggressive goal. The menu is still built to the floor. Nothing else changes. ## Rounding, and what to record Round the final target to the nearest 50 kcal. ```markdown ### Energy BMR = (10×78) + (6.25×164) − (5×52) − 161 = 1384 → 1380 Activity: 3 sessions/wk, 45 min, seated job → 1.55 TDEE = 1380 × 1.55 = 2139 Goal: "Lose body fat" + "Build muscle" → deficit wins → −15% → 1818 Floor: max(1380 × 1.1, 1400) = 1518 — not binding Target: 1800 kcal/day ``` This number is the agent's working figure. It goes in the client menu only if `nut_tracked_before` = `"Currently tracking"`, per [Scope & limits](../project/scope-and-limits.md). --- Previous: [2. Safety screen](02-safety-screen.md) · Next: [4. Macro split](04-macro-split.md) # 4. Macro split The target split is **45% carbohydrate, 20–30% protein, 25–30% fat**, and the energy conversions are: | | | |---|---| | 1 g carbohydrate | 4 kcal | | 1 g protein | 4 kcal | | 1 g fat | 9 kcal | But the percentages are not where the calculation starts. ## Protein is set from body weight, not from a percentage A percentage of a fat-loss target can land well below what the client needs to hold onto muscle, and the lower the target the worse it gets — which is exactly backwards, because that is when protein matters most. So: **protein grams come from g/kg first**, and the percentage is a check on the result rather than the source of it. | Situation | Target | |---|---| | Perimenopausal or post-menopausal | **1.5–1.8 g/kg** — see [Menopause](../reference/menopause.md#protein-per-kilogram) | | Otherwise, active (2+ sessions/week) | 1.4–1.6 g/kg | | Otherwise, sedentary | 1.2–1.4 g/kg | | Deficit of any size | Add 0.1 g/kg to whichever band applies | Pick within a band by training load — more sessions, higher in the band. ### Which weight `meas_weight_kg`. Where BMI is over 30, use adjusted weight: ``` ideal = 25 × (height_m)² adj = ideal + 0.4 × (actual − ideal) ``` Otherwise the protein target scales with fat mass, which is not the tissue it is for, and the number comes out somewhere no one is going to eat. ## Then fat, then carbohydrate 1. **Protein kcal** = protein g × 4 2. **Fat** = 27.5% of the target as a starting point (the midpoint of 25–30%), converted at 9 kcal/g 3. **Carbohydrate** = whatever remains, at 4 kcal/g Then check the result against the intended split. ## The check, and what to do when it fails | Check | If it fails | |---|---| | Protein between 20% and 30% of kcal | See below | | Fat between 25% and 30% | Adjust fat to bring it inside; recompute carbohydrate | | Carbohydrate ≥ 35% | See below | **Protein above 30%.** Happens on a low target with a high g/kg — a small menopausal client in a deficit. Keep the grams. The g/kg figure is a physiological requirement and the percentage is a description of the plate; when they conflict, the requirement wins. Take fat to 25% to give carbohydrate room, and note that the split runs protein-heavy by design. **Carbohydrate below 35%.** Same cause, same answer: pull fat down to 25% first. If carbohydrate is still below 35%, accept it — but do not go below 30%, and if the arithmetic demands that, the energy target is too low. Recheck [the floor](03-energy-target.md#floors). **Protein below 20%.** Only on a high target for a large, sedentary client. Raise protein to 20% of kcal — nothing is lost by exceeding a g/kg minimum. !!! note "45% is a destination, not a constraint" A carbohydrate share of 40–48% is a normal outcome and needs no comment. The figure is there to keep the plan from drifting into a low-carbohydrate one by accident, not to be hit exactly. ## Per-meal protein The daily total is not the whole instruction. Protein has to be **distributed**, because each meal is a separate stimulus and one that falls below the threshold does less work. - **30–40 g in each of the three main meals** - 10–15 g in each snack This is a hard requirement where [the menopause protocol](../reference/menopause.md#protein-per-meal) applies and a strong preference otherwise. It is the constraint that most often forces a breakfast to be rebuilt: toast and coffee cannot reach 30 g, and in this questionnaire's population that is what breakfast usually is. Carry the per-meal figure forward — [step 6](06-meal-schedule.md) needs it. ## Worked example Continuing the client from step 3: 52 y, female, 78 kg, 164 cm, perimenopausal, 3 sessions/week, fat-loss deficit, target 1800 kcal. ``` BMI 29.0 → under 30, use actual weight Menopause protocol, 3 sessions/week → 1.6 g/kg Deficit → +0.1 → 1.7 g/kg Protein 78 × 1.7 = 133 g → 532 kcal → 29.6% Fat 27.5% of 1800 = 495 kcal → 55 g Carb 1800 − 532 − 495 = 773 kcal → 193 g → 42.9% Check: protein 29.6% — inside 20–30% ✓ fat 27.5% — inside 25–30% ✓ carb 42.9% — at or above 35% ✓ No adjustment needed. → 133 g protein · 193 g carbohydrate · 55 g fat → per main meal: 30–40 g protein ``` Note how close the protein check came to failing. At 1600 kcal the same 133 g would be 33% and the fat would have come down to 25% to make room — that is the common case, not a rare one, and it is why the check exists. Record it, then hand on: ```markdown ### Macros Protein 133 g (1.7 g/kg, menopause protocol + deficit) · 29.6% Carb 193 g · 42.9% Fat 55 g · 27.5% Per main meal: 30–40 g protein. Per snack: 10–15 g. ``` --- Previous: [3. Energy target](03-energy-target.md) · Next: [5. Portion exchanges](05-portion-exchanges.md) # 5. Portion exchanges Grams become portions. This is the step that turns a macro target into something a client can actually follow, because nobody weighs 193 g of carbohydrate but everybody can count four slices of bread. Reconcile against **macro grams**, never against kcal. The kcal column in the exchange table is a conventional rounded figure and reads about 3% low across a day; chasing it produces portion counts that miss the macros they were derived from. ## The order matters Groups are not filled in an arbitrary order, because several of them contribute to more than one macro. Filling protein before starch, for example, overshoots protein — starch carries 3 g of it per portion, and by the time seven portions of starch are in, that is another 21 g. So: **fix the groups that are set by rule, then fill the ones that absorb the remainder, in dependency order.** ### 1. Fix the rule-driven groups These are not derived from the macros. They come from the standing guidance. | Group | Count | Why | |---|---|---| | **Vegetable** | **5–8**, default 7 | [Half the plate](07-plate-and-combinations.md) at lunch and dinner is 3 portions each; one more elsewhere | | **Fruit** | **2** | Two a day, tart preferred — see [Exchange lists](../reference/exchange-lists.md#fruit-1-portion) | | **Dairy** | **0–3**, default 2 | 0 if avoided or not tolerated; 2 normally; 3 where [calcium](../reference/supplements.md#calcium) is a priority | Vegetables are a **minimum** the client may exceed, but the count used in the arithmetic is the one written down. Do not budget for 5 and plan for 8. ### 2. Subtract what they contribute ``` remaining_carb = carb_target − (veg + fruit + dairy carb) remaining_protein = protein_target − (veg + dairy protein) remaining_fat = fat_target − (dairy fat) ``` ### 3. Starch — from the remaining carbohydrate Starch is the only group left that carries meaningful carbohydrate, so it is fully determined: ``` starch = round(remaining_carb / 15) ``` Then subtract what starch contributes: 3 g protein and 1 g fat per portion. ### 4. Protein — from what is left of the protein ``` protein = round(remaining_protein / 7) ``` Then subtract 1 g of fat per portion. ### 5. Fat — from what is left of the fat ``` fat = round(remaining_fat / 5) ``` ### 6. Reconcile Add the six groups back up and compare to the targets from [step 4](04-macro-split.md). | Macro | Tolerance | |---|---| | Protein | ±5 g | | Carbohydrate | ±10 g | | Fat | ±5 g | Outside tolerance, adjust by **one portion at a time**, cheapest first: - Carbohydrate off → ±1 starch (moves carb 15, protein 3, fat 1) - Fat off → ±1 fat (moves fat 5 only — the cleanest lever there is) - Protein off → ±1 protein (moves protein 7, fat 1) - Still stuck → ±1 vegetable, staying within 5–8 Re-check after each single move. Two moves at once is how a reconciliation oscillates. ## Worked example Continuing: 133 g protein, 193 g carbohydrate, 55 g fat. ``` 1 Fixed: veg 7 · fruit 2 · dairy 2 veg → carb 35, protein 14 fruit → carb 30 dairy → carb 24, protein 16, fat 6 2 remaining_carb = 193 − 35 − 30 − 24 = 104 remaining_protein = 133 − 14 − 16 = 103 remaining_fat = 55 − 6 = 49 3 starch = round(104 / 15) = 7 → carb 105, protein 21, fat 7 4 protein = round((103 − 21) / 7) = round(82 / 7) = 12 → protein 84, fat 12 5 fat = round((49 − 7 − 12) / 5) = round(30 / 5) = 6 → fat 30 6 Totals carb 105 + 35 + 30 + 24 = 194 target 193 +1 ✓ protein 21 + 84 + 14 + 16 = 135 target 133 +2 ✓ fat 7 + 12 + 30 + 6 = 55 target 55 0 ✓ ``` First pass, inside tolerance on all three. That is normal — the order of operations is what makes it normal. ```markdown ### Daily portions starch 7 · protein 12 · fat 6 · vegetable 7 · fruit 2 · dairy 2 Delivers: carb 194 g · protein 135 g · fat 55 g ``` ## Legumes are counted twice, and that is not a rounding error Half a cup of cooked legumes is **1 starch portion and 1 protein portion**. When [step 7](07-plate-and-combinations.md) fills a starch slot with lentils, it is also filling a protein slot, and both counters come down. This is what makes a plant-forward menu arithmetically possible. It is also the easiest place to make a mistake in the other direction — counting the lentils twice against the same slot, or forgetting the second count entirely and leaving the client 20 g of protein short. Step 9 checks it. ## When a group has to be dropped A vegan client has no dairy portions. A client who cannot tolerate any of the listed starches at all is rarer but happens. Do not adjust downstream. **Come back to this step**, set the group to zero, and re-run from part 2. The macro targets from step 4 do not change; what changes is which groups absorb them. | Dropped | Absorbed by | |---|---| | Dairy | +1 protein, +1 starch, +1 fat approximately | | All animal protein | Legumes — which raises starch, so recheck carbohydrate | | Gluten-containing starch | Nothing changes; the group survives, the food list shrinks | | Fruit | Vegetables and starch. Do not drop fruit for a preference — see [step 8](08-personalise.md) | If dropping a group cannot be absorbed inside tolerance, the constraint is tighter than the menu can express, and *Notes for you* says so plainly rather than the arithmetic quietly failing. --- Previous: [4. Macro split](04-macro-split.md) · Next: [6. Meal schedule](06-meal-schedule.md) # 6. Meal schedule The day is **three main meals and two snacks**, on a clock. Not "eat when hungry", and not six small meals — five occasions, at times that line up with when the body handles food best. This step moves portions between slots. It never changes how many there are. ## The shape of the day | Slot | Window | Weight | Rule | |---|---|---|---| | On waking | — | — | Water only. No food for **90–120 minutes** | | **Breakfast** | 08:00–10:00 | Light to medium | ~25% | | Snack 1 | between | Small | ~10% | | **Lunch** | 12:00–14:30 | **Heaviest** | ~35% | | Snack 2 | between | Small | ~10% | | **Dinner** | 18:00–20:00 | Light | ~20% | Percentages are of the day's energy and are approximate — they are a shape to aim at, not a constraint to hit. The rules below are the constraints. ### Waking No food for 90–120 minutes after waking. Water on an empty stomach immediately — see [Hydration](../reference/hydration-and-behaviour.md#the-drinking-schedule). Coffee after the first meal, not before it. For a 06:30 riser this puts breakfast at 08:00–08:30, which is why the breakfast window opens where it does. For someone waking at 05:00 the window opens at 06:30–07:00 and the whole day shifts earlier; see [below](#when-the-day-doesnt-fit). ### Gaps **3 to 5 hours between main meals.** A snack sits in a gap; it does not create a new one. Under 3 hours the previous meal is still being handled; over 5 and the client arrives at the next meal hungry enough that pace and portion both go. Where `sys_digestive_nausea` is `"Yes"`, keep to the short end — 3 to 3½. ### Dinner **Two rules, both hard.** 1. **Finished by 20:00.** 20:30 at the absolute latest. Nothing after. 2. **No starch.** Protein, vegetables and fat only. The no-starch dinner is the most visible thing about this menu and the thing clients ask about most. It is worth being able to state the reason: carbohydrate tolerance falls through the evening, and an evening starch load sits against a long overnight fast doing nothing useful. Vegetables and protein at that hour digest better and sleep better. Fruit is not a dinner food either — it is carbohydrate. Fruit portions go in the snacks or at breakfast. !!! warning "Late-night eating" Nothing after dinner. Where a client's `nut_last_meal` shows 22:00 or later, this is the largest change the menu makes to their day, and it needs to be in *Notes for you* as a change rather than buried as a time in a table. ## Distributing the portions Work in this order — each constraint is tighter than the one after it. ### 1. Dinner first, because it is the most constrained Zero starch, zero fruit, and still 30–40 g of protein. That is 4–5 protein portions plus vegetables and fat, and there is no other way to build it. Assign dinner before anything else and the rest of the day has room; assign it last and the protein portions are already spent. ### 2. Lunch, because it is the largest Roughly a third of the day. Half the plate vegetables (3 portions), a quarter protein, a quarter starch — see [step 7](07-plate-and-combinations.md). This is where the biggest starch allocation goes. ### 3. Breakfast, against the protein floor 30–40 g of protein. This is where the requirement bites: a breakfast of bread and coffee reaches about 8 g. Reaching 30 needs eggs, dairy, cottage cheese, Greek yoghurt or legumes, and often two of them. Breakfast is *light to medium* in energy but not light in protein. Those are not in conflict — protein is the cheapest macro per calorie in this system. ### 4. Snacks, with whatever is left 10–15 g of protein each. Typically a fruit portion plus a protein or fat portion; a dairy portion works well in one of them. Snack 2 sits between lunch and dinner, which is the afternoon slump — see [`life_energy_crash`](08-personalise.md). ### 5. Check - [ ] Portions per group sum **exactly** to the daily counts from step 5 - [ ] Dinner has zero starch, zero fruit - [ ] Each main meal is 30–40 g protein - [ ] Lunch is the largest slot by energy - [ ] Dinner is smaller than lunch - [ ] No gap under 3 h or over 5 h ## Worked example Continuing: 7 starch · 12 protein · 6 fat · 7 vegetable · 2 fruit · 2 dairy. Client wakes 06:30, works a seated job, trains three evenings a week. | Slot | Time | Starch | Protein | Fat | Veg | Fruit | Dairy | Protein g | kcal | |---|---|---|---|---|---|---|---|---|---| | Breakfast | 08:00–08:30 | 2 | 2 | 1 | 1 | — | 1 | **30** | 400 | | 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 | 19:00–19:30 | **0** | 4 | 2 | 3 | — | — | **34** | 305 | | **Total** | | **7** | **12** | **6** | **7** | **2** | **2** | 135 | 1745 | Every column sums to its daily count. Main meals at 30, 39 and 34 g of protein. Lunch is the largest slot at 34% of the day, dinner the smallest main at 17%. Gaps: 4½ h and 5½ h between mains — the second is at the limit, which is what the 16:30 snack is for. ## When the day doesn't fit ### Shift work `life_shift_work` = `"Yes"`. The clock times are not usable; the **structure** is. Anchor everything to waking rather than to the hour: | | | |---|---| | Waking + 0 | Water | | Waking + 1½–2 h | Meal 1 | | Then | 3–5 h gaps, five occasions | | Last meal | ≥ 3 h before sleep, no starch | Read `life_shift_pattern` and write the menu against the client's actual rotation. If the pattern rotates, give the shape and say it applies to each rotation. ### Early or late risers Shift the whole day. A 05:00 riser eats at 06:30, 09:30, 12:00, 15:30, 18:00 — same gaps, same rules. A 10:00 riser eats at 11:30, 14:00, 16:30, and dinner still ends by 20:00, which compresses their afternoon. Say so. ### Training in the evening Common, and it collides with two things at once. **The dinner rule.** Put snack 2 **before** the session, with its starch portion, and keep dinner after it as specified — light, no starch. `nut_around_training` says what the client already does. **The 3–5 h gap.** It cannot hold. A client waking at 06:30 eats first at 08:15; a dinner late enough to follow an evening session puts lunch and dinner more than five hours apart no matter where lunch sits, because the day is simply longer than three gaps of five hours. This is the one documented exception: **on a training day the lunch-to-dinner gap may reach 6 hours, and snack 2 is what covers it.** Place that snack in the second half of the gap, and give it protein and fat rather than fruit alone. Where the client does *not* train, the ordinary 3–5 h rule applies and the menu should show the earlier dinner. ### Fewer than five occasions `nut_meals_per_day` of 2, a client who cannot eat at work, an intermittent fasting pattern in `nut_pattern`. Consolidate the snacks into the mains rather than dropping their portions — but keep 30–40 g of protein per meal, which is harder with three occasions than five, and say what was consolidated. Do not consolidate into fewer than three occasions. Below that the per-meal protein cannot be met. --- Previous: [5. Portion exchanges](05-portion-exchanges.md) · Next: [7. Plate & combinations](07-plate-and-combinations.md) # 7. Plate & combinations Portion counts become food. Each slot gets three or four options, and **every option in a slot carries that slot's exact portion counts** — that is what lets the client choose without the plan moving. ## The plate For lunch and dinner, the shape is fixed: | | | |---|---| | **Half the plate** | Vegetables — cooked and raw together, not one or the other | | | **A quarter** | | Protein — plant-based preferred | | | **A quarter** | Complex carbohydrate — at dinner, this quarter is absent and the vegetables take it | Raw *and* cooked in the same meal, deliberately. They do different things, and a plate of only salad or only cooked vegetables gets half of each. "Empty" carbohydrate — refined grain, anything from [the food-quality lists](../reference/food-quality.md) — does not fill the carbohydrate quarter. There is no version of the plate where it does. ## Complete plant protein Plant protein is preferred, and the way to make it complete is to **combine legumes and grains at roughly 1:1**. Neither carries the full amino-acid profile alone; together they do. | Combination | Counts as | |---|---| | Brown rice + lentils | 2 starch + 1 protein per ½ cup of each | | Quinoa + chickpeas | 2 starch + 1½ protein per ½ cup of each | | Quinoa + lentils | 2 starch + 1½ protein per ½ cup of each | | Buckwheat, on its own | 1 starch + ½ protein per ½ cup | | Wholemeal pita + hummus | 2 starch + 1 protein + 1 fat | | Wholemeal bread + tahini | 1 starch + 1 fat | Buckwheat and quinoa are complete without a partner, which makes them the shortcut when a slot is tight. Remember that legumes count in **two groups** — see [step 5](05-portion-exchanges.md#legumes-are-counted-twice-and-that-is-not-a-rounding-error). Half a cup of lentils fills a starch portion and a protein portion at once, so a rice-and-lentil dish made from ½ cup of each is 2 starch and 1 protein — not 2 starch alone. ## Combinations that work These are the pairings the menu builds options from. They are not arbitrary suggestions — each pairs a carbohydrate with fibre, fat or protein so the insulin response is blunted rather than sharp. === "Bread & spreads" Wholemeal bread with tahini · hummus · avocado · peanut butter · pesto · pastrami One starch, one to two fat, and the fat is what stops the bread acting alone. Bread on its own is not an option in this menu. === "Grains & vegetables" Brown rice with vegetables · sweet potato or potato with vegetables · wholemeal pasta with sugar-free tomato sauce · oats with legumes Check the pasta sauce: most jarred sauces carry [added sugar](../reference/food-quality.md#hidden-sugars). === "Fruit & fat" Apple — or any fruit — with almonds or walnuts The default snack shape. Fruit alone is a fast carbohydrate; fruit with nuts is a snack. === "Dairy & vegetables" Yoghurt or labneh with vegetables · brown rice cakes with a natural spread and vegetables "Natural spread" means tahini, hummus or nut butter — not anything with an ingredient list. === "Patties" **Baked**, never fried, with a fresh fruit salad and a little cinnamon Legume or fish patties. The cinnamon is not decorative — it is there instead of the sugar the fruit salad would otherwise seem to need. ## Writing an option An option is one line naming real food in real quantities, whose portions match the slot's counts exactly. **Lunch slot: 4 starch · 3 protein · 2 fat · 3 vegetable** > > Rice and lentils: ½ cup cooked brown rice and ½ cup cooked lentils, with > 100 g baked chicken breast, a large salad (3 portions) and 2 tsp olive oil. Counting it back: rice ½ cup = 1 starch. Lentils ½ cup = 1 starch **and** 1 protein. Chicken 100 g ≈ 3 protein… which is 4 protein against a slot of 3. **That option does not balance.** Drop the chicken to 60 g (2 protein) and add a starch — ¾ cup of rice — and it does. This is the ordinary work of this step: write it, count it back, adjust. An option that has not been counted back is not finished. ### Four rules for options 1. **Name quantities the client can measure.** Cups, slices, tablespoons, grams for meat and fish. Not "a portion of", not "some". 2. **Vary across the options.** Four lunches built on rice are one lunch. Reach across the [exchange lists](../reference/exchange-lists.md) — different grains, different proteins, different vegetables. 3. **At least one plant-protein option per main slot.** Plant protein is the preference; a slot where all four options are animal protein has not applied it. 4. **Every option passes [the food-quality check](../reference/food-quality.md#the-check-step-9-runs).** No refined grain, no oil from the avoid list, no added sugar, nothing fried. ## Fibre, placed rather than hoped for The fibre target is 25–30 g and it comes from where the legumes and wholegrains land. Before moving on, add up a representative day — one option from each slot — and check it. **Under 25 g:** the fix is usually one legume portion moved into lunch. **Well over 30 g:** expect this. A build that is wholegrain throughout, with seven vegetable portions and a legume option, lands nearer 45 g at a typical energy target — the portion system delivers more fibre than the target asks for, and for an adapted eater that is fine. What it is not fine for is someone arriving from a low-fibre baseline, and it makes [the ramp rule](../reference/fibre-and-gut.md#the-ramp-rule) the binding constraint rather than the target. **Do not solve an overshoot by cutting portions.** Portion counts are fixed by [step 5](05-portion-exchanges.md). Solve it inside the counts: | Instead of | For the first weeks | |---|---| | 1 cup cooked legumes | ½ cup, tinned and rinsed | | Oats | Rice, potato or sweet potato in the same starch count | | Raw cruciferous | The same vegetables, cooked | | Wholemeal pita and bread at the same meal | One of them, with a lower-fibre starch beside it | Then say so in *Notes for you*, as a first-weeks instruction with a date to revisit — not as a permanent feature of the menu. ## Cruciferous and phytoestrogens Where [the menopause protocol](../reference/menopause.md) applies, two things must be placeable every day: - A cruciferous vegetable in at least two slots' options - A phytoestrogen source — most easily 2 tbsp of ground flaxseed, which is 2 fat portions and goes into yoghurt, oats or a salad Neither can be left to chance across four options where the client might pick around them. --- Previous: [6. Meal schedule](06-meal-schedule.md) · Next: [8. Personalise](08-personalise.md) # 8. Personalise This step is a filter, not a design step. **It may change which food fills a portion. It may not change how many portions are in a slot.** Where a constraint cannot be satisfied without changing a count, go back to [step 5](05-portion-exchanges.md), rebuild the daily distribution with that group excluded, and re-run 6 and 7. That loop is expected, not a failure. Apply the constraints in the order below — hard exclusions first, because everything after them is choosing among what remains. ## 1. Hard exclusions Nothing on this list ever appears in the menu, including inside a combination suggestion or a "for example". | Source | Read as | |---|---| | `health_allergies` | Absolute. Free text — read it whole, including severity | | `nut_intolerances` | Absolute | | `nut_pattern` | Vegetarian, vegan, pescatarian, halal, kosher — structural | | `nut_dislikes` | Absolute. A disliked food in a menu is a menu the client ignores | !!! danger "Read the exceptions in free text" `nut_dislikes` = "I don't eat fish except tuna, and no coriander" is three facts, and a menu built on "dislikes fish" loses the tuna. Free text is read whole, and the exception is the part that matters. Where free text is genuinely ambiguous — a severe allergy without a food named clearly enough to exclude — that is a [conditional stop](../reference/red-flags.md), not a guess. ### Patterns, concretely | `nut_pattern` | Effect | |---|---| | Vegetarian | No meat, fish. Eggs and dairy stay. Legumes carry most protein | | Vegan | Also no eggs, dairy. **Dairy portions → 0, rebuild from step 5.** Flag B12 — see [Supplements](../reference/supplements.md) | | Pescatarian | Fish and seafood stay; no meat or poultry | | Halal / Kosher | No pork; kosher additionally separates meat and dairy — no option may combine them in one meal | | Low carb / keto | Conflicts with the 45% target. Build to the target and say why in *Notes for you*; do not silently build a low-carb plan | | Paleo | No grains, legumes or dairy. Very tight against this portion system — rebuild from step 5 and expect the fibre target to be hard | | Intermittent fasting | A scheduling constraint — see [step 6](06-meal-schedule.md#fewer-than-five-occasions) | | Mediterranean | Already what this menu builds. No change | ## 2. Medical constraints carried from step 2 | Finding | Effect on the options | |---|---| | Type 2 / pre-diabetes | Starch always paired with fat, fibre or protein — never alone. Even spread across slots. A line in the menu that their clinician reviews the carbohydrate targets | | Thyroid disorder | Soy to one portion a day, separated from medication. Timing note only | | High blood pressure | No added salt in options; olives and tinned fish noted as salty. No other change | | Perimenopause / post-menopause | [The protocol](../reference/menopause.md) — cruciferous daily, phytoestrogen daily, protein per meal | | Digestive symptoms | [Fibre ramp](../reference/fibre-and-gut.md#reading-the-digestive-answers), cooked over raw cruciferous, sugar alcohols out | | Iron supplement | Separate from coffee and dairy portions | | Vitamin D supplement | Placed with the slot that has a fat portion | ## 3. Practical constraints These decide whether the menu is followed at all, and they are underweighted far more often than the clinical ones. ### Cooking `nut_cooking_confidence` (1–5), `nut_who_cooks`, `nut_who_shops`. | | | |---|---| | 1–2 | No option may need more than one cooking step. Tinned legumes, pre-cooked grains, raw vegetables, grilled or baked protein. No recipe requiring timing | | 3 | One option per slot may be more involved; the rest are simple | | 4–5 | No constraint | | `nut_who_cooks` = someone else | Options must be describable to that person. Simpler, more conventional dishes | | `nut_who_cooks` = nobody, eat out | See below | ### Meals out `nut_meals_out`. Above about 5 a week, the menu that only works at home fails most of the time. Give each main slot **one option that is orderable** — grilled protein with salad and a starch side is available almost everywhere. Say in *Notes for you* that restaurant food is cooked in [oils this menu avoids](../reference/food-quality.md#fats) and that this is a reason to cook at home where possible, stated once and without repetition. ### Budget `pref_budget`. Where it is tight, lean on the cheap end of every group: legumes, eggs, seasonal vegetables, tinned fish, oats, tahini. Avoid salmon, berries out of season, pre-prepared anything. Never comment on the budget itself. ### Household `nut_household_eating`. Where the household eats very differently, prefer options that are a variation on a shared meal — the same dish with a different starch portion — over options requiring a separate meal to be cooked. ### Travel and shift work `life_travel` weekly or constant: at least one option per slot needing no kitchen. `life_shift_work`: the anchored schedule from [step 6](06-meal-schedule.md#shift-work). ### Accessibility and eating limitation `pref_accessibility`, `nut_eating_limitation` + its detail. Chewing or swallowing difficulty, standing to cook, getting food to the mouth — each constrains texture or preparation directly. Read the detail field and apply what it actually says. ## 4. Preference Last, and only among what has survived. `nut_ffq` shows what the client already eats often; where two options are otherwise equal, choose the food already in their week. `nut_appetite` = `"Low"` argues for denser options within the same counts; `"Large"` for higher-volume ones — more vegetables, more whole fruit. `life_energy_crash` = `"Yes, most days"` puts the afternoon snack squarely in the slump and gives it protein and fat rather than fruit alone. ## The count check After every substitution, count the option back against its slot. This is the only invariant this step has, and the substitution most likely to break it is the one that swaps an animal protein for legumes — because [legumes carry a starch portion too](05-portion-exchanges.md#legumes-are-counted-twice-and-that-is-not-a-rounding-error). ``` Swap: 90 g chicken (3 protein) → 1½ cups lentils (3 protein + 3 starch) Slot was 4 starch · 3 protein → now 7 starch · 3 protein ✗ Fix: drop the rice from 4 starch to 1 ✓ ``` ## What this step must not do - **Not change portion counts.** Ever. Go back to step 5. - **Not drop fruit or vegetables for a preference.** A disliked vegetable is replaced by another vegetable, not removed. - **Not argue with a stated pattern.** A client who says vegan gets a vegan menu. The menu may note what it cannot cover; it does not relitigate. - **Not add a food the client excluded**, however small the quantity or however well it fits. - **Not narrow the menu to safety.** Four options that are all rice and chicken satisfy every constraint and fail the client. --- Previous: [7. Plate & combinations](07-plate-and-combinations.md) · Next: [9. Assemble & check](09-assemble-and-check.md) # 9. Assemble & check Write the menu into [the schema's shape](../menu/schema.md), then run the checklist. The checklist is the deliverable's actual quality gate — a menu that has not been through it is not finished, however good it looks. ## Assembling Fill the schema's top-level keys in this order — not the order they appear in [the schema page](../menu/schema.md), but the order that catches problems early. Write both languages together for each, per [the style guide](../menu/style-guide.md#write-both-languages-together). 1. **`dailyTarget` and `dailyPortions`** — carried forward from steps 3–5. Not client-facing, but everything else is checked against them. 2. **`slots`** — each slot's `portions`, then its 3–4 `options` from steps 7 and 8, each option's `items` written out with their own portions. 3. **`notes`** — every personalisation that the client would otherwise experience as an unexplained choice. See below. 4. **`assumptions`** — every gap from the digest that changed the menu. An empty array if there were none. 5. **`drinking`** and **`atTheTable`** — from [Hydration & eating behaviour](../reference/hydration-and-behaviour.md), near-verbatim in both languages, each `atTheTable` rule flagged `essential` per the sensitivity filter. 6. **`client`, `writtenDate`, `reviewWeeks`, `disclaimer`** — the fixed parts; `disclaimer` must equal the schema's `const` exactly, in both languages. ### What belongs in *Notes for you* Personalisation the client can see the effect of but not the reason for. Each one sentence, no arithmetic: - The largest change from their current eating — usually breakfast protein or the dinner starch - Any symptom accommodated (bloating → cooked vegetables and a slower fibre ramp) - Supplement timing notes — see [Supplements](../reference/supplements.md) - Where a group was dropped or heavily restricted, and what replaced it - Where the floor bound the energy target - Alcohol, if `health_alcohol_units` shows regular intake — once, no elaboration - That the menu should be shared with their dietitian, where `health_practitioners` names one - Restaurant oils, where `nut_meals_out` is high ### What belongs in `assumptions` Anything a missing answer forced. In the client's terms, not the field's — one `localizedText` entry per assumption: ```json { "en": "The weight you gave was your own estimate rather than a measurement — worth updating if you weigh yourself.", "he": "…" } ``` ## The checklist Run `bun scripts/validate-menu.ts menus/.json` first — it is the arithmetic half of this list (marked **validator** below), run mechanically. The rest still needs a human read. ### Arithmetic - [ ] **validator** — Slot portion counts sum exactly to `dailyPortions` - [ ] **validator** — `dailyPortions` delivers the macros from step 4 within tolerance (±5 g protein, ±10 g carb, ±5 g fat) - [ ] Energy target (`dailyTarget.kcal`) is at or above [the floor](03-energy-target.md#floors) — not checked by the validator - [ ] **validator** — Every option's items sum to its slot's `portions` exactly - [ ] Legume items carry **both** `starch` and `protein` in their `portions` - [ ] **validator** — Each main meal reaches 30–40 g of protein - [ ] A representative day reaches **at least** 25 g of fibre; where it runs well above 30 g, the [ramp](../reference/fibre-and-gut.md#the-ramp-rule) is applied against the client's baseline and noted ### Schedule - [ ] Five occasions - [ ] First meal is 90–120 min after the client's stated waking time - [ ] Gaps between mains are 3–5 h — or up to 6 h on an evening-training day, with snack 2 placed in the second half of the gap - [ ] Dinner ends by 20:00 (20:30 absolute latest) - [ ] **schema** — Dinner contains no starch and no fruit (the schema fixes both to `0` for `id: "dinner"`, so this fails to validate rather than needing a read) - [ ] Lunch is the largest slot - [ ] Shift workers have the anchored schedule, not clock times ### Food - [ ] Nothing from `health_allergies`, `nut_intolerances` or `nut_dislikes` appears **anywhere**, including inside combination text - [ ] `nut_pattern` honoured in every option - [ ] No refined grain - [ ] No oil from [the avoid list](../reference/food-quality.md#fats) - [ ] No added sugar under [any of its names](../reference/food-quality.md#hidden-sugars) - [ ] No sweetener other than stevia, monk fruit, erythritol - [ ] Nothing fried, no processed meat, no ultra-processed item - [ ] At least one plant-protein option per main slot - [ ] Options within a slot are genuinely different from each other - [ ] Where [the menopause protocol](../reference/menopause.md) applies: cruciferous placeable daily, phytoestrogen source daily ### Safety and scope - [ ] Step 2 was run and cleared - [ ] No diagnosis, no interpretation of any test, no medication advice - [ ] No supplement recommended — only recognised and timed - [ ] No promised outcome, no timeline, no predicted weight - [ ] **schema** — `disclaimer.en` and `disclaimer.he` both equal the schema's `const` exactly ### The client - [ ] **schema** — Every string has both `en` and `he`, per [the style guide](../menu/style-guide.md) - [ ] Both languages read naturally, not as a translation of each other — not checkable by the schema - [ ] No kcal figures rendered to the client, unless `nut_tracked_before` = `"Currently tracking"` — `dailyTarget.kcal` itself is fine, it is not client-facing - [ ] **No weight, BMI or body-fat figure** where `meas_weight_aware` or `meas_blind_weigh` said not to - [ ] Numbers dropped entirely where `nut_disordered_history` = `"Prefer not to say"` - [ ] `essential: false` rules dropped where [the sensitivity filter](../reference/hydration-and-behaviour.md#at-the-table) applies - [ ] No comment on the client's weight, shape or eating history - [ ] Every gap that changed the menu is in `assumptions` - [ ] Every personalisation the client would notice is in `notes` ## The four failures worth naming These are the ones that survive a casual read. **The option that doesn't balance.** Written from a picture of a meal rather than from the slot's counts, and it looks entirely reasonable. Only counting back catches it. Count every option back — not a sample. **The excluded food in the combination text.** The options are clean, and then [step 7's combination list](07-plate-and-combinations.md#combinations-that-work) gets quoted into the menu with peanut butter in it, for the client with the peanut allergy. Search the finished file for every excluded food by name. **The starch that crept into dinner.** Usually as a legume — swapped in for protein, carrying its starch portion with it, into the one slot that may not have any. **The four identical options.** Every constraint satisfied, every count correct, and four variations of chicken with rice. It passes everything except being worth following. ## Then Write the file to `menus/-.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 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 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 **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 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 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 !!! 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 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 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 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 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.