Skip to content

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.

  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, 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
  • 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:

{
  "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
  • validatordailyPortions 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 — 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 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
  • 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
  • schemadisclaimer.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
  • 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 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