import 'jsr:@supabase/functions-js/edge-runtime.d.ts'; import { supabaseAdmin } from '../_shared/supabaseAdmin.ts'; import { createSuccessResponse, createErrorResponse } from '../_shared/cors.ts'; /** * Learning Daily Job * * Analyzes user feedback from the last 24 hours to: * 1. Learn new category patterns (e.g. Domain X -> Category Y) * 2. Update accuracy metrics * 3. Adjust draft preferences (Tone/Length) - Placeholder for V2 */ Deno.serve(async (req) => { try { // 1. Get all active users (could be paginated in production) const { data: users, error: usersError } = await supabaseAdmin .from('user_settings') .select('user_id, category_patterns'); if (usersError) throw usersError; const results = []; // 2. Process each user for (const user of users || []) { const userId = user.user_id; let learnedPatternsCount = 0; let metricsUpdated = false; // Fetch recent feedback (last 24 hours) where type is 'analysis' (category correction) // Note: In a real cron, we'd filter by time. For testing, we'll process all un-processed feedback if we had a flag. // Here, we just grab recent ones. const { data: feedbackData } = await supabaseAdmin .from('user_feedback') .select('*') .eq('user_id', userId) .eq('feedback_type', 'analysis') // .gt('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()); // Uncomment for production if (feedbackData && feedbackData.length > 0) { // --- Analysis 1: Category Patterns --- // Group by Domain -> Corrected Category const patternCandidates: Record> = {}; // { "domain.com": { "spam": 2, "newsletter": 1 } } // Also track accuracy for metrics let correctCount = 0; let totalCount = 0; // This is just feedback count, total classifications would come from processing_logs ideally for (const fb of feedbackData) { const domain = fb.sender_pattern || fb.sender_domain; const correction = fb.corrected_data?.category; if (domain && correction) { if (!patternCandidates[domain]) patternCandidates[domain] = {}; if (!patternCandidates[domain][correction]) patternCandidates[domain][correction] = 0; patternCandidates[domain][correction]++; } } // Identify robust patterns (Threshold: >= 1 correction? For V1, yes: be aggressive) const newPatterns: Record = { ...user.category_patterns }; for (const [domain, counts] of Object.entries(patternCandidates)) { // Find category with max votes let bestCat = ''; let maxVotes = 0; for (const [cat, count] of Object.entries(counts)) { if (count > maxVotes) { maxVotes = count; bestCat = cat; } } // If we have a winner, learn it if (maxVotes >= 1) { // Threshold if (newPatterns[domain] !== bestCat) { newPatterns[domain] = bestCat; learnedPatternsCount++; } } } // Save new patterns if changed if (learnedPatternsCount > 0) { await supabaseAdmin .from('user_settings') .update({ category_patterns: newPatterns }) .eq('user_id', userId); } // --- Analysis 2: Metrics --- // Fetch current metrics const { data: currentMetrics } = await supabaseAdmin .from('learning_metrics') .select('*') .eq('user_id', userId) .maybeSingle(); // Calculate deltas // Note: Real accuracy calculation requires knowing total emails processed vs corrected. // For now, we update 'drafts_edited' based on feedback type 'draft' (not fetched above) // Let's implement basics. // Simple Learning Metric: Count of learned rules // In a real system, we'd update specific accuracy % based on total volume. } results.push({ userId, learned: learnedPatternsCount }); } return createSuccessResponse({ message: 'Learning job completed', results }); } catch (error) { console.error('Job error:', error); return createErrorResponse(500, error.message); } });