feat(recipe-generation): matsäkerhetslint för rå fågel/fläsk/ägg/fisk

This commit is contained in:
Sven (AAMOS AI)
2026-08-09 20:57:58 +07:00
parent e4efb150aa
commit c6b9b0278d
2 changed files with 111 additions and 0 deletions
@@ -71,6 +71,55 @@ const DEFAULT_OPTIONS: Required<VerificationOptions> = {
requireProtein: false,
};
/** Ingredienser som kräver ett säkerhetssteg med genomstekning/temperatur (spec §61.3). */
const RAW_PROTEIN_REQUIRING_SAFE_COOKING = new Set([
// Fågel
"chicken_breast",
"chicken_thigh",
// Fläsk/nöt/köttfärs
"pork_loin",
"minced_beef",
"minced_mixed",
"meatball_pork_beef",
"falukorv",
// Ägg
"egg",
// Fisk/skaldjur
"cod",
"salmon",
"shrimp",
"anchovy_swedish",
"pickled_herring",
]);
const SAFE_COOKING_KEYWORDS_SV = [
/\btemperatur\b/i,
/\b°\s*c\b/i,
/\bgrader\b/i,
/\bgenomstekt\b/i,
/\bgenomkokt\b/i,
/\bgenomgrillad\b/i,
/\bgenomv\w+\b/i,
/\btill(?:s)? den är klar\b/i,
/\btill(?:s)? köttet släpper vätskan\b/i,
/\b72\s*c?\b/i,
/\b74\s*c?\b/i,
/\b75\s*c?\b/i,
/\b63\s*c?\b/i,
/\b65\s*c?\b/i,
/\b70\s*c?\b/i,
];
function requiresSafeCookingStep(ingredientIds: string[]): boolean {
return ingredientIds.some((id) => RAW_PROTEIN_REQUIRING_SAFE_COOKING.has(id));
}
function hasSafeCookingStep(steps: RecipeCandidate["steps"]): boolean {
return steps.some((s) =>
SAFE_COOKING_KEYWORDS_SV.some((re) => re.test(s.instructionSv)),
);
}
/**
* Kör en kandidat genom verifieringspipelinen.
* Returnerar alltid ett VerificationResult; status sätts beroende på om
@@ -203,6 +252,13 @@ export async function verifyCandidate(
}
}
// ── 4b. Matsäkerhetslint: rå fågel/fläsk/ägg/fisk kräver genomstekningssteg ──
if (requiresSafeCookingStep(canonicalIds) && !hasSafeCookingStep(candidate.steps)) {
reasons.push(
"Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
);
}
// ── 5. Dedup mot befintliga recept ────────────────────────────────────────
if (similarity) {
const isDup = await similarity.hasSimilarity(candidate.titleSv);