Part 2 of 7 · Menu sync series ~4 min read

How a menu change gets made

The sync only pushes what’s in the master menu. So the first job is making it easy to change the master menu, wherever the owner happens to be. There are three ways a change gets in: somebody edits the Drive sheet, somebody taps a dish sold-out on a phone page, or somebody forwards a supplier price list to a dedicated address. The first one is obvious. The other two exist because in real life nobody opens a spreadsheet on a busy lunch service to mark the special 86’d.

Key takeaways

  • Three intake lanes feed one master menu: the Drive sheet, a phone quick-edit lane, and a supplier-price lane.
  • Forwarded price lists are read by Textract; Bedrock Haiku 4.5 matches each line to an item and proposes a price.
  • Every proposed price change goes to the owner’s Slack for one-tap approval before it lands in the menu.
  • The quick-edit lane is a tiny phone page for the two changes that happen mid-service: sold-out and small tweaks.
  • The Drive sheet stays the canonical store. The other lanes are conveniences that write into it.

Three lanes into one master menu

Three intake lanes funnel into one master menu A diagram with three vertical lane columns at the top and a single unified row at the bottom. Lane one, Drive sheet: somebody types an edit directly into the Google Sheet that holds the master menu; the menu-sync Lambda mirrors the sheet to S3 on each change, and the planner reads from there. Lane two, Quick edit: somebody opens a small phone page and taps a dish sold-out or nudges a price; the page posts to a Function URL Lambda that writes the change to the Drive sheet via the Sheets API; no approval is needed for these small, reversible changes. Lane three, Supplier prices: somebody forwards a supplier price list to a dedicated address, prices-at-your-restaurant; SES writes the raw MIME to S3; a parser Lambda runs Textract on the file, then calls Bedrock Haiku 4.5 to match each line to a menu item and propose the new price; the proposed changes get posted to the owner's Slack with Approve and Edit buttons; on approve, the prices are written to the Drive sheet via the Sheets API. All three lanes converge on the same Drive sheet, which the menu-sync Lambda keeps mirrored to S3 for the planner to read. A note at the bottom: the Drive sheet stays the source of truth — the other lanes are conveniences that write changes into it. Lane 1 · manual Drive sheet • Owner edits a row directly • menu-sync mirrors to S3 on change • Planner reads from S3 • Source of truth stays in Drive Lane 2 · phone page Quick edit • Tap a dish sold-out on phone • Function URL writes the change • No approval for small, reversible edits • Lands in sheet via Sheets API Lane 3 · SES + Textract Supplier prices • Forward price list to prices-address • SES writes MIME to S3 • Textract reads it; Haiku 4.5 matches items • Slack one-tap approve → sheet Master menu sheet (source of truth) name · description · category · price · sold-out · seasonal · channels menu-sync mirrors it to S3 on each change — planner reads from S3 to planner, on change The Drive sheet stays the source of truth — the other lanes are conveniences that write changes into it.
Fig 2. Three lanes converge on one master menu sheet. The sheet is the source of truth; the phone lane and the supplier lane are conveniences that write into it. The menu-sync Lambda mirrors the sheet to S3 so the planner can read it without hitting Drive on every change.

Lane 1: the Drive sheet itself

The simplest lane. Open the master menu sheet in Drive, edit a row, save. The columns are short: name, description, category, price, a sold-out flag, a seasonal flag, and a small list of which channels the item publishes to. A change-triggered Lambda — menu-sync — exports the sheet as plain JSON via the Drive API and writes it to s3://ms-menu-source/menu.json whenever the sheet changes. The planner reads from S3, not Drive directly. That keeps Drive API calls predictable and gives you S3 versioning for free, so a bad bulk-edit can be rolled back in one click.

This lane covers the cases where you’re sitting down with the menu in front of you — adding a new seasonal dish, rewriting a description, reorganizing categories. Most planned menu work goes in this way.

Lane 2: the quick-edit page (the lane staff actually use mid-service)

Two changes happen in the middle of a busy service: a dish sells out, or a price needs a quick nudge. Nobody is opening a spreadsheet for those. So the quick-edit lane is a tiny phone web page — a list of every item with a sold-out toggle and a price field. Tap the short rib sold-out and the page posts to a small quick-edit Function URL Lambda, which writes the change straight to the Drive sheet via the Sheets API and triggers the planner.

These changes don’t need approval. Marking a dish sold-out is reversible (toggle it back when the kitchen restocks), and a small price nudge inside the auto-sync limit is exactly the kind of routine change the rules already allow to flow. The page is locked behind a simple staff PIN stored in Parameter Store, so only the floor team can reach it. The point of this lane is speed: the change a server makes in two taps should be live everywhere before the next table orders.

Lane 3: supplier prices

Costs change when suppliers change them, and they almost always arrive as a PDF or spreadsheet attached to an email. Forcing the owner to retype a price list into the menu sheet is a chore that gets skipped — and skipping it means selling food at last quarter’s cost.

Lane 3 picks up forwarded price lists. Set up a dedicated address — something like prices@your-restaurant.com — via Amazon SES. Forward a supplier list to it and the system takes over. SES writes the raw MIME to s3://ms-raw-mime/. The S3 PUT triggers a parser Lambda that runs Amazon Textract on the attachment to read the lines, then calls Bedrock Haiku 4.5 to match each supplier line to a menu item and propose an updated cost. The prompt is short: “Match each line to a menu item by name. Return JSON only. Don’t invent a match you’re unsure of — mark it for review instead.” The proposals go to the owner’s Slack with approve, edit, and discard buttons. On approve, the new prices are written to the sheet.

Every proposed price goes to a human first for a simple reason: a price the model misread is worse than a price that never updated at all. The misread one quietly charges the wrong amount everywhere until somebody notices on the month-end numbers.

Why the menu stays the source of truth

Three lanes in, but only one place the planner actually reads. That’s a deliberate constraint. If two lanes both wrote directly to the channels, every “why does the website say this?” question would mean checking three places. Funneling everything through the Drive sheet means there is exactly one row per item, and any staff member can read or edit it without learning a new tool. The convenience lanes are first-class for getting changes in, but they always pass through the sheet on the way.

Next post: how the planner reads the master menu, compares it against what each channel currently shows, and picks one of four moves per channel.

All posts