From 2ad71f2636a3a4c68912e4fd508f052a3ddd1fef Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Thu, 16 Apr 2026 09:12:24 -0700 Subject: [PATCH] =?UTF-8?q?feat(recipes):=20pantry=20match=20floor=20filte?= =?UTF-8?q?r=20=E2=80=94=20'can=20make=20now'=20toggle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds pantry_match_only flag to RecipeRequest. When enabled, any recipe with one or more missing ingredients (after swaps) is excluded from results. Swapped ingredients count as covered. Frontend: toggle checkbox in recipe settings panel, disabled when shopping mode is active (the two modes are mutually exclusive). Hides the max-missing input when pantry-match-only is on (irrelevant there). Closes #63 --- app/models/schemas/recipe.py | 1 + app/services/recipe/recipe_engine.py | 5 +++++ frontend/src/components/RecipesView.vue | 15 +++++++++++++-- frontend/src/services/api.ts | 1 + frontend/src/stores/recipes.ts | 3 +++ 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/models/schemas/recipe.py b/app/models/schemas/recipe.py index f3c1640..139f383 100644 --- a/app/models/schemas/recipe.py +++ b/app/models/schemas/recipe.py @@ -83,6 +83,7 @@ class RecipeRequest(BaseModel): nutrition_filters: NutritionFilters = Field(default_factory=NutritionFilters) excluded_ids: list[int] = Field(default_factory=list) shopping_mode: bool = False + pantry_match_only: bool = False # when True, only return recipes with zero missing ingredients unit_system: str = "metric" # "metric" | "imperial" diff --git a/app/services/recipe/recipe_engine.py b/app/services/recipe/recipe_engine.py index 907d2be..46a42d3 100644 --- a/app/services/recipe/recipe_engine.py +++ b/app/services/recipe/recipe_engine.py @@ -699,6 +699,11 @@ class RecipeEngine: if not req.shopping_mode and effective_max_missing is not None and len(missing) > effective_max_missing: continue + # "Can make now" toggle: drop any recipe that still has missing ingredients + # after swaps are applied. Swapped items count as covered. + if req.pantry_match_only and missing: + continue + # L1 match ratio gate: drop results where less than 60% of the recipe's # ingredients are in the pantry. Prevents low-signal results like a # 10-ingredient recipe matching on only one common item. diff --git a/frontend/src/components/RecipesView.vue b/frontend/src/components/RecipesView.vue index efd97c7..0fa715b 100644 --- a/frontend/src/components/RecipesView.vue +++ b/frontend/src/components/RecipesView.vue @@ -169,6 +169,17 @@ No recipes containing these ingredients will appear. + +
+ +

+ Only recipes where every ingredient is in your pantry — no substitutions, no shopping. +

+
+
- -
+ +
{ const category = ref(null) const wildcardConfirmed = ref(false) const shoppingMode = ref(false) + const pantryMatchOnly = ref(false) const nutritionFilters = ref({ max_calories: null, max_sugar_g: null, @@ -176,6 +177,7 @@ export const useRecipesStore = defineStore('recipes', () => { nutrition_filters: nutritionFilters.value, excluded_ids: [...excluded], shopping_mode: shoppingMode.value, + pantry_match_only: pantryMatchOnly.value, } } @@ -306,6 +308,7 @@ export const useRecipesStore = defineStore('recipes', () => { category, wildcardConfirmed, shoppingMode, + pantryMatchOnly, nutritionFilters, dismissedIds, dismissedCount,