histlearn commited on
Commit
15d3e2a
·
verified ·
1 Parent(s): 5c3ea1f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +308 -143
app.py CHANGED
@@ -6,13 +6,13 @@ 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
  logging.basicConfig(
17
  level=logging.INFO,
18
  format='%(asctime)s - %(levelname)s - %(message)s'
@@ -32,7 +32,11 @@ class DataProcessor:
32
  total_seconds = int(td.total_seconds())
33
  hours, remainder = divmod(total_seconds, 3600)
34
  minutes, seconds = divmod(remainder, 60)
35
- return f"{hours:02}:{minutes:02}:{seconds:02}"
 
 
 
 
36
 
37
  @staticmethod
38
  def normalize_html_to_csv(input_html_path: str, output_csv_path: str) -> None:
@@ -95,38 +99,32 @@ class StudentAnalyzer:
95
 
96
  def calculate_metrics(self) -> pd.DataFrame:
97
  metrics_df = pd.DataFrame()
98
-
99
  for _, aluno in self.alunos_df.iterrows():
100
  aluno_pattern = aluno['Aluno_Pattern']
101
  aluno_tarefas = self.tarefas_df[self.tarefas_df['Aluno_Pattern'] == aluno_pattern]
102
-
103
  if not aluno_tarefas.empty:
104
  duracao_total = aluno_tarefas['Duração'].sum()
105
  acertos_total = aluno_tarefas['Nota'].sum()
106
-
107
- # Calcula eficiência como float diretamente
108
- eficiencia = (acertos_total / duracao_total.total_seconds() * 3600)
109
-
110
  metrics = {
111
  'Nome do Aluno': aluno['Nome do Aluno'],
112
  'Tarefas Completadas': len(aluno_tarefas),
113
  'Acertos Absolutos': acertos_total,
114
  'Total Tempo': str(duracao_total),
115
  'Tempo Médio por Tarefa': str(duracao_total / len(aluno_tarefas)),
116
- 'Eficiência': eficiencia # Armazena como float, não como string
117
  }
118
  metrics_df = pd.concat([metrics_df, pd.DataFrame([metrics])], ignore_index=True)
119
-
120
- # Converte explicitamente a coluna Eficiência para float
121
- metrics_df['Eficiência'] = metrics_df['Eficiência'].astype(float)
122
- return metrics_df
123
 
124
  class ReportGenerator:
125
  def __init__(self, data: pd.DataFrame):
126
  self.data = data
127
  self.stats = self.calculate_statistics()
128
  self.data['Nível'] = self.data['Acertos Absolutos'].apply(self.classify_performance)
129
- self.data = self.data.sort_values('Acertos Absolutos', ascending=False)
130
 
131
  def classify_performance(self, acertos):
132
  if acertos >= 10:
@@ -146,8 +144,10 @@ class ReportGenerator:
146
  'media_tempo': str(pd.to_timedelta(self.data['Total Tempo']).mean())
147
  }
148
 
149
- top_students = self.data.nlargest(3, 'Acertos Absolutos')
150
- basic_stats['top_performers'] = top_students[['Nome do Aluno', 'Acertos Absolutos']].values.tolist()
 
 
151
 
152
  efficient_students = self.data.nlargest(3, 'Eficiência')[
153
  ['Nome do Aluno', 'Eficiência', 'Acertos Absolutos']
@@ -157,185 +157,339 @@ class ReportGenerator:
157
  return basic_stats
158
 
159
  def generate_graphs(self) -> List[plt.Figure]:
 
160
  graphs = []
161
-
162
  # 1. Distribuição por nível
163
  plt.figure(figsize=(12, 6))
164
  nivel_counts = self.data['Nível'].value_counts()
165
- colors = {'Avançado': 'green', 'Intermediário': 'yellow', 'Necessita Atenção': 'red'}
 
166
  bars = plt.bar(nivel_counts.index, nivel_counts.values)
167
  for i, bar in enumerate(bars):
168
  bar.set_color(colors[nivel_counts.index[i]])
169
  plt.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
170
  str(nivel_counts.values[i]),
171
  ha='center', va='bottom')
172
- plt.title('Distribuição dos Alunos por Nível de Desempenho')
173
- plt.ylabel('Número de Alunos')
 
 
174
  graphs.append(plt.gcf())
175
  plt.close()
176
 
177
  # 2. Top 10 alunos
178
  plt.figure(figsize=(12, 6))
179
  top_10 = self.data.head(10)
180
- plt.barh(top_10['Nome do Aluno'], top_10['Acertos Absolutos'])
181
- plt.title('Top 10 Alunos - Acertos Absolutos')
182
- plt.xlabel('Número de Acertos')
183
- for i, v in enumerate(top_10['Acertos Absolutos']):
184
- plt.text(v, i, f'{v:.0f}', va='center')
 
 
 
 
 
 
 
185
  plt.tight_layout()
186
  graphs.append(plt.gcf())
187
  plt.close()
188
 
189
  # 3. Relação tempo x acertos
190
- plt.figure(figsize=(10, 6))
191
  for nivel in colors:
192
  mask = self.data['Nível'] == nivel
193
  tempo = pd.to_timedelta(self.data[mask]['Total Tempo']).dt.total_seconds() / 60
194
- plt.scatter(tempo, self.data[mask]['Acertos Absolutos'],
195
- c=colors[nivel], label=nivel, alpha=0.6)
196
- plt.title('Relação Tempo x Acertos por Nível')
197
- plt.xlabel('Tempo Total (minutos)')
198
- plt.ylabel('Número de Acertos')
 
199
  plt.legend()
200
  plt.grid(True, alpha=0.3)
201
  graphs.append(plt.gcf())
202
  plt.close()
203
 
204
- # 4. Relação Tarefas x Acertos
205
- plt.figure(figsize=(10, 6))
206
- plt.scatter(self.data['Tarefas Completadas'], self.data['Acertos Absolutos'])
207
- plt.title('Relação entre Tarefas Completadas e Acertos')
208
- plt.xlabel('Número de Tarefas Completadas')
209
- plt.ylabel('Número de Acertos')
210
- plt.grid(True, alpha=0.3)
211
-
212
- # Linha de tendência
213
  z = np.polyfit(self.data['Tarefas Completadas'], self.data['Acertos Absolutos'], 1)
214
  p = np.poly1d(z)
215
- plt.plot(self.data['Tarefas Completadas'], p(self.data['Tarefas Completadas']),
216
- "r--", alpha=0.8, label='Tendência')
 
 
 
 
 
217
  plt.legend()
 
218
  graphs.append(plt.gcf())
219
  plt.close()
220
 
221
  return graphs
222
-
223
  def generate_pdf(self, output_path: str, graphs: List[plt.Figure]) -> None:
 
224
  class PDF(FPDF):
225
  def header(self):
226
  self.set_font('Arial', 'B', 15)
227
- self.cell(0, 10, 'Relatório de Desempenho - Análise Detalhada', 0, 1, 'C')
 
228
  self.ln(10)
229
 
230
  pdf = PDF('L', 'mm', 'A4')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
- # Sumário executivo
233
  pdf.add_page()
234
- pdf.set_font('Arial', 'B', 12)
235
- pdf.cell(0, 10, 'Sumário Executivo', 0, 1)
236
- pdf.set_font('Arial', '', 10)
 
 
 
 
237
 
238
- summary_text = f"""
239
- Visão Geral da Turma:
240
- - Média de Acertos: {self.stats['media_acertos']:.1f}
241
- - Desvio Padrão: {self.stats['desvio_padrao']:.1f}
242
- - Mediana: {self.stats['mediana_acertos']:.1f}
243
- - Total de Alunos: {self.stats['total_alunos']}
244
- - Média de Tarefas por Aluno: {self.stats['media_tarefas']:.1f}
245
- - Tempo Médio Total: {self.stats['media_tempo']}
 
 
 
246
  """
247
- pdf.multi_cell(0, 10, summary_text)
248
 
249
- pdf.ln()
250
- pdf.cell(0, 10, "Top 3 - Maiores Números de Acertos:", 0, 1)
 
 
 
 
 
 
251
  for aluno, acertos in self.stats['top_performers']:
252
- pdf.cell(0, 10, f"- {aluno}: {acertos:.0f} acertos", 0, 1)
253
 
254
- pdf.ln()
255
- pdf.cell(0, 10, "Alunos Mais Eficientes:", 0, 1)
256
  for aluno, eficiencia, acertos in self.stats['most_efficient']:
257
- # Formata a eficiência com 2 casas decimais ao exibir
258
- pdf.cell(0, 10, f"- {aluno}: Eficiência {eficiencia:.2f} (Acertos: {acertos:.0f})", 0, 1)
259
 
 
260
  for i, graph in enumerate(graphs):
261
  pdf.add_page()
262
  graph_path = f'temp_graph_{i}.png'
263
- graph.savefig(graph_path)
264
  pdf.image(graph_path, x=10, y=30, w=270)
265
  os.remove(graph_path)
266
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
  for nivel in ['Avançado', 'Intermediário', 'Necessita Atenção']:
268
  alunos_nivel = self.data[self.data['Nível'] == nivel]
269
  if not alunos_nivel.empty:
270
  pdf.add_page()
271
- pdf.set_font('Arial', 'B', 12)
272
- pdf.cell(0, 10, f'Alunos - Nível {nivel}', 0, 1)
 
 
 
 
 
 
 
 
 
 
273
 
274
- columns = ['Nome do Aluno', 'Acertos Absolutos', 'Tarefas', 'Tempo Total', 'Eficiência']
275
- widths = [80, 30, 30, 30, 30]
276
- pdf.set_font('Arial', 'B', 8)
277
- for i, col in enumerate(columns):
278
- pdf.cell(widths[i], 7, col, 1)
279
  pdf.ln()
280
 
281
- pdf.set_font('Arial', '', 8)
 
282
  for _, row in alunos_nivel.iterrows():
283
- pdf.cell(widths[0], 6, str(row['Nome do Aluno'])[:40], 1)
284
- pdf.cell(widths[1], 6, str(row['Acertos Absolutos']), 1)
285
- pdf.cell(widths[2], 6, str(row['Tarefas Completadas']), 1)
286
- pdf.cell(widths[3], 6, str(row['Total Tempo']), 1)
287
- pdf.cell(widths[4], 6, str(row['Eficiência']), 1)
 
 
 
288
  pdf.ln()
289
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  pdf.output(output_path)
291
 
292
- def process_files(html_file, excel_files) -> Tuple[str, str, str]:
293
- try:
294
- temp_dir = "temp_files"
295
- os.makedirs(temp_dir, exist_ok=True)
296
-
297
- for file in os.listdir(temp_dir):
298
- os.remove(os.path.join(temp_dir, file))
299
-
300
- html_path = os.path.join(temp_dir, "alunos.htm")
301
- with open(html_path, "wb") as f:
302
- f.write(html_file)
303
-
304
- excel_paths = []
305
- for i, excel_file in enumerate(excel_files):
306
- excel_path = os.path.join(temp_dir, f"tarefa_{i}.xlsx")
307
- with open(excel_path, "wb") as f:
308
- f.write(excel_file)
309
- excel_paths.append(excel_path)
310
-
311
- processor = DataProcessor()
312
- alunos_csv_path = os.path.join(temp_dir, "alunos.csv")
313
- processor.normalize_html_to_csv(html_path, alunos_csv_path)
314
-
315
- tarefas_df = pd.DataFrame()
316
- for excel_path in excel_paths:
317
- csv_path = excel_path.replace('.xlsx', '.csv')
318
- processor.normalize_excel_to_csv(excel_path, csv_path)
319
- df = pd.read_csv(csv_path)
320
- tarefas_df = pd.concat([tarefas_df, df], ignore_index=True)
321
-
322
- alunos_df = pd.read_csv(alunos_csv_path)
323
- analyzer = StudentAnalyzer(tarefas_df, alunos_df)
324
- results_df = analyzer.prepare_data()
325
-
326
- report_generator = ReportGenerator(results_df)
327
- graphs = report_generator.generate_graphs()
 
 
 
 
 
 
328
 
329
- output_html = os.path.join(temp_dir, "relatorio.html")
330
- output_pdf = os.path.join(temp_dir, "relatorio.pdf")
331
- results_df.to_html(output_html, index=False)
332
- report_generator.generate_pdf(output_pdf, graphs)
 
333
 
334
- return results_df.to_html(index=False), output_html, output_pdf
335
 
336
- except Exception as e:
337
- logging.error(f"Erro no processamento: {str(e)}")
338
- raise
339
 
340
  # Interface Gradio
341
  theme = gr.themes.Default(
@@ -346,28 +500,39 @@ theme = gr.themes.Default(
346
  )
347
 
348
  with gr.Blocks(theme=theme) as interface:
349
- gr.Markdown("# Sistema de Análise de Desempenho Acadêmico")
 
 
 
 
 
 
 
350
 
351
  with gr.Row():
352
  with gr.Column():
353
- gr.Markdown("## Lista de Alunos (arquivo .htm)")
354
- html_file = gr.File(label="Upload do arquivo HTML", type="binary")
 
 
 
 
355
 
356
  with gr.Column():
357
- gr.Markdown("## Relatórios de Tarefas (arquivos .xlsx)")
358
- excel_files = gr.Files(label="Upload dos arquivos Excel", type="binary", file_count="multiple")
359
-
360
- generate_btn = gr.Button("Gerar Relatório", variant="primary")
361
- output_html = gr.HTML()
362
- download_html_btn = gr.File(label="Download HTML Report")
363
- download_pdf_btn = gr.File(label="Download PDF Report")
364
-
365
- generate_btn.click(
366
- fn=process_files,
367
- inputs=[html_file, excel_files],
368
- outputs=[output_html, download_html_btn, download_pdf_btn]
369
- )
370
-
371
- # Iniciar a aplicação
372
- if __name__ == "__main__":
373
- interface.launch()
 
6
  import matplotlib.pyplot as plt
7
  from datetime import timedelta
8
  from fpdf import FPDF
 
9
  import seaborn as sns
10
  from typing import Tuple, Dict, List
11
  import logging
12
  import warnings
13
  warnings.filterwarnings('ignore')
14
 
15
+ # Configuração de logging
16
  logging.basicConfig(
17
  level=logging.INFO,
18
  format='%(asctime)s - %(levelname)s - %(message)s'
 
32
  total_seconds = int(td.total_seconds())
33
  hours, remainder = divmod(total_seconds, 3600)
34
  minutes, seconds = divmod(remainder, 60)
35
+ if hours > 0:
36
+ return f"{hours}h {minutes}min {seconds}s"
37
+ elif minutes > 0:
38
+ return f"{minutes}min {seconds}s"
39
+ return f"{seconds}s"
40
 
41
  @staticmethod
42
  def normalize_html_to_csv(input_html_path: str, output_csv_path: str) -> None:
 
99
 
100
  def calculate_metrics(self) -> pd.DataFrame:
101
  metrics_df = pd.DataFrame()
102
+
103
  for _, aluno in self.alunos_df.iterrows():
104
  aluno_pattern = aluno['Aluno_Pattern']
105
  aluno_tarefas = self.tarefas_df[self.tarefas_df['Aluno_Pattern'] == aluno_pattern]
106
+
107
  if not aluno_tarefas.empty:
108
  duracao_total = aluno_tarefas['Duração'].sum()
109
  acertos_total = aluno_tarefas['Nota'].sum()
110
+
 
 
 
111
  metrics = {
112
  'Nome do Aluno': aluno['Nome do Aluno'],
113
  'Tarefas Completadas': len(aluno_tarefas),
114
  'Acertos Absolutos': acertos_total,
115
  'Total Tempo': str(duracao_total),
116
  'Tempo Médio por Tarefa': str(duracao_total / len(aluno_tarefas)),
117
+ 'Eficiência': (acertos_total / duracao_total.total_seconds() * 3600)
118
  }
119
  metrics_df = pd.concat([metrics_df, pd.DataFrame([metrics])], ignore_index=True)
120
+
121
+ return metrics_df.sort_values('Acertos Absolutos', ascending=False)
 
 
122
 
123
  class ReportGenerator:
124
  def __init__(self, data: pd.DataFrame):
125
  self.data = data
126
  self.stats = self.calculate_statistics()
127
  self.data['Nível'] = self.data['Acertos Absolutos'].apply(self.classify_performance)
 
128
 
129
  def classify_performance(self, acertos):
130
  if acertos >= 10:
 
144
  'media_tempo': str(pd.to_timedelta(self.data['Total Tempo']).mean())
145
  }
146
 
147
+ top_students = self.data.nlargest(3, 'Acertos Absolutos')[
148
+ ['Nome do Aluno', 'Acertos Absolutos']
149
+ ].values.tolist()
150
+ basic_stats['top_performers'] = top_students
151
 
152
  efficient_students = self.data.nlargest(3, 'Eficiência')[
153
  ['Nome do Aluno', 'Eficiência', 'Acertos Absolutos']
 
157
  return basic_stats
158
 
159
  def generate_graphs(self) -> List[plt.Figure]:
160
+ plt.style.use('seaborn')
161
  graphs = []
162
+
163
  # 1. Distribuição por nível
164
  plt.figure(figsize=(12, 6))
165
  nivel_counts = self.data['Nível'].value_counts()
166
+ colors = {'Avançado': '#2ecc71', 'Intermediário': '#f1c40f', 'Necessita Atenção': '#e74c3c'}
167
+
168
  bars = plt.bar(nivel_counts.index, nivel_counts.values)
169
  for i, bar in enumerate(bars):
170
  bar.set_color(colors[nivel_counts.index[i]])
171
  plt.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
172
  str(nivel_counts.values[i]),
173
  ha='center', va='bottom')
174
+
175
+ plt.title('Distribuição dos Alunos por Nível de Desempenho', pad=20, fontsize=12)
176
+ plt.ylabel('Número de Alunos', labelpad=10)
177
+ plt.grid(True, alpha=0.3)
178
  graphs.append(plt.gcf())
179
  plt.close()
180
 
181
  # 2. Top 10 alunos
182
  plt.figure(figsize=(12, 6))
183
  top_10 = self.data.head(10)
184
+ bars = plt.barh(top_10['Nome do Aluno'], top_10['Acertos Absolutos'],
185
+ color='#3498db')
186
+
187
+ plt.title('Top 10 Alunos - Acertos Absolutos', pad=20, fontsize=12)
188
+ plt.xlabel('Número de Acertos', labelpad=10)
189
+
190
+ for i, bar in enumerate(bars):
191
+ plt.text(bar.get_width(), bar.get_y() + bar.get_height()/2,
192
+ f' {bar.get_width():.0f}',
193
+ va='center')
194
+
195
+ plt.grid(True, alpha=0.3)
196
  plt.tight_layout()
197
  graphs.append(plt.gcf())
198
  plt.close()
199
 
200
  # 3. Relação tempo x acertos
201
+ plt.figure(figsize=(12, 6))
202
  for nivel in colors:
203
  mask = self.data['Nível'] == nivel
204
  tempo = pd.to_timedelta(self.data[mask]['Total Tempo']).dt.total_seconds() / 60
205
+ plt.scatter(tempo, self.data[mask]['Acertos Absolutos'],
206
+ c=colors[nivel], label=nivel, alpha=0.6, s=100)
207
+
208
+ plt.title('Relação entre Tempo e Acertos por Nível', pad=20, fontsize=12)
209
+ plt.xlabel('Tempo Total (minutos)', labelpad=10)
210
+ plt.ylabel('Número de Acertos', labelpad=10)
211
  plt.legend()
212
  plt.grid(True, alpha=0.3)
213
  graphs.append(plt.gcf())
214
  plt.close()
215
 
216
+ # 4. Relação Tarefas x Acertos com linha de tendência
217
+ plt.figure(figsize=(12, 6))
218
+ plt.scatter(self.data['Tarefas Completadas'], self.data['Acertos Absolutos'],
219
+ color='#3498db', alpha=0.6, s=100)
220
+
 
 
 
 
221
  z = np.polyfit(self.data['Tarefas Completadas'], self.data['Acertos Absolutos'], 1)
222
  p = np.poly1d(z)
223
+ x_range = np.linspace(self.data['Tarefas Completadas'].min(),
224
+ self.data['Tarefas Completadas'].max(), 100)
225
+ plt.plot(x_range, p(x_range), "r--", alpha=0.8, label='Tendência')
226
+
227
+ plt.title('Relação entre Tarefas Completadas e Acertos', pad=20, fontsize=12)
228
+ plt.xlabel('Número de Tarefas Completadas', labelpad=10)
229
+ plt.ylabel('Número de Acertos', labelpad=10)
230
  plt.legend()
231
+ plt.grid(True, alpha=0.3)
232
  graphs.append(plt.gcf())
233
  plt.close()
234
 
235
  return graphs
236
+
237
  def generate_pdf(self, output_path: str, graphs: List[plt.Figure]) -> None:
238
+ """Gera relatório em PDF com análise detalhada."""
239
  class PDF(FPDF):
240
  def header(self):
241
  self.set_font('Arial', 'B', 15)
242
+ self.set_fill_color(240, 240, 240)
243
+ self.cell(0, 15, 'Relatório de Desempenho - Análise Detalhada', 0, 1, 'C', True)
244
  self.ln(10)
245
 
246
  pdf = PDF('L', 'mm', 'A4')
247
+
248
+ # Introdução
249
+ pdf.add_page()
250
+ pdf.set_font('Arial', 'B', 14)
251
+ pdf.set_fill_color(240, 240, 240)
252
+ pdf.cell(0, 10, 'Introdução', 0, 1, 'L', True)
253
+ pdf.ln(5)
254
+ pdf.set_font('Arial', '', 11)
255
+ intro_text = """
256
+ Este relatório apresenta uma análise abrangente do desempenho dos alunos nas atividades realizadas.
257
+ Os dados são analisados considerando três aspectos principais:
258
+
259
+ • Acertos: Total de questões respondidas corretamente
260
+ • Engajamento: Número de tarefas completadas
261
+ • Dedicação: Tempo investido nas atividades
262
+
263
+ Os alunos são classificados em três níveis de acordo com seu desempenho:
264
+ • Avançado: 10 ou mais acertos - Excelente domínio do conteúdo
265
+ • Intermediário: 5 a 9 acertos - Bom entendimento, com espaço para melhorias
266
+ • Necessita Atenção: Menos de 5 acertos - Requer suporte adicional
267
+
268
+ A eficiência é medida em acertos por hora, permitindo identificar alunos que
269
+ conseguem bons resultados com uso eficiente do tempo.
270
+ """
271
+ pdf.multi_cell(0, 7, intro_text)
272
 
273
+ # Visão Geral
274
  pdf.add_page()
275
+ pdf.set_font('Arial', 'B', 14)
276
+ pdf.cell(0, 10, 'Visão Geral da Turma', 0, 1, 'L', True)
277
+ pdf.ln(5)
278
+
279
+ tempo_medio = pd.to_timedelta(self.stats['media_tempo'])
280
+ minutos = int(tempo_medio.total_seconds() // 60)
281
+ segundos = int(tempo_medio.total_seconds() % 60)
282
 
283
+ pdf.set_font('Arial', '', 11)
284
+ stats_text = f"""
285
+ Participação e Resultados:
286
+ Total de Alunos Participantes: {self.stats['total_alunos']}
287
+ Média de Tarefas por Aluno: {self.stats['media_tarefas']:.1f}
288
+ Média de Acertos: {self.stats['media_acertos']:.1f}
289
+ Tempo Médio de Dedicação: {minutos} minutos e {segundos} segundos
290
+
291
+ Distribuição de Desempenho:
292
+ • Desvio Padrão: {self.stats['desvio_padrao']:.1f} acertos
293
+ • Mediana: {self.stats['mediana_acertos']:.1f} acertos
294
  """
295
+ pdf.multi_cell(0, 7, stats_text)
296
 
297
+ # Destaques
298
+ pdf.ln(5)
299
+ pdf.set_font('Arial', 'B', 12)
300
+ pdf.cell(0, 10, 'Destaques de Desempenho', 0, 1)
301
+ pdf.set_font('Arial', '', 11)
302
+
303
+ pdf.ln(3)
304
+ pdf.cell(0, 7, "🏆 Melhores Desempenhos:", 0, 1)
305
  for aluno, acertos in self.stats['top_performers']:
306
+ pdf.cell(0, 7, f" {aluno}: {acertos:.0f} acertos", 0, 1)
307
 
308
+ pdf.ln(3)
309
+ pdf.cell(0, 7, " Maior Eficiência:", 0, 1)
310
  for aluno, eficiencia, acertos in self.stats['most_efficient']:
311
+ pdf.cell(0, 7, f"• {aluno}: {eficiencia:.1f} acertos/hora ({acertos:.0f} acertos totais)", 0, 1)
 
312
 
313
+ # Gráficos e Análises
314
  for i, graph in enumerate(graphs):
315
  pdf.add_page()
316
  graph_path = f'temp_graph_{i}.png'
317
+ graph.savefig(graph_path, dpi=300, bbox_inches='tight')
318
  pdf.image(graph_path, x=10, y=30, w=270)
319
  os.remove(graph_path)
320
+
321
+ # Título e explicação para cada gráfico
322
+ pdf.ln(150) # Espaço após o gráfico
323
+ pdf.set_font('Arial', 'B', 12)
324
+
325
+ if i == 0:
326
+ pdf.cell(0, 10, 'Análise da Distribuição por Nível', 0, 1, 'L', True)
327
+ pdf.set_font('Arial', '', 11)
328
+ pdf.multi_cell(0, 6, """
329
+ Este gráfico ilustra como os alunos estão distribuídos entre os três níveis de desempenho.
330
+ • Verde: Alunos no nível Avançado - demonstram excelente compreensão
331
+ • Amarelo: Alunos no nível Intermediário - bom progresso com espaço para melhorias
332
+ • Vermelho: Alunos que Necessitam Atenção - requerem suporte adicional
333
+ """)
334
+
335
+ elif i == 1:
336
+ pdf.cell(0, 10, 'Top 10 Alunos por Acertos', 0, 1, 'L', True)
337
+ pdf.set_font('Arial', '', 11)
338
+ pdf.multi_cell(0, 6, """
339
+ Destaca os dez alunos com maior número de acertos absolutos.
340
+ Este ranking permite:
341
+ • Identificar exemplos de sucesso na turma
342
+ • Reconhecer diferentes níveis de excelência
343
+ • Estabelecer metas realistas para os demais alunos
344
+ """)
345
+
346
+ elif i == 2:
347
+ pdf.cell(0, 10, 'Relação Tempo x Desempenho', 0, 1, 'L', True)
348
+ pdf.set_font('Arial', '', 11)
349
+ pdf.multi_cell(0, 6, """
350
+ Mostra a relação entre tempo dedicado e número de acertos.
351
+ Pontos importantes:
352
+ • Cores indicam o nível de cada aluno
353
+ • Posição vertical mostra o número de acertos
354
+ • Posição horizontal indica o tempo total dedicado
355
+ • Dispersão dos pontos revela diferentes padrões de estudo
356
+ """)
357
+
358
+ elif i == 3:
359
+ pdf.cell(0, 10, 'Progresso por Número de Tarefas', 0, 1, 'L', True)
360
+ pdf.set_font('Arial', '', 11)
361
+ pdf.multi_cell(0, 6, """
362
+ Analisa se mais tarefas realizadas resultam em melhor desempenho.
363
+ A linha de tendência (tracejada) indica:
364
+ • Correlação entre quantidade de tarefas e acertos
365
+ • Expectativa média de progresso
366
+ • Alunos acima da linha superam a expectativa da turma
367
+ """)
368
+
369
+ # Detalhamento por Nível
370
  for nivel in ['Avançado', 'Intermediário', 'Necessita Atenção']:
371
  alunos_nivel = self.data[self.data['Nível'] == nivel]
372
  if not alunos_nivel.empty:
373
  pdf.add_page()
374
+ pdf.set_font('Arial', 'B', 14)
375
+ pdf.cell(0, 10, f'Detalhamento - Nível {nivel}', 0, 1, 'L', True)
376
+ pdf.ln(5)
377
+
378
+ # Tabela
379
+ colunas = [
380
+ ('Nome do Aluno', 80),
381
+ ('Acertos', 25),
382
+ ('Tarefas', 25),
383
+ ('Tempo Total', 35),
384
+ ('Eficiência', 25)
385
+ ]
386
 
387
+ # Cabeçalho da tabela
388
+ pdf.set_font('Arial', 'B', 10)
389
+ pdf.set_fill_color(230, 230, 230)
390
+ for titulo, largura in colunas:
391
+ pdf.cell(largura, 8, titulo, 1, 0, 'C', True)
392
  pdf.ln()
393
 
394
+ # Dados
395
+ pdf.set_font('Arial', '', 10)
396
  for _, row in alunos_nivel.iterrows():
397
+ tempo = pd.to_timedelta(row['Total Tempo'])
398
+ tempo_str = f"{int(tempo.total_seconds() // 60)}min {int(tempo.total_seconds() % 60)}s"
399
+
400
+ pdf.cell(80, 7, str(row['Nome do Aluno'])[:40], 1)
401
+ pdf.cell(25, 7, f"{row['Acertos Absolutos']:.0f}", 1, 0, 'C')
402
+ pdf.cell(25, 7, str(row['Tarefas Completadas']), 1, 0, 'C')
403
+ pdf.cell(35, 7, tempo_str, 1, 0, 'C')
404
+ pdf.cell(25, 7, f"{float(row['Eficiência']):.1f}", 1, 0, 'C')
405
  pdf.ln()
406
 
407
+ # Recomendações Finais
408
+ pdf.add_page()
409
+ pdf.set_font('Arial', 'B', 14)
410
+ pdf.cell(0, 10, 'Recomendações e Próximos Passos', 0, 1, 'L', True)
411
+ pdf.ln(5)
412
+
413
+ pdf.set_font('Arial', '', 11)
414
+ percent_necessita_atencao = len(self.data[self.data['Nível'] == 'Necessita Atenção']) / len(self.data) * 100
415
+
416
+ recom_text = f"""
417
+ Com base na análise dos dados, recomenda-se:
418
+
419
+ 1. Ações Imediatas:
420
+ • Implementar monitoria com alunos do nível Avançado
421
+ • Realizar reforço focado nos {percent_necessita_atencao:.1f}% que necessitam atenção
422
+ • Desenvolver planos de estudo personalizados
423
+
424
+ 2. Melhorias no Processo:
425
+ • Acompanhamento individualizado dos alunos com baixo desempenho
426
+ • Feedback regular sobre o progresso
427
+ • Atividades extras para alunos com alta eficiência
428
+
429
+ 3. Próximos Passos:
430
+ • Compartilhar resultados individuais
431
+ • Agendar sessões de reforço
432
+ • Reconhecer publicamente bons desempenhos
433
+ • Estabelecer metas claras de melhoria
434
+ """
435
+ pdf.multi_cell(0, 7, recom_text)
436
+
437
  pdf.output(output_path)
438
 
439
+ def process_files(html_file, excel_files) -> Tuple[str, str, str]:
440
+ """Processa arquivos e gera relatório."""
441
+ try:
442
+ temp_dir = "temp_files"
443
+ os.makedirs(temp_dir, exist_ok=True)
444
+
445
+ # Limpar diretório temporário
446
+ for file in os.listdir(temp_dir):
447
+ os.remove(os.path.join(temp_dir, file))
448
+
449
+ # Salvar arquivos
450
+ html_path = os.path.join(temp_dir, "alunos.htm")
451
+ with open(html_path, "wb") as f:
452
+ f.write(html_file)
453
+
454
+ excel_paths = []
455
+ for i, excel_file in enumerate(excel_files):
456
+ excel_path = os.path.join(temp_dir, f"tarefa_{i}.xlsx")
457
+ with open(excel_path, "wb") as f:
458
+ f.write(excel_file)
459
+ excel_paths.append(excel_path)
460
+
461
+ # Processar arquivos
462
+ processor = DataProcessor()
463
+ alunos_csv_path = os.path.join(temp_dir, "alunos.csv")
464
+ processor.normalize_html_to_csv(html_path, alunos_csv_path)
465
+
466
+ tarefas_df = pd.DataFrame()
467
+ for excel_path in excel_paths:
468
+ csv_path = excel_path.replace('.xlsx', '.csv')
469
+ processor.normalize_excel_to_csv(excel_path, csv_path)
470
+ df = pd.read_csv(csv_path)
471
+ tarefas_df = pd.concat([tarefas_df, df], ignore_index=True)
472
+
473
+ # Análise
474
+ alunos_df = pd.read_csv(alunos_csv_path)
475
+ analyzer = StudentAnalyzer(tarefas_df, alunos_df)
476
+ results_df = analyzer.prepare_data()
477
+
478
+ # Gerar relatório
479
+ report_generator = ReportGenerator(results_df)
480
+ graphs = report_generator.generate_graphs()
481
 
482
+ # Salvar outputs
483
+ output_html = os.path.join(temp_dir, "relatorio.html")
484
+ output_pdf = os.path.join(temp_dir, "relatorio.pdf")
485
+ results_df.to_html(output_html, index=False)
486
+ report_generator.generate_pdf(output_pdf, graphs)
487
 
488
+ return results_df.to_html(index=False), output_html, output_pdf
489
 
490
+ except Exception as e:
491
+ logging.error(f"Erro no processamento: {str(e)}")
492
+ raise
493
 
494
  # Interface Gradio
495
  theme = gr.themes.Default(
 
500
  )
501
 
502
  with gr.Blocks(theme=theme) as interface:
503
+ gr.Markdown("""
504
+ # Sistema de Análise de Desempenho Acadêmico
505
+
506
+ Este sistema analisa o desempenho dos alunos e gera um relatório detalhado com:
507
+ - Análise estatística completa
508
+ - Visualizações gráficas
509
+ - Recomendações personalizadas
510
+ """)
511
 
512
  with gr.Row():
513
  with gr.Column():
514
+ gr.Markdown("## Lista de Alunos")
515
+ html_file = gr.File(
516
+ label="Arquivo HTML com lista de alunos (.htm)",
517
+ type="binary",
518
+ file_types=[".htm", ".html"]
519
+ )
520
 
521
  with gr.Column():
522
+ gr.Markdown("## Relatórios de Tarefas")
523
+ excel_files = gr.Files(
524
+ label="Arquivos Excel com dados das tarefas (.xlsx)",
525
+ type="binary",
526
+ file_count="multiple",
527
+ file_types=[".xlsx"]
528
+ )
529
+
530
+ with gr.Row():
531
+ generate_btn = gr.Button("Gerar Relatório", variant="primary", size="lg")
532
+
533
+ with gr.Row():
534
+ output_html = gr.HTML()
535
+
536
+ with gr.Row():
537
+ with gr.Column():
538
+ download_html_btn =