histlearn commited on
Commit
17ddc31
·
verified ·
1 Parent(s): e6864f6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +326 -6
app.py CHANGED
@@ -157,19 +157,339 @@ class ReportGenerator:
157
  return basic_stats
158
 
159
  def generate_graphs(self) -> List[plt.Figure]:
 
160
  graphs = []
161
- # Gráficos omitidos para compactar. Retorne aqui se desejar.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  return graphs
163
 
164
  def generate_pdf(self, output_path: str, graphs: List[plt.Figure]) -> None:
165
- pdf = FPDF() # Adicione aqui os gráficos.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  pdf.output(output_path)
167
 
168
  def process_files(html_file, excel_files) -> Tuple[str, str, str]:
169
- temp_dir = "temp_files"
170
- os.makedirs(temp_dir, exist_ok=True)
171
- # Lógica principal omitida para espaço.
172
- return "Relatório gerado"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
  # Interface Gradio
175
  theme = gr.themes.Default(
 
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(