From 989c242d2c1197cc99ee73215321ef9bd0610657 Mon Sep 17 00:00:00 2001 From: "Sven (AAMOS AI)" Date: Tue, 11 Aug 2026 06:42:09 +0700 Subject: [PATCH] gapfill: importera nollcells-recept i seed --- .../src/seed/data/verified-recipes.ts | 817 +++++++++++++++++- .../scripts/import-gapfill-to-seed.ts | 86 ++ 2 files changed, 902 insertions(+), 1 deletion(-) create mode 100644 packages/recipe-generation/scripts/import-gapfill-to-seed.ts diff --git a/packages/database/src/seed/data/verified-recipes.ts b/packages/database/src/seed/data/verified-recipes.ts index c260211..7536f52 100644 --- a/packages/database/src/seed/data/verified-recipes.ts +++ b/packages/database/src/seed/data/verified-recipes.ts @@ -3,7 +3,7 @@ import type { } from "./recipes.js"; /** - * Committed seed catalog: 246 verified recipes (JSON export converted once). + * Committed seed catalog: 254 verified recipes (JSON export converted once). * Allergens and nutrition are re-derived at seed time from canonical ingredients. * Verification statuses preserved: editorial + verified. */ @@ -35096,5 +35096,820 @@ export const VERIFIED_RECIPES: SeedRecipe[] = [ "text": "Rör ner matlagningsgrädden och låt soppan bli varm igen. Smaka av med salt och svartpeppar." } ] + }, + { + "slug": "kramig-flaskytterfile-med-svamp-och-potatis", + "titleSv": "Krämig fläskytterfilé med svamp och potatis", + "descriptionSv": "En enkel och smakrik vardagsrätt med mör fläskytterfilé i en krämig svampsås, serverad med kokt potatis. Perfekt när tiden är knapp men du vill äta gott.", + "cuisine": "swedish", + "mealTypes": [ + "dinner" + ], + "tags": [ + "quick", + "high_protein" + ], + "methods": [ + "stovetop" + ], + "equipment": [ + "stove" + ], + "difficulty": "beginner", + "prepMin": 15, + "cookMin": 30, + "portions": 4, + "spiceLevel": 1, + "mealPrepFriendly": true, + "freezerFriendly": false, + "peakSeasons": [], + "holidayTags": [], + "variantType": "standard", + "dnaProtein": "pork_loin", + "dnaCarb": "potato", + "dnaVegetables": [ + "onion", + "garlic" + ], + "dnaFlavor": [ + "fresh", + "balanced" + ], + "verificationStatus": "verified", + "storageGuidanceSv": "Förvara eventuella rester i en lufttät behållare i kylskåp i upp till 2 dagar.", + "ingredients": [ + { + "ing": "pork_loin", + "nameSv": "fläskytterfilé", + "qty": 500, + "unit": "GRAM", + "optional": false + }, + { + "ing": "potato", + "nameSv": "potatis", + "qty": 8, + "unit": "COUNT", + "note": "medelstora", + "optional": false + }, + { + "ing": "mushroom", + "nameSv": "champinjoner", + "qty": 250, + "unit": "GRAM", + "optional": false + }, + { + "ing": "onion", + "nameSv": "gul lök", + "qty": 1, + "unit": "COUNT", + "optional": false + }, + { + "ing": "garlic", + "nameSv": "vitlök", + "qty": 2, + "unit": "CLOVE", + "optional": false + }, + { + "ing": "cooking_cream", + "nameSv": "matlagningsgrädde", + "qty": 2.5, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "rapeseed_oil", + "nameSv": "rapsolja", + "qty": 1, + "unit": "TABLESPOON", + "note": "till stekning", + "optional": false + }, + { + "ing": "salt", + "nameSv": "salt", + "qty": 1, + "unit": "TEASPOON", + "optional": false + }, + { + "ing": "black_pepper", + "nameSv": "svartpeppar", + "qty": 1, + "unit": "MILLILITER", + "note": "nymald", + "optional": false + } + ], + "steps": [ + { + "text": "Skala och koka potatisen i saltat vatten tills den är mjuk, cirka 20-25 minuter.", + "timerSeconds": 1500 + }, + { + "text": "Putsa fläskytterfilén och skär den i cirka 1,5 cm tjocka skivor. Salta och peppra skivorna." + }, + { + "text": "Skala och finhacka lök och vitlök. Skiva champinjonerna." + }, + { + "text": "Hetta upp rapsolja i en stekpanna. Stek fläskytterfiléskivorna på medelhög värme tills de är genomstekta och har en innertemperatur på minst 68°C, cirka 3-4 minuter per sida. Ta upp köttet och lägg åt sidan.", + "timerSeconds": 480, + "tip": "Stek i omgångar om pannan blir för full för att få en fin stekyta." + }, + { + "text": "Tillsätt mer olja om det behövs i samma panna. Fräs lök, vitlök och champinjoner tills löken är mjuk och champinjonerna fått fin färg." + }, + { + "text": "Häll i matlagningsgrädden och låt såsen sjuda i några minuter. Smaka av med salt och peppar.", + "timerSeconds": 180 + }, + { + "text": "Lägg tillbaka fläskytterfilén i såsen och låt den bli varm. Servera genast med den kokta potatisen." + } + ] + }, + { + "slug": "flaskytterfile-med-apple-och-timjan", + "titleSv": "Fläskytterfilé med äpple och timjan", + "descriptionSv": "En härlig kombination av sött äpple och aromatisk timjan som lyfter den mjälla fläskytterfilén. Serveras med kokt ris för en komplett måltid.", + "cuisine": "swedish", + "mealTypes": [ + "dinner" + ], + "tags": [ + "quick", + "high_protein" + ], + "methods": [ + "stovetop" + ], + "equipment": [ + "stove" + ], + "difficulty": "beginner", + "prepMin": 15, + "cookMin": 30, + "portions": 4, + "spiceLevel": 1, + "mealPrepFriendly": true, + "freezerFriendly": false, + "peakSeasons": [], + "holidayTags": [], + "variantType": "standard", + "dnaProtein": "pork_loin", + "dnaCarb": "rice_white", + "dnaVegetables": [ + "onion" + ], + "dnaFlavor": [ + "fresh", + "balanced" + ], + "verificationStatus": "verified", + "storageGuidanceSv": "Rester kan förvaras i kylskåp i en lufttät behållare i upp till 2 dagar.", + "ingredients": [ + { + "ing": "pork_loin", + "nameSv": "fläskytterfilé", + "qty": 500, + "unit": "GRAM", + "optional": false + }, + { + "ing": "rice_white", + "nameSv": "ris (jasmin/basmati)", + "qty": 300, + "unit": "GRAM", + "note": "torrt", + "optional": false + }, + { + "ing": "apple", + "nameSv": "äpple", + "qty": 2, + "unit": "COUNT", + "optional": false + }, + { + "ing": "onion", + "nameSv": "gul lök", + "qty": 1, + "unit": "COUNT", + "optional": false + }, + { + "ing": "cooking_cream", + "nameSv": "matlagningsgrädde", + "qty": 2.5, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "thyme_dried", + "nameSv": "torkad timjan", + "qty": 1, + "unit": "TEASPOON", + "optional": false + }, + { + "ing": "rapeseed_oil", + "nameSv": "rapsolja", + "qty": 1, + "unit": "TABLESPOON", + "note": "till stekning", + "optional": false + }, + { + "ing": "salt", + "nameSv": "salt", + "qty": 1, + "unit": "TEASPOON", + "optional": false + }, + { + "ing": "black_pepper", + "nameSv": "svartpeppar", + "qty": 1, + "unit": "MILLILITER", + "note": "nymald", + "optional": false + } + ], + "steps": [ + { + "text": "Koka riset enligt anvisningarna på förpackningen." + }, + { + "text": "Putsa fläskytterfilén och skär den i cirka 1,5 cm tjocka skivor. Salta och peppra skivorna." + }, + { + "text": "Skala och hacka löken. Kärna ur och skär äpplena i klyftor eller tjocka skivor." + }, + { + "text": "Hetta upp rapsolja i en stekpanna. Stek fläskytterfiléskivorna på medelhög värme tills de är genomstekta och har en innertemperatur på minst 68°C, cirka 3-4 minuter per sida. Ta upp köttet och lägg åt sidan.", + "timerSeconds": 480, + "tip": "Stek i omgångar om pannan blir för full för att få en fin stekyta." + }, + { + "text": "Tillsätt mer olja om det behövs i samma panna. Fräs lök och äppelklyftor tills löken är mjuk och äpplena börjar mjukna." + }, + { + "text": "Häll i matlagningsgrädden och tillsätt timjan. Låt såsen sjuda i några minuter. Smaka av med salt och peppar.", + "timerSeconds": 180 + }, + { + "text": "Lägg tillbaka fläskytterfilén i såsen och låt den bli varm. Servera genast med det kokta riset." + } + ] + }, + { + "slug": "flaskytterfile-med-senapssas-och-broccoli", + "titleSv": "Fläskytterfilé med senapssås och broccoli", + "descriptionSv": "En klassisk kombination av fläskytterfilé och en krämig senapssås. Serveras med ångad broccoli och kokt potatis för en balanserad måltid.", + "cuisine": "swedish", + "mealTypes": [ + "dinner" + ], + "tags": [ + "quick", + "high_protein" + ], + "methods": [ + "stovetop" + ], + "equipment": [ + "stove" + ], + "difficulty": "beginner", + "prepMin": 15, + "cookMin": 30, + "portions": 4, + "spiceLevel": 2, + "mealPrepFriendly": true, + "freezerFriendly": false, + "peakSeasons": [], + "holidayTags": [], + "variantType": "standard", + "dnaProtein": "pork_loin", + "dnaCarb": "potato", + "dnaVegetables": [ + "onion" + ], + "dnaFlavor": [ + "fresh", + "balanced" + ], + "verificationStatus": "verified", + "storageGuidanceSv": "Förvara eventuella rester i en lufttät behållare i kylskåp i upp till 2 dagar.", + "ingredients": [ + { + "ing": "pork_loin", + "nameSv": "fläskytterfilé", + "qty": 500, + "unit": "GRAM", + "optional": false + }, + { + "ing": "potato", + "nameSv": "potatis", + "qty": 8, + "unit": "COUNT", + "note": "medelstora", + "optional": false + }, + { + "ing": "broccoli", + "nameSv": "broccoli", + "qty": 1, + "unit": "COUNT", + "note": "ca 300g", + "optional": false + }, + { + "ing": "onion", + "nameSv": "gul lök", + "qty": 1, + "unit": "COUNT", + "optional": false + }, + { + "ing": "mustard", + "nameSv": "senap (dijon eller grovkornig)", + "qty": 2, + "unit": "TABLESPOON", + "optional": false + }, + { + "ing": "cooking_cream", + "nameSv": "matlagningsgrädde", + "qty": 2.5, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "rapeseed_oil", + "nameSv": "rapsolja", + "qty": 1, + "unit": "TABLESPOON", + "note": "till stekning", + "optional": false + }, + { + "ing": "salt", + "nameSv": "salt", + "qty": 1, + "unit": "TEASPOON", + "optional": false + }, + { + "ing": "black_pepper", + "nameSv": "svartpeppar", + "qty": 1, + "unit": "MILLILITER", + "note": "nymald", + "optional": false + } + ], + "steps": [ + { + "text": "Skala och koka potatisen i saltat vatten tills den är mjuk, cirka 20-25 minuter.", + "timerSeconds": 1500 + }, + { + "text": "Dela broccolin i mindre buketter och koka eller ånga den tills den är mjuk men fortfarande har lite tuggmotstånd, cirka 5-7 minuter.", + "timerSeconds": 420 + }, + { + "text": "Putsa fläskytterfilén och skär den i cirka 1,5 cm tjocka skivor. Salta och peppra skivorna." + }, + { + "text": "Skala och finhacka löken." + }, + { + "text": "Hetta upp rapsolja i en stekpanna. Stek fläskytterfiléskivorna på medelhög värme tills de är genomstekta och har en innertemperatur på minst 68°C, cirka 3-4 minuter per sida. Ta upp köttet och lägg åt sidan.", + "timerSeconds": 480, + "tip": "Stek i omgångar om pannan blir för full för att få en fin stekyta." + }, + { + "text": "Tillsätt mer olja om det behövs i samma panna. Fräs löken tills den är mjuk och glansig." + }, + { + "text": "Rör ner senap och matlagningsgrädde. Låt såsen sjuda i några minuter. Smaka av med salt och peppar.", + "timerSeconds": 180 + }, + { + "text": "Lägg tillbaka fläskytterfilén i såsen och låt den bli varm. Servera genast med den kokta potatisen och broccolin." + } + ] + }, + { + "slug": "snabb-banansmoothie", + "titleSv": "Snabb banansmoothie", + "descriptionSv": "En energigivande och mättande smoothie med banan, yoghurt och havredryck. Perfekt när du behöver en snabb frukost eller ett mellanmål.", + "cuisine": "swedish", + "mealTypes": [ + "breakfast" + ], + "tags": [ + "quick", + "kid_friendly" + ], + "methods": [ + "no_cook" + ], + "equipment": [], + "difficulty": "beginner", + "prepMin": 5, + "cookMin": 0, + "portions": 4, + "spiceLevel": 0, + "mealPrepFriendly": false, + "freezerFriendly": false, + "peakSeasons": [], + "holidayTags": [], + "variantType": "standard", + "dnaCarb": "oats", + "dnaVegetables": [], + "dnaFlavor": [ + "fresh", + "balanced" + ], + "verificationStatus": "verified", + "storageGuidanceSv": "Smoothien bör drickas direkt för bästa smak och konsistens. Kan förvaras i kylskåp i max 1 dygn, men kan då skikta sig.", + "ingredients": [ + { + "ing": "banana", + "nameSv": "banan", + "qty": 2, + "unit": "COUNT", + "note": "mogna", + "optional": false + }, + { + "ing": "yoghurt_natural", + "nameSv": "naturell yoghurt 3 %", + "qty": 4, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "oat_drink", + "nameSv": "havredryck", + "qty": 2, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "oats", + "nameSv": "havregryn", + "qty": 0.5, + "unit": "DECILITER", + "note": "för extra mättnad", + "optional": true + } + ], + "steps": [ + { + "text": "Skala bananerna och bryt dem i mindre bitar." + }, + { + "text": "Lägg bananbitarna, naturell yoghurt, havredryck och eventuellt havregryn i en mixer." + }, + { + "text": "Mixa tills smoothien är slät och krämig. Tillsätt mer havredryck om du vill ha en tunnare konsistens." + }, + { + "text": "Häll upp i glas och servera direkt." + } + ] + }, + { + "slug": "havregrynsgrot-med-banan-och-kanel", + "titleSv": "Havregrynsgröt med banan och kanel", + "descriptionSv": "En klassisk och värmande frukost som är både enkel att göra och mättande. Toppas med skivad banan och en nypa kanel.", + "cuisine": "swedish", + "mealTypes": [ + "breakfast" + ], + "tags": [ + "quick", + "high_protein" + ], + "methods": [ + "stovetop" + ], + "equipment": [ + "stove" + ], + "difficulty": "beginner", + "prepMin": 5, + "cookMin": 10, + "portions": 4, + "spiceLevel": 0, + "mealPrepFriendly": false, + "freezerFriendly": false, + "peakSeasons": [], + "holidayTags": [], + "variantType": "standard", + "dnaCarb": "oats", + "dnaVegetables": [], + "dnaFlavor": [ + "fresh", + "balanced" + ], + "verificationStatus": "verified", + "storageGuidanceSv": "Gröten smakar bäst nygjord. Eventuella rester kan förvaras i kylskåp i en lufttät behållare i upp till 1 dag och värmas upp med lite extra mjölk.", + "ingredients": [ + { + "ing": "oats", + "nameSv": "havregryn", + "qty": 4, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "milk_1_5", + "nameSv": "mellanmjölk 1,5 %", + "qty": 8, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "salt", + "nameSv": "salt", + "qty": 0.5, + "unit": "TEASPOON", + "optional": false + }, + { + "ing": "banana", + "nameSv": "banan", + "qty": 2, + "unit": "COUNT", + "optional": false + }, + { + "ing": "cinnamon", + "nameSv": "kanel", + "qty": 1, + "unit": "TEASPOON", + "optional": false + } + ], + "steps": [ + { + "text": "Blanda havregryn, mjölk och salt i en kastrull." + }, + { + "text": "Koka upp under omrörning och låt gröten sjuda på svag värme i cirka 3-5 minuter, eller tills den har önskad konsistens. Rör om då och då.", + "timerSeconds": 300, + "tip": "Tillsätt mer mjölk om du vill ha en lösare gröt." + }, + { + "text": "Skala och skiva bananerna." + }, + { + "text": "Fördela gröten i skålar och toppa med skivad banan och kanel. Servera genast." + } + ] + }, + { + "slug": "yoghurt-med-jordgubbar-och-havregryn", + "titleSv": "Yoghurt med jordgubbar och havregryn", + "descriptionSv": "En fräsch och enkel frukost eller mellanmål med krämig yoghurt, söta jordgubbar och knapriga havregryn.", + "cuisine": "swedish", + "mealTypes": [ + "breakfast" + ], + "tags": [ + "quick", + "kid_friendly" + ], + "methods": [ + "no_cook" + ], + "equipment": [], + "difficulty": "beginner", + "prepMin": 5, + "cookMin": 0, + "portions": 4, + "spiceLevel": 0, + "mealPrepFriendly": false, + "freezerFriendly": false, + "peakSeasons": [], + "holidayTags": [], + "variantType": "standard", + "dnaCarb": "oats", + "dnaVegetables": [], + "dnaFlavor": [ + "fresh", + "balanced" + ], + "verificationStatus": "verified", + "storageGuidanceSv": "Bör ätas direkt för bästa upplevelse. Förvara ingredienserna separat och montera vid servering.", + "ingredients": [ + { + "ing": "yoghurt_natural", + "nameSv": "naturell yoghurt 3 %", + "qty": 8, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "strawberry", + "nameSv": "jordgubbar (färska eller frysta)", + "qty": 200, + "unit": "GRAM", + "optional": false + }, + { + "ing": "oats", + "nameSv": "havregryn", + "qty": 1, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "honey", + "nameSv": "honung", + "qty": 20, + "unit": "GRAM", + "note": "för sötma", + "optional": true + } + ], + "steps": [ + { + "text": "Skölj och skiva jordgubbarna om de är färska. Om de är frysta, tina dem lätt." + }, + { + "text": "Fördela yoghurten i fyra skålar." + }, + { + "text": "Toppa yoghurten med skivade jordgubbar och havregryn." + }, + { + "text": "Ringla över lite honung om så önskas. Servera genast." + } + ] + }, + { + "slug": "yoghurt-med-apple-banan-och-kanel", + "titleSv": "Yoghurt med äpple, banan och kanel", + "descriptionSv": "En enkel och näringsrik frukost eller mellanmål med naturell yoghurt, färsk frukt och en touch av kanel.", + "cuisine": "swedish", + "mealTypes": [ + "breakfast" + ], + "tags": [ + "quick", + "kid_friendly" + ], + "methods": [ + "no_cook" + ], + "equipment": [], + "difficulty": "beginner", + "prepMin": 5, + "cookMin": 0, + "portions": 4, + "spiceLevel": 0, + "mealPrepFriendly": false, + "freezerFriendly": false, + "peakSeasons": [], + "holidayTags": [], + "variantType": "standard", + "dnaVegetables": [], + "dnaFlavor": [ + "fresh", + "balanced" + ], + "verificationStatus": "verified", + "storageGuidanceSv": "Bör ätas direkt för bästa upplevelse. Förvara ingredienserna separat och montera vid servering.", + "ingredients": [ + { + "ing": "yoghurt_natural", + "nameSv": "naturell yoghurt 3 %", + "qty": 8, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "apple", + "nameSv": "äpple", + "qty": 1, + "unit": "COUNT", + "optional": false + }, + { + "ing": "banana", + "nameSv": "banan", + "qty": 1, + "unit": "COUNT", + "optional": false + }, + { + "ing": "cinnamon", + "nameSv": "kanel", + "qty": 0.5, + "unit": "TEASPOON", + "optional": false + }, + { + "ing": "honey", + "nameSv": "honung", + "qty": 10, + "unit": "GRAM", + "note": "för sötma", + "optional": true + } + ], + "steps": [ + { + "text": "Skölj äpplet, kärna ur och tärna det i små bitar. Skala bananen och skiva den." + }, + { + "text": "Fördela yoghurten i fyra skålar." + }, + { + "text": "Toppa yoghurten med tärnat äpple och skivad banan." + }, + { + "text": "Strö över kanel och ringla eventuellt över lite honung. Servera genast." + } + ] + }, + { + "slug": "yoghurt-med-honung-och-en-nypa-kanel", + "titleSv": "Yoghurt med honung och en nypa kanel", + "descriptionSv": "En enkel och klassisk frukost eller mellanmål som är snabb att svänga ihop. Naturell yoghurt med sötma från honung och värmande kanel.", + "cuisine": "swedish", + "mealTypes": [ + "breakfast" + ], + "tags": [ + "quick", + "kid_friendly" + ], + "methods": [ + "no_cook" + ], + "equipment": [], + "difficulty": "beginner", + "prepMin": 2, + "cookMin": 0, + "portions": 4, + "spiceLevel": 0, + "mealPrepFriendly": false, + "freezerFriendly": false, + "peakSeasons": [], + "holidayTags": [], + "variantType": "standard", + "dnaVegetables": [], + "dnaFlavor": [ + "fresh", + "balanced" + ], + "verificationStatus": "verified", + "storageGuidanceSv": "Bör ätas direkt för bästa upplevelse. Förvara ingredienserna separat och montera vid servering.", + "ingredients": [ + { + "ing": "yoghurt_natural", + "nameSv": "naturell yoghurt 3 %", + "qty": 8, + "unit": "DECILITER", + "optional": false + }, + { + "ing": "honey", + "nameSv": "honung", + "qty": 40, + "unit": "GRAM", + "optional": false + }, + { + "ing": "cinnamon", + "nameSv": "kanel", + "qty": 0.5, + "unit": "TEASPOON", + "optional": false + } + ], + "steps": [ + { + "text": "Fördela yoghurten i fyra skålar." + }, + { + "text": "Ringla generöst med honung över yoghurten i varje skål." + }, + { + "text": "Strö över en nypa kanel på varje portion. Servera genast." + } + ] } ]; diff --git a/packages/recipe-generation/scripts/import-gapfill-to-seed.ts b/packages/recipe-generation/scripts/import-gapfill-to-seed.ts new file mode 100644 index 0000000..7a5a0fd --- /dev/null +++ b/packages/recipe-generation/scripts/import-gapfill-to-seed.ts @@ -0,0 +1,86 @@ +#!/usr/bin/env tsx +/** + * STEG 2 — importera verifierade gap-fill-recept i seed-katalogen. + * + * - Läser export-JSON från gapfill-nullcells-export.json. + * - Upsertar mot befintliga SEED_RECIPES på slug. + * - Regenererar packages/database/src/seed/data/verified-recipes.ts programmatiskt. + * - Hand-editerar ALDRIG verified-recipes.ts. + */ + +import { SEED_RECIPES, type SeedRecipe } from "../../database/src/seed/data/recipes.js"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; + +const EXPORT_PATH = path.resolve( + import.meta.dirname ?? "..", + "..", + "output", + "gapfill-nullcells-export.json", +); + +const VERIFIED_RECIPES_PATH = path.resolve( + import.meta.dirname ?? "..", + "..", + "..", + "database", + "src", + "seed", + "data", + "verified-recipes.ts", +); + +interface ExportEnvelope { + recipes: SeedRecipe[]; +} + +async function main() { + const raw = await fs.readFile(EXPORT_PATH, "utf-8"); + const envelope: ExportEnvelope = JSON.parse(raw); + const newRecipes = envelope.recipes; + + const existingBySlug = new Map(SEED_RECIPES.map((r) => [r.slug, r])); + let upserted = 0; + let added = 0; + + // Behåll befintlig ordning; nya läggs sist i export-ordning. + const merged: SeedRecipe[] = [...SEED_RECIPES]; + + for (const recipe of newRecipes) { + if (existingBySlug.has(recipe.slug)) { + const idx = merged.findIndex((r) => r.slug === recipe.slug); + if (idx !== -1) merged[idx] = recipe; + upserted++; + } else { + existingBySlug.set(recipe.slug, recipe); + merged.push(recipe); + added++; + } + } + + const fileBody = `import type { + SeedRecipe, +} from "./recipes.js"; + +/** + * Committed seed catalog: ${merged.length} verified recipes (JSON export converted once). + * Allergens and nutrition are re-derived at seed time from canonical ingredients. + * Verification statuses preserved: editorial + verified. + */ +export const VERIFIED_RECIPES: SeedRecipe[] = ${JSON.stringify(merged, null, 2)}; +`; + + await fs.writeFile(VERIFIED_RECIPES_PATH, fileBody, "utf-8"); + + console.error(`[import-gapfill] Befintliga recept: ${SEED_RECIPES.length}`); + console.error(`[import-gapfill] Nya recept: ${newRecipes.length}`); + console.error(`[import-gapfill] Upsertade: ${upserted}`); + console.error(`[import-gapfill] Tillagda: ${added}`); + console.error(`[import-gapfill] Nytt totalt: ${merged.length}`); + console.error(`[import-gapfill] Skrev: ${VERIFIED_RECIPES_PATH}`); +} + +main().catch((err) => { + console.error("[import-gapfill] Fatal:", err); + process.exit(1); +});