9. Assemble & check¶
Write the menu into the schema's shape, 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, but the order that catches problems early. Write both languages together for each, per the style guide.
dailyTargetanddailyPortions— carried forward from steps 3–5. Not client-facing, but everything else is checked against them.slots— each slot'sportions, then its 3–4optionsfrom steps 7 and 8, each option'sitemswritten out with their own portions.notes— every personalisation that the client would otherwise experience as an unexplained choice. See below.assumptions— every gap from the digest that changed the menu. An empty array if there were none.drinkingandatTheTable— from Hydration & eating behaviour, near-verbatim in both languages, eachatTheTablerule flaggedessentialper the sensitivity filter.client,writtenDate,reviewWeeks,disclaimer— the fixed parts;disclaimermust equal the schema'sconstexactly, 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
- Where a group was dropped or heavily restricted, and what replaced it
- Where the floor bound the energy target
- Alcohol, if
health_alcohol_unitsshows regular intake — once, no elaboration - That the menu should be shared with their dietitian, where
health_practitionersnames one - Restaurant oils, where
nut_meals_outis high
What belongs in assumptions¶
Anything a missing answer forced. In the client's terms, not the field's — one
localizedText entry per assumption:
{
"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/<the-file>.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 —
dailyPortionsdelivers 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 — not checked by the validator - validator — Every option's items sum to its slot's
portionsexactly - Legume items carry both
starchandproteinin theirportions - 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 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
0forid: "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_intolerancesornut_dislikesappears anywhere, including inside combination text -
nut_patternhonoured in every option - No refined grain
- No oil from the avoid list
- No added sugar under any of its names
- 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 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.enanddisclaimer.heboth equal the schema'sconstexactly
The client¶
- schema — Every string has both
enandhe, per the style guide - 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.kcalitself is fine, it is not client-facing - No weight, BMI or body-fat figure where
meas_weight_awareormeas_blind_weighsaid not to - Numbers dropped entirely where
nut_disordered_history="Prefer not to say" -
essential: falserules dropped where the sensitivity filter 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 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/<client-slug>-<YYYY-MM-DD>.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 · See it done: Worked example