From f1d35dd1ac9ac870e9e0721cc63773189d434ff0 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Tue, 21 Apr 2026 15:05:16 -0700 Subject: [PATCH] feat(recipes): 'Not today' per-session ingredient exclusions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users often have ingredients they want to avoid today (out of stock, not feeling it) that aren't true allergies. The new 'Not today' filter lets them exclude specific ingredients per session without permanently modifying their allergy list. - recipe.py schema: exclude_ingredients field (list[str], default []) - recipe_engine.py: filters corpus results when any ingredient is in exclude_set - llm_recipe.py: injects exclusions into both prompt templates so LLM-generated recipes respect the constraint at generation time - RecipesView.vue: tag-chip UI with Enter/comma input, removes on × click - stores/recipes.ts: excludeIngredients reactive list (not persisted to localStorage) --- app/models/schemas/recipe.py | 1 + app/services/recipe/llm_recipe.py | 6 +++ app/services/recipe/recipe_engine.py | 5 +++ frontend/src/components/RecipesView.vue | 52 +++++++++++++++++++++++++ frontend/src/stores/recipes.ts | 24 ++++++++++++ 5 files changed, 88 insertions(+) diff --git a/app/models/schemas/recipe.py b/app/models/schemas/recipe.py index ebb07e7..0d2b61d 100644 --- a/app/models/schemas/recipe.py +++ b/app/models/schemas/recipe.py @@ -100,6 +100,7 @@ class RecipeRequest(BaseModel): allergies: list[str] = Field(default_factory=list) nutrition_filters: NutritionFilters = Field(default_factory=NutritionFilters) excluded_ids: list[int] = Field(default_factory=list) + exclude_ingredients: list[str] = Field(default_factory=list) shopping_mode: bool = False pantry_match_only: bool = False # when True, only return recipes with zero missing ingredients complexity_filter: str | None = None # 'easy' | 'moderate' | 'involved' — None = any diff --git a/app/services/recipe/llm_recipe.py b/app/services/recipe/llm_recipe.py index 00afb0c..18ee718 100644 --- a/app/services/recipe/llm_recipe.py +++ b/app/services/recipe/llm_recipe.py @@ -68,6 +68,9 @@ class LLMRecipeGenerator: if allergy_list: lines.append(f"IMPORTANT — must NOT contain: {', '.join(allergy_list)}") + if req.exclude_ingredients: + lines.append(f"IMPORTANT — user does not want these today: {', '.join(req.exclude_ingredients)}. Do not include them.") + lines.append("") lines.append(f"Covered culinary elements: {', '.join(covered_elements) or 'none'}") @@ -124,6 +127,9 @@ class LLMRecipeGenerator: if allergy_list: lines.append(f"Must NOT contain: {', '.join(allergy_list)}") + if req.exclude_ingredients: + lines.append(f"Do not use today: {', '.join(req.exclude_ingredients)}") + unit_line = ( "Use metric units (grams, ml, Celsius) for all quantities and temperatures." if req.unit_system == "metric" diff --git a/app/services/recipe/recipe_engine.py b/app/services/recipe/recipe_engine.py index d8bdbca..a594436 100644 --- a/app/services/recipe/recipe_engine.py +++ b/app/services/recipe/recipe_engine.py @@ -672,6 +672,7 @@ class RecipeEngine: profiles = self._classifier.classify_batch(req.pantry_items) gaps = self._classifier.identify_gaps(profiles) pantry_set = _expand_pantry_set(req.pantry_items, req.secondary_pantry_items or None) + exclude_set = _expand_pantry_set(req.exclude_ingredients) if req.exclude_ingredients else set() if req.level >= 3: from app.services.recipe.llm_recipe import LLMRecipeGenerator @@ -715,6 +716,10 @@ class RecipeEngine: except Exception: ingredient_names = [] + # Skip recipes that require any ingredient the user has excluded. + if exclude_set and any(_ingredient_in_pantry(n, exclude_set) for n in ingredient_names): + continue + # Compute missing ingredients, detecting pantry coverage first. # When covered, collect any prep-state annotations (e.g. "melted butter" # → note "Melt the butter before starting.") to surface separately. diff --git a/frontend/src/components/RecipesView.vue b/frontend/src/components/RecipesView.vue index 7671b29..f4a87c9 100644 --- a/frontend/src/components/RecipesView.vue +++ b/frontend/src/components/RecipesView.vue @@ -169,6 +169,31 @@ No recipes containing these ingredients will appear. + +
+ +
+ + {{ tag }} + + +
+ + Recipes containing these won't appear. Press Enter or comma to add. +
+