From 9b3456b080b4d5aefc8234a7ef7dbaf6e8336a2a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 23:54:34 +0000 Subject: [PATCH] feat(veckoplan): tillbehor (t.ex. potatisgratang) foreslar proteinbit Rena tillbehor utan protein i titeln (potatisgratang, mos, pommes) far en not: 'servera med en proteinbit (ryggbiff/entrecote, kyckling, fisk eller tofu/halloumi)'. Kott-/korv-/fiskgratanger raknas som hela ratter. Verifierat mot riktig data: 5 av 19 gratang-traffar klassas som tillbehor, inga falska traffar pa proteinratter. --- apps/worker/src/processors/weekplan.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/apps/worker/src/processors/weekplan.ts b/apps/worker/src/processors/weekplan.ts index 53a271a..52e3403 100644 --- a/apps/worker/src/processors/weekplan.ts +++ b/apps/worker/src/processors/weekplan.ts @@ -232,6 +232,17 @@ export async function processGenerateWeekPlan( `${r.cuisine}|${isSoup(r) ? "soup" : "dish"}`; let lastSignature: string | null = null; + // Tillbehör/sides (t.ex. potatisgratäng, mos, pommes) är inte en hel middag + // i sig. Om titeln redan innehåller ett protein (köttfärs-/korvgratäng) är den + // komplett. Annars föreslår vi en proteinbit att servera till. + const proteinWords = + /kött|korv|kyckling|lax|fisk|fläsk|skinka|biff|färs|räk|tofu|halloumi|ägg|bön|lins|kikärt|falafel|quorn|chorizo|bacon|lamm|anka|kalkon|tonfisk|torsk|sill/i; + const sideWords = /gratäng|potatismos|rotmos|pommes|klyftpotatis|potatisstomp|potatissallad/i; + const isSideDish = (r: { titleSv: string }) => + sideWords.test(r.titleSv) && !proteinWords.test(r.titleSv); + const sideSuggestion = + "Tillbehör – servera med en proteinbit: t.ex. en stekt eller grillad ryggbiff eller entrecôte, kyckling, fisk, eller tofu/halloumi för en vegetarisk variant."; + for (let day = 0; day < days; day++) { const date = new Date(Date.parse(data.input.weekStartDate) + day * 86_400_000) .toISOString() @@ -288,6 +299,13 @@ export async function processGenerateWeekPlan( recipeUseCount.set(chosen.recipe.id, (recipeUseCount.get(chosen.recipe.id) ?? 0) + 1); usedRecipeIds.add(chosen.recipe.id); const expiring = chosen.coverage.expiringUsed[0]; + const notes: string[] = []; + if (isSideDish(chosen.recipe) && (mealType === "lunch" || mealType === "dinner")) { + notes.push(sideSuggestion); + } + if (expiring) { + notes.push(`Använder ${expiring.displayNameSv.toLowerCase()} som bör ätas snart.`); + } entries.push({ weekPlanId, date, @@ -296,9 +314,7 @@ export async function processGenerateWeekPlan( titleSv: chosen.recipe.titleSv, portions, status: "planned", - rescheduleReasonSv: expiring - ? `Använder ${expiring.displayNameSv.toLowerCase()} som bör ätas snart.` - : null, + rescheduleReasonSv: notes.length > 0 ? notes.join(" ") : null, sortOrder: entries.length, }); }