From d8c472c594f97f329d82e54495e1dd59dcc99273 Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Thu, 9 Apr 2026 21:06:29 +0200 Subject: [PATCH] feat: include custom meals in round-off combinatorial food pool Custom meals are now resolved to per100g nutrition data and added to the base food pool, allowing them to appear in 1-3 food combo suggestions alongside pantry items and favorites. --- .../api/fitness/round-off-day/+server.ts | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/routes/api/fitness/round-off-day/+server.ts b/src/routes/api/fitness/round-off-day/+server.ts index e8e51265..60e73451 100644 --- a/src/routes/api/fitness/round-off-day/+server.ts +++ b/src/routes/api/fitness/round-off-day/+server.ts @@ -5,6 +5,7 @@ import { dbConnect } from '$utils/db'; import { BLS_DB } from '$lib/data/blsDb'; import { getBlsEntryByCode, getNutritionEntryByFdcId } from '$lib/server/nutritionMatcher'; import { Recipe } from '$models/Recipe'; +import { CustomMeal } from '$models/CustomMeal'; import { FavoriteIngredient } from '$models/FavoriteIngredient'; import { FoodLogEntry } from '$models/FoodLogEntry'; import { OpenFoodFact } from '$models/OpenFoodFact'; @@ -188,10 +189,43 @@ export const GET: RequestHandler = async ({ url, locals }) => { allFoods.push(f); } - // 3. Find best combos (1-3 foods) + // 3. Resolve custom meals and add to food pool + const customMeals = await CustomMeal.find({ createdBy: user.nickname }).lean(); + for (const meal of customMeals as any[]) { + if (!meal.ingredients?.length) continue; + let totalGrams = 0; + let totalCal = 0, totalP = 0, totalF = 0, totalC = 0; + for (const ing of meal.ingredients) { + const g = ing.amountGrams ?? 100; + totalGrams += g; + const f = g / 100; + totalCal += (ing.per100g?.calories ?? 0) * f; + totalP += (ing.per100g?.protein ?? 0) * f; + totalF += (ing.per100g?.fat ?? 0) * f; + totalC += (ing.per100g?.carbs ?? 0) * f; + } + if (totalGrams <= 0 || totalCal <= 5) continue; + const key = `custom:${meal._id}`; + if (allFoodsSeen.has(key)) continue; + allFoodsSeen.add(key); + allFoods.push({ + source: 'custom', + id: String(meal._id), + name: meal.name, + nameEn: meal.name, + per100g: { + calories: totalCal / totalGrams * 100, + protein: totalP / totalGrams * 100, + fat: totalF / totalGrams * 100, + carbs: totalC / totalGrams * 100, + }, + }); + } + + // 4. Find best combos (1-3 foods) const foodCombos = findBestCombos(allFoods, remaining, 'pantry', limit * 2); - // 4. Find best recipes (single items only, no combos) + // 5. Find best recipes (single items only, no combos) const recipes = await Recipe.find( { cachedPer100g: { $exists: true, $ne: null } }, { name: 1, short_name: 1, cachedPer100g: 1, cachedTotalGrams: 1, portions: 1 } @@ -217,12 +251,12 @@ export const GET: RequestHandler = async ({ url, locals }) => { const recipeCombos = findBestCombos(resolvedRecipes, remaining, 'recipe', limit, 1); - // 5. Merge and sort by score (lower = better) + // 6. Merge and sort by score (lower = better) const all: ComboSuggestion[] = [...foodCombos, ...recipeCombos]; all.sort((a, b) => a.score - b.score); const suggestions = all.slice(0, limit); - // 6. Store in cache + // 7. Store in cache await RoundOffCache.findOneAndUpdate( { createdBy: user.nickname, date: today }, {