Compare commits

..

3 Commits

Author SHA1 Message Date
Lucas Santana
f883a6e9c2 feat: melhora layout da análise de redações com seção dedicada para competências do ENEM
Some checks are pending
Docker Build and Push / build (push) Waiting to run
- Separa critérios gerais e competências do ENEM em seções distintas
- Adiciona nova seção dedicada com layout aprimorado para competências do ENEM
- Melhora visualização das barras de progresso e justificativas
- Inclui descrições detalhadas para cada competência
- Implementa cards coloridos para melhor organização visual
- Aprimora apresentação dos critérios gerais de avaliação

patch: Apenas melhorias visuais, sem alterações na funcionalidade
2025-02-12 20:03:12 -03:00
Lucas Santana
2ff79ced53 feat: adiciona competências ENEM na análise de redações
- Adiciona campos para armazenar as 5 competências do ENEM na tabela essay_analyses
- Atualiza função analyze-essay para salvar notas e justificativas das competências
- Adiciona restrições para validar valores entre 0 e 200 pontos
- Atualiza documentação com comentários nos campos

patch: atualização incremental que adiciona funcionalidade sem quebrar compatibilidade
2025-02-12 19:29:12 -03:00
Lucas Santana
374ac90a3b feat: Adicionando análises do ENEM 2025-02-12 19:26:05 -03:00
8 changed files with 633 additions and 109 deletions

View File

@ -182,25 +182,29 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
- Atualizado payload da Edge Function para incluir `language_type` - Atualizado payload da Edge Function para incluir `language_type`
- Melhorada tipagem para suporte a múltiplos idiomas - Melhorada tipagem para suporte a múltiplos idiomas
## [1.4.0] - 2024-01-31 ## [1.4.0] - 2024-03-28
### Adicionado ### Adicionado
- Integração completa com a tabela `languages` do banco de dados - Novas competências na análise de redações:
- Suporte para ícones de bandeira nos seletores de idioma - Domínio da língua (0-200 pontos)
- Instruções específicas por idioma na interface - Compreensão da proposta (0-200 pontos)
- Hook `useLanguages` para gerenciamento centralizado de idiomas - Seleção de argumentos (0-200 pontos)
- Mecanismos linguísticos (0-200 pontos)
### Modificado - Proposta de intervenção (0-200 pontos)
- Removido `LANGUAGE_OPTIONS` hard coded do `StoryGenerator`
- Atualizado `CreateStoryPage` para usar `DEFAULT_LANGUAGE` do type
- Melhorada a validação de idiomas usando dados do banco
- Aprimorada a UX do seletor de idiomas com ícones e instruções
### Técnico ### Técnico
- Refatorado o sistema de idiomas para usar dados dinâmicos do banco - Adicionados novos campos na tabela `essay_analyses` para armazenar as competências
- Adicionada tipagem forte para dados de idioma - Atualizada a função `analyze-essay` para salvar as notas e justificativas das competências
- Implementada validação robusta de códigos de idioma - Adicionada restrição para garantir que os valores das competências estejam entre 0 e 200
- Melhorada a estrutura de componentes para suportar dados dinâmicos
### Modificado
- Melhorado o layout da página de análise de redações:
- Separação clara entre critérios gerais e competências do ENEM
- Nova seção dedicada às competências do ENEM com layout aprimorado
- Barras de progresso mais visíveis para as competências
- Adicionadas descrições detalhadas para cada competência
- Cards coloridos para justificativas das competências
- Melhorias visuais nos critérios gerais de avaliação
## [1.5.0] - 2024-03-19 ## [1.5.0] - 2024-03-19

View File

@ -17,7 +17,11 @@ interface TimeFilterOption {
months: number | null; months: number | null;
} }
const METRICS_CONFIG: MetricConfig[] = [ interface MetricNames {
[key: string]: string;
}
const WRITING_METRICS: MetricConfig[] = [
{ key: 'score', name: 'Nota Geral', color: '#6366f1' }, { key: 'score', name: 'Nota Geral', color: '#6366f1' },
{ key: 'adequacy', name: 'Adequação', color: '#f43f5e' }, { key: 'adequacy', name: 'Adequação', color: '#f43f5e' },
{ key: 'coherence', name: 'Coerência', color: '#0ea5e9' }, { key: 'coherence', name: 'Coerência', color: '#0ea5e9' },
@ -26,6 +30,14 @@ const METRICS_CONFIG: MetricConfig[] = [
{ key: 'grammar', name: 'Gramática', color: '#f59e0b' } { key: 'grammar', name: 'Gramática', color: '#f59e0b' }
]; ];
const ENEM_METRICS: MetricConfig[] = [
{ key: 'language_domain', name: 'Domínio da Língua', color: '#f43f5e' },
{ key: 'proposal_comprehension', name: 'Compreensão da Proposta', color: '#0ea5e9' },
{ key: 'argument_selection', name: 'Seleção de Argumentos', color: '#10b981' },
{ key: 'linguistic_mechanisms', name: 'Mecanismos Linguísticos', color: '#8b5cf6' },
{ key: 'intervention_proposal', name: 'Proposta de Intervenção', color: '#f59e0b' }
];
const TIME_FILTERS: TimeFilterOption[] = [ const TIME_FILTERS: TimeFilterOption[] = [
{ value: '3m', label: '3 meses', months: 3 }, { value: '3m', label: '3 meses', months: 3 },
{ value: '6m', label: '6 meses', months: 6 }, { value: '6m', label: '6 meses', months: 6 },
@ -39,13 +51,28 @@ interface WritingMetricsChartProps {
} }
export function WritingMetricsChart({ data = [], className = '' }: WritingMetricsChartProps) { export function WritingMetricsChart({ data = [], className = '' }: WritingMetricsChartProps) {
const [visibleMetrics, setVisibleMetrics] = React.useState<Set<string>>( const [visibleWritingMetrics, setVisibleWritingMetrics] = React.useState<Set<string>>(
new Set(METRICS_CONFIG.map(metric => metric.key)) new Set(WRITING_METRICS.map(metric => metric.key))
);
const [visibleEnemMetrics, setVisibleEnemMetrics] = React.useState<Set<string>>(
new Set(ENEM_METRICS.map(metric => metric.key))
); );
const [timeFilter, setTimeFilter] = React.useState<TimeFilter>('12m'); const [timeFilter, setTimeFilter] = React.useState<TimeFilter>('12m');
const toggleMetric = (metricKey: string) => { const toggleWritingMetric = (metricKey: string) => {
setVisibleMetrics(prev => { setVisibleWritingMetrics(prev => {
const newSet = new Set(prev);
if (newSet.has(metricKey)) {
newSet.delete(metricKey);
} else {
newSet.add(metricKey);
}
return newSet;
});
};
const toggleEnemMetric = (metricKey: string) => {
setVisibleEnemMetrics(prev => {
const newSet = new Set(prev); const newSet = new Set(prev);
if (newSet.has(metricKey)) { if (newSet.has(metricKey)) {
newSet.delete(metricKey); newSet.delete(metricKey);
@ -76,13 +103,13 @@ export function WritingMetricsChart({ data = [], className = '' }: WritingMetric
const filteredData = React.useMemo(() => filterDataByTime(data), [data, timeFilter]); const filteredData = React.useMemo(() => filterDataByTime(data), [data, timeFilter]);
return ( const renderChart = (title: string, description: string, metrics: MetricConfig[], visibleMetrics: Set<string>, toggleMetric: (key: string) => void) => (
<div className={`bg-white rounded-xl shadow-sm border border-gray-200 p-8 ${className}`}> <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8 mb-8">
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="space-y-1"> <div className="space-y-1">
<h2 className="text-xl font-semibold text-gray-900">Evolução da Escrita por Semana</h2> <h2 className="text-xl font-semibold text-gray-900">{title}</h2>
<p className="text-sm text-gray-500">Acompanhe seu progresso na escrita ao longo do tempo</p> <p className="text-sm text-gray-500">{description}</p>
</div> </div>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
{/* Filtro de Período */} {/* Filtro de Período */}
@ -106,7 +133,7 @@ export function WritingMetricsChart({ data = [], className = '' }: WritingMetric
</div> </div>
<div <div
className="text-gray-400 hover:text-gray-600 cursor-help transition-colors" className="text-gray-400 hover:text-gray-600 cursor-help transition-colors"
title="Gráfico mostrando a evolução das suas métricas de escrita ao longo das semanas" title={`Gráfico mostrando a evolução das suas ${title.toLowerCase()} ao longo das semanas`}
> >
<HelpCircle className="h-4 w-4" /> <HelpCircle className="h-4 w-4" />
</div> </div>
@ -115,7 +142,7 @@ export function WritingMetricsChart({ data = [], className = '' }: WritingMetric
{/* Pill Buttons */} {/* Pill Buttons */}
<div className="flex flex-wrap gap-2 p-1"> <div className="flex flex-wrap gap-2 p-1">
{METRICS_CONFIG.map(metric => ( {metrics.map(metric => (
<button <button
key={metric.key} key={metric.key}
onClick={() => toggleMetric(metric.key)} onClick={() => toggleMetric(metric.key)}
@ -179,15 +206,9 @@ export function WritingMetricsChart({ data = [], className = '' }: WritingMetric
/> />
<Tooltip <Tooltip
formatter={(value: number, name: string) => { formatter={(value: number, name: string) => {
const metricNames: { [key: string]: string } = { const metricNames: MetricNames = metrics.reduce((acc, m) => ({ ...acc, [m.key]: m.name }), {
score: 'Nota Geral',
adequacy: 'Adequação',
coherence: 'Coerência',
cohesion: 'Coesão',
vocabulary: 'Vocabulário',
grammar: 'Gramática',
minutesWriting: 'Minutos Escrevendo' minutesWriting: 'Minutos Escrevendo'
}; });
return [value, metricNames[name] || name]; return [value, metricNames[name] || name];
}} }}
contentStyle={{ contentStyle={{
@ -207,7 +228,7 @@ export function WritingMetricsChart({ data = [], className = '' }: WritingMetric
paddingBottom: '20px' paddingBottom: '20px'
}} }}
/> />
{METRICS_CONFIG.map(metric => ( {metrics.map(metric => (
visibleMetrics.has(metric.key) && ( visibleMetrics.has(metric.key) && (
<Line <Line
key={metric.key} key={metric.key}
@ -238,4 +259,23 @@ export function WritingMetricsChart({ data = [], className = '' }: WritingMetric
</div> </div>
</div> </div>
); );
return (
<div className={className}>
{renderChart(
"Evolução da Escrita por Semana",
"Acompanhe seu progresso na escrita ao longo do tempo",
WRITING_METRICS,
visibleWritingMetrics,
toggleWritingMetric
)}
{renderChart(
"Evolução das Competências do ENEM",
"Acompanhe seu progresso nas competências do ENEM ao longo do tempo",
ENEM_METRICS,
visibleEnemMetrics,
toggleEnemMetric
)}
</div>
);
} }

View File

@ -67,6 +67,16 @@ interface EssayAnalysis {
content_feedback: string; content_feedback: string;
language_feedback: string; language_feedback: string;
}>; }>;
language_domain_value: number;
language_domain_justification: string;
proposal_comprehension_value: number;
proposal_comprehension_justification: string;
argument_selection_value: number;
argument_selection_justification: string;
linguistic_mechanisms_value: number;
linguistic_mechanisms_justification: string;
intervention_proposal_value: number;
intervention_proposal_justification: string;
} }
interface ProcessedEssayAnalysis { interface ProcessedEssayAnalysis {
@ -86,6 +96,28 @@ interface ProcessedEssayAnalysis {
content_feedback: string; content_feedback: string;
language_feedback: string; language_feedback: string;
}; };
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;
};
};
} }
export function StudentDashboardPage() { export function StudentDashboardPage() {
@ -170,6 +202,11 @@ export function StudentDashboardPage() {
cohesion: 0, cohesion: 0,
vocabulary: 0, vocabulary: 0,
grammar: 0, grammar: 0,
language_domain: 0,
proposal_comprehension: 0,
argument_selection: 0,
linguistic_mechanisms: 0,
intervention_proposal: 0,
minutesWriting: 0 minutesWriting: 0
}; };
} }
@ -181,6 +218,11 @@ export function StudentDashboardPage() {
acc[week].cohesion += analysis.scores.cohesion; acc[week].cohesion += analysis.scores.cohesion;
acc[week].vocabulary += analysis.scores.vocabulary; acc[week].vocabulary += analysis.scores.vocabulary;
acc[week].grammar += analysis.scores.grammar; acc[week].grammar += analysis.scores.grammar;
acc[week].language_domain += analysis.competencies.language_domain.value;
acc[week].proposal_comprehension += analysis.competencies.proposal_comprehension.value;
acc[week].argument_selection += analysis.competencies.argument_selection.value;
acc[week].linguistic_mechanisms += analysis.competencies.linguistic_mechanisms.value;
acc[week].intervention_proposal += analysis.competencies.intervention_proposal.value;
acc[week].minutesWriting += 30; // Tempo médio estimado por redação acc[week].minutesWriting += 30; // Tempo médio estimado por redação
return acc; return acc;
@ -195,6 +237,11 @@ export function StudentDashboardPage() {
cohesion: Math.round(data.cohesion / data.count), cohesion: Math.round(data.cohesion / data.count),
vocabulary: Math.round(data.vocabulary / data.count), vocabulary: Math.round(data.vocabulary / data.count),
grammar: Math.round(data.grammar / data.count), grammar: Math.round(data.grammar / data.count),
language_domain: Math.round(data.language_domain / data.count),
proposal_comprehension: Math.round(data.proposal_comprehension / data.count),
argument_selection: Math.round(data.argument_selection / data.count),
linguistic_mechanisms: Math.round(data.linguistic_mechanisms / data.count),
intervention_proposal: Math.round(data.intervention_proposal / data.count),
minutesWriting: data.minutesWriting minutesWriting: data.minutesWriting
})) }))
.sort((a, b) => a.week.localeCompare(b.week)); .sort((a, b) => a.week.localeCompare(b.week));
@ -342,7 +389,17 @@ export function StudentDashboardPage() {
structure_feedback, structure_feedback,
content_feedback, content_feedback,
language_feedback language_feedback
) ),
language_domain_value,
language_domain_justification,
proposal_comprehension_value,
proposal_comprehension_justification,
argument_selection_value,
argument_selection_justification,
linguistic_mechanisms_value,
linguistic_mechanisms_justification,
intervention_proposal_value,
intervention_proposal_justification
) )
`) `)
.eq('student_id', session.user.id) .eq('student_id', session.user.id)
@ -373,6 +430,28 @@ export function StudentDashboardPage() {
structure_feedback: analysis.essay_analysis_feedback?.[0]?.structure_feedback || '', structure_feedback: analysis.essay_analysis_feedback?.[0]?.structure_feedback || '',
content_feedback: analysis.essay_analysis_feedback?.[0]?.content_feedback || '', content_feedback: analysis.essay_analysis_feedback?.[0]?.content_feedback || '',
language_feedback: analysis.essay_analysis_feedback?.[0]?.language_feedback || '' language_feedback: analysis.essay_analysis_feedback?.[0]?.language_feedback || ''
},
competencies: {
language_domain: {
value: analysis.language_domain_value || 0,
justification: analysis.language_domain_justification || ''
},
proposal_comprehension: {
value: analysis.proposal_comprehension_value || 0,
justification: analysis.proposal_comprehension_justification || ''
},
argument_selection: {
value: analysis.argument_selection_value || 0,
justification: analysis.argument_selection_justification || ''
},
linguistic_mechanisms: {
value: analysis.linguistic_mechanisms_value || 0,
justification: analysis.linguistic_mechanisms_justification || ''
},
intervention_proposal: {
value: analysis.intervention_proposal_value || 0,
justification: analysis.intervention_proposal_justification || ''
}
} }
}; };
}) })
@ -389,14 +468,24 @@ export function StudentDashboardPage() {
coherence: acc.coherence + (analysis.scores?.coherence || 0), coherence: acc.coherence + (analysis.scores?.coherence || 0),
cohesion: acc.cohesion + (analysis.scores?.cohesion || 0), cohesion: acc.cohesion + (analysis.scores?.cohesion || 0),
vocabulary: acc.vocabulary + (analysis.scores?.vocabulary || 0), vocabulary: acc.vocabulary + (analysis.scores?.vocabulary || 0),
grammar: acc.grammar + (analysis.scores?.grammar || 0) grammar: acc.grammar + (analysis.scores?.grammar || 0),
language_domain: acc.language_domain + (analysis.competencies?.language_domain?.value || 0),
proposal_comprehension: acc.proposal_comprehension + (analysis.competencies?.proposal_comprehension?.value || 0),
argument_selection: acc.argument_selection + (analysis.competencies?.argument_selection?.value || 0),
linguistic_mechanisms: acc.linguistic_mechanisms + (analysis.competencies?.linguistic_mechanisms?.value || 0),
intervention_proposal: acc.intervention_proposal + (analysis.competencies?.intervention_proposal?.value || 0)
}), { }), {
score: 0, score: 0,
adequacy: 0, adequacy: 0,
coherence: 0, coherence: 0,
cohesion: 0, cohesion: 0,
vocabulary: 0, vocabulary: 0,
grammar: 0 grammar: 0,
language_domain: 0,
proposal_comprehension: 0,
argument_selection: 0,
linguistic_mechanisms: 0,
intervention_proposal: 0
}); });
console.log('Soma das métricas:', metricsSum); console.log('Soma das métricas:', metricsSum);
@ -412,7 +501,12 @@ export function StudentDashboardPage() {
averageCoherence: Math.round(metricsSum.coherence / totalAnalyses), averageCoherence: Math.round(metricsSum.coherence / totalAnalyses),
averageCohesion: Math.round(metricsSum.cohesion / totalAnalyses), averageCohesion: Math.round(metricsSum.cohesion / totalAnalyses),
averageVocabulary: Math.round(metricsSum.vocabulary / totalAnalyses), averageVocabulary: Math.round(metricsSum.vocabulary / totalAnalyses),
averageGrammar: Math.round(metricsSum.grammar / totalAnalyses) averageGrammar: Math.round(metricsSum.grammar / totalAnalyses),
averageLanguageDomain: Math.round(metricsSum.language_domain / totalAnalyses),
averageProposalComprehension: Math.round(metricsSum.proposal_comprehension / totalAnalyses),
averageArgumentSelection: Math.round(metricsSum.argument_selection / totalAnalyses),
averageLinguisticMechanisms: Math.round(metricsSum.linguistic_mechanisms / totalAnalyses),
averageInterventionProposal: Math.round(metricsSum.intervention_proposal / totalAnalyses)
}; };
console.log('Métricas de escrita calculadas:', writingMetrics); console.log('Métricas de escrita calculadas:', writingMetrics);
@ -543,72 +637,6 @@ export function StudentDashboardPage() {
{/* Gráfico de Evolução da Escrita */} {/* Gráfico de Evolução da Escrita */}
<WritingMetricsChart data={weeklyMetrics.writing} className="mb-8" /> <WritingMetricsChart data={weeklyMetrics.writing} className="mb-8" />
</div> </div>
{/* Histórias Recentes */}
<div>
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-900">Histórias Recentes</h2>
<button
onClick={() => navigate('/aluno/historias')}
className="flex items-center gap-2 text-purple-600 hover:text-purple-700"
>
Ver todas
</button>
</div>
{recentStories.length === 0 ? (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
<BookOpen className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">
Nenhuma história ainda
</h3>
<p className="text-gray-500 mb-6">
Comece sua jornada criando sua primeira história!
</p>
<button
onClick={() => navigate('/aluno/historias/nova')}
className="inline-flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition"
>
<Plus className="h-5 w-5" />
Criar História
</button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{recentStories.map((story) => (
<div
key={story.id}
className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden cursor-pointer hover:shadow-md transition"
onClick={() => navigate(`/aluno/historias/${story.id}`)}
>
{story.cover && (
<div className="relative aspect-video">
<img
src={`${story.cover.image_url}?width=400&height=300&quality=80&format=webp`}
alt={story.title}
className="w-full h-48 object-cover"
loading="lazy"
/>
</div>
)}
<div className="p-6">
<h3 className="font-medium text-gray-900 mb-2">{story.title}</h3>
<div className="flex items-center justify-between text-sm text-gray-500">
<span>{new Date(story.created_at).toLocaleDateString()}</span>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${
story.status === 'published'
? 'bg-green-100 text-green-800'
: 'bg-yellow-100 text-yellow-800'
}`}>
{story.status === 'published' ? 'Publicada' : 'Rascunho'}
</span>
</div>
</div>
</div>
))}
</div>
)}
</div>
</div> </div>
); );
} }

View File

@ -3,7 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom';
import { supabase } from '@/lib/supabase'; import { supabase } from '@/lib/supabase';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ArrowLeft, CheckCircle2, XCircle } from 'lucide-react'; import { ArrowLeft, CheckCircle2, XCircle, BookOpen, Brain, MessageSquare, Puzzle, Target } from 'lucide-react';
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
interface EssayAnalysis { interface EssayAnalysis {
@ -25,6 +25,28 @@ interface EssayAnalysis {
vocabulary: number; vocabulary: number;
grammar: 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;
};
};
created_at: string; created_at: string;
} }
@ -64,6 +86,16 @@ interface EssayAnalysisData {
vocabulary: number; vocabulary: number;
grammar: number; grammar: number;
}>; }>;
language_domain_value: number;
language_domain_justification: string;
proposal_comprehension_value: number;
proposal_comprehension_justification: string;
argument_selection_value: number;
argument_selection_justification: string;
linguistic_mechanisms_value: number;
linguistic_mechanisms_justification: string;
intervention_proposal_value: number;
intervention_proposal_justification: string;
} }
export function EssayAnalysis() { export function EssayAnalysis() {
@ -117,7 +149,17 @@ export function EssayAnalysis() {
cohesion, cohesion,
vocabulary, vocabulary,
grammar grammar
) ),
language_domain_value,
language_domain_justification,
proposal_comprehension_value,
proposal_comprehension_justification,
argument_selection_value,
argument_selection_justification,
linguistic_mechanisms_value,
linguistic_mechanisms_justification,
intervention_proposal_value,
intervention_proposal_justification
`) `)
.eq('essay_id', id) .eq('essay_id', id)
.order('created_at', { ascending: false }) .order('created_at', { ascending: false })
@ -143,6 +185,28 @@ export function EssayAnalysis() {
cohesion: analysisData.scores[0]?.cohesion || 0, cohesion: analysisData.scores[0]?.cohesion || 0,
vocabulary: analysisData.scores[0]?.vocabulary || 0, vocabulary: analysisData.scores[0]?.vocabulary || 0,
grammar: analysisData.scores[0]?.grammar || 0 grammar: analysisData.scores[0]?.grammar || 0
},
competencies: {
language_domain: {
value: analysisData.language_domain_value || 0,
justification: analysisData.language_domain_justification || ''
},
proposal_comprehension: {
value: analysisData.proposal_comprehension_value || 0,
justification: analysisData.proposal_comprehension_justification || ''
},
argument_selection: {
value: analysisData.argument_selection_value || 0,
justification: analysisData.argument_selection_justification || ''
},
linguistic_mechanisms: {
value: analysisData.linguistic_mechanisms_value || 0,
justification: analysisData.linguistic_mechanisms_justification || ''
},
intervention_proposal: {
value: analysisData.intervention_proposal_value || 0,
justification: analysisData.intervention_proposal_justification || ''
}
} }
}; };
@ -305,6 +369,107 @@ export function EssayAnalysis() {
</Card> </Card>
</div> </div>
</div> </div>
{/* Nova seção de Competências do ENEM */}
<div className="mt-8 bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 className="text-2xl font-bold text-gray-900 mb-6">Competências do ENEM</h2>
<div className="grid grid-cols-1 gap-8">
{/* Competência 1 */}
<div className="space-y-4">
<div className="flex items-center gap-3">
<BookOpen className="h-6 w-6 text-purple-600" />
<div>
<h3 className="text-lg font-semibold text-gray-900">Competência 1: Domínio da Língua</h3>
<p className="text-sm text-gray-500">Demonstrar domínio da modalidade escrita formal da Língua Portuguesa</p>
</div>
<div className="ml-auto">
<span className="text-2xl font-bold text-purple-600">{analysis.competencies.language_domain.value}</span>
<span className="text-gray-500">/200</span>
</div>
</div>
<Progress value={(analysis.competencies.language_domain.value / 200) * 100} className="h-3" />
<p className="text-gray-600 bg-purple-50 rounded-lg p-4 border border-purple-100">
{analysis.competencies.language_domain.justification}
</p>
</div>
{/* Competência 2 */}
<div className="space-y-4">
<div className="flex items-center gap-3">
<Brain className="h-6 w-6 text-blue-600" />
<div>
<h3 className="text-lg font-semibold text-gray-900">Competência 2: Compreensão da Proposta</h3>
<p className="text-sm text-gray-500">Compreender a proposta de redação e aplicar conceitos das várias áreas de conhecimento</p>
</div>
<div className="ml-auto">
<span className="text-2xl font-bold text-blue-600">{analysis.competencies.proposal_comprehension.value}</span>
<span className="text-gray-500">/200</span>
</div>
</div>
<Progress value={(analysis.competencies.proposal_comprehension.value / 200) * 100} className="h-3" />
<p className="text-gray-600 bg-blue-50 rounded-lg p-4 border border-blue-100">
{analysis.competencies.proposal_comprehension.justification}
</p>
</div>
{/* Competência 3 */}
<div className="space-y-4">
<div className="flex items-center gap-3">
<MessageSquare className="h-6 w-6 text-green-600" />
<div>
<h3 className="text-lg font-semibold text-gray-900">Competência 3: Seleção de Argumentos</h3>
<p className="text-sm text-gray-500">Selecionar, relacionar, organizar e interpretar informações, fatos, opiniões e argumentos</p>
</div>
<div className="ml-auto">
<span className="text-2xl font-bold text-green-600">{analysis.competencies.argument_selection.value}</span>
<span className="text-gray-500">/200</span>
</div>
</div>
<Progress value={(analysis.competencies.argument_selection.value / 200) * 100} className="h-3" />
<p className="text-gray-600 bg-green-50 rounded-lg p-4 border border-green-100">
{analysis.competencies.argument_selection.justification}
</p>
</div>
{/* Competência 4 */}
<div className="space-y-4">
<div className="flex items-center gap-3">
<Puzzle className="h-6 w-6 text-orange-600" />
<div>
<h3 className="text-lg font-semibold text-gray-900">Competência 4: Mecanismos Linguísticos</h3>
<p className="text-sm text-gray-500">Demonstrar conhecimento dos mecanismos linguísticos necessários para a construção da argumentação</p>
</div>
<div className="ml-auto">
<span className="text-2xl font-bold text-orange-600">{analysis.competencies.linguistic_mechanisms.value}</span>
<span className="text-gray-500">/200</span>
</div>
</div>
<Progress value={(analysis.competencies.linguistic_mechanisms.value / 200) * 100} className="h-3" />
<p className="text-gray-600 bg-orange-50 rounded-lg p-4 border border-orange-100">
{analysis.competencies.linguistic_mechanisms.justification}
</p>
</div>
{/* Competência 5 */}
<div className="space-y-4">
<div className="flex items-center gap-3">
<Target className="h-6 w-6 text-red-600" />
<div>
<h3 className="text-lg font-semibold text-gray-900">Competência 5: Proposta de Intervenção</h3>
<p className="text-sm text-gray-500">Elaborar proposta de intervenção para o problema abordado</p>
</div>
<div className="ml-auto">
<span className="text-2xl font-bold text-red-600">{analysis.competencies.intervention_proposal.value}</span>
<span className="text-gray-500">/200</span>
</div>
</div>
<Progress value={(analysis.competencies.intervention_proposal.value / 200) * 100} className="h-3" />
<p className="text-gray-600 bg-red-50 rounded-lg p-4 border border-red-100">
{analysis.competencies.intervention_proposal.justification}
</p>
</div>
</div>
</div>
</div> </div>
); );
} }

View File

@ -21,6 +21,11 @@ export interface WritingMetrics {
averageCohesion: number; averageCohesion: number;
averageVocabulary: number; averageVocabulary: number;
averageGrammar: number; averageGrammar: number;
averageLanguageDomain: number;
averageProposalComprehension: number;
averageArgumentSelection: number;
averageLinguisticMechanisms: number;
averageInterventionProposal: number;
} }
export interface WeeklyReadingMetrics { export interface WeeklyReadingMetrics {
@ -43,6 +48,11 @@ export interface WeeklyWritingMetrics {
cohesion: number; cohesion: number;
vocabulary: number; vocabulary: number;
grammar: number; grammar: number;
language_domain: number;
proposal_comprehension: number;
argument_selection: number;
linguistic_mechanisms: number;
intervention_proposal: number;
minutesWriting: number; minutesWriting: number;
} }

View File

@ -27,6 +27,28 @@ interface EssayAnalysisResponse {
vocabulary: number; vocabulary: number;
grammar: 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 { interface EssayType {
@ -175,6 +197,69 @@ Forneça uma análise detalhada considerando:
7. Pontos a melhorar 7. Pontos a melhorar
8. Sugestões específicas para aprimoramento 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<br> Precisão vocabular<br> Obediência às regras gramaticais<br> 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<br> Desvios de pontuação que não comprometem o sentido<br> 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<br> Problemas de concordância<br> Problemas de regência<br> Problemas de estrutura sintática<br> 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<br> Períodos incompletos<br> Graves problemas de pontuação<br> Desvios graves de grafia e acentuação<br> 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<br> Presença de gírias e marcas de oralidade<br> 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<br> Estrutura completa (introdução, argumentos, conclusão)<br> 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<br> Boa argumentação<br> 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<br> Argumentação previsível<br> 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<br> Argumentação falha<br> 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<br> Ausência de argumentação<br> Pode apresentar texto narrativo |
| 0 ponto | Fuga ao tema/não atendimento à estrutura dissertativo-argumentativa. | Desenvolve outro tema<br> 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<br> Explicita tese clara<br> 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<br> 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<br> Informações aleatórias<br> Argumentos pouco convincentes |
| 80 pontos | Apresenta informações, fatos e opiniões relacionados ao tema, mas desorganizados ou contraditórios. | Argumentos pouco articulados<br> 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<br> Informações desarticuladas |
| 0 ponto | Apresenta informações, fatos e opiniões não relacionados ao tema. | Informações incoerentes<br> 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<br> 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<br> Poucos desvios nos conectores<br> Algumas repetições desnecessárias |
| 120 pontos | Articula as partes do texto, de forma mediana, com inadequações. | Algumas inadequações nos recursos coesivos<br> Frases fragmentadas ocasionais<br> Problemas de paragrafação |
| 80 pontos | Articula as partes do texto, de forma insuficiente, com muitas inadequações. | Muitas inadequações<br> Frases fragmentadas frequentes<br> Problemas de estrutura |
| 40 pontos | Articula as partes do texto de forma precária. | Graves problemas de articulação<br> Períodos muito longos ou fragmentados<br> 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<br> Proposta foge ao tema |
Responda em formato JSON seguindo exatamente esta estrutura: Responda em formato JSON seguindo exatamente esta estrutura:
{ {
"overall_score": number, // 0 a 100 "overall_score": number, // 0 a 100
@ -192,6 +277,28 @@ Responda em formato JSON seguindo exatamente esta estrutura:
"cohesion": number, // 0 a 100 - Coesão "cohesion": number, // 0 a 100 - Coesão
"vocabulary": number, // 0 a 100 - Vocabulário "vocabulary": number, // 0 a 100 - Vocabulário
"grammar": number // 0 a 100 - Gramática e ortografia "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
}
} }
}` }`
@ -219,7 +326,7 @@ Responda em formato JSON seguindo exatamente esta estrutura:
schema: { schema: {
type: "object", type: "object",
additionalProperties: false, additionalProperties: false,
required: ["overall_score", "suggestions", "feedback", "strengths", "improvements", "criteria_scores"], required: ["overall_score", "suggestions", "feedback", "strengths", "improvements", "criteria_scores", "competencies"],
properties: { properties: {
overall_score: { overall_score: {
type: "number", type: "number",
@ -290,6 +397,93 @@ Responda em formato JSON seguindo exatamente esta estrutura:
description: "Gramática e ortografia (0-100)" 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"
}
}
}
}
} }
} }
} }
@ -310,7 +504,18 @@ Responda em formato JSON seguindo exatamente esta estrutura:
.insert({ .insert({
essay_id, essay_id,
overall_score: analysis.overall_score, overall_score: analysis.overall_score,
suggestions: analysis.suggestions 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() .select()
.single() .single()

View File

@ -0,0 +1,52 @@
-- Adiciona campos de competências na tabela essay_analyses
ALTER TABLE essay_analyses
ADD COLUMN language_domain_value INTEGER,
ADD COLUMN language_domain_justification TEXT,
ADD COLUMN proposal_comprehension_value INTEGER,
ADD COLUMN proposal_comprehension_justification TEXT,
ADD COLUMN argument_selection_value INTEGER,
ADD COLUMN argument_selection_justification TEXT,
ADD COLUMN linguistic_mechanisms_value INTEGER,
ADD COLUMN linguistic_mechanisms_justification TEXT,
ADD COLUMN intervention_proposal_value INTEGER,
ADD COLUMN intervention_proposal_justification TEXT;
-- Adiciona restrições para garantir que os valores estejam entre 0 e 200
ALTER TABLE essay_analyses
ADD CONSTRAINT check_competency_values
CHECK (
language_domain_value BETWEEN 0 AND 200 AND
proposal_comprehension_value BETWEEN 0 AND 200 AND
argument_selection_value BETWEEN 0 AND 200 AND
linguistic_mechanisms_value BETWEEN 0 AND 200 AND
intervention_proposal_value BETWEEN 0 AND 200
);
-- Atualiza as políticas de segurança para incluir os novos campos
ALTER POLICY "Estudantes podem ver suas próprias análises" ON essay_analyses
USING (
essay_id IN (
SELECT id FROM student_essays
WHERE student_id = auth.uid()
)
);
-- Comentários para documentação
COMMENT ON COLUMN essay_analyses.language_domain_value IS 'Valor da competência de domínio da língua (0-200)';
COMMENT ON COLUMN essay_analyses.language_domain_justification IS 'Justificativa para a nota de domínio da língua';
COMMENT ON COLUMN essay_analyses.proposal_comprehension_value IS 'Valor da competência de compreensão da proposta (0-200)';
COMMENT ON COLUMN essay_analyses.proposal_comprehension_justification IS 'Justificativa para a nota de compreensão da proposta';
COMMENT ON COLUMN essay_analyses.argument_selection_value IS 'Valor da competência de seleção de argumentos (0-200)';
COMMENT ON COLUMN essay_analyses.argument_selection_justification IS 'Justificativa para a nota de seleção de argumentos';
COMMENT ON COLUMN essay_analyses.linguistic_mechanisms_value IS 'Valor da competência de mecanismos linguísticos (0-200)';
COMMENT ON COLUMN essay_analyses.linguistic_mechanisms_justification IS 'Justificativa para a nota de mecanismos linguísticos';
COMMENT ON COLUMN essay_analyses.intervention_proposal_value IS 'Valor da competência de proposta de intervenção (0-200)';
COMMENT ON COLUMN essay_analyses.intervention_proposal_justification IS 'Justificativa para a nota de proposta de intervenção';

View File

@ -0,0 +1,20 @@
-- Remove a restrição de verificação dos valores
ALTER TABLE essay_analyses
DROP CONSTRAINT IF EXISTS check_competency_values;
-- Remove os campos de competências
ALTER TABLE essay_analyses
DROP COLUMN IF EXISTS language_domain_value,
DROP COLUMN IF EXISTS language_domain_justification,
DROP COLUMN IF EXISTS proposal_comprehension_value,
DROP COLUMN IF EXISTS proposal_comprehension_justification,
DROP COLUMN IF EXISTS argument_selection_value,
DROP COLUMN IF EXISTS argument_selection_justification,
DROP COLUMN IF EXISTS linguistic_mechanisms_value,
DROP COLUMN IF EXISTS linguistic_mechanisms_justification,
DROP COLUMN IF EXISTS intervention_proposal_value,
DROP COLUMN IF EXISTS intervention_proposal_justification;