Hack Apertus 2026Track 2B

Your Bon knows what you eat.

A Bon is a Swiss till receipt. Bon·Appétit reads yours with Apertus, Switzerland's open language model, and shows what your household ate this week on the Swiss food pyramid. It all happens on your own computer.

Runs on your computerNo accountOpen source

Apache-2.0 on GitHub. The method and results are in the report.

The problem

Receipts are written for the till.

Shops cut product names to fit a narrow paper roll, in the shop's language, with house-brand codes in front. The Migros Cumulus export uses the same names and has no barcodes, so there is nothing to look up.

What a receipt says

Five lines from two shops, exactly as printed: M-BUDG, QP and PRIX GAR. are house brands, and RUEBLI is Swiss German for carrots.

What Bon·Appétit reads

Each line becomes a food from the federal Swiss Food Composition Database (FSVO). When no entry fits, the line waits for you instead of being guessed.

How it works

Search finds candidates. Apertus picks and checks.

Search proposes foods from the Swiss food table. Apertus then works on each line in three steps: it reads the line, picks one candidate by its number or answers 0, and cross-checks the pick. The cross-check cut the typical error from 123 to 102 kcal per 100 g. Every number you see comes from the federal record.

Reading

Apertus reads the shorthand.

Apertus 8B works on each line three times. First it writes the line out in plain English, names the language, says whether it is food and gives a generic name in the style of the German food table. Then it sees a short list of candidates and answers with the number of the one that fits, or 0. Last, it estimates the product per 100 g. That estimate is never shown: it only decides which of the top eight candidates from the federal food table fits best, with a head start for its own pick.

The candidates come from all 1,145 foods in the federal food table, found by lexical token matching plus Swiss multilingual embeddings derived from Apertus by Andreas Martin. A line that gets 0 waits for you.

1,145 Swiss foodsCross-check per 100 g Lexical and embedding search Answers 0 when nothing fits
Checking

A food layer checks every pick.

Before a link counts, plain rules look at it. A line sold in litres cannot be a solid like cheese or chocolate, and the pack size has to make sense for the food. Bags and detergent are set aside as non-food.

A Swiss glossary adds words like Rüebli, Nüsslisalat and Peperoni, plus common French and Italian food words, to the search and to the pick prompt. Quantities come from the line itself, converted by code.

Unit against category Pack-size check Swiss food glossary Non-food filter
Privacy

Air-gapped by default.

A year of shopping says a lot about health, religion and money. Under the revised Swiss data protection act that is sensitive personal data, so it stays on your machine.

make airgap puts the app and a llama.cpp model server on a Docker network marked internal, with no route out. Before it starts, the app tries four outside hosts and refuses to run if any of them answers. There is no account and no telemetry. If you would rather use a Swiss cloud, point LLM_BASE_URL at it.

Internal Docker network Egress test No telemetry Swiss cloud optional
Modelapertus-v1.5-8b, local
Networkinternal
Language
Suggestion of the weekKaufen Sie statt Süssigkeiten und Süssgetränken frisches Gemüse und Früchte, um die Ernährungsempfehlungen besser zu erfüllen.
Around the model

The parts that make it usable.

Migros CSV and Coop PDF

Import the Cumulus CSV export from Migros, or the text of a Coop PDF receipt.

Four languages

Receipts in German, French and Italian. The weekly suggestion in those three and in English.

Fixes for open data

A simple calorie sanity check (4 kcal per gram of protein and carbohydrate, 9 per gram of fat) flags 3,035 of 86,517 Swiss products on Open Food Facts (3.5%) whose printed calories contradict their macronutrients; 433 look like kJ typed as kcal. The list is in eval/off_energy_flags.csv.

Try it

Try it on this page.

Pick a sample receipt, set how many people share the shopping and choose a language for the suggestions. Everything below recalculates in your browser.

Receipt

Loading sample receipts.

Suggestions in

Bon·AppétitSample
Results are being computed.The sample week appears here once it has loaded.

What Apertus taught us.

  1. Apertus read RUEBLI as kale.

    The 8B model read RUEBLI, Swiss German for carrot, as kale and once as flatbread. A short glossary of Swiss food words now goes into the search and the prompt.

  2. Spelling instructions slip.

    Asked for Swiss spelling, it still wrote ß in German suggestions. Code converts it to ss.

  3. A strict grammar made it worse.

    A full JSON schema grammar made each call about four times slower and made the model answer "no match" for almost every line. Plain JSON mode, with the schema checked in code, fixed both.

  4. It rarely says it is sure.

    So the app does not ask it for a confidence score. It chooses a list number, or 0.

Evidence

Tested against printed labels.

Thousands of Swiss products on Open Food Facts carry the nutrition values printed on their packs. We hide the label, link the product from its name or from a receipt-style truncation of it, and compare the estimate with what the pack says.

Off by more than 100 kcal per 100 g

Share of products, lower is better

Share of products whose estimate is off by more than 100 kcal per 100 g.

computingseconds per receipt line
computingCHF per 1,000 receipts

Error against the printed label

Results are being computed
Per method: typical energy error per 100 g, share of products off by more than 100 kcal, typical sugar and salt error, share within 20 percent on energy, food group accuracy, share of lines left for you, and number of products.
MethodTypical kcal error
per 100 g
Off by over
100 kcal
Sugar errorSalt errorkcal within 20%Food group rightLeft for youn

Baselines.

Fuzzy string matching, the way a 2018 hackathon app did it. Embeddings alone. Apertus alone, asked to guess the nutrients with no database. Then Apertus pick only, and Bon·Appétit with the cross-check.

The best possible row is the score a perfect link to a generic food could reach. The federal food table holds generic foods and the packs are specific brands, so even a perfect link has some error.

Limits.

Purchases are not intake. Food gets shared, thrown away or eaten outside the house, and a receipt shows none of that.

The rules that turn product names into receipt-style truncations are synthetic, and the report documents them. Federal food table values are generic, so a brand's recipe can differ.

Prior work.

TooSweet, from the Open Food Hackdays in 2018, ranked Cumulus purchases by sugar. ETH Zurich's FoodCoach and Receipt2Nutrition research studied nutrition from shopping data before us.

Bon·Appétit adds a local model that can abstain, and a check layer behind it.

Open data finding3,035

of 86,517 Swiss products on Open Food Facts list calories that contradict their own fat, carbohydrate and protein values. 433 look like kilojoules typed as kilocalories.

Energy check: 4 kcal per gram of protein and carbohydrate, 9 per gram of fat, 2 per gram of fibre; flagged when printed and computed energy differ by more than 25% and 25 kcal per 100 g. Alcoholic drinks skipped.

The list, for Open Food Facts volunteers
Who it is for

Households first, then the people who help them.

You already hold the purchase data. Bon·Appétit turns it into a nutrition picture without sending it to a third party and without a per-request fee.

Households

See what a week of shopping adds up to, privately, with swaps you can make in the same shop.

Dietitians and home care

Start from what a client actually bought, with their consent, instead of a food diary written from memory.

Insurer prevention programmes

Offer members a weekly view that stays on their own computer, so the receipts never reach the insurer.

Retailers' loyalty apps

Run the linker next to the purchase data a loyalty programme already holds, with the Swiss pyramid as the reference.

Cantonal health promotion

Use the open code in nutrition programmes in German, French or Italian speaking cantons.

Researchers

Reuse the linker module and the label-based evaluation to study shopping data and diet.

Run it

Three commands on your own computer.

You need git and Docker. Everything runs in a container on your machine.

Terminal
$ git clone https://github.com/kinnuworks/bon-appetit
$ cd bon-appetit
$ make run

make airgap runs the app with no route out. make eval re-scores the stored predictions and checks their SHA-256.

Swiss cloud instead
$ export LLM_NAME=swiss-ai/Apertus-v1.5-8B
$ export LLM_BASE_URL=https://your-swiss-endpoint/v1
$ export LLM_API_KEY=your-key
$ make run

With these set, receipt lines go to that endpoint instead of the local model. Pick a provider that runs in Switzerland under Swiss law.

Questions

Honest answers.

Is this medical advice?

No. It describes what a household bought and compares that with the Swiss dietary recommendations. For a diagnosis or a diet plan, talk to a doctor or a registered dietitian.

Does anything leave my computer?

Not with make airgap. The app and the model server sit on a Docker network marked internal, with no route out, and the app refuses to start if any of four outside hosts answers. There is no account and no telemetry. If you set LLM_BASE_URL, the receipt lines go to that endpoint, so choose one you trust.

Why not just use barcodes?

The Migros Cumulus export has no barcodes, and a paper receipt does not print them. The shortened product name is all there is to work with.

What about Coop?

Coop PDF receipts are read as text. Lines with Coop house brands such as Naturaplan, Qualité & Prix and Prix Garantie go through the same reader as Migros lines.

What if it gets an item wrong?

Every line shows the food from the federal food table it was linked to, so a wrong link is easy to spot. The demo on this page keeps the real mistakes, such as iced tea linked to lemon juice. When no candidate fits, Apertus answers 0 and the line waits for you, and the food layer rejects links whose unit or pack size makes no sense.

Which model runs it?

Apertus v1.5 8B, running locally; the air-gapped setup serves it with llama.cpp. It works on each line in three steps: it reads the line, picks a candidate by its number, and cross-checks the pick against its own estimate per 100 g, which is never shown. It never produces a nutrient value: code computes every quantity from the FSVO database.

Can I use it in French or Italian?

Yes. It reads receipts in German, French and Italian, and writes the weekly suggestion in German, French, Italian or English.

How accurate is it?

See the evidence section. Estimates are measured against the nutrition labels printed on Swiss products on Open Food Facts, next to simpler baselines and a best possible row that shows how close a perfect link to a generic food could get. What a household buys is still not exactly what it eats.

Bon·Appétit app icon: a receipt with a leaf growing from its top edge

Read last week's shopping. On your own computer.

Open source, in four languages, with no account to make.