feat(egna-recept): auto-publicera anvandarrecept efter AI-kontroll + dubblettkoll

Publiceringsflodet for anvandarinskickade recept gar nu hela vagen
automatiskt: submitted -> AI-granskning -> dubblettkontroll -> published.
Bara AI-underkanda eller exakta dubbletter (jaccard >= 0.9) stoppas.
Tidigare hamnade allt i in_moderation for manuell granskning.
This commit is contained in:
Claude
2026-08-19 21:17:01 +00:00
parent e3cced8b62
commit ba776daaa1
+10 -9
View File
@@ -53,6 +53,7 @@ export async function processModerateRecipe(ctx: WorkerContext, recipeId: string
.limit(500); .limit(500);
const mySet = new Set(ingredients.map((i) => i.canonicalIngredientId)); const mySet = new Set(ingredients.map((i) => i.canonicalIngredientId));
let exactDuplicate = false;
for (const candidate of candidates) { for (const candidate of candidates) {
const otherIngredients = await ctx.db const otherIngredients = await ctx.db
.select({ canonicalIngredientId: schema.recipeIngredients.canonicalIngredientId }) .select({ canonicalIngredientId: schema.recipeIngredients.canonicalIngredientId })
@@ -66,6 +67,7 @@ export async function processModerateRecipe(ctx: WorkerContext, recipeId: string
if (jaccard >= 0.6) { if (jaccard >= 0.6) {
const classification = const classification =
jaccard >= 0.9 ? "duplicate" : jaccard >= 0.75 ? "variant" : "inspired"; jaccard >= 0.9 ? "duplicate" : jaccard >= 0.75 ? "variant" : "inspired";
if (classification === "duplicate") exactDuplicate = true;
await ctx.db await ctx.db
.insert(schema.recipeSimilarities) .insert(schema.recipeSimilarities)
.values({ .values({
@@ -79,22 +81,21 @@ export async function processModerateRecipe(ctx: WorkerContext, recipeId: string
} }
} }
// 3. Till mänsklig moderering (spec §35 steg 4). Vanliga rätter får ha // 3. Auto-publicering: AI:n granskar, dubblettkollar och lägger ut direkt.
// legitima varianter dubbletter avgörs av människa, inte automatik. // Recept som AI:n underkänner (steg 1) eller som är EXAKTA dubbletter
// (jaccard ≥ 0.9) publiceras inte allt annat går live automatiskt.
const flagsNote = const flagsNote =
result.status === "ok" && result.output result.status === "ok" && result.output
? result.output.flags.map((f) => `[${f.severity}] ${f.messageSv}`).join(" ") ? result.output.flags.map((f) => `[${f.severity}] ${f.messageSv}`).join(" ")
: "AI-kontrollen kunde inte köras manuell granskning krävs."; : "AI-kontroll kunde inte köras.";
const dupCount = await ctx.db
.select({ count: sql<number>`count(*)` })
.from(schema.recipeSimilarities)
.where(eq(schema.recipeSimilarities.recipeAId, recipeId));
await ctx.db await ctx.db
.update(schema.recipes) .update(schema.recipes)
.set({ .set({
status: "in_moderation", status: exactDuplicate ? "rejected" : "published",
moderationNote: `${flagsNote} Dubblettkandidater: ${Number(dupCount[0]?.count ?? 0)}.`.trim(), moderationNote: exactDuplicate
? `Publicerades inte för lik ett befintligt recept. ${flagsNote}`.trim()
: `Auto-publicerad efter AI-kontroll. ${flagsNote}`.trim(),
updatedAt: new Date(), updatedAt: new Date(),
}) })
.where(eq(schema.recipes.id, recipeId)); .where(eq(schema.recipes.id, recipeId));