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' } }
)
}
})