From-Scratch Build · Recommendation Systems

Savory

A content-based recipe recommender that learns what you like to cook from the ingredients and tags of recipes you enjoy — not from star ratings. It builds a taste profile and ranks unseen recipes by how close they sit to it.

PythonTF-IDFCosine similarity Taste centroidMMR diversityCold start

What it is

Recommending by flavour, not by rating

Recipes carry their meaning on the surface: an ingredient list, a cuisine, a handful of tags. That makes food a natural home for content-based recommendation — you can describe why two recipes are similar without a crowd of other users. Savory turns each recipe into a TF-IDF vector over its ingredients and tags, builds a cook's taste profile as the centroid of what they liked, and ranks new dishes by cosine similarity to that profile.

Because the signal is the recipe itself, Savory can recommend a brand-new dish nobody has rated yet, return "more like this" for a single recipe, fall back to a diverse popular list for a brand-new cook, and optionally use MMR to keep results from collapsing into five near-identical dishes. Every recommendation comes with a plain reason: the ingredients it shares with what you already like.

Try it

Live preview

The real engine is the Python package in this repo (savory/, scikit-learn TF-IDF + cosine, 12 passing tests). This in-browser demo loads the same committed data/recipes.json and reimplements the identical math in JavaScript so you can play with it here. Numbers match the Python output.

Pick a few recipes you'd cook

Loading the recipe dataset…

    The method

    From a pantry of recipes to your next dish

    data

    Recipe corpus

    240 synthetic recipes across 10 cuisines, each with ingredients, tags and prep time. Deterministically generated, committed as JSON.

    represent

    TF-IDF vectors

    Ingredients + tags + cuisine become content tokens; TF-IDF down-weights staples like onion and garlic.

    profile

    Taste centroid

    A cook's profile is the L2-normalised centroid of the vectors of recipes they liked.

    match

    Cosine ranking

    Rank unseen recipes by cosine similarity to the profile; already-liked items are excluded.

    diversify

    MMR re-rank

    Optional Maximal Marginal Relevance trades a little relevance for variety.

    fallback

    Cold start

    No likes yet? Return popular recipes spread across cuisines so a new cook gets a sensible, diverse list.

    Architecture

    How a recipe is recommended

    1. Describe

      Turn every recipe into a TF-IDF vector over its ingredient, tag and cuisine tokens.

    2. Profile

      Average the vectors of the recipes a cook liked into a single taste profile.

    3. Match

      Score the catalogue by cosine similarity to that profile.

    4. Diversify

      Optionally re-rank with MMR so the list isn't all the same dish.

    5. Explain

      Return the best unseen recipes, each with the ingredients it shares with your likes.

    Reflection

    What building it taught me