import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' import { getCorsHeaders } from '../_shared/cors.ts' import { OpenAI } from "https://deno.land/x/openai@v4.24.0/mod.ts"; interface EssayAnalysisRequest { essay_id: string; content: string; type_id: string; genre_id: string; } interface EssayAnalysisResponse { overall_score: number; suggestions: string; feedback: { structure: string; content: string; language: string; }; strengths: string[]; improvements: string[]; criteria_scores: { adequacy: number; coherence: number; cohesion: number; vocabulary: number; grammar: number; }; competencies: { language_domain: { value: number; justification: string; }; proposal_comprehension: { value: number; justification: string; }; argument_selection: { value: number; justification: string; }; linguistic_mechanisms: { value: number; justification: string; }; intervention_proposal: { value: number; justification: string; }; }; } interface EssayType { id: string; slug: string; title: string; description: string; } interface EssayGenre { id: string; type_id: string; slug: string; title: string; description: string; requirements: { min_words: number; max_words: number; required_elements: string[]; }; } serve(async (req) => { console.log(`[${new Date().toISOString()}] Nova requisição recebida`) // Handle CORS if (req.method === 'OPTIONS') { console.log('Requisição OPTIONS - CORS preflight') return new Response('ok', { headers: getCorsHeaders(req) }) } try { // Criar cliente Supabase console.log('Inicializando cliente Supabase...') const supabaseUrl = Deno.env.get('SUPABASE_URL') const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') if (!supabaseUrl || !supabaseServiceKey) { throw new Error('Variáveis de ambiente do Supabase não configuradas') } const supabaseClient = createClient(supabaseUrl, supabaseServiceKey, { auth: { persistSession: false, autoRefreshToken: false, } }) // Criar cliente OpenAI console.log('Inicializando cliente OpenAI...') const openaiKey = Deno.env.get('OPENAI_API_KEY') if (!openaiKey) { throw new Error('OPENAI_API_KEY não configurada') } const openai = new OpenAI({ apiKey: openaiKey, }); // Obter dados da requisição console.log('Obtendo dados da requisição...') const requestData = await req.json() console.log('Dados recebidos:', JSON.stringify(requestData, null, 2)) const { essay_id, content, type_id, genre_id }: EssayAnalysisRequest = requestData // Validar dados obrigatórios if (!essay_id || !content || !type_id || !genre_id) { console.error('Dados obrigatórios faltando:', { essay_id, content: !!content, type_id, genre_id }) return new Response( JSON.stringify({ error: 'Dados obrigatórios não fornecidos', details: { essay_id: !essay_id, content: !content, type_id: !type_id, genre_id: !genre_id } }), { status: 400, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Buscar informações do tipo e gênero console.log('Buscando informações do tipo e gênero...') const { data: typeData, error: typeError } = await supabaseClient .from('essay_types') .select('*') .eq('id', type_id) .single() if (typeError) { console.error('Erro ao buscar tipo:', typeError) } const { data: genreData, error: genreError } = await supabaseClient .from('essay_genres') .select('*') .eq('id', genre_id) .single() if (genreError) { console.error('Erro ao buscar gênero:', genreError) } if (typeError || genreError || !typeData || !genreData) { return new Response( JSON.stringify({ error: 'Tipo ou gênero não encontrado', details: { typeError: typeError?.message, genreError: genreError?.message } }), { status: 404, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } const essayType: EssayType = typeData const essayGenre: EssayGenre = genreData console.log('Tipo e gênero encontrados:', { type: essayType.title, genre: essayGenre.title }) // Construir prompt para a análise console.log('Construindo prompt para análise...') const prompt = `Você é um professor especialista em análise de textos. Analise a redação a seguir considerando que é do tipo "${essayType.title}" e gênero "${essayGenre.title}". Requisitos específicos do gênero: - Mínimo de palavras: ${essayGenre.requirements.min_words} - Máximo de palavras: ${essayGenre.requirements.max_words} - Elementos obrigatórios: ${essayGenre.requirements.required_elements.join(', ')} Texto para análise: ${content} Forneça uma análise detalhada considerando: 1. Adequação ao tipo e gênero textual 2. Coerência e coesão textual 3. Vocabulário e linguagem 4. Gramática e ortografia 5. Elementos obrigatórios do gênero 6. Pontos fortes 7. Pontos a melhorar 8. Sugestões específicas para aprimoramento Essas são as competências do ENEM para você analisar a redação: - Competência 1: Domínio da língua - Competência 2: Compreensão da proposta - Competência 3: Seleção de argumentos - Competência 4: Mecanismos linguísticos - Competência 5: Proposta de intervenção ## Competência 1: Demonstrar domínio da modalidade escrita formal da Língua Portuguesa | Nota | Grade de Correção | Características | |------|------------------|-----------------| | 200 pontos | Demonstra excelente domínio da modalidade escrita formal da Língua Portuguesa e de escolha de registro. Desvios gramaticais ou de convenções da escrita serão aceitos somente como excepcionalidade e quando não caracterizem reincidência. | • Ausência de marcas de oralidade e de registro informal
• Precisão vocabular
• Obediência às regras gramaticais
• Não apresenta ou apresenta pouquíssimos desvios gramaticais leves e de convenções da escrita | | 160 pontos | Demonstra bom domínio da modalidade escrita formal da Língua Portuguesa e de escolha de registro, com poucos desvios gramaticais e de convenções da escrita. | • Poucos desvios gramaticais leves
• Desvios de pontuação que não comprometem o sentido
• Desvios de ortografia e acentuação que não comprometem o sentido | | 120 pontos | Demonstra domínio mediano da modalidade escrita formal da Língua Portuguesa e de escolha de registro, com alguns desvios gramaticais e de convenções da escrita. | • Alguns desvios gramaticais graves
• Problemas de concordância
• Problemas de regência
• Problemas de estrutura sintática
• Marcas de oralidade | | 80 pontos | Demonstra domínio insuficiente da modalidade escrita formal da Língua Portuguesa, com muitos desvios gramaticais, de escolha de registro e de convenções da escrita. | • Grande quantidade de desvios graves
• Períodos incompletos
• Graves problemas de pontuação
• Desvios graves de grafia e acentuação
• Presença de gíria | | 40 pontos | Demonstra domínio precário da modalidade escrita formal da Língua Portuguesa, de forma sistemática, com diversificados e frequentes desvios gramaticais. | • Graves e frequentes desvios gramaticais
• Presença de gírias e marcas de oralidade
• Desestruturação sintática em excesso | | 0 ponto | Demonstra desconhecimento da modalidade escrita formal da Língua Portuguesa. | • Excesso de desvios que impossibilitam a compreensão | ## Competência 2: Compreender a proposta de redação e aplicar conceitos das várias áreas de conhecimento | Nota | Grade de Correção | Características | |------|------------------|-----------------| | 200 pontos | Desenvolve o tema por meio de argumentação consistente, a partir de um repertório sociocultural produtivo, e apresenta excelente domínio do texto dissertativo-argumentativo. | • Tema muito bem desenvolvido
• Estrutura completa (introdução, argumentos, conclusão)
• Argumentos originais, além dos textos motivadores | | 160 pontos | Desenvolve o tema por meio de argumentação consistente e apresenta bom domínio do texto dissertativo-argumentativo. | • Desenvolve bem o tema
• Boa argumentação
• Não se limita aos textos motivadores | | 120 pontos | Desenvolve o tema por meio de argumentação previsível e apresenta domínio mediano do texto dissertativo-argumentativo. | • Abordagem superficial
• Argumentação previsível
• Reproduz ideias do senso comum | | 80 pontos | Desenvolve o tema recorrendo à cópia de trechos dos textos motivadores ou apresenta domínio insuficiente do texto dissertativo-argumentativo. | • Tendência ao tangenciamento
• Argumentação falha
• Cópia dos textos motivadores | | 40 pontos | Apresenta o assunto, tangenciando o tema, ou demonstra domínio precário do texto dissertativo-argumentativo. | • Tangencia o tema
• Ausência de argumentação
• Pode apresentar texto narrativo | | 0 ponto | Fuga ao tema/não atendimento à estrutura dissertativo-argumentativa. | • Desenvolve outro tema
• Usa outra estrutura textual | ## Competência 3: Selecionar, relacionar, organizar e interpretar informações, fatos, opiniões e argumentos | Nota | Grade de Correção | Características | |------|------------------|-----------------| | 200 pontos | Apresenta informações, fatos e opiniões relacionados ao tema proposto, de forma consistente e organizada, configurando autoria. | • Seleciona e organiza informações consistentemente
• Explicita tese clara
• Argumentos comprovam a tese | | 160 pontos | Apresenta informações, fatos e opiniões relacionados ao tema, de forma organizada, com indícios de autoria. | • Organização consistente
• Argumentos previsíveis mas próprios | | 120 pontos | Apresenta informações, fatos e opiniões relacionados ao tema, limitados aos argumentos dos textos motivadores. | • Organização pouco consistente
• Informações aleatórias
• Argumentos pouco convincentes | | 80 pontos | Apresenta informações, fatos e opiniões relacionados ao tema, mas desorganizados ou contraditórios. | • Argumentos pouco articulados
• Reprodução dos textos motivadores | | 40 pontos | Apresenta informações, fatos e opiniões pouco relacionados ao tema ou incoerentes. | • Sem defesa de ponto de vista
• Informações desarticuladas | | 0 ponto | Apresenta informações, fatos e opiniões não relacionados ao tema. | • Informações incoerentes
• Sem ponto de vista | ## Competência 4: Demonstrar conhecimento dos mecanismos linguísticos necessários para a construção da argumentação | Nota | Grade de Correção | Características | |------|------------------|-----------------| | 200 pontos | Articula bem as partes do texto e apresenta repertório diversificado de recursos coesivos. | • Articulação muito boa
• Pleno domínio dos recursos coesivos | | 160 pontos | Articula as partes do texto com poucas inadequações e apresenta repertório diversificado de recursos coesivos. | • Boa articulação
• Poucos desvios nos conectores
• Algumas repetições desnecessárias | | 120 pontos | Articula as partes do texto, de forma mediana, com inadequações. | • Algumas inadequações nos recursos coesivos
• Frases fragmentadas ocasionais
• Problemas de paragrafação | | 80 pontos | Articula as partes do texto, de forma insuficiente, com muitas inadequações. | • Muitas inadequações
• Frases fragmentadas frequentes
• Problemas de estrutura | | 40 pontos | Articula as partes do texto de forma precária. | • Graves problemas de articulação
• Períodos muito longos ou fragmentados
• Ausência de conectores | | 0 ponto | Apresenta informações desconexas. | • Não se configura como texto | ## Competência 5: Elaborar proposta de intervenção para o problema abordado | Nota | Grade de Correção | Características | |------|------------------|-----------------| | 200 pontos | Elabora muito bem proposta de intervenção, detalhada, relacionada ao tema. | • Proposta detalhada (o que fazer, como fazer, meios e participantes) | | 160 pontos | Elabora bem proposta de intervenção relacionada ao tema. | • Proposta relacionada mas não totalmente detalhada | | 120 pontos | Elabora, de forma mediana, proposta de intervenção relacionada ao tema. | • Proposta relacionada mas sem detalhamento | | 80 pontos | Elabora, de forma insuficiente, proposta de intervenção. | • Proposta não atende totalmente à discussão | | 40 pontos | Apresenta proposta de intervenção vaga, precária ou relacionada apenas ao assunto. | • Proposta vaga ou não configurada adequadamente | | 0 ponto | Não apresenta proposta de intervenção ou apresenta proposta não relacionada ao tema. | • Ausência de proposta
• Proposta foge ao tema | Responda em formato JSON seguindo exatamente esta estrutura: { "overall_score": number, // 0 a 100 "suggestions": string, // Sugestões específicas "feedback": { "structure": string, // Feedback sobre estrutura e organização "content": string, // Feedback sobre conteúdo e ideias "language": string // Feedback sobre linguagem e gramática }, "strengths": string[], // Lista de pontos fortes "improvements": string[], // Lista de pontos a melhorar "criteria_scores": { "adequacy": number, // 0 a 100 - Adequação ao tema/gênero "coherence": number, // 0 a 100 - Coerência textual "cohesion": number, // 0 a 100 - Coesão "vocabulary": number, // 0 a 100 - Vocabulário "grammar": number // 0 a 100 - Gramática e ortografia }, "competencies": { "language_domain": { value: number, // 0 a 200 - Competência 1 justification: string // Justificativa para a pontuação }, "proposal_comprehension": { value: number, // 0 a 200 - Competência 2 justification: string // Justificativa para a pontuação }, "argument_selection": { value: number, // 0 a 200 - Competência 3 justification: string // Justificativa para a pontuação }, "linguistic_mechanisms": { value: number, // 0 a 200 - Competência 4 justification: string // Justificativa para a pontuação }, "intervention_proposal": { value: number, // 0 a 200 - Competência 5 justification: string // Justificativa para a pontuação } } }` // Realizar análise com OpenAI console.log('Enviando requisição para OpenAI...') try { const completion = await openai.chat.completions.create({ model: "gpt-4o-mini", messages: [ { role: "system", content: "Você é um professor especialista em análise de textos, com vasta experiência em avaliação de redações." }, { role: "user", content: prompt } ], response_format: { type: "json_schema", json_schema: { name: "essay_analysis", strict: true, description: "Análise detalhada da redação", schema: { type: "object", additionalProperties: false, required: ["overall_score", "suggestions", "feedback", "strengths", "improvements", "criteria_scores", "competencies"], properties: { overall_score: { type: "number", description: "Pontuação geral da redação (0-100)" }, suggestions: { type: "string", description: "Sugestões específicas para aprimoramento" }, feedback: { type: "object", additionalProperties: false, required: ["structure", "content", "language"], properties: { structure: { type: "string", description: "Feedback sobre estrutura e organização" }, content: { type: "string", description: "Feedback sobre conteúdo e ideias" }, language: { type: "string", description: "Feedback sobre linguagem e gramática" } } }, strengths: { type: "array", items: { type: "string", description: "Ponto forte da redação" }, description: "Lista de pontos fortes da redação" }, improvements: { type: "array", items: { type: "string", description: "Ponto a melhorar na redação" }, description: "Lista de pontos a melhorar na redação" }, criteria_scores: { type: "object", additionalProperties: false, required: ["adequacy", "coherence", "cohesion", "vocabulary", "grammar"], properties: { adequacy: { type: "number", description: "Adequação ao tema/gênero (0-100)" }, coherence: { type: "number", description: "Coerência textual (0-100)" }, cohesion: { type: "number", description: "Coesão textual (0-100)" }, vocabulary: { type: "number", description: "Vocabulário (0-100)" }, grammar: { type: "number", description: "Gramática e ortografia (0-100)" } } }, competencies: { type: "object", additionalProperties: false, required: [ "language_domain", "proposal_comprehension", "argument_selection", "linguistic_mechanisms", "intervention_proposal" ], properties: { language_domain: { type: "object", additionalProperties: false, required: ["value", "justification"], properties: { value: { type: "number", description: "Pontuação da competência (0-200)" }, justification: { type: "string", description: "Justificativa para a pontuação" } } }, proposal_comprehension: { type: "object", additionalProperties: false, required: ["value", "justification"], properties: { value: { type: "number", description: "Pontuação da competência (0-200)" }, justification: { type: "string", description: "Justificativa para a pontuação" } } }, argument_selection: { type: "object", additionalProperties: false, required: ["value", "justification"], properties: { value: { type: "number", description: "Pontuação da competência (0-200)" }, justification: { type: "string", description: "Justificativa para a pontuação" } } }, linguistic_mechanisms: { type: "object", additionalProperties: false, required: ["value", "justification"], properties: { value: { type: "number", description: "Pontuação da competência (0-200)" }, justification: { type: "string", description: "Justificativa para a pontuação" } } }, intervention_proposal: { type: "object", additionalProperties: false, required: ["value", "justification"], properties: { value: { type: "number", description: "Pontuação da competência (0-200)" }, justification: { type: "string", description: "Justificativa para a pontuação" } } } } } } } } } }); console.log('Resposta recebida da OpenAI') const analysis: EssayAnalysisResponse = JSON.parse(completion.choices[0].message.content) console.log('Análise gerada:', JSON.stringify(analysis, null, 2)) // Salvar análise no banco console.log('Salvando análise no banco...') // Primeiro, criar a análise principal const { data: analysisData, error: analysisError } = await supabaseClient .from('essay_analyses') .insert({ essay_id, overall_score: analysis.overall_score, suggestions: analysis.suggestions, // Campos de competências language_domain_value: analysis.competencies.language_domain.value, language_domain_justification: analysis.competencies.language_domain.justification, proposal_comprehension_value: analysis.competencies.proposal_comprehension.value, proposal_comprehension_justification: analysis.competencies.proposal_comprehension.justification, argument_selection_value: analysis.competencies.argument_selection.value, argument_selection_justification: analysis.competencies.argument_selection.justification, linguistic_mechanisms_value: analysis.competencies.linguistic_mechanisms.value, linguistic_mechanisms_justification: analysis.competencies.linguistic_mechanisms.justification, intervention_proposal_value: analysis.competencies.intervention_proposal.value, intervention_proposal_justification: analysis.competencies.intervention_proposal.justification }) .select() .single() if (analysisError) { console.error('Erro ao salvar análise principal:', analysisError) return new Response( JSON.stringify({ error: 'Erro ao salvar análise', details: analysisError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Salvar feedback const { error: feedbackError } = await supabaseClient .from('essay_analysis_feedback') .insert({ analysis_id: analysisData.id, structure_feedback: analysis.feedback.structure, content_feedback: analysis.feedback.content, language_feedback: analysis.feedback.language }) if (feedbackError) { console.error('Erro ao salvar feedback:', feedbackError) return new Response( JSON.stringify({ error: 'Erro ao salvar feedback', details: feedbackError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Salvar pontos fortes const strengths = analysis.strengths.map(strength => ({ analysis_id: analysisData.id, strength })) const { error: strengthsError } = await supabaseClient .from('essay_analysis_strengths') .insert(strengths) if (strengthsError) { console.error('Erro ao salvar pontos fortes:', strengthsError) return new Response( JSON.stringify({ error: 'Erro ao salvar pontos fortes', details: strengthsError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Salvar pontos a melhorar const improvements = analysis.improvements.map(improvement => ({ analysis_id: analysisData.id, improvement })) const { error: improvementsError } = await supabaseClient .from('essay_analysis_improvements') .insert(improvements) if (improvementsError) { console.error('Erro ao salvar pontos a melhorar:', improvementsError) return new Response( JSON.stringify({ error: 'Erro ao salvar pontos a melhorar', details: improvementsError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Salvar notas por critério const { error: scoresError } = await supabaseClient .from('essay_analysis_scores') .insert({ analysis_id: analysisData.id, adequacy: analysis.criteria_scores.adequacy, coherence: analysis.criteria_scores.coherence, cohesion: analysis.criteria_scores.cohesion, vocabulary: analysis.criteria_scores.vocabulary, grammar: analysis.criteria_scores.grammar }) if (scoresError) { console.error('Erro ao salvar notas:', scoresError) return new Response( JSON.stringify({ error: 'Erro ao salvar notas', details: scoresError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } // Atualizar status da redação console.log('Atualizando status da redação...') const { error: updateError } = await supabaseClient .from('student_essays') .update({ status: 'analyzed' }) .eq('id', essay_id) if (updateError) { console.error('Erro ao atualizar status:', updateError) return new Response( JSON.stringify({ error: 'Erro ao atualizar status da redação', details: updateError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } console.log('Análise concluída com sucesso') // Retornar análise return new Response( JSON.stringify(analysis), { headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } catch (openaiError) { console.error('Erro na chamada da OpenAI:', openaiError) return new Response( JSON.stringify({ error: 'Erro ao gerar análise', details: openaiError.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } } catch (error) { console.error('Erro geral na função:', error) return new Response( JSON.stringify({ error: 'Erro interno do servidor', details: error.message }), { status: 500, headers: { ...getCorsHeaders(req), 'Content-Type': 'application/json' } } ) } })