histlearn commited on
Commit
a68d1e8
1 Parent(s): 1e86c59

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -42
app.py CHANGED
@@ -248,16 +248,45 @@ def gerar_paleta_cores(n_cores):
248
  return cores_base[:n_cores]
249
 
250
  def plotar_evolucao_bimestres(disciplinas_dados, temp_dir, titulo=None, nome_arquivo=None):
 
251
  n_disciplinas = len(disciplinas_dados)
252
 
 
 
 
253
  plt.figure(figsize=(11.69, 8.27))
 
254
  cores = gerar_paleta_cores(n_disciplinas)
255
- marcadores = ['o', 's', '^', 'D', 'v', '<', '>', 'p', '*', 'h']
256
- estilos_linha = ['-', '--', '-.', ':'] * (n_disciplinas // 4 + 1)
257
 
 
 
 
258
  deslocamentos = np.linspace(-0.03, 0.03, n_disciplinas)
259
- anotacoes_usadas = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
 
 
261
  for idx, disc_data in enumerate(disciplinas_dados):
262
  notas = pd.Series(disc_data['notas'])
263
  bimestres_cursados = disc_data['bimestres_cursados']
@@ -269,75 +298,164 @@ def plotar_evolucao_bimestres(disciplinas_dados, temp_dir, titulo=None, nome_arq
269
  bimestres_deslocados = [bim + desloc for bim in bimestres]
270
 
271
  if notas_validas:
 
272
  plt.plot(bimestres_deslocados, notas_validas,
273
- color=cores[idx % len(cores)],
274
- marker=marcadores[idx % len(marcadores)],
275
- markersize=8,
276
- linewidth=2,
277
- label=disc_data['disciplina'],
278
- linestyle=estilos_linha[idx % len(estilos_linha)])
 
279
 
280
- for bim, nota in zip(bimestres_deslocados, notas_validas):
281
- y_offset = 10
282
- while any(abs(y - (nota + y_offset/20)) < 0.4 for y, _ in anotacoes_usadas.get(bim, [])):
283
- y_offset += 5
284
-
285
- plt.annotate(f"{nota:.1f}", (bim, nota), xytext=(0, y_offset),
286
- textcoords="offset points", ha='center', va='bottom', fontsize=9,
287
- bbox=dict(facecolor='white', edgecolor=cores[idx % len(cores)], alpha=0.8, pad=2))
288
- anotacoes_usadas.setdefault(bim, []).append((nota + y_offset/20, nota))
289
-
290
- plt.title(titulo or 'Evolução das Médias por Disciplina', pad=20, fontsize=14, fontweight='bold')
291
- plt.xlabel('Bimestres', fontsize=12, labelpad=10)
292
- plt.ylabel('Notas', fontsize=12, labelpad=10)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  plt.xticks([1, 2, 3, 4], ['1º Bim', '2º Bim', '3º Bim', '4º Bim'])
294
  plt.ylim(0, ESCALA_MAXIMA_NOTAS)
295
- plt.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.5)
 
 
 
 
 
 
 
 
 
 
 
 
296
  plt.tight_layout()
297
 
 
298
  nome_arquivo = nome_arquivo or 'evolucao_notas.png'
299
  plot_path = os.path.join(temp_dir, nome_arquivo)
300
  plt.savefig(plot_path, bbox_inches='tight', dpi=300)
301
  plt.close()
302
  return plot_path
303
-
304
  def plotar_graficos_destacados(disciplinas_dados, temp_dir):
 
305
  n_disciplinas = len(disciplinas_dados)
306
 
307
- fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[1, 1])
308
- plt.subplots_adjust(hspace=0.5)
 
 
 
309
 
310
  disciplinas = [d['disciplina'] for d in disciplinas_dados]
311
  medias_notas = [d['media_notas'] for d in disciplinas_dados]
312
  medias_freq = [d['media_freq'] for d in disciplinas_dados]
313
 
314
- cores_notas = ['#E74C3C' if media < LIMITE_APROVACAO_NOTA else '#2ECC71' for media in medias_notas]
315
- cores_freq = ['#E74C3C' if media < LIMITE_APROVACAO_FREQ else '#2ECC71' for media in medias_freq]
 
 
 
 
 
316
 
 
317
  media_global = np.mean(medias_notas)
318
  freq_global = np.mean(medias_freq)
319
 
320
- ax1.bar(disciplinas, medias_notas, color=cores_notas)
321
- ax1.set_title('Média de Notas por Disciplina', fontsize=14, fontweight='bold')
 
322
  ax1.set_ylim(0, ESCALA_MAXIMA_NOTAS)
323
- ax1.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.3)
324
- ax1.set_xticklabels(disciplinas, rotation=45, ha='right', va='top', fontsize=10)
325
- ax1.set_ylabel('Notas', fontsize=12)
326
 
327
- for idx, (disc, media) in enumerate(zip(disciplinas, medias_notas)):
328
- ax1.text(idx, media + 0.2, f'{media:.1f}', ha='center', color='black')
 
329
 
330
- ax2.bar(disciplinas, medias_freq, color=cores_freq)
331
- ax2.set_title('Frequência Média por Disciplina', fontsize=14, fontweight='bold')
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  ax2.set_ylim(0, 110)
333
- ax2.axhline(y=LIMITE_APROVACAO_FREQ, color='r', linestyle='--', alpha=0.3)
334
- ax2.set_xticklabels(disciplinas, rotation=45, ha='right', va='top', fontsize=10)
335
- ax2.set_ylabel('Frequência (%)', fontsize=12)
336
 
337
- for idx, (disc, freq) in enumerate(zip(disciplinas, medias_freq)):
338
- ax2.text(idx, freq + 1, f'{freq:.1f}%', ha='center', color='black')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
339
 
340
  plt.tight_layout()
 
 
341
  plot_path = os.path.join(temp_dir, 'medias_frequencias.png')
342
  plt.savefig(plot_path, bbox_inches='tight', dpi=300)
343
  plt.close()
 
248
  return cores_base[:n_cores]
249
 
250
  def plotar_evolucao_bimestres(disciplinas_dados, temp_dir, titulo=None, nome_arquivo=None):
251
+ """Plota gráfico de evolução das notas por bimestre com visualização refinada."""
252
  n_disciplinas = len(disciplinas_dados)
253
 
254
+ if n_disciplinas == 0:
255
+ raise ValueError("Nenhuma disciplina válida encontrada para plotar.")
256
+
257
  plt.figure(figsize=(11.69, 8.27))
258
+
259
  cores = gerar_paleta_cores(n_disciplinas)
260
+ marcadores = ['o', 's', '^', 'D', 'v', '<', '>', 'p', 'h', '*']
261
+ estilos_linha = ['-', '--', '-.', ':', '-', '--', '-.', ':', '-', '--']
262
 
263
+ plt.grid(True, linestyle='--', alpha=0.3, zorder=0)
264
+
265
+ # Deslocamento ainda menor e mais refinado
266
  deslocamentos = np.linspace(-0.03, 0.03, n_disciplinas)
267
+
268
+ # Estrutura para armazenar as posições das anotações já utilizadas
269
+ anotacoes_usadas = {} # formato: {bimestre: [(y, texto)]}
270
+
271
+ # Primeira passagem: coletar todos os valores e determinar grupos
272
+ grupos_notas = {} # {bimestre: {nota: [índices]}}
273
+ for idx, disc_data in enumerate(disciplinas_dados):
274
+ notas = pd.Series(disc_data['notas'])
275
+ bimestres_cursados = disc_data['bimestres_cursados']
276
+
277
+ if bimestres_cursados:
278
+ notas_validas = [nota for i, nota in enumerate(notas, 1) if i in bimestres_cursados and nota is not None]
279
+ bimestres = [bim for bim in bimestres_cursados if notas[bim-1] is not None]
280
+
281
+ for bim, nota in zip(bimestres, notas_validas):
282
+ if nota is not None:
283
+ if bim not in grupos_notas:
284
+ grupos_notas[bim] = {}
285
+ if nota not in grupos_notas[bim]:
286
+ grupos_notas[bim][nota] = []
287
+ grupos_notas[bim][nota].append(idx)
288
 
289
+ # Segunda passagem: plotar e anotar
290
  for idx, disc_data in enumerate(disciplinas_dados):
291
  notas = pd.Series(disc_data['notas'])
292
  bimestres_cursados = disc_data['bimestres_cursados']
 
298
  bimestres_deslocados = [bim + desloc for bim in bimestres]
299
 
300
  if notas_validas:
301
+ # Plotar linha e pontos
302
  plt.plot(bimestres_deslocados, notas_validas,
303
+ color=cores[idx % len(cores)],
304
+ marker=marcadores[idx % len(marcadores)],
305
+ markersize=7,
306
+ linewidth=1.5,
307
+ label=disc_data['disciplina'],
308
+ linestyle=estilos_linha[idx % len(estilos_linha)],
309
+ alpha=0.8)
310
 
311
+ # Adicionar anotações com posicionamento otimizado
312
+ for bim_orig, bim_desloc, nota in zip(bimestres, bimestres_deslocados, notas_validas):
313
+ if nota is not None:
314
+ # Verificar se é o primeiro índice para esta nota neste bimestre
315
+ if grupos_notas[bim_orig][nota][0] == idx:
316
+ # Determinar posição vertical da anotação
317
+ if bim_orig not in anotacoes_usadas:
318
+ anotacoes_usadas[bim_orig] = []
319
+
320
+ # Encontrar posição vertical disponível
321
+ y_base = nota
322
+ y_offset = 10
323
+ texto = f"{nota:.1f}"
324
+
325
+ # Verificar sobreposição com anotações existentes
326
+ while any(abs(y - (y_base + y_offset/20)) < 0.4 for y, _ in anotacoes_usadas.get(bim_orig, [])):
327
+ y_offset += 5
328
+
329
+ # Adicionar anotação
330
+ plt.annotate(texto,
331
+ (bim_orig, nota),
332
+ textcoords="offset points",
333
+ xytext=(0, y_offset),
334
+ ha='center',
335
+ va='bottom',
336
+ fontsize=8,
337
+ bbox=dict(facecolor='white',
338
+ edgecolor='none',
339
+ alpha=0.8,
340
+ pad=0.5))
341
+
342
+ anotacoes_usadas[bim_orig].append((nota + y_offset/20, texto))
343
+
344
+ # Usar título personalizado se fornecido
345
+ titulo_grafico = titulo or 'Evolução das Médias por Disciplina ao Longo dos Bimestres'
346
+ plt.title(titulo_grafico, pad=20, fontsize=12, fontweight='bold')
347
+
348
+ plt.xlabel('Bimestres', fontsize=10)
349
+ plt.ylabel('Notas', fontsize=10)
350
  plt.xticks([1, 2, 3, 4], ['1º Bim', '2º Bim', '3º Bim', '4º Bim'])
351
  plt.ylim(0, ESCALA_MAXIMA_NOTAS)
352
+
353
+ # Adicionar linha de aprovação
354
+ plt.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.3)
355
+ plt.text(0.02, LIMITE_APROVACAO_NOTA + 0.1, 'Média mínima para aprovação',
356
+ transform=plt.gca().get_yaxis_transform(), color='r', alpha=0.5)
357
+
358
+ # Ajustar legenda
359
+ if n_disciplinas > 8:
360
+ plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=8,
361
+ ncol=max(1, n_disciplinas // 12))
362
+ else:
363
+ plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', ncol=1)
364
+
365
  plt.tight_layout()
366
 
367
+ # Usar nome de arquivo personalizado se fornecido
368
  nome_arquivo = nome_arquivo or 'evolucao_notas.png'
369
  plot_path = os.path.join(temp_dir, nome_arquivo)
370
  plt.savefig(plot_path, bbox_inches='tight', dpi=300)
371
  plt.close()
372
  return plot_path
373
+
374
  def plotar_graficos_destacados(disciplinas_dados, temp_dir):
375
+ """Plota gráficos de médias e frequências com destaques."""
376
  n_disciplinas = len(disciplinas_dados)
377
 
378
+ if not n_disciplinas:
379
+ raise ValueError("Nenhuma disciplina válida encontrada no boletim.")
380
+
381
+ # Criar figura
382
+ plt.figure(figsize=(12, 10))
383
 
384
  disciplinas = [d['disciplina'] for d in disciplinas_dados]
385
  medias_notas = [d['media_notas'] for d in disciplinas_dados]
386
  medias_freq = [d['media_freq'] for d in disciplinas_dados]
387
 
388
+ # Criar subplot com mais espaço entre os gráficos
389
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[1, 1])
390
+ plt.subplots_adjust(hspace=0.5) # Aumentar espaço entre os gráficos
391
+
392
+ # Definir cores baseadas nos limites de aprovação
393
+ cores_notas = ['red' if media < LIMITE_APROVACAO_NOTA else '#2ecc71' for media in medias_notas]
394
+ cores_freq = ['red' if media < LIMITE_APROVACAO_FREQ else '#2ecc71' for media in medias_freq]
395
 
396
+ # Calcular médias globais
397
  media_global = np.mean(medias_notas)
398
  freq_global = np.mean(medias_freq)
399
 
400
+ # Gráfico de notas
401
+ barras_notas = ax1.bar(disciplinas, medias_notas, color=cores_notas)
402
+ ax1.set_title('Média de Notas por Disciplina', pad=20, fontsize=12, fontweight='bold')
403
  ax1.set_ylim(0, ESCALA_MAXIMA_NOTAS)
404
+ ax1.grid(True, axis='y', alpha=0.3, linestyle='--')
 
 
405
 
406
+ # Melhorar a apresentação dos rótulos
407
+ ax1.set_xticklabels(disciplinas, rotation=45, ha='right', va='top')
408
+ ax1.set_ylabel('Notas', fontsize=10, labelpad=10)
409
 
410
+ # Adicionar linha de média mínima
411
+ ax1.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.3)
412
+ ax1.text(0.02, LIMITE_APROVACAO_NOTA + 0.1, 'Média mínima (5,0)',
413
+ transform=ax1.get_yaxis_transform(), color='r', alpha=0.7)
414
+
415
+ # Valores nas barras de notas
416
+ for barra in barras_notas:
417
+ altura = barra.get_height()
418
+ ax1.text(barra.get_x() + barra.get_width()/2., altura,
419
+ f'{altura:.1f}',
420
+ ha='center', va='bottom', fontsize=8)
421
+
422
+ # Gráfico de frequências
423
+ barras_freq = ax2.bar(disciplinas, medias_freq, color=cores_freq)
424
+ ax2.set_title('Frequência Média por Disciplina', pad=20, fontsize=12, fontweight='bold')
425
  ax2.set_ylim(0, 110)
426
+ ax2.grid(True, axis='y', alpha=0.3, linestyle='--')
 
 
427
 
428
+ # Melhorar a apresentação dos rótulos
429
+ ax2.set_xticklabels(disciplinas, rotation=45, ha='right', va='top')
430
+ ax2.set_ylabel('Frequência (%)', fontsize=10, labelpad=10)
431
+
432
+ # Adicionar linha de frequência mínima
433
+ ax2.axhline(y=LIMITE_APROVACAO_FREQ, color='r', linestyle='--', alpha=0.3)
434
+ ax2.text(0.02, LIMITE_APROVACAO_FREQ + 1, 'Frequência mínima (75%)',
435
+ transform=ax2.get_yaxis_transform(), color='r', alpha=0.7)
436
+
437
+ # Valores nas barras de frequência
438
+ for barra in barras_freq:
439
+ altura = barra.get_height()
440
+ ax2.text(barra.get_x() + barra.get_width()/2., altura,
441
+ f'{altura:.1f}%',
442
+ ha='center', va='bottom', fontsize=8)
443
+
444
+ # Título global com informações de média
445
+ plt.suptitle(
446
+ f'Desempenho Geral\nMédia Global: {media_global:.1f} | Frequência Global: {freq_global:.1f}%',
447
+ y=0.98, fontsize=14, fontweight='bold'
448
+ )
449
+
450
+ # Aviso de risco de reprovação se necessário
451
+ if freq_global < LIMITE_APROVACAO_FREQ:
452
+ plt.figtext(0.5, 0.02,
453
+ "Atenção: Risco de Reprovação por Baixa Frequência",
454
+ ha="center", fontsize=11, color="red", weight='bold')
455
 
456
  plt.tight_layout()
457
+
458
+ # Salvar o gráfico
459
  plot_path = os.path.join(temp_dir, 'medias_frequencias.png')
460
  plt.savefig(plot_path, bbox_inches='tight', dpi=300)
461
  plt.close()