test(recipe-generation): hårda allergen-tester där härledda allergener överkör AI

This commit is contained in:
Sven (AAMOS AI)
2026-08-09 20:09:31 +07:00
parent f321d53b30
commit 1f746dc822
3 changed files with 55 additions and 1 deletions
+2
View File
@@ -20,6 +20,8 @@ export interface RecipeCandidate {
freezerFriendly: boolean;
sourceType: "ai_generated";
confidence: number;
/** AI:s påstådda allergener — används aldrig som facit; alltid härledda ur ingredienser. */
aiClaimedAllergens?: string[];
}
export interface RecipeCandidateIngredient {
@@ -135,7 +135,11 @@ export async function verifyCandidate(
}
// ── 2. Allergener (härledda, aldrig AI) ──────────────────────────────────
const allergens = deriveRecipeAllergens(canonicalIds, safetyInfos);
// AI:s eventuella påståenden om allergener ignoreras fullständigt.
// Säkerheten härleds alltid deterministiskt ur canonical_ingredients.
const aiClaims = candidate.aiClaimedAllergens ?? [];
const derivedAllergens = deriveRecipeAllergens(canonicalIds, safetyInfos);
const allergens = derivedAllergens;
// ── 3. Näring (beräknad via nutrition-engine) ────────────────────────────
const calcIngredients: RecipeIngredientForCalc[] = candidate.ingredients.map((ing) => ({
@@ -153,6 +153,54 @@ describe("verifyCandidate", () => {
expect(result.allergens).toEqual([]);
});
it("överkör AI:n när AI påstår inga allergener men ingrediens har mjölk", async () => {
const milkIngredient: ReturnType<CanonicalIngredientLookup["getById"]> = {
id: "milk_3",
nutritionPer100: {
basis: "per_100_g",
values: { kcal: 60, proteinG: 3.4, carbsG: 4.7, fatG: 3, saturatedFatG: 1.9, fiberG: 0, sugarG: 4.7, saltG: 0.1 },
},
defaultUnit: "DECILITER",
densityGPerMl: 1.03,
gramsPerPiece: null,
allergens: ["milk"],
isVegan: false,
isVegetarian: true,
containsGluten: false,
containsLactose: true,
isPork: false,
isBeef: false,
isAlcohol: false,
};
const lookup: CanonicalIngredientLookup = {
getById(id: string) {
return id === "milk_3" ? milkIngredient : undefined;
},
};
const c = makeCandidate({
aiClaimedAllergens: [],
ingredients: [
{ canonicalIngredientId: "milk_3", displayNameSv: "mjölk", quantity: 5, unit: "DECILITER", optional: false, note: null },
],
});
const result = await verifyCandidate(c, lookup, noSimilarity);
expect(result.allergens).toContain("milk");
});
it("slänger AI:n:s falska gluten-allergen när ingen ingrediens innehåller gluten", async () => {
const c = makeCandidate({
aiClaimedAllergens: ["gluten"],
ingredients: [
{ canonicalIngredientId: "kycklingfile", displayNameSv: "kycklingfilé", quantity: 500, unit: "GRAM", optional: false, note: null },
{ canonicalIngredientId: "ris", displayNameSv: "ris", quantity: 300, unit: "GRAM", optional: false, note: null },
{ canonicalIngredientId: "krossade_tomater", displayNameSv: "krossade tomater", quantity: 400, unit: "GRAM", optional: false, note: null },
],
});
const result = await verifyCandidate(c, mockIngredients, noSimilarity);
expect(result.allergens).not.toContain("gluten");
expect(result.status).toBe("verified");
});
it("detekterar dubblett", async () => {
const dupSimilarity: SimilarityLookup = {
async hasSimilarity() {