feat(recipes): pantry match floor filter — 'can make now' toggle
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
This commit is contained in:
parent
0de6182f48
commit
2ad71f2636
5 changed files with 23 additions and 2 deletions
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -169,6 +169,17 @@
|
|||
<span id="allergy-hint" class="form-hint">No recipes containing these ingredients will appear.</span>
|
||||
</div>
|
||||
|
||||
<!-- Can Make Now toggle -->
|
||||
<div class="form-group">
|
||||
<label class="flex-start gap-sm shopping-toggle">
|
||||
<input type="checkbox" v-model="recipesStore.pantryMatchOnly" :disabled="recipesStore.shoppingMode" />
|
||||
<span class="form-label" style="margin-bottom: 0;">Can make now (no missing ingredients)</span>
|
||||
</label>
|
||||
<p v-if="recipesStore.pantryMatchOnly && !recipesStore.shoppingMode" class="text-sm text-secondary mt-xs">
|
||||
Only recipes where every ingredient is in your pantry — no substitutions, no shopping.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Shopping Mode (temporary home — moves to Shopping tab in #71) -->
|
||||
<div class="form-group">
|
||||
<label class="flex-start gap-sm shopping-toggle">
|
||||
|
|
@ -180,8 +191,8 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Max Missing — hidden in shopping mode -->
|
||||
<div v-if="!recipesStore.shoppingMode" class="form-group">
|
||||
<!-- Max Missing — hidden in shopping mode or pantry-match-only mode -->
|
||||
<div v-if="!recipesStore.shoppingMode && !recipesStore.pantryMatchOnly" class="form-group">
|
||||
<label class="form-label" for="max-missing">Max Missing Ingredients <span class="text-muted text-xs">(optional)</span></label>
|
||||
<input
|
||||
id="max-missing"
|
||||
|
|
|
|||
|
|
@ -533,6 +533,7 @@ export interface RecipeRequest {
|
|||
nutrition_filters: NutritionFilters
|
||||
excluded_ids: number[]
|
||||
shopping_mode: boolean
|
||||
pantry_match_only: boolean
|
||||
}
|
||||
|
||||
export interface Staple {
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ export const useRecipesStore = defineStore('recipes', () => {
|
|||
const category = ref<string | null>(null)
|
||||
const wildcardConfirmed = ref(false)
|
||||
const shoppingMode = ref(false)
|
||||
const pantryMatchOnly = ref(false)
|
||||
const nutritionFilters = ref<NutritionFilters>({
|
||||
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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue