fix(rekommendationer): vardagens tidsgräns gömmer inte längre högtids-/bakrecept
Buggen: när användaren uttryckligen valde en högtid (t.ex. Kräftskiva), en tagg (Baka) eller sökte, hårdfiltrerade scoreCandidate bort alla recept vars totalTimeMinutes översteg hushållets maxCookingMinutesWeekday (parts.time=-1, rankAll släpper <0). De smala högtidspoolerna kollapsade då till 0-1 recept trots att flera fanns korrekt taggade. Reproducerat mot riktig seedad databas: med en 20-min vardagsgräns gav kräftskiva 2, grillsäsong 2, skolstart 0, bak nästan tomt. Med fixen ger de 4, 5, 3 respektive 18 – medan standardvyn "Rekommenderat" fortfarande respekterar tidsgränsen (3 snabba middagar). Fix: uttrycklig avsikt (holiday/tag/search) förbigår vardagens tidsgräns – tiden blir mjuk rankning i stället för hårt filter. En uttrycklig q.maxMinutes i förfrågan respekteras fortfarande. Regressionstest låser mekanismen.
This commit is contained in:
@@ -330,10 +330,19 @@ export async function recommendationRoutes(app: FastifyInstance) {
|
||||
// --- 8. Tolka "jag är sugen på" (spec §19) ---
|
||||
const craving = q.craving ? parseCraving(q.craving) : null;
|
||||
|
||||
// Uttrycklig avsikt: användaren har valt en högtid, en tagg (t.ex. Baka)
|
||||
// eller sökt på namn. Då ska hushållets vardags-tidsgräns INTE hårt gömma
|
||||
// maten de faktiskt bett om (spec §28) – en kräftskiva eller ett bak tar
|
||||
// tid. Tiden blir mjuk rankning i stället för diskvalificerande filter.
|
||||
// En uttrycklig tidsgräns i förfrågan (q.maxMinutes) respekteras alltid.
|
||||
const hasExplicitIntent = !!(q.holiday || q.tag || q.search);
|
||||
|
||||
const ctx: RecommendationContext = {
|
||||
mealType: q.mealType,
|
||||
persons: q.persons ?? (members.length ? members.length : 1),
|
||||
maxMinutes: q.maxMinutes ?? myPrefs?.maxCookingMinutesWeekday ?? undefined,
|
||||
maxMinutes:
|
||||
q.maxMinutes ??
|
||||
(hasExplicitIntent ? undefined : (myPrefs?.maxCookingMinutesWeekday ?? undefined)),
|
||||
maxCostMinorPerPortion: q.maxCostMinorPerPortion,
|
||||
remainingProteinG: Math.max(0, daySummary.remaining.proteinG),
|
||||
remainingKcal: Math.max(0, daySummary.remaining.kcal),
|
||||
|
||||
@@ -113,6 +113,16 @@ describe("poängsättning (spec §18)", () => {
|
||||
expect(ranked).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("utan tidsgräns behålls långsamma recept (t.ex. vid uttrycklig högtid/bak)", () => {
|
||||
// Fixen: när användaren uttryckligen valt en högtid/tagg/sök sätter
|
||||
// endpointen maxMinutes=undefined, så kräftskiva och bak inte gömms av
|
||||
// vardagens tidsgräns. Långsamma recept ska då rankas – inte försvinna.
|
||||
const slow = candidate({ recipeId: "slow", totalTimeMinutes: 90 });
|
||||
const ranked = rankAll([slow], { ...ctx, maxMinutes: undefined });
|
||||
expect(ranked).toHaveLength(1);
|
||||
expect(ranked[0]!.recipeId).toBe("slow");
|
||||
});
|
||||
|
||||
it("nyligen lagat straffas (variation)", () => {
|
||||
const recent = scoreCandidate(candidate({ daysSinceLastCooked: 2 }), ctx);
|
||||
const old = scoreCandidate(candidate({ daysSinceLastCooked: 30 }), ctx);
|
||||
|
||||
Reference in New Issue
Block a user