My friend Maya has peanut, tree-nut, and sesame allergies. Every group dinner starts the same way: her squinting at
ingredient lists, googling "does pesto have nuts" (it does โ pine nuts), and quietly eating plain rice while everyone else
shares satay.
So I built her SafePlate โ an allergy-aware meal planner that reads every ingredient of every recipe for her:
- ๐ฅ Her profile, not a generic one โ name, allergies, and how strict to be ("Sensitive" vs "Severe: flag every trace").
- ๐ฝ๏ธ A verdict on every recipe โ All clear, Worth a closer look, or Not safe for Maya โ with the exact ingredient that triggered it and why.
- ๐ต๏ธ It catches the hidden ones โ pesto โ pine nuts, Worcestershire sauce โ anchovies, marzipan โ almonds, tahini โ sesame, green curry paste โ shrimp paste.
- ๐ Every flag comes with a swap โ peanut sauce โ sunflower seed butter, tahini โ sunflower seed butter + lemon.
- ๐ท๏ธ "Check anything" scanner โ paste any ingredient list (a sauce label, a snack bar) and it flags what's risky for her.
- ๐๏ธ One-click week planner โ a full week of safe plates, saved locally on her device.
๐ Live demo: https://karleeov-dev-hack.kimi.page
It's fully shareable by URL, which makes for a fun try-it-yourself:
-
https://karleeov-dev-hack.kimi.page?friend=Maya&allergens=peanut,tree-nut,sesameโ pre-loaded profile -
https://karleeov-dev-hack.kimi.page?scan=nutella%20spread,%20tahini%20drizzle,%20oat%20milk,%20eggplant,%20honey%20roasted %20peanuts,%20rice%20crackersโ auto-runs the scanner on a tricky list. Three of those are dangerous for Maya; three only sound dangerous.
First visit downloads the model (~23 MB); after that it's cached and the whole app works offline.
## Code
https://github.com/karleeov/Maya
No build step, no framework โ index.html, app.js, recipes.js, styles.css. The phrasebook, veto table, and swap
list in recipes.js are plain readable data, so adapting SafePlate for a different allergy set means editing a list, not a
model.
## How I Built It
The core is all-MiniLM-L6-v2, an open-weight sentence-embedding model (Apache-2.0), running entirely in the
browser through transformers.js + ONNX Runtime Web. Ingredients and allergen profiles become 384-dimensional vectors;
cosine similarity is what knows marzipan lives suspiciously close to almonds.
But here's the honest part: embeddings alone were too noisy to trust with someone's health. I validated the scorer
offline against the exact quantized ONNX build the browser ships, and the false-positive ceiling was brutal โ rice noodles scored 0.80 (because "egg noodles" exist),
โ eggtomato sauce โ fish hit 0.76, butter โ tree nuts 0.74. Pure semantic
search would have cried wolf about pancakes and stayed quiet about satay.
So SafePlate is a hybrid:
-
A curated, word-boundary-aware phrasebook per allergen carries precision โ it knows
eggplantโeggandbuttermilkโbutter. - The embedding layer rides shotgun โ powering the "semantically close to X (matched term)" explanations, catching long-tail phrasings, and driving the caution tier.
- A small veto table encodes domain truth the model can't know: coconut milk isn't dairy, oat milk is fine, peanut butter contains no butter.
- Severity modes shift the thresholds, because "sensitive" and "anaphylactic" are not the same product. In Severe mode it still raises coconut as a tree-nut caution (the FDA lists it as one) โ I'd rather explain a false alarm to Maya than apologize for a missed one.
The model downloads from the Hugging Face hub (with a mirror fallback), caches in the browser, and after that the app โ
model included โ runs with the Wi-Fi off.
t asking anyone's permission.
- Hackable. If her little cousin has a different allergy set, we edit a data list โ not a billing plan.
The honest trade-off: a small open model is dumber than a frontier closed one. That's why the architecture is hybrid โ
the open model does what it's good at (semantics, explanations, long-tail matching) and boring curated data does what it's
good at (precision). Open source didn't lower the ceiling; it changed where I put the walls.



Top comments (0)