import gradio as gr import pandas as pd import numpy as np import re import os import matplotlib.pyplot as plt from datetime import timedelta from fpdf import FPDF from weasyprint import HTML import seaborn as sns from typing import Tuple, Dict, List import logging import warnings warnings.filterwarnings('ignore') # Configuração de logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) class DataProcessor: @staticmethod def parse_duration(duration_str: str) -> timedelta: """Converte string de duração em objeto timedelta.""" try: h, m, s = map(int, duration_str.split(':')) return timedelta(hours=h, minutes=m, seconds=s) except: return timedelta(0) @staticmethod def format_timedelta(td: timedelta) -> str: """Formata timedelta para string no formato HH:MM:SS.""" total_seconds = int(td.total_seconds()) hours, remainder = divmod(total_seconds, 3600) minutes, seconds = divmod(remainder, 60) return f"{hours:02}:{minutes:02}:{seconds:02}" @staticmethod def normalize_html_to_csv(input_html_path: str, output_csv_path: str) -> None: """Converte arquivo HTML para CSV.""" try: html_data = pd.read_html(input_html_path) data = html_data[0] data.to_csv(output_csv_path, index=False, encoding='utf-8-sig') logging.info(f"HTML normalizado com sucesso: {output_csv_path}") except Exception as e: logging.error(f"Erro ao normalizar HTML: {str(e)}") raise @staticmethod def normalize_excel_to_csv(input_excel_path: str, output_csv_path: str) -> None: """Converte arquivo Excel para CSV.""" try: excel_data = pd.read_excel(input_excel_path) unnecessary_columns = [col for col in excel_data.columns if 'Unnamed' in str(col)] if unnecessary_columns: excel_data = excel_data.drop(columns=unnecessary_columns) excel_data.to_csv(output_csv_path, index=False, encoding='utf-8-sig') logging.info(f"Excel normalizado com sucesso: {output_csv_path}") except Exception as e: logging.error(f"Erro ao normalizar Excel: {str(e)}") raise class StudentAnalyzer: def __init__(self, tarefas_df: pd.DataFrame, alunos_df: pd.DataFrame): self.tarefas_df = tarefas_df self.alunos_df = alunos_df self.processor = DataProcessor() def prepare_data(self) -> pd.DataFrame: """Prepara e limpa os dados para análise.""" # Limpeza de colunas self.tarefas_df.columns = self.tarefas_df.columns.str.strip() self.alunos_df.columns = self.alunos_df.columns.str.strip() # Verifica colunas necessárias required_columns = ['Aluno', 'Nota', 'Duração'] if not all(col in self.tarefas_df.columns for col in required_columns): raise ValueError("Colunas obrigatórias não encontradas no arquivo de tarefas") # Processamento de duração self.tarefas_df['Duração'] = self.tarefas_df['Duração'].apply(self.processor.parse_duration) return self.match_students() def match_students(self) -> pd.DataFrame: """Relaciona dados de alunos com suas tarefas.""" def generate_aluno_pattern(ra, dig_ra): ra_str = str(ra).zfill(9) return f"{ra_str[1]}{ra_str[2:]}{dig_ra}-sp".lower() self.alunos_df['Aluno_Pattern'] = self.alunos_df.apply( lambda row: generate_aluno_pattern(row['RA'], row['Dig. RA']), axis=1 ) def extract_pattern(nome): if isinstance(nome, str): match = re.search(r'\d+.*', nome.lower()) return match.group(0) if match else None return None self.tarefas_df['Aluno_Pattern'] = self.tarefas_df['Aluno'].apply(extract_pattern) return self.calculate_metrics() def calculate_metrics(self) -> pd.DataFrame: """Calcula métricas de desempenho dos alunos.""" metrics_df = pd.DataFrame() for _, aluno in self.alunos_df.iterrows(): aluno_pattern = aluno['Aluno_Pattern'] aluno_tarefas = self.tarefas_df[self.tarefas_df['Aluno_Pattern'] == aluno_pattern] if not aluno_tarefas.empty: metrics = { 'Nome do Aluno': aluno['Nome do Aluno'], 'Tarefas Completadas': len(aluno_tarefas), 'Acertos Absolutos': aluno_tarefas['Nota'].sum(), 'Total Tempo': str(aluno_tarefas['Duração'].sum()), 'Média de Acertos': f"{(aluno_tarefas['Nota'].sum() / (len(aluno_tarefas) * 2) * 100):.2f}%", 'Tempo Médio por Tarefa': str(aluno_tarefas['Duração'].mean()), 'Eficiência': f"{(aluno_tarefas['Nota'].sum() / aluno_tarefas['Duração'].sum().total_seconds() * 3600):.2f}" } metrics_df = pd.concat([metrics_df, pd.DataFrame([metrics])], ignore_index=True) return metrics_df class ReportGenerator: def __init__(self, data: pd.DataFrame): self.data = data self.stats = self.calculate_statistics() def calculate_statistics(self) -> Dict: """Calcula estatísticas gerais da turma.""" return { 'media_acertos': float(self.data['Média de Acertos'].str.rstrip('%').astype(float).mean()), 'desvio_padrao': float(self.data['Média de Acertos'].str.rstrip('%').astype(float).std()), 'mediana_acertos': float(self.data['Média de Acertos'].str.rstrip('%').astype(float).median()), 'total_alunos': len(self.data), 'media_tarefas': float(self.data['Tarefas Completadas'].mean()), 'media_tempo': str(pd.to_timedelta(self.data['Total Tempo']).mean()) } def generate_graphs(self) -> List[plt.Figure]: """Gera gráficos para o relatório.""" graphs = [] # Distribuição de notas plt.figure(figsize=(10, 6)) acertos_data = pd.Series(self.data['Média de Acertos']).str.rstrip('%').astype(float) sns.histplot(data=acertos_data, bins=10) plt.axvline(self.stats['media_acertos'], color='r', linestyle='--', label=f'Média ({self.stats["media_acertos"]:.1f}%)') plt.title('Distribuição das Notas') plt.xlabel('Percentual de Acertos') plt.ylabel('Número de Alunos') plt.legend() graphs.append(plt.gcf()) plt.close() # Relação tempo x desempenho plt.figure(figsize=(10, 6)) tempo_segundos = pd.to_timedelta(self.data['Total Tempo']).dt.total_seconds() acertos = pd.Series(self.data['Média de Acertos']).str.rstrip('%').astype(float) plt.scatter(tempo_segundos / 60, acertos) plt.title('Tempo x Desempenho') plt.xlabel('Tempo Total (minutos)') plt.ylabel('Percentual de Acertos') graphs.append(plt.gcf()) plt.close() return graphs def generate_pdf(self, output_path: str, graphs: List[plt.Figure]) -> None: """Gera relatório em PDF.""" class PDF(FPDF): def header(self): self.set_font('Arial', 'B', 15) self.cell(0, 10, 'Relatório de Desempenho - Análise Detalhada', 0, 1, 'C') self.ln(10) pdf = PDF('L', 'mm', 'A4') # Sumário executivo pdf.add_page() pdf.set_font('Arial', 'B', 12) pdf.cell(0, 10, 'Sumário Executivo', 0, 1) pdf.set_font('Arial', '', 10) summary_text = f""" Análise da Turma: - Média de Acertos: {self.stats['media_acertos']:.1f}% - Desvio Padrão: {self.stats['desvio_padrao']:.1f}% - Mediana: {self.stats['mediana_acertos']:.1f}% - Número de Alunos: {self.stats['total_alunos']} - Média de Tarefas por Aluno: {self.stats['media_tarefas']:.1f} - Tempo Médio Total: {self.stats['media_tempo']} """ pdf.multi_cell(0, 10, summary_text) # Gráficos for i, graph in enumerate(graphs): pdf.add_page() graph_path = f'temp_graph_{i}.png' graph.savefig(graph_path) pdf.image(graph_path, x=10, y=30, w=270) os.remove(graph_path) # Tabela de alunos pdf.add_page() pdf.set_font('Arial', 'B', 12) pdf.cell(0, 10, 'Desempenho Individual', 0, 1) # Cabeçalhos columns = ['Nome do Aluno', 'Média de Acertos', 'Tarefas', 'Tempo Total', 'Eficiência'] widths = [80, 30, 30, 30, 30] pdf.set_font('Arial', 'B', 8) for i, col in enumerate(columns): pdf.cell(widths[i], 7, col, 1) pdf.ln() # Dados pdf.set_font('Arial', '', 8) for _, row in self.data.iterrows(): pdf.cell(widths[0], 6, str(row['Nome do Aluno'])[:40], 1) pdf.cell(widths[1], 6, str(row['Média de Acertos']), 1) pdf.cell(widths[2], 6, str(row['Tarefas Completadas']), 1) pdf.cell(widths[3], 6, str(row['Total Tempo']), 1) pdf.cell(widths[4], 6, str(row['Eficiência']), 1) pdf.ln() pdf.output(output_path) def process_files(html_file, excel_files) -> Tuple[str, str, str]: """Processa arquivos e gera relatório.""" try: # Criar diretório temporário temp_dir = "temp_files" os.makedirs(temp_dir, exist_ok=True) # Limpar diretório temporário for file in os.listdir(temp_dir): os.remove(os.path.join(temp_dir, file)) # Salvar arquivos html_path = os.path.join(temp_dir, "alunos.htm") with open(html_path, "wb") as f: f.write(html_file) excel_paths = [] for i, excel_file in enumerate(excel_files): excel_path = os.path.join(temp_dir, f"tarefa_{i}.xlsx") with open(excel_path, "wb") as f: f.write(excel_file) excel_paths.append(excel_path) # Processar arquivos processor = DataProcessor() alunos_csv_path = os.path.join(temp_dir, "alunos.csv") processor.normalize_html_to_csv(html_path, alunos_csv_path) tarefas_df = pd.DataFrame() for excel_path in excel_paths: csv_path = excel_path.replace('.xlsx', '.csv') processor.normalize_excel_to_csv(excel_path, csv_path) df = pd.read_csv(csv_path) tarefas_df = pd.concat([tarefas_df, df], ignore_index=True) # Análise alunos_df = pd.read_csv(alunos_csv_path) analyzer = StudentAnalyzer(tarefas_df, alunos_df) results_df = analyzer.prepare_data() # Gerar relatório report_generator = ReportGenerator(results_df) graphs = report_generator.generate_graphs() # Salvar outputs output_html = os.path.join(temp_dir, "relatorio.html") output_pdf = os.path.join(temp_dir, "relatorio.pdf") results_df.to_html(output_html, index=False) report_generator.generate_pdf(output_pdf, graphs) return results_df.to_html(index=False), output_html, output_pdf except Exception as e: logging.error(f"Erro no processamento: {str(e)}") raise # Interface Gradio theme = gr.themes.Default( primary_hue="blue", secondary_hue="gray", font=["Arial", "sans-serif"], font_mono=["Courier New", "monospace"], ) with gr.Blocks(theme=theme) as interface: gr.Markdown("# Sistema de Análise de Desempenho Acadêmico") with gr.Row(): with gr.Column(): gr.Markdown("## Lista de Alunos (arquivo .htm)") html_file = gr.File(label="Upload do arquivo HTML", type="binary") with gr.Column(): gr.Markdown("## Relatórios de Tarefas (arquivos .xlsx)") excel_files = gr.Files(label="Upload dos arquivos Excel", type="binary", file_count="multiple") generate_btn = gr.Button("Gerar Relatório", variant="primary") output_html = gr.HTML() download_html_btn = gr.File(label="Download HTML Report") download_pdf_btn = gr.File(label="Download PDF Report") generate_btn.click( fn=process_files, inputs=[html_file, excel_files], outputs=[output_html, download_html_btn, download_pdf_btn] ) interface.launch()