fix(recommendation-engine): S4-FIX — opersonliga vyer fungerar utan samtycke via depersonalize(weights)
This commit is contained in:
@@ -211,3 +211,8 @@ export function viewWeights(view: RecommendationView): ScoringWeights {
|
||||
return DEFAULT_WEIGHTS;
|
||||
}
|
||||
}
|
||||
|
||||
/** Nollställ de personliga axlarna i en viktmängd — används när samtycke saknas. */
|
||||
export function depersonalize(weights: ScoringWeights): ScoringWeights {
|
||||
return { ...weights, memoryFit: 0, tasteFit: 0, cookingAssumptionFit: 0 };
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { MemoryItem, TasteSignal } from "@app/shared-types";
|
||||
import {
|
||||
containsForbiddenCopy,
|
||||
DEFAULT_WEIGHTS,
|
||||
depersonalize,
|
||||
easterSunday,
|
||||
HEALTH_VIEW_WEIGHTS,
|
||||
isEventActive,
|
||||
@@ -403,4 +404,69 @@ describe("S4 rekommendationsvyer", () => {
|
||||
expect(viewWeights("health").nutritionFit).toBe(HEALTH_VIEW_WEIGHTS.nutritionFit);
|
||||
expect(viewWeights("pantry").coverage).toBe(PANTRY_VIEW_WEIGHTS.coverage);
|
||||
});
|
||||
|
||||
it("opersonliga vyerna fungerar utan samtycke genom depersonalize", () => {
|
||||
const nonPersonalCtx: RecommendationContext = {
|
||||
...viewCtx,
|
||||
personalizationEnabled: false,
|
||||
memoryItems: undefined,
|
||||
tasteSignals: undefined,
|
||||
cookingAssumptions: undefined,
|
||||
};
|
||||
|
||||
const tasty = candidate({
|
||||
recipeId: "tasty",
|
||||
titleSv: "Svensk köttbullsgryta",
|
||||
cuisine: "swedish",
|
||||
coverage: fullCoverage,
|
||||
nutritionPerPortion: { ...nutrition, proteinG: 20 },
|
||||
});
|
||||
|
||||
const pantry = candidate({
|
||||
recipeId: "pantry",
|
||||
titleSv: "Italiensk pastarätt",
|
||||
cuisine: "italian",
|
||||
coverage: {
|
||||
...fullCoverage,
|
||||
expiringUsed: [
|
||||
{
|
||||
canonicalIngredientId: "pasta",
|
||||
displayNameSv: "pastan",
|
||||
required: 200,
|
||||
unit: "GRAM",
|
||||
availableInUnit: 250,
|
||||
covered: true,
|
||||
optional: false,
|
||||
mostUrgentDaysLeft: 1,
|
||||
usesExpiringItem: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
nutritionPerPortion: { ...nutrition, proteinG: 15 },
|
||||
});
|
||||
|
||||
const healthy = candidate({
|
||||
recipeId: "healthy",
|
||||
titleSv: "Kyckling och quinoa",
|
||||
cuisine: "greek",
|
||||
coverage: fullCoverage,
|
||||
nutritionPerPortion: { ...nutrition, proteinG: 50 },
|
||||
});
|
||||
|
||||
// default utan samtycke = depersonalize(DEFAULT_WEIGHTS) == NON_PERSONALIZED_WEIGHTS
|
||||
const defaultRanked = rankAll([pantry, healthy, tasty], nonPersonalCtx, depersonalize(viewWeights("default")));
|
||||
const nonPersonalDefaultRanked = rankAll([pantry, healthy, tasty], nonPersonalCtx, NON_PERSONALIZED_WEIGHTS);
|
||||
expect(defaultRanked.map((r) => r.recipeId)).toEqual(nonPersonalDefaultRanked.map((r) => r.recipeId));
|
||||
|
||||
// vyerna är fortfarande skilda från default även utan samtycke
|
||||
expect(rankAll([tasty, healthy, pantry], nonPersonalCtx, depersonalize(viewWeights("pantry")))[0]?.recipeId).toBe("pantry");
|
||||
expect(rankAll([tasty, pantry, healthy], nonPersonalCtx, depersonalize(viewWeights("health")))[0]?.recipeId).toBe("healthy");
|
||||
|
||||
// taste-vyn honoreras men personliga axlar är nollade
|
||||
const tasteWeights = depersonalize(viewWeights("taste"));
|
||||
expect(tasteWeights.memoryFit).toBe(0);
|
||||
expect(tasteWeights.tasteFit).toBe(0);
|
||||
expect(tasteWeights.cookingAssumptionFit).toBe(0);
|
||||
expect(tasteWeights.taste).toBe(TASTE_VIEW_WEIGHTS.taste);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user