The problem
A restaurant should be about food and guests. In practice it runs a menu tool, a till, a kitchen printer, a payment terminal and a fiscal register, and someone still types the menu in by hand. We counted 43 separate tasks.
Pesto puts them in one system: the menu and QR ordering, the till, floor and kitchen screens, stock, payments and receipts. It is a side project I build with a small team. My focus is the AI that does the typing.
A menu from a photo
The owner photographs the paper menu. Google Vision reads the text with its position on the page, so a price stays with its dish. Claude turns that into a structured menu through structured output to a strict schema: sections, dishes, prices, allergens and nutrition.
Parsing starts with the first page and runs while the owner goes through onboarding. By the time they reach the dishes, the menu is there.
Translations, and a bug worth telling
Translations run in batches while the owner checks the dishes. An edited dish is sent again; the others stay as they are.
One dish name in quotes broke every batch: in German the model opened the quote with „ and closed it with a straight quote, which ended the JSON string early. Moving to structured output fixed the parsing, but curly quotes still reached the decoder as bare quotes. Now the prompt asks for « » and a test fails if anyone makes it typographically nicer again.
AI that drafts, people who decide
Claude drafts a recipe card for every dish the first time the menu is reviewed, so stock can follow sales. It never guesses a price: a guessed cost is a guessed margin.
A photographed delivery note is read by the model and shown as a form. Nothing is written until a person confirms it.
Testing the real thing
Each AI stage is tested on its own against expected JSON for real menu photos.
The end-to-end suite runs a whole restaurant shift in a real browser against the real database: sign up, upload a menu, order at a table, cook it, serve it, take the money. Only Vision and Claude are faked, at the network boundary. The app has no test mode.
Order totals, payment state and permissions are database triggers and policies, not app code, so every way into the data obeys them. Parity tests fail if the SQL and the TypeScript copies drift apart.