import gradio as gr import pandas as pd import re import os import matplotlib.pyplot as plt from datetime import timedelta from fpdf import FPDF import numpy as np def parse_duration(duration_str): try: h, m, s = map(int, duration_str.split(':')) return timedelta(hours=h, minutes=m, seconds=s) except: return timedelta(0) def format_timedelta(td): 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}" def normalize_html_to_csv(input_html_path, output_csv_path): html_data = pd.read_html(input_html_path) data = html_data[0] data.to_csv(output_csv_path, index=False) def normalize_multiple_excel_to_csv(input_directory, output_directory): input_excel_paths = [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith('.xlsx')] output_csv_paths = [os.path.join(output_directory, os.path.splitext(f)[0] + '.csv') for f in os.listdir(input_directory) if f.endswith('.xlsx')] for input_excel_path, output_csv_path in zip(input_excel_paths, output_csv_paths): excel_data = pd.read_excel(input_excel_path) unnecessary_columns = [col for col in excel_data.columns if 'Unnamed' in col] if unnecessary_columns: excel_data = excel_data.drop(columns=unnecessary_columns) excel_data.to_csv(output_csv_path, index=False) def extract_aluno_pattern(nome): if isinstance(nome, str): match = re.search(r"(\d{8,9}-\w{2})", nome.lower()) return match.group(1) if match else None return None def match_alunos(tarefas_csv_path, alunos_csv_path, contador_csv_path): try: tarefas_df = pd.read_csv(tarefas_csv_path) alunos_df = pd.read_csv(alunos_csv_path) except pd.errors.EmptyDataError: print(f"Arquivo {tarefas_csv_path} ou {alunos_csv_path} está vazio. Pulando...") return tarefas_df.columns = tarefas_df.columns.str.strip() alunos_df.columns = alunos_df.columns.str.strip() if 'Aluno' not in tarefas_df.columns or 'Nota' not in tarefas_df.columns or 'Duração' not in tarefas_df.columns: print(f"Colunas 'Aluno', 'Nota' ou 'Duração' não encontradas no arquivo {tarefas_csv_path}. Pulando este arquivo.") return try: contador_df = pd.read_csv(contador_csv_path) except FileNotFoundError: contador_df = pd.DataFrame(columns=['Nome do Aluno', 'Tarefas Completadas', 'Acertos Absolutos', 'Total Tempo']) if 'Tarefas Completadas' not in contador_df.columns: contador_df['Tarefas Completadas'] = 0 if 'Acertos Absolutos' not in contador_df.columns: contador_df['Acertos Absolutos'] = 0 if 'Total Tempo' not in contador_df.columns: contador_df['Total Tempo'] = '00:00:00' def generate_aluno_pattern(ra, dig_ra): ra_str = str(ra).zfill(9) ra_without_first_two_digits = ra_str[2:] return f"{ra_str[1]}{ra_without_first_two_digits}{dig_ra}-sp".lower() alunos_df['Aluno_Pattern'] = alunos_df.apply(lambda row: generate_aluno_pattern(row['RA'], row['Dig. RA']), axis=1) def extract_aluno_pattern(nome): if isinstance(nome, str): match = re.search(r'\d+.*', nome.lower()) return match.group(0) if match else None return None tarefas_df['Aluno_Pattern'] = tarefas_df['Aluno'].apply(extract_aluno_pattern) tarefas_df['Duração'] = tarefas_df['Duração'].apply(parse_duration) matched_alunos = alunos_df[alunos_df['Aluno_Pattern'].isin(tarefas_df['Aluno_Pattern'])] result_df = matched_alunos[['Nome do Aluno']].drop_duplicates() for aluno in result_df['Nome do Aluno']: aluno_pattern = alunos_df.loc[alunos_df['Nome do Aluno'] == aluno, 'Aluno_Pattern'].values[0] aluno_tarefas = tarefas_df[tarefas_df['Aluno_Pattern'] == aluno_pattern] nota_total = aluno_tarefas['Nota'].sum() tempo_total = aluno_tarefas['Duração'].sum() if aluno in contador_df['Nome do Aluno'].values: contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Tarefas Completadas'] += 1 contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Acertos Absolutos'] += nota_total current_total_tempo = pd.to_timedelta(contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Total Tempo'].values[0]) contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Total Tempo'] = str(current_total_tempo + tempo_total) else: contador_df = pd.concat([contador_df, pd.DataFrame({'Nome do Aluno': [aluno], 'Tarefas Completadas': [1], 'Acertos Absolutos': [nota_total], 'Total Tempo': [str(tempo_total)]})], ignore_index=True) contador_df.to_csv(contador_csv_path, index=False) return result_df def remove_outliers(data, column, threshold=3): mean = data[column].mean() std = data[column].std() return data[(data[column] > mean - threshold * std) & (data[column] < mean + threshold * std)] def process_all_tarefas_in_directory(directory, alunos_csv_path, contador_csv_path, relatorio_csv_path): tarefas_files = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.csv') and f not in ['alunos_fim.csv', 'contador_tarefas.csv']] for i, tarefas_file in enumerate(tarefas_files): match_alunos(tarefas_file, alunos_csv_path, contador_csv_path) process_relatorios(contador_csv_path, relatorio_csv_path) def process_relatorios(contador_csv_path, relatorio_csv_path): contador_df = pd.read_csv(contador_csv_path) contador_df['Média de Acertos'] = ((contador_df['Acertos Absolutos'] / (contador_df['Tarefas Completadas'] * 2)) * 100).round(2).astype(str) + '%' contador_df['Total Tempo'] = pd.to_timedelta(contador_df['Total Tempo']) contador_df['Tempo Médio por Tarefa'] = contador_df['Total Tempo'] / contador_df['Tarefas Completadas'] contador_df['Total Tempo'] = contador_df['Total Tempo'].apply(format_timedelta) contador_df['Tempo Médio por Tarefa'] = contador_df['Tempo Médio por Tarefa'].apply(format_timedelta) contador_df = contador_df.sort_values(by='Tarefas Completadas', ascending=False) # Remove outliers e calcula o tempo médio por tarefa da turma tempo_medio_por_tarefa = pd.to_timedelta(contador_df['Tempo Médio por Tarefa']) tempo_medio_por_tarefa = remove_outliers(pd.DataFrame({'Tempo Médio por Tarefa': tempo_medio_por_tarefa}), 'Tempo Médio por Tarefa') media_tempo_medio_turma = tempo_medio_por_tarefa['Tempo Médio por Tarefa'].mean() media_tempo_medio_turma = format_timedelta(media_tempo_medio_turma) contador_df.to_csv(relatorio_csv_path, index=False) return contador_df, media_tempo_medio_turma def generate_pdf_report(dataframe, media_tempo_medio_turma, output_pdf_path): class PDF(FPDF): def header(self): self.set_font('Arial', 'B', 12) self.cell(0, 10, 'Relatório de Tarefas', 0, 1, 'C') self.cell(0, 10, f'Tempo Médio por Tarefa da Turma (ajustado): {media_tempo_medio_turma}', 0, 1, 'C') def footer(self): self.set_y(-15) self.set_font('Arial', 'I', 8) self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C') def add_table(self, dataframe): self.set_font('Arial', 'B', 10) col_widths = [30, 40, 40, 30, 30, 40] row_height = self.font_size * 2 # Adiciona os cabeçalhos for i, col in enumerate(dataframe.columns): self.multi_cell(col_widths[i], row_height, col, border=1, align='C', ln=3) self.ln(row_height) # Adiciona os dados com quebras de página self.set_font('Arial', '', 10) for row in dataframe.itertuples(index=False): if self.get_y() > self.page_break_trigger - row_height: self.add_page() self.set_font('Arial', 'B', 10) for i, col in enumerate(dataframe.columns): self.multi_cell(col_widths[i], row_height, col, border=1, align='C', ln=3) self.ln(row_height) self.set_font('Arial', '', 10) for i, item in enumerate(row): self.multi_cell(col_widths[i], row_height, str(item), border=1, align='C', ln=3) self.ln(row_height) def add_image(self, image_path): self.add_page() self.image(image_path, x=10, y=10, w=270) pdf = PDF(orientation='L', unit='mm', format='A4') pdf.add_page() pdf.add_table(dataframe) # Gerar gráficos e adicionar ao PDF def add_bar_labels(bars): for bar in bars: height = bar.get_height() plt.annotate(f'{height}', xy=(bar.get_x() + bar.get_width() / 2, height), xytext=(0, 3), # 3 points vertical offset textcoords="offset points", ha='center', va='bottom') top_students = dataframe.nlargest(5, 'Acertos Absolutos') plt.figure(figsize=(10, 6)) bars = plt.bar(top_students['Nome do Aluno'], top_students['Acertos Absolutos'], color='blue') plt.xlabel('Nome do Aluno') plt.ylabel('Acertos Absolutos') plt.title('Top 5 Alunos - Acertos Absolutos') plt.xticks(rotation=45, ha='right') add_bar_labels(bars) plt.tight_layout() graph_path = 'top_5_acertos_absolutos.png' plt.savefig(graph_path) pdf.add_image(graph_path) plt.figure(figsize=(10, 6)) bars = plt.bar(top_students['Nome do Aluno'], top_students['Média de Acertos'].str.rstrip('%').astype('float'), color='green') plt.xlabel('Nome do Aluno') plt.ylabel('Percentual de Acertos (%)') plt.title('Top 5 Alunos - Percentual de Acertos') plt.xticks(rotation=45, ha='right') add_bar_labels(bars) plt.tight_layout() graph_path = 'top_5_percentual_acertos.png' plt.savefig(graph_path) pdf.add_image(graph_path) plt.figure(figsize=(10, 6)) bars = plt.bar(top_students['Nome do Aluno'], top_students['Tarefas Completadas'], color='red') plt.xlabel('Nome do Aluno') plt.ylabel('Tarefas Completadas') plt.title('Top 5 Alunos - Tarefas Completadas') plt.xticks(rotation=45, ha='right') add_bar_labels(bars) plt.tight_layout() graph_path = 'top_5_tarefas_completadas.png' plt.savefig(graph_path) pdf.add_image(graph_path) pdf.output(output_pdf_path) def processar_relatorio(html_file, tarefa_files): input_directory = "temp_files" # Diretório temporário para os arquivos output_directory = "temp_files" os.makedirs(input_directory, exist_ok=True) os.makedirs(output_directory, exist_ok=True) # Limpa o diretório temporário antes de cada execução (opcional, mas recomendado) for filename in os.listdir(input_directory): file_path = os.path.join(input_directory, filename) if os.path.isfile(file_path): os.remove(file_path) # Salva os arquivos enviados html_path = os.path.join(input_directory, "alunos.htm") with open(html_path, "wb") as f: f.write(html_file) for idx, tarefa_file in enumerate(tarefa_files): tarefa_path = os.path.join(input_directory, f"tarefa_{idx}.xlsx") with open(tarefa_path, "wb") as f: f.write(tarefa_file) # Normaliza os arquivos alunos_csv_path = os.path.join(output_directory, "alunos_fim.csv") normalize_html_to_csv(html_path, alunos_csv_path) normalize_multiple_excel_to_csv(input_directory, output_directory) # Processa os dados e gera o relatório contador_csv_path = os.path.join(output_directory, "contador_tarefas.csv") relatorio_csv_path = os.path.join(output_directory, "relatorio_final.csv") process_all_tarefas_in_directory(output_directory, alunos_csv_path, contador_csv_path, relatorio_csv_path) df, media_tempo_medio_turma = process_relatorios(contador_csv_path, relatorio_csv_path) # Salva o relatório em HTML e PDF html_output_path = os.path.join(output_directory, "relatorio_final.html") df.to_html(html_output_path, index=False) pdf_output_path = os.path.join(output_directory, "relatorio_final.pdf") generate_pdf_report(df, media_tempo_medio_turma, pdf_output_path) return df.to_html(index=False), html_output_path, pdf_output_path # Tema personalizado theme = gr.themes.Default( primary_hue="blue", # Cor principal (tons de azul) secondary_hue="gray", # Cor secundária (tons de cinza) font=["Arial", "sans-serif"], # Família de fontes font_mono=["Courier New", "monospace"], # Fonte para código ) # --- Interface Gradio --- with gr.Blocks(theme=theme) as interface: gr.Markdown("# Processamento de Relatórios de Tarefas") with gr.Row(): # Crie uma nova linha para os botões download_html_btn = gr.Button("Baixar Relatório HTML") download_pdf_btn = gr.Button("Baixar Relatório PDF") pdf_output = gr.File(label="Download PDF Report", visible=False, elem_id="pdf_output") html_output = gr.File(label="Download HTML Report", visible=False, elem_id="html_output") def wrapper(html_file, excel_files): html_content, html_path, pdf_path = processar_relatorio(html_file, excel_files) return { output_html: html_path, # Retorna o caminho do arquivo HTML pdf_output: pdf_path, # Retorna o caminho do PDF para o componente oculto } generate_btn.click( fn=wrapper, inputs=[html_file, excel_files], outputs=[html_output, pdf_output] ) # Workaround for older Gradio versions download_html_btn.click(None, [], [], _js="downloadHTML") download_pdf_btn.click(None, [], [], _js="downloadPDF") interface.launch( _js=""" function downloadHTML() { const a = document.createElement('a'); a.href = document.getElementById('html_output').href; a.download = 'relatorio_final.html'; a.click(); } function downloadPDF() { const a = document.createElement('a'); a.href = document.getElementById('pdf_output').href; a.download = 'relatorio_final.pdf'; a.click(); } """ )