feat(seed): import 246 verified recipes as committed seed catalog

- Convert C:\Users\Public\cibello-verified-final-result.json to
  packages/database/src/seed/data/verified-recipes.ts (committed, hermetic).
- Replace/extend the 23 placeholder SEED_RECIPES via re-export.
- Upsert on slug with onConflictDoUpdate of ALL recipe fields.
- Hard-reject recipes with unmapped canonical ingredients (0 rejected this batch).
- Derive allergens and compute nutrition per portion from ingredients at seed time.
- Run food-safety lint; auto-patch 8 legacy raw-protein recipes with safe-cooking phrase.
- Resolve 4 suspected title duplicates via variantOfSlug.
- Guard brand name: creatorDisplayName uses BRAND.name, never JSON placeholder.
- Preserve verificationStatus (editorial/verified) and source-registry FKs.
- Extend allergen-invariant test to cover full 246-recipe catalog.
- Stabilise API tests by disabling file parallelism (flakiness from shared DB state).

Refs: seed pipeline, brand-guard, safety canary
This commit is contained in:
Sven (AAMOS AI)
2026-08-11 00:47:08 +07:00
parent 4687f46384
commit b1b6eb994c
4 changed files with 35142 additions and 1080 deletions
+36 -36
View File
@@ -735,47 +735,47 @@ async function insertRecipe(
? (slugToId.get(recipe.variantOfSlug) ?? null)
: null;
const recipeValues = {
slug: recipe.slug,
titleSv: recipe.titleSv,
descriptionSv: recipe.descriptionSv,
country: recipe.country ?? null,
cuisine: recipe.cuisine,
mealTypes: recipe.mealTypes,
tags: recipe.tags,
methods: recipe.methods,
equipment: recipe.equipment,
difficulty: recipe.difficulty,
prepTimeMinutes: recipe.prepMin,
cookTimeMinutes: recipe.cookMin,
totalTimeMinutes: recipe.prepMin + recipe.cookMin,
portions: recipe.portions,
nutritionPerPortion: nutrition.perPortion,
allergens: [...allergens].sort(),
spiceLevel: recipe.spiceLevel,
estimatedCostMinorPerPortion: costPerPortion,
storageGuidanceSv: recipe.storageGuidanceSv ?? null,
mealPrepFriendly: recipe.mealPrepFriendly,
freezerFriendly: recipe.freezerFriendly,
peakSeasons: recipe.peakSeasons,
holidayTags: recipe.holidayTags,
dna,
variantType: recipe.variantType ?? "standard",
variantOfRecipeId,
status: "published" as const,
verificationStatus: recipe.verificationStatus ?? "editorial",
sourceType: "own_editorial" as const,
sourceRegistryId,
creatorDisplayName: `${BRAND.name} Redaktion`,
};
const [row] = await db
.insert(schema.recipes)
.values({
slug: recipe.slug,
titleSv: recipe.titleSv,
descriptionSv: recipe.descriptionSv,
country: recipe.country ?? null,
cuisine: recipe.cuisine,
mealTypes: recipe.mealTypes,
tags: recipe.tags,
methods: recipe.methods,
equipment: recipe.equipment,
difficulty: recipe.difficulty,
prepTimeMinutes: recipe.prepMin,
cookTimeMinutes: recipe.cookMin,
totalTimeMinutes: recipe.prepMin + recipe.cookMin,
portions: recipe.portions,
nutritionPerPortion: nutrition.perPortion,
allergens: [...allergens].sort(),
spiceLevel: recipe.spiceLevel,
estimatedCostMinorPerPortion: costPerPortion,
storageGuidanceSv: recipe.storageGuidanceSv ?? null,
mealPrepFriendly: recipe.mealPrepFriendly,
freezerFriendly: recipe.freezerFriendly,
peakSeasons: recipe.peakSeasons,
holidayTags: recipe.holidayTags,
dna,
variantType: recipe.variantType ?? "standard",
variantOfRecipeId,
status: "published",
verificationStatus: "editorial",
sourceType: "own_editorial",
sourceRegistryId,
creatorDisplayName: `${BRAND.name} Redaktion`,
})
.values(recipeValues)
.onConflictDoUpdate({
target: schema.recipes.slug,
set: {
nutritionPerPortion: nutrition.perPortion,
allergens: [...allergens].sort(),
dna,
...recipeValues,
updatedAt: new Date(),
},
})