histlearn commited on
Commit
1f01146
·
verified ·
1 Parent(s): 6b078d2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +292 -349
app.py CHANGED
@@ -1,367 +1,310 @@
1
  import gradio as gr
2
  import pandas as pd
 
3
  import re
4
  import os
5
  import matplotlib.pyplot as plt
6
  from datetime import timedelta
7
  from fpdf import FPDF
8
  from weasyprint import HTML
9
- from PyPDF2 import PdfMerger
10
- import numpy as np
11
-
12
- def parse_duration(duration_str):
13
- try:
14
- h, m, s = map(int, duration_str.split(':'))
15
- return timedelta(hours=h, minutes=m, seconds=s)
16
- except:
17
- return timedelta(0)
18
-
19
- def format_timedelta(td):
20
- total_seconds = int(td.total_seconds())
21
- hours, remainder = divmod(total_seconds, 3600)
22
- minutes, seconds = divmod(remainder, 60)
23
- return f"{hours:02}:{minutes:02}:{seconds:02}"
24
-
25
- def normalize_html_to_csv(input_html_path, output_csv_path):
26
- html_data = pd.read_html(input_html_path)
27
- data = html_data[0]
28
- data.to_csv(output_csv_path, index=False, encoding='utf-8-sig')
29
-
30
- def normalize_multiple_excel_to_csv(input_directory, output_directory):
31
- input_excel_paths = [os.path.join(input_directory, f) for f in os.listdir(input_directory) if f.endswith('.xlsx')]
32
- 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')]
33
- for input_excel_path, output_csv_path in zip(input_excel_paths, output_csv_paths):
34
- excel_data = pd.read_excel(input_excel_path)
35
- unnecessary_columns = [col for col in excel_data.columns if 'Unnamed' in col]
36
- if unnecessary_columns:
37
- excel_data = excel_data.drop(columns=unnecessary_columns)
38
- excel_data.to_csv(output_csv_path, index=False, encoding='utf-8-sig')
39
-
40
- def extract_aluno_pattern(nome):
41
- if isinstance(nome, str):
42
- match = re.search(r"(\d{8,9}-\w{2})", nome.lower())
43
- return match.group(1) if match else None
44
- return None
45
-
46
- def match_alunos(tarefas_csv_path, alunos_csv_path, contador_csv_path):
47
- try:
48
- tarefas_df = pd.read_csv(tarefas_csv_path, encoding='utf-8-sig')
49
- alunos_df = pd.read_csv(alunos_csv_path, encoding='utf-8-sig')
50
- except pd.errors.EmptyDataError:
51
- print(f"Arquivo {tarefas_csv_path} ou {alunos_csv_path} está vazio. Pulando...")
52
- return
53
-
54
- tarefas_df.columns = tarefas_df.columns.str.strip()
55
- alunos_df.columns = alunos_df.columns.str.strip()
56
-
57
- if 'Aluno' not in tarefas_df.columns or 'Nota' not in tarefas_df.columns or 'Duração' not in tarefas_df.columns:
58
- print(f"Colunas 'Aluno', 'Nota' ou 'Duração' não encontradas no arquivo {tarefas_csv_path}. Pulando este arquivo.")
59
- return
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  try:
62
- contador_df = pd.read_csv(contador_csv_path, encoding='utf-8-sig')
63
- except FileNotFoundError:
64
- contador_df = pd.DataFrame(columns=['Nome do Aluno', 'Tarefas Completadas', 'Acertos Absolutos', 'Total Tempo'])
65
-
66
- if 'Tarefas Completadas' not in contador_df.columns:
67
- contador_df['Tarefas Completadas'] = 0
68
- if 'Acertos Absolutos' not in contador_df.columns:
69
- contador_df['Acertos Absolutos'] = 0
70
- if 'Total Tempo' not in contador_df.columns:
71
- contador_df['Total Tempo'] = '00:00:00'
72
-
73
- def generate_aluno_pattern(ra, dig_ra):
74
- ra_str = str(ra).zfill(9)
75
- ra_without_first_two_digits = ra_str[2:]
76
- return f"{ra_str[1]}{ra_without_first_two_digits}{dig_ra}-sp".lower()
77
-
78
- alunos_df['Aluno_Pattern'] = alunos_df.apply(lambda row: generate_aluno_pattern(row['RA'], row['Dig. RA']), axis=1)
79
-
80
- def extract_aluno_pattern(nome):
81
- if isinstance(nome, str):
82
- match = re.search(r'\d+.*', nome.lower())
83
- return match.group(0) if match else None
84
- return None
85
-
86
- tarefas_df['Aluno_Pattern'] = tarefas_df['Aluno'].apply(extract_aluno_pattern)
87
- tarefas_df['Duração'] = tarefas_df['Duração'].apply(parse_duration)
88
-
89
- matched_alunos = alunos_df[alunos_df['Aluno_Pattern'].isin(tarefas_df['Aluno_Pattern'])]
90
-
91
- result_df = matched_alunos[['Nome do Aluno']].drop_duplicates()
92
-
93
- for aluno in result_df['Nome do Aluno']:
94
- aluno_pattern = alunos_df.loc[alunos_df['Nome do Aluno'] == aluno, 'Aluno_Pattern'].values[0]
95
- aluno_tarefas = tarefas_df[tarefas_df['Aluno_Pattern'] == aluno_pattern]
96
- nota_total = aluno_tarefas['Nota'].sum()
97
- tempo_total = aluno_tarefas['Duração'].sum()
98
-
99
- if aluno in contador_df['Nome do Aluno'].values:
100
- contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Tarefas Completadas'] += 1
101
- contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Acertos Absolutos'] += nota_total
102
- current_total_tempo = pd.to_timedelta(contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Total Tempo'].values[0])
103
- contador_df.loc[contador_df['Nome do Aluno'] == aluno, 'Total Tempo'] = str(current_total_tempo + tempo_total)
104
- else:
105
- 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)
106
-
107
- contador_df.to_csv(contador_csv_path, index=False, encoding='utf-8-sig')
108
-
109
- return result_df
110
-
111
- def remove_outliers(data, column, threshold=3):
112
- mean = data[column].mean()
113
- std = data[column].std()
114
- return data[(data[column] > mean - threshold * std) & (data[column] < mean + threshold * std)]
115
-
116
- def process_all_tarefas_in_directory(directory, alunos_csv_path, contador_csv_path, relatorio_csv_path):
117
- 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']]
118
-
119
- for i, tarefas_file in enumerate(tarefas_files):
120
- match_alunos(tarefas_file, alunos_csv_path, contador_csv_path)
121
-
122
- process_relatorios(contador_csv_path, relatorio_csv_path)
123
-
124
- def process_relatorios(contador_csv_path, relatorio_csv_path):
125
- contador_df = pd.read_csv(contador_csv_path, encoding='utf-8-sig')
126
- contador_df['Média de Acertos'] = ((contador_df['Acertos Absolutos'] / (contador_df['Tarefas Completadas'] * 2)) * 100).round(2).astype(str) + '%'
127
- contador_df['Total Tempo'] = pd.to_timedelta(contador_df['Total Tempo'])
128
- contador_df['Tempo Médio por Tarefa'] = contador_df['Total Tempo'] / contador_df['Tarefas Completadas']
129
- contador_df['Total Tempo'] = contador_df['Total Tempo'].apply(format_timedelta)
130
- contador_df['Tempo Médio por Tarefa'] = contador_df['Tempo Médio por Tarefa'].apply(format_timedelta)
131
- contador_df = contador_df.sort_values(by='Nome do Aluno', ascending=True)
132
-
133
- # Remove outliers e calcula o tempo médio por tarefa da turma
134
- tempo_medio_por_tarefa = pd.to_timedelta(contador_df['Tempo Médio por Tarefa'])
135
- tempo_medio_por_tarefa = remove_outliers(pd.DataFrame({'Tempo Médio por Tarefa': tempo_medio_por_tarefa}), 'Tempo Médio por Tarefa')
136
- media_tempo_medio_turma = tempo_medio_por_tarefa['Tempo Médio por Tarefa'].mean()
137
- media_tempo_medio_turma = format_timedelta(media_tempo_medio_turma)
138
-
139
- # Calcula médias gerais da turma
140
- media_acertos_turma = (contador_df['Acertos Absolutos'] / (contador_df['Tarefas Completadas'] * 2)).mean() * 100
141
- media_tarefas_turma = contador_df['Tarefas Completadas'].mean()
142
-
143
- contador_df.to_csv(relatorio_csv_path, index=False, encoding='utf-8-sig')
144
- return contador_df, media_tempo_medio_turma, media_acertos_turma, media_tarefas_turma
145
-
146
- def generate_pdf_report(dataframe, media_tempo_medio_turma, media_acertos_turma, media_tarefas_turma, html_path, output_pdf_path):
147
- class PDF(FPDF):
148
- def header(self):
149
- self.set_font('Arial', 'B', 12)
150
- self.cell(0, 10, 'Relatório de Tarefas', 0, 1, 'C')
151
-
152
- def footer(self):
153
- self.set_y(-15)
154
- self.set_font('Arial', 'I', 8)
155
- self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C')
156
-
157
- def add_image(self, image_path):
158
- self.add_page()
159
- self.image(image_path, x=10, y=10, w=270)
160
-
161
- pdf = PDF(orientation='L', unit='mm', format='A4')
162
-
163
- # Gerar gráficos e adicionar ao PDF
164
- def add_bar_labels(bars, labels):
165
- for bar, label in zip(bars, labels):
166
- height = bar.get_height()
167
- plt.annotate(f'{label}',
168
- xy=(bar.get_x() + bar.get_width() / 2, height),
169
- xytext=(0, 3), # 3 points vertical offset
170
- textcoords="offset points",
171
- ha='center', va='bottom')
172
-
173
- top_students = dataframe.nlargest(5, 'Acertos Absolutos')
174
-
175
- plt.figure(figsize=(10, 6))
176
- bars = plt.bar(top_students['Nome do Aluno'], top_students['Acertos Absolutos'], color='blue')
177
- plt.xlabel('Nome do Aluno')
178
- plt.ylabel('Acertos Absolutos')
179
- plt.title('Top 5 Alunos - Acertos Absolutos')
180
- plt.xticks(rotation=45, ha='right')
181
- add_bar_labels(bars, top_students['Acertos Absolutos'])
182
- plt.tight_layout()
183
- graph_path = 'top_5_acertos_absolutos.png'
184
- plt.savefig(graph_path)
185
- pdf.add_image(graph_path)
186
-
187
- plt.figure(figsize=(10, 6))
188
- bars = plt.bar(top_students['Nome do Aluno'], top_students['Média de Acertos'].str.rstrip('%').astype('float'), color='green')
189
- plt.xlabel('Nome do Aluno')
190
- plt.ylabel('Percentual de Acertos (%)')
191
- plt.title('Top 5 Alunos - Percentual de Acertos')
192
- plt.xticks(rotation=45, ha='right')
193
- add_bar_labels(bars, top_students['Média de Acertos'].str.rstrip('%').astype('float'))
194
- plt.tight_layout()
195
- graph_path = 'top_5_percentual_acertos.png'
196
- plt.savefig(graph_path)
197
- pdf.add_image(graph_path)
198
-
199
- plt.figure(figsize=(10, 6))
200
- bars = plt.bar(top_students['Nome do Aluno'], top_students['Tarefas Completadas'], color='red')
201
- plt.xlabel('Nome do Aluno')
202
- plt.ylabel('Tarefas Completadas')
203
- plt.title('Top 5 Alunos - Tarefas Completadas')
204
- plt.xticks(rotation=45, ha='right')
205
- add_bar_labels(bars, top_students['Tarefas Completadas'])
206
- plt.tight_layout()
207
- graph_path = 'top_5_tarefas_completadas.png'
208
- plt.savefig(graph_path)
209
- pdf.add_image(graph_path)
210
-
211
- # Adiciona gráfico de alunos que passam mais tempo fazendo as tarefas
212
- dataframe['Total Tempo'] = pd.to_timedelta(dataframe['Total Tempo'])
213
- top_time_students = dataframe.nlargest(5, 'Total Tempo')
214
- plt.figure(figsize=(10, 6))
215
- bars = plt.bar(top_time_students['Nome do Aluno'], top_time_students['Total Tempo'].dt.total_seconds(), color='purple')
216
- plt.xlabel('Nome do Aluno')
217
- plt.ylabel('Tempo Total (hh:mm:ss)')
218
- plt.title('Top 5 Alunos - Tempo Total')
219
- plt.xticks(rotation=45, ha='right')
220
- add_bar_labels(bars, top_time_students['Total Tempo'].apply(format_timedelta))
221
- plt.tight_layout()
222
- graph_path = 'top_5_tempo_total.png'
223
- plt.savefig(graph_path)
224
- pdf.add_image(graph_path)
225
-
226
- # Adiciona gráfico de resumo da turma
227
- metrics = ['Tempo Médio (hh:mm:ss)', 'Média de Acertos (%)', 'Média de Tarefas']
228
- values = [media_tempo_medio_turma, media_acertos_turma, media_tarefas_turma]
229
-
230
- # Convertendo os valores para strings apropriadas
231
- values_str = [media_tempo_medio_turma, f"{media_acertos_turma:.2f}", f"{media_tarefas_turma:.2f}"]
232
-
233
- plt.figure(figsize=(10, 6))
234
- bars = plt.bar(metrics, values_str, color=['blue', 'green', 'red'])
235
- plt.xlabel('Métricas')
236
- plt.ylabel('Valores')
237
- plt.title('Resumo da Turma')
238
- plt.xticks(rotation=45, ha='right')
239
- for bar, value in zip(bars, values_str):
240
- plt.annotate(f'{value}',
241
- xy=(bar.get_x() + bar.get_width() / 2, bar.get_height()),
242
- xytext=(0, 3), # 3 points vertical offset
243
- textcoords="offset points",
244
- ha='center', va='bottom')
245
- plt.tight_layout()
246
- graph_path = 'resumo_turma.png'
247
- plt.savefig(graph_path)
248
- pdf.add_image(graph_path)
249
-
250
- # Salvar o PDF com os gráficos
251
- temp_graphics_pdf = 'temp_graphics.pdf'
252
- pdf.output(temp_graphics_pdf)
253
-
254
- # Estilo personalizado para bordas da tabela
255
- html_style = """
256
- <style>
257
- .dataframe {
258
- border-collapse: collapse;
259
- width: 100%;
260
- }
261
- .dataframe th, .dataframe td {
262
- border: 1px solid black;
263
- padding: 8px;
264
- text-align: left;
265
- }
266
- </style>
267
- """
268
-
269
- # Ler o conteúdo HTML e adicionar o estilo personalizado
270
- with open(html_path, 'r', encoding='utf-8-sig') as f:
271
- html_content = f.read()
272
-
273
- html_content = html_style + html_content
274
-
275
- temp_html_path = 'temp_html_with_borders.html'
276
- with open(temp_html_path, 'w', encoding='utf-8-sig') as f:
277
- f.write(html_content)
278
-
279
- temp_html_pdf = 'temp_html.pdf'
280
- HTML(temp_html_path).write_pdf(temp_html_pdf)
281
-
282
- # Combinar os PDFs
283
- merger = PdfMerger()
284
- merger.append(temp_html_pdf)
285
- merger.append(temp_graphics_pdf)
286
- merger.write(output_pdf_path)
287
- merger.close()
288
-
289
- # Remover arquivos temporários
290
- os.remove(temp_graphics_pdf)
291
- os.remove(temp_html_pdf)
292
- os.remove(temp_html_path)
293
-
294
- def processar_relatorio(html_file, tarefa_files):
295
- input_directory = "temp_files" # Diretório temporário para os arquivos
296
- output_directory = "temp_files"
297
-
298
- os.makedirs(input_directory, exist_ok=True)
299
- os.makedirs(output_directory, exist_ok=True)
300
-
301
- # Limpa o diretório temporário antes de cada execução (opcional, mas recomendado)
302
- for filename in os.listdir(input_directory):
303
- file_path = os.path.join(input_directory, filename)
304
- if os.path.isfile(file_path):
305
- os.remove(file_path)
306
-
307
- # Salva os arquivos enviados
308
- html_path = os.path.join(input_directory, "alunos.htm")
309
- with open(html_path, "wb") as f:
310
- f.write(html_file)
311
-
312
- for idx, tarefa_file in enumerate(tarefa_files):
313
- tarefa_path = os.path.join(input_directory, f"tarefa_{idx}.xlsx")
314
- with open(tarefa_path, "wb") as f:
315
- f.write(tarefa_file)
316
-
317
- # Normaliza os arquivos
318
- alunos_csv_path = os.path.join(output_directory, "alunos_fim.csv")
319
- normalize_html_to_csv(html_path, alunos_csv_path)
320
- normalize_multiple_excel_to_csv(input_directory, output_directory)
321
-
322
- # Processa os dados e gera o relatório
323
- contador_csv_path = os.path.join(output_directory, "contador_tarefas.csv")
324
- relatorio_csv_path = os.path.join(output_directory, "relatorio_final.csv")
325
- process_all_tarefas_in_directory(output_directory, alunos_csv_path, contador_csv_path, relatorio_csv_path)
326
- df, media_tempo_medio_turma, media_acertos_turma, media_tarefas_turma = process_relatorios(contador_csv_path, relatorio_csv_path)
327
-
328
- # Salva o relatório em HTML e PDF
329
- html_output_path = os.path.join(output_directory, "relatorio_final.html")
330
- df.to_html(html_output_path, index=False, encoding='utf-8-sig')
331
-
332
- pdf_output_path = os.path.join(output_directory, "relatorio_final.pdf")
333
- generate_pdf_report(df, media_tempo_medio_turma, media_acertos_turma, media_tarefas_turma, html_output_path, pdf_output_path)
334
-
335
- return df.to_html(index=False), html_output_path, pdf_output_path
336
-
337
- # Tema personalizado
338
  theme = gr.themes.Default(
339
- primary_hue="blue", # Cor principal (tons de azul)
340
- secondary_hue="gray", # Cor secundária (tons de cinza)
341
- font=["Arial", "sans-serif"], # Família de fontes
342
- font_mono=["Courier New", "monospace"], # Fonte para código
343
  )
344
 
345
- # --- Interface Gradio ---
346
  with gr.Blocks(theme=theme) as interface:
347
- gr.Markdown("# Processamento de Relatórios de Tarefas")
 
348
  with gr.Row():
349
  with gr.Column():
350
- gr.Markdown("## Arquivo HTML (alunos.htm)")
351
- html_file = gr.File(label="Arraste o arquivo .htm aqui", type="binary")
 
352
  with gr.Column():
353
- gr.Markdown("## Arquivos Excel (Relatórios de Tarefas)")
354
- excel_files = gr.Files(label="Arraste os arquivos .xlsx aqui", type="binary", file_count="multiple")
355
-
356
- generate_btn = gr.Button("Gerar Relatório", variant="primary") # Destaque no botão
357
- output_html = gr.HTML()
358
- download_html_btn = gr.File(label="Download HTML Report")
359
- download_pdf_btn = gr.File(label="Download PDF Report")
360
-
361
- def wrapper(html_file, excel_files):
362
- html_content, html_path, pdf_path = processar_relatorio(html_file, excel_files)
363
- return html_content, html_path, pdf_path
364
-
365
- generate_btn.click(fn=wrapper, inputs=[html_file, excel_files], outputs=[output_html, download_html_btn, download_pdf_btn])
366
 
367
- interface.launch()
 
1
  import gradio as gr
2
  import pandas as pd
3
+ import numpy as np
4
  import re
5
  import os
6
  import matplotlib.pyplot as plt
7
  from datetime import timedelta
8
  from fpdf import FPDF
9
  from weasyprint import HTML
10
+ import seaborn as sns
11
+ from typing import Tuple, Dict, List
12
+ import logging
13
+ import warnings
14
+ warnings.filterwarnings('ignore')
15
+
16
+ # Configuração de logging
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(levelname)s - %(message)s'
20
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ class DataProcessor:
23
+ @staticmethod
24
+ def parse_duration(duration_str: str) -> timedelta:
25
+ """Converte string de duração em objeto timedelta."""
26
+ try:
27
+ h, m, s = map(int, duration_str.split(':'))
28
+ return timedelta(hours=h, minutes=m, seconds=s)
29
+ except:
30
+ return timedelta(0)
31
+
32
+ @staticmethod
33
+ def format_timedelta(td: timedelta) -> str:
34
+ """Formata timedelta para string no formato HH:MM:SS."""
35
+ total_seconds = int(td.total_seconds())
36
+ hours, remainder = divmod(total_seconds, 3600)
37
+ minutes, seconds = divmod(remainder, 60)
38
+ return f"{hours:02}:{minutes:02}:{seconds:02}"
39
+
40
+ @staticmethod
41
+ def normalize_html_to_csv(input_html_path: str, output_csv_path: str) -> None:
42
+ """Converte arquivo HTML para CSV."""
43
+ try:
44
+ html_data = pd.read_html(input_html_path)
45
+ data = html_data[0]
46
+ data.to_csv(output_csv_path, index=False, encoding='utf-8-sig')
47
+ logging.info(f"HTML normalizado com sucesso: {output_csv_path}")
48
+ except Exception as e:
49
+ logging.error(f"Erro ao normalizar HTML: {str(e)}")
50
+ raise
51
+
52
+ @staticmethod
53
+ def normalize_excel_to_csv(input_excel_path: str, output_csv_path: str) -> None:
54
+ """Converte arquivo Excel para CSV."""
55
+ try:
56
+ excel_data = pd.read_excel(input_excel_path)
57
+ unnecessary_columns = [col for col in excel_data.columns if 'Unnamed' in str(col)]
58
+ if unnecessary_columns:
59
+ excel_data = excel_data.drop(columns=unnecessary_columns)
60
+ excel_data.to_csv(output_csv_path, index=False, encoding='utf-8-sig')
61
+ logging.info(f"Excel normalizado com sucesso: {output_csv_path}")
62
+ except Exception as e:
63
+ logging.error(f"Erro ao normalizar Excel: {str(e)}")
64
+ raise
65
+
66
+ class StudentAnalyzer:
67
+ def __init__(self, tarefas_df: pd.DataFrame, alunos_df: pd.DataFrame):
68
+ self.tarefas_df = tarefas_df
69
+ self.alunos_df = alunos_df
70
+ self.processor = DataProcessor()
71
+
72
+ def prepare_data(self) -> pd.DataFrame:
73
+ """Prepara e limpa os dados para análise."""
74
+ # Limpeza de colunas
75
+ self.tarefas_df.columns = self.tarefas_df.columns.str.strip()
76
+ self.alunos_df.columns = self.alunos_df.columns.str.strip()
77
+
78
+ # Verifica colunas necessárias
79
+ required_columns = ['Aluno', 'Nota', 'Duração']
80
+ if not all(col in self.tarefas_df.columns for col in required_columns):
81
+ raise ValueError("Colunas obrigatórias não encontradas no arquivo de tarefas")
82
+
83
+ # Processamento de duração
84
+ self.tarefas_df['Duração'] = self.tarefas_df['Duração'].apply(self.processor.parse_duration)
85
+
86
+ return self.match_students()
87
+
88
+ def match_students(self) -> pd.DataFrame:
89
+ """Relaciona dados de alunos com suas tarefas."""
90
+ def generate_aluno_pattern(ra, dig_ra):
91
+ ra_str = str(ra).zfill(9)
92
+ return f"{ra_str[1]}{ra_str[2:]}{dig_ra}-sp".lower()
93
+
94
+ self.alunos_df['Aluno_Pattern'] = self.alunos_df.apply(
95
+ lambda row: generate_aluno_pattern(row['RA'], row['Dig. RA']), axis=1
96
+ )
97
+
98
+ def extract_pattern(nome):
99
+ if isinstance(nome, str):
100
+ match = re.search(r'\d+.*', nome.lower())
101
+ return match.group(0) if match else None
102
+ return None
103
+
104
+ self.tarefas_df['Aluno_Pattern'] = self.tarefas_df['Aluno'].apply(extract_pattern)
105
+
106
+ return self.calculate_metrics()
107
+
108
+ def calculate_metrics(self) -> pd.DataFrame:
109
+ """Calcula métricas de desempenho dos alunos."""
110
+ metrics_df = pd.DataFrame()
111
+
112
+ for _, aluno in self.alunos_df.iterrows():
113
+ aluno_pattern = aluno['Aluno_Pattern']
114
+ aluno_tarefas = self.tarefas_df[self.tarefas_df['Aluno_Pattern'] == aluno_pattern]
115
+
116
+ if not aluno_tarefas.empty:
117
+ metrics = {
118
+ 'Nome do Aluno': aluno['Nome do Aluno'],
119
+ 'Tarefas Completadas': len(aluno_tarefas),
120
+ 'Acertos Absolutos': aluno_tarefas['Nota'].sum(),
121
+ 'Total Tempo': str(aluno_tarefas['Duração'].sum()),
122
+ 'Média de Acertos': f"{(aluno_tarefas['Nota'].sum() / (len(aluno_tarefas) * 2) * 100):.2f}%",
123
+ 'Tempo Médio por Tarefa': str(aluno_tarefas['Duração'].mean()),
124
+ 'Eficiência': f"{(aluno_tarefas['Nota'].sum() / aluno_tarefas['Duração'].sum().total_seconds() * 3600):.2f}"
125
+ }
126
+ metrics_df = pd.concat([metrics_df, pd.DataFrame([metrics])], ignore_index=True)
127
+
128
+ return metrics_df
129
+
130
+ class ReportGenerator:
131
+ def __init__(self, data: pd.DataFrame):
132
+ self.data = data
133
+ self.stats = self.calculate_statistics()
134
+
135
+ def calculate_statistics(self) -> Dict:
136
+ """Calcula estatísticas gerais da turma."""
137
+ return {
138
+ 'media_acertos': float(self.data['Média de Acertos'].str.rstrip('%').astype(float).mean()),
139
+ 'desvio_padrao': float(self.data['Média de Acertos'].str.rstrip('%').astype(float).std()),
140
+ 'mediana_acertos': float(self.data['Média de Acertos'].str.rstrip('%').astype(float).median()),
141
+ 'total_alunos': len(self.data),
142
+ 'media_tarefas': float(self.data['Tarefas Completadas'].mean()),
143
+ 'media_tempo': str(pd.to_timedelta(self.data['Total Tempo']).mean())
144
+ }
145
+
146
+ def generate_graphs(self) -> List[plt.Figure]:
147
+ """Gera gráficos para o relatório."""
148
+ graphs = []
149
+
150
+ # Distribuição de notas
151
+ plt.figure(figsize=(10, 6))
152
+ sns.histplot(data=self.data, x='Média de Acertos'.str.rstrip('%').astype(float), bins=10)
153
+ plt.axvline(self.stats['media_acertos'], color='r', linestyle='--', label=f'Média ({self.stats["media_acertos"]:.1f}%)')
154
+ plt.title('Distribuição das Notas')
155
+ plt.xlabel('Percentual de Acertos')
156
+ plt.ylabel('Número de Alunos')
157
+ plt.legend()
158
+ graphs.append(plt.gcf())
159
+ plt.close()
160
+
161
+ # Relação tempo x desempenho
162
+ plt.figure(figsize=(10, 6))
163
+ tempo_segundos = pd.to_timedelta(self.data['Total Tempo']).dt.total_seconds()
164
+ acertos = self.data['Média de Acertos'].str.rstrip('%').astype(float)
165
+ plt.scatter(tempo_segundos / 60, acertos)
166
+ plt.title('Tempo x Desempenho')
167
+ plt.xlabel('Tempo Total (minutos)')
168
+ plt.ylabel('Percentual de Acertos')
169
+ graphs.append(plt.gcf())
170
+ plt.close()
171
+
172
+ return graphs
173
+
174
+ def generate_pdf(self, output_path: str, graphs: List[plt.Figure]) -> None:
175
+ """Gera relatório em PDF."""
176
+ class PDF(FPDF):
177
+ def header(self):
178
+ self.set_font('Arial', 'B', 15)
179
+ self.cell(0, 10, 'Relatório de Desempenho - Análise Detalhada', 0, 1, 'C')
180
+ self.ln(10)
181
+
182
+ pdf = PDF('L', 'mm', 'A4')
183
+
184
+ # Sumário executivo
185
+ pdf.add_page()
186
+ pdf.set_font('Arial', 'B', 12)
187
+ pdf.cell(0, 10, 'Sumário Executivo', 0, 1)
188
+ pdf.set_font('Arial', '', 10)
189
+
190
+ summary_text = f"""
191
+ Análise da Turma:
192
+ • Média de Acertos: {self.stats['media_acertos']:.1f}%
193
+ • Desvio Padrão: {self.stats['desvio_padrao']:.1f}%
194
+ • Mediana: {self.stats['mediana_acertos']:.1f}%
195
+ • Número de Alunos: {self.stats['total_alunos']}
196
+ • Média de Tarefas por Aluno: {self.stats['media_tarefas']:.1f}
197
+ • Tempo Médio Total: {self.stats['media_tempo']}
198
+ """
199
+ pdf.multi_cell(0, 10, summary_text)
200
+
201
+ # Gráficos
202
+ for i, graph in enumerate(graphs):
203
+ pdf.add_page()
204
+ graph_path = f'temp_graph_{i}.png'
205
+ graph.savefig(graph_path)
206
+ pdf.image(graph_path, x=10, y=30, w=270)
207
+ os.remove(graph_path)
208
+
209
+ # Tabela de alunos
210
+ pdf.add_page()
211
+ pdf.set_font('Arial', 'B', 12)
212
+ pdf.cell(0, 10, 'Desempenho Individual', 0, 1)
213
+
214
+ # Cabeçalhos
215
+ columns = ['Nome do Aluno', 'Média de Acertos', 'Tarefas', 'Tempo Total', 'Eficiência']
216
+ widths = [80, 30, 30, 30, 30]
217
+ pdf.set_font('Arial', 'B', 8)
218
+ for i, col in enumerate(columns):
219
+ pdf.cell(widths[i], 7, col, 1)
220
+ pdf.ln()
221
+
222
+ # Dados
223
+ pdf.set_font('Arial', '', 8)
224
+ for _, row in self.data.iterrows():
225
+ pdf.cell(widths[0], 6, str(row['Nome do Aluno'])[:40], 1)
226
+ pdf.cell(widths[1], 6, str(row['Média de Acertos']), 1)
227
+ pdf.cell(widths[2], 6, str(row['Tarefas Completadas']), 1)
228
+ pdf.cell(widths[3], 6, str(row['Total Tempo']), 1)
229
+ pdf.cell(widths[4], 6, str(row['Eficiência']), 1)
230
+ pdf.ln()
231
+
232
+ pdf.output(output_path)
233
+
234
+ def process_files(html_file, excel_files) -> Tuple[str, str, str]:
235
+ """Processa arquivos e gera relatório."""
236
  try:
237
+ # Criar diretório temporário
238
+ temp_dir = "temp_files"
239
+ os.makedirs(temp_dir, exist_ok=True)
240
+
241
+ # Limpar diretório temporário
242
+ for file in os.listdir(temp_dir):
243
+ os.remove(os.path.join(temp_dir, file))
244
+
245
+ # Salvar arquivos
246
+ html_path = os.path.join(temp_dir, "alunos.htm")
247
+ with open(html_path, "wb") as f:
248
+ f.write(html_file)
249
+
250
+ excel_paths = []
251
+ for i, excel_file in enumerate(excel_files):
252
+ excel_path = os.path.join(temp_dir, f"tarefa_{i}.xlsx")
253
+ with open(excel_path, "wb") as f:
254
+ f.write(excel_file)
255
+ excel_paths.append(excel_path)
256
+
257
+ # Processar arquivos
258
+ processor = DataProcessor()
259
+ alunos_csv_path = os.path.join(temp_dir, "alunos.csv")
260
+ processor.normalize_html_to_csv(html_path, alunos_csv_path)
261
+
262
+ tarefas_df = pd.DataFrame()
263
+ for excel_path in excel_paths:
264
+ csv_path = excel_path.replace('.xlsx', '.csv')
265
+ processor.normalize_excel_to_csv(excel_path, csv_path)
266
+ df = pd.read_csv(csv_path)
267
+ tarefas_df = pd.concat([tarefas_df, df], ignore_index=True)
268
+
269
+ # Análise
270
+ alunos_df = pd.read_csv(alunos_csv_path)
271
+ analyzer = StudentAnalyzer(tarefas_df, alunos_df)
272
+ results_df = analyzer.prepare_data()
273
+
274
+ # Gerar relatório
275
+ report_generator = ReportGenerator(results_df)
276
+ graphs = report_generator.generate_graphs()
277
+
278
+ # Salvar outputs
279
+ output_html = os.path.join(temp_dir, "relatorio.html")
280
+ output_pdf = os.path.join(temp_dir, "relatorio.pdf")
281
+ results_df.to_html(output_html, index=False)
282
+ report_generator.generate_pdf(output_pdf, graphs)
283
+
284
+ return results_df.to_html(index=False), output_html, output_pdf
285
+
286
+ except Exception as e:
287
+ logging.error(f"Erro no processamento: {str(e)}")
288
+ raise
289
+
290
+ # Interface Gradio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  theme = gr.themes.Default(
292
+ primary_hue="blue",
293
+ secondary_hue="gray",
294
+ font=["Arial", "sans-serif"],
295
+ font_mono=["Courier New", "monospace"],
296
  )
297
 
 
298
  with gr.Blocks(theme=theme) as interface:
299
+ gr.Markdown("# Sistema de Análise de Desempenho Acadêmico")
300
+
301
  with gr.Row():
302
  with gr.Column():
303
+ gr.Markdown("## Lista de Alunos (arquivo .htm)")
304
+ html_file = gr.File(label="Upload do arquivo HTML", type="binary")
305
+
306
  with gr.Column():
307
+ gr.Markdown("## Relatórios de Tarefas (arquivos .xlsx)")
308
+ excel_files = gr.Files(label="Upload dos arquivos Excel", type="binary", file_count="multiple")
 
 
 
 
 
 
 
 
 
 
 
309
 
310
+ generate_btn = gr.Button("