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.
This commit is contained in:
Claude
2026-08-19 23:54:34 +00:00
parent ec33634b3d
commit 9b3456b080
+19 -3
View File
@@ -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,
});
}