From 6cc56a198a0935a8ef1169116e424962b6e0fba3 Mon Sep 17 00:00:00 2001 From: AyrisAI Date: Thu, 20 Aug 2026 05:47:55 +0300 Subject: [PATCH] perf(ai-scanner): drop confidence scoring to simplify AI menu import Removes the per-item confidence field end to end (prompt, extraction parsing, ai_import_items writes, API responses, low-confidence badge in the mobile review screen). ai_import_items.confidence column is left in place but unused (nullable, no destructive migration). --- apps/api/src/lib/ai-scanner.ts | 33 ++++++------ apps/api/src/routes/ai-imports.ts | 15 +----- .../mobile/src/app/(onboarding)/ai-review.tsx | 51 ++----------------- packages/shared/src/types.ts | 3 -- 4 files changed, 19 insertions(+), 83 deletions(-) diff --git a/apps/api/src/lib/ai-scanner.ts b/apps/api/src/lib/ai-scanner.ts index 6e07079..9553230 100644 --- a/apps/api/src/lib/ai-scanner.ts +++ b/apps/api/src/lib/ai-scanner.ts @@ -9,7 +9,6 @@ interface ExtractionResult { const SYSTEM_PROMPT = `Sen profesyonel bir restoran menü analiz ve OCR uzmanısın. Sana verilen menü görselini analiz et ve menüdeki tüm kategorileri, yemek/içecek isimlerini, açıklamalarını ve fiyatlarını (TL cinsinden sayı olarak) tespit et. -Ayrıca her ürünün fiyatının ve isminin doğruluğu için 0.00 ile 1.00 arasında bir confidence (güven) skoru belirle (Örn: net okunanlar için 0.95-0.99, silik veya şüpheli olanlar için 0.65-0.80). SADECE aşağıdaki JSON formatında geçerli bir JSON yanıtı ver: { @@ -20,8 +19,7 @@ SADECE aşağıdaki JSON formatında geçerli bir JSON yanıtı ver: { "name": "Ürün Adı", "description": "Ürün açıklaması veya null", - "price": 150.0, - "confidence": 0.98 + "price": 150.0 } ] } @@ -52,7 +50,6 @@ function cleanAndParseJson(text: string): { categories: AiExtractedCategory[] } name: item.name || "İsimsiz Ürün", description: item.description || null, price: typeof item.price === "number" ? item.price : Number(String(item.price).replace(/[^\d.]/g, "")) || 0, - confidence: typeof item.confidence === "number" ? Math.min(1, Math.max(0, item.confidence)) : 0.95, })), })); @@ -173,38 +170,38 @@ function extractWithFallback(imageIdentifier: string): ExtractionResult { id: "cat-1", name: "Çorbalar", items: [ - { id: "item-1-1", name: "Mercimek Çorbası", description: "Taze nane, tereyağlı kıtır kruton ve limon ile", price: 120, confidence: 0.98 }, - { id: "item-1-2", name: "Ezogelin Çorbası", description: "Geleneksel Güneydoğu usulü acılı ezogelin", price: 130, confidence: 0.96 }, - { id: "item-1-3", name: "Kelle Paça Çorbası", description: "Sarımsak ve sirke sosu ile", price: 210, confidence: 0.72 }, + { id: "item-1-1", name: "Mercimek Çorbası", description: "Taze nane, tereyağlı kıtır kruton ve limon ile", price: 120 }, + { id: "item-1-2", name: "Ezogelin Çorbası", description: "Geleneksel Güneydoğu usulü acılı ezogelin", price: 130 }, + { id: "item-1-3", name: "Kelle Paça Çorbası", description: "Sarımsak ve sirke sosu ile", price: 210 }, ], }, { id: "cat-2", name: "Kebaplar & Izgaralar", items: [ - { id: "item-2-1", name: "Adana Kebap", description: "Közlenmiş biber, domates, sumaklı soğan ve lavaş ile", price: 340, confidence: 0.99 }, - { id: "item-2-2", name: "Urfa Kebap", description: "Acısız zırh kıyması, lavaş ve köz sebzeler", price: 340, confidence: 0.97 }, - { id: "item-2-3", name: "Kuzu Şiş", description: "Marine edilmiş taze kuzu eti, bulgur pilavı ile", price: 420, confidence: 0.94 }, - { id: "item-2-4", name: "Tavuk Şiş", description: "Özel sosla marine edilmiş tavuk göğsü", price: 280, confidence: 0.68 }, + { id: "item-2-1", name: "Adana Kebap", description: "Közlenmiş biber, domates, sumaklı soğan ve lavaş ile", price: 340 }, + { id: "item-2-2", name: "Urfa Kebap", description: "Acısız zırh kıyması, lavaş ve köz sebzeler", price: 340 }, + { id: "item-2-3", name: "Kuzu Şiş", description: "Marine edilmiş taze kuzu eti, bulgur pilavı ile", price: 420 }, + { id: "item-2-4", name: "Tavuk Şiş", description: "Özel sosla marine edilmiş tavuk göğsü", price: 280 }, ], }, { id: "cat-3", name: "Tatlılar", items: [ - { id: "item-3-1", name: "Künefe", description: "Hakiki Hatay peynirli, Antep fıstıklı sıcak künefe", price: 190, confidence: 0.98 }, - { id: "item-3-2", name: "Fıstıklı Baklava (4 Dilim)", description: "Gaziantep usulü tereyağlı çıtır baklava", price: 240, confidence: 0.91 }, - { id: "item-3-3", name: "Fırın Sütlaç", description: "Kavrulmuş fındık parçaları ile", price: 130, confidence: 0.78 }, + { id: "item-3-1", name: "Künefe", description: "Hakiki Hatay peynirli, Antep fıstıklı sıcak künefe", price: 190 }, + { id: "item-3-2", name: "Fıstıklı Baklava (4 Dilim)", description: "Gaziantep usulü tereyağlı çıtır baklava", price: 240 }, + { id: "item-3-3", name: "Fırın Sütlaç", description: "Kavrulmuş fındık parçaları ile", price: 130 }, ], }, { id: "cat-4", name: "İçecekler", items: [ - { id: "item-4-1", name: "Yayık Ayranı", description: "Bol köpüklü taze köy ayranı", price: 45, confidence: 0.99 }, - { id: "item-4-2", name: "Şalgam Suyu", description: "Acılı / Acısız Adana şalgamı", price: 40, confidence: 0.95 }, - { id: "item-4-3", name: "Kola / Meşrubat (330ml)", description: "Soğuk kutu meşrubat çeşitleri", price: 55, confidence: 0.96 }, - { id: "item-4-4", name: "Su (500ml)", description: "Doğal kaynak suyu", price: 20, confidence: 0.99 }, + { id: "item-4-1", name: "Yayık Ayranı", description: "Bol köpüklü taze köy ayranı", price: 45 }, + { id: "item-4-2", name: "Şalgam Suyu", description: "Acılı / Acısız Adana şalgamı", price: 40 }, + { id: "item-4-3", name: "Kola / Meşrubat (330ml)", description: "Soğuk kutu meşrubat çeşitleri", price: 55 }, + { id: "item-4-4", name: "Su (500ml)", description: "Doğal kaynak suyu", price: 20 }, ], }, ]; diff --git a/apps/api/src/routes/ai-imports.ts b/apps/api/src/routes/ai-imports.ts index 3b47595..6120124 100644 --- a/apps/api/src/routes/ai-imports.ts +++ b/apps/api/src/routes/ai-imports.ts @@ -85,7 +85,6 @@ export const aiImportsRoutes: FastifyPluginAsync = async (app) => { item_name: string; description: string | null; price: number; - confidence: number; }[] = []; for (const cat of extraction.categories) { @@ -96,7 +95,6 @@ export const aiImportsRoutes: FastifyPluginAsync = async (app) => { item_name: item.name, description: item.description ?? null, price: item.price, - confidence: item.confidence, }); } } @@ -116,15 +114,9 @@ export const aiImportsRoutes: FastifyPluginAsync = async (app) => { }) .eq("id", importRecord.id); - let lowConfidenceCount = 0; let totalItems = 0; for (const cat of extraction.categories) { - for (const itm of cat.items) { - totalItems++; - if (itm.confidence < 0.85) { - lowConfidenceCount++; - } - } + totalItems += cat.items.length; } const response: AiImportResponse = { @@ -133,7 +125,6 @@ export const aiImportsRoutes: FastifyPluginAsync = async (app) => { model: extraction.model, categories: extraction.categories, totalItems, - lowConfidenceCount, }; return reply.code(200).send(response); @@ -185,11 +176,9 @@ export const aiImportsRoutes: FastifyPluginAsync = async (app) => { // Group items into categories const categoryMap = new Map(); let totalItems = 0; - let lowConfidenceCount = 0; for (const item of rawItems ?? []) { totalItems++; - if ((item.confidence ?? 1) < 0.85) lowConfidenceCount++; const catName = item.category_name || "Genel"; if (!categoryMap.has(catName)) { @@ -205,7 +194,6 @@ export const aiImportsRoutes: FastifyPluginAsync = async (app) => { name: item.item_name, description: item.description, price: Number(item.price ?? 0), - confidence: Number(item.confidence ?? 0.95), }); } @@ -215,7 +203,6 @@ export const aiImportsRoutes: FastifyPluginAsync = async (app) => { model: importRecord.model, categories: Array.from(categoryMap.values()), totalItems, - lowConfidenceCount, }; return reply.send(response); diff --git a/apps/mobile/src/app/(onboarding)/ai-review.tsx b/apps/mobile/src/app/(onboarding)/ai-review.tsx index 2896636..0480dc7 100644 --- a/apps/mobile/src/app/(onboarding)/ai-review.tsx +++ b/apps/mobile/src/app/(onboarding)/ai-review.tsx @@ -33,10 +33,6 @@ export default function OnboardingAiReviewStep() { }, []); const totalItems = categories.reduce((acc, c) => acc + c.items.length, 0); - const lowConfidenceItems = categories.reduce( - (acc, c) => acc + c.items.filter((i) => i.confidence < 0.85).length, - 0, - ); function updateItem(categoryId: string, itemId: string, field: "name" | "price" | "description", value: string) { setCategories((prev) => @@ -48,7 +44,7 @@ export default function OnboardingAiReviewStep() { if (itm.id !== itemId) return itm; if (field === "price") { const parsed = Number(value.replace(",", ".")); - return { ...itm, price: Number.isNaN(parsed) ? 0 : parsed, confidence: 1 }; + return { ...itm, price: Number.isNaN(parsed) ? 0 : parsed }; } return { ...itm, [field]: value }; }), @@ -75,7 +71,6 @@ export default function OnboardingAiReviewStep() { name: "Yeni Ürün", price: 100, description: "", - confidence: 1, }; setCategories((prev) => @@ -206,24 +201,6 @@ export default function OnboardingAiReviewStep() { - {lowConfidenceItems > 0 ? ( - - - - {lowConfidenceItems} ürünün fiyatı veya ismi silik çıkmış olabilir, lütfen sarı etiketli ürünleri kontrol edin. - - - ) : null} {/* Categories & Items List */} @@ -269,39 +246,17 @@ export default function OnboardingAiReviewStep() { {/* Items in Category */} {category.items.map((item) => { - const isLowConfidence = item.confidence < 0.85; return ( - {isLowConfidence ? ( - - - - Fiyatı / İsmi Kontrol Edin (%{Math.round(item.confidence * 100)} Güven) - - - ) : null} -