histlearn commited on
Commit
3a3ebd5
·
verified ·
1 Parent(s): 4fdd9e0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +114 -135
app.py CHANGED
@@ -14,32 +14,82 @@ from datetime import datetime
14
  matplotlib.use('Agg')
15
 
16
  # Configurações globais
17
- ESCALA_MAXIMA_NOTAS = 12 # Aumentado para melhor visualização
18
  LIMITE_APROVACAO_NOTA = 5
19
  LIMITE_APROVACAO_FREQ = 75
20
  BIMESTRES = ['1º Bimestre', '2º Bimestre', '3º Bimestre', '4º Bimestre']
21
- CONCEITOS_VALIDOS = ['ES', 'EP', 'ET'] # Conceitos não numéricos válidos
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  def converter_nota(valor):
24
  """Converte valor de nota para float, tratando casos especiais e conceitos."""
25
  if pd.isna(valor) or valor == '-' or valor == 'N' or valor == '' or valor == 'None':
26
  return None
27
 
28
- # Se for string, limpar e verificar se é conceito
29
  if isinstance(valor, str):
30
  valor_limpo = valor.strip().upper()
31
  if valor_limpo in CONCEITOS_VALIDOS:
32
- # Converter conceitos para valores numéricos
33
  conceitos_map = {'ET': 10, 'ES': 8, 'EP': 6}
34
  return conceitos_map.get(valor_limpo)
35
 
36
- # Tentar converter para número
37
  try:
38
  return float(valor_limpo.replace(',', '.'))
39
  except:
40
  return None
41
 
42
- # Se for número, retornar diretamente
43
  if isinstance(valor, (int, float)):
44
  return float(valor)
45
 
@@ -57,12 +107,11 @@ def calcular_frequencia_media(frequencias):
57
  freq_validas = []
58
  for freq in frequencias:
59
  try:
60
- # Limpar string e converter para número
61
  if isinstance(freq, str):
62
  freq = freq.strip().replace('%', '').replace(',', '.')
63
  if freq and freq != '-':
64
  valor = float(freq)
65
- if valor > 0: # Considerar apenas frequências positivas
66
  freq_validas.append(valor)
67
  except:
68
  continue
@@ -80,10 +129,8 @@ def extrair_tabelas_pdf(pdf_path):
80
  if len(tables) == 0:
81
  raise ValueError("Nenhuma tabela foi extraída do PDF.")
82
 
83
- # Processar a primeira tabela
84
  df = tables[0].df
85
 
86
- # Extrair nome do aluno e outras informações se disponível
87
  info_aluno = {}
88
  for i, row in df.iterrows():
89
  if 'Nome do Aluno' in str(row[0]):
@@ -95,14 +142,11 @@ def extrair_tabelas_pdf(pdf_path):
95
  elif 'Turma' in str(row[0]):
96
  info_aluno['turma'] = row[1].strip() if len(row) > 1 else ''
97
 
98
- # Encontrar a tabela de notas
99
  for i, table in enumerate(tables):
100
  df_temp = table.df
101
- # Verificar se é a tabela de notas
102
  if any('Disciplina' in str(col) for col in df_temp.iloc[0]) or \
103
  any('Bimestre' in str(col) for col in df_temp.iloc[0]):
104
  df = df_temp
105
- # Renomear as colunas corretamente
106
  df = df.rename(columns={
107
  0: 'Disciplina',
108
  1: 'Nota B1', 2: 'Freq B1', 3: '%Freq B1', 4: 'AC B1',
@@ -116,17 +160,6 @@ def extrair_tabelas_pdf(pdf_path):
116
  if df.empty:
117
  raise ValueError("A tabela extraída está vazia.")
118
 
119
- # Adicionar informações do aluno ao DataFrame
120
- for key, value in info_aluno.items():
121
- df.attrs[key] = value
122
-
123
- return df
124
-
125
- except Exception as e:
126
- print(f"Erro na extração das tabelas: {str(e)}")
127
- raise
128
-
129
- # Adicionar informações do aluno ao DataFrame
130
  for key, value in info_aluno.items():
131
  df.attrs[key] = value
132
 
@@ -135,7 +168,6 @@ def extrair_tabelas_pdf(pdf_path):
135
  except Exception as e:
136
  print(f"Erro na extração das tabelas: {str(e)}")
137
  raise
138
-
139
  def obter_disciplinas_validas(df):
140
  """Identifica disciplinas válidas no boletim com seus dados."""
141
  colunas_notas = ['Nota B1', 'Nota B2', 'Nota B3', 'Nota B4']
@@ -148,7 +180,6 @@ def obter_disciplinas_validas(df):
148
  if pd.isna(disciplina) or disciplina == '':
149
  continue
150
 
151
- # Coletar notas e frequências
152
  notas = []
153
  freqs = []
154
  bimestres_cursados = []
@@ -165,7 +196,6 @@ def obter_disciplinas_validas(df):
165
  notas.append(None)
166
  freqs.append(None)
167
 
168
- # Calcular médias apenas se houver dados válidos
169
  if bimestres_cursados:
170
  media_notas = calcular_media_bimestres(notas)
171
  media_freq = calcular_frequencia_media(freqs)
@@ -180,7 +210,7 @@ def obter_disciplinas_validas(df):
180
  })
181
 
182
  return disciplinas_dados
183
-
184
  def gerar_paleta_cores(n_cores):
185
  """Gera uma paleta de cores distintas para o número de disciplinas."""
186
  cores_base = [
@@ -197,7 +227,7 @@ def gerar_paleta_cores(n_cores):
197
 
198
  return cores_base[:n_cores]
199
 
200
- def plotar_evolucao_bimestres(disciplinas_dados, temp_dir):
201
  """Plota gráfico de evolução das notas por bimestre com visualização refinada."""
202
  n_disciplinas = len(disciplinas_dados)
203
 
@@ -252,8 +282,8 @@ def plotar_evolucao_bimestres(disciplinas_dados, temp_dir):
252
  plt.plot(bimestres_deslocados, notas_validas,
253
  color=cores[idx % len(cores)],
254
  marker=marcadores[idx % len(marcadores)],
255
- markersize=7, # Reduzido para menor sobreposição
256
- linewidth=1.5, # Linha mais fina
257
  label=disc_data['disciplina'],
258
  linestyle=estilos_linha[idx % len(estilos_linha)],
259
  alpha=0.8)
@@ -291,8 +321,10 @@ def plotar_evolucao_bimestres(disciplinas_dados, temp_dir):
291
 
292
  anotacoes_usadas[bim_orig].append((nota + y_offset/20, texto))
293
 
294
- plt.title('Evolução das Médias por Disciplina ao Longo dos Bimestres',
295
- pad=20, fontsize=12, fontweight='bold')
 
 
296
  plt.xlabel('Bimestres', fontsize=10)
297
  plt.ylabel('Notas', fontsize=10)
298
  plt.xticks([1, 2, 3, 4], ['1º Bim', '2º Bim', '3º Bim', '4º Bim'])
@@ -312,112 +344,28 @@ def plotar_evolucao_bimestres(disciplinas_dados, temp_dir):
312
 
313
  plt.tight_layout()
314
 
315
- plot_path = os.path.join(temp_dir, 'evolucao_notas.png')
 
 
316
  plt.savefig(plot_path, bbox_inches='tight', dpi=300)
317
  plt.close()
318
  return plot_path
319
 
320
- def plotar_graficos_destacados(disciplinas_dados, temp_dir):
321
- """Plota gráficos de médias e frequências com destaques."""
322
- n_disciplinas = len(disciplinas_dados)
323
-
324
- if not n_disciplinas:
325
- raise ValueError("Nenhuma disciplina válida encontrada no boletim.")
326
-
327
- # Aumentar a figura para melhor visualização
328
- plt.figure(figsize=(12, 10))
329
-
330
- disciplinas = [d['disciplina'] for d in disciplinas_dados]
331
- medias_notas = [d['media_notas'] for d in disciplinas_dados]
332
- medias_freq = [d['media_freq'] for d in disciplinas_dados]
333
-
334
- # Criar subplot com mais espaço entre os gráficos
335
- fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[1, 1])
336
- plt.subplots_adjust(hspace=0.5) # Aumentar espaço entre os gráficos
337
-
338
- cores_notas = ['red' if media < LIMITE_APROVACAO_NOTA else '#2ecc71' for media in medias_notas]
339
- cores_freq = ['red' if media < LIMITE_APROVACAO_FREQ else '#2ecc71' for media in medias_freq]
340
-
341
- media_global = np.mean(medias_notas)
342
- freq_global = np.mean(medias_freq)
343
-
344
- # Gráfico de notas
345
- barras_notas = ax1.bar(disciplinas, medias_notas, color=cores_notas)
346
- ax1.set_title('Média de Notas por Disciplina', pad=20, fontsize=12, fontweight='bold')
347
- ax1.set_ylim(0, ESCALA_MAXIMA_NOTAS)
348
- ax1.grid(True, axis='y', alpha=0.3, linestyle='--')
349
-
350
- # Melhorar a apresentação dos rótulos
351
- ax1.set_xticklabels(disciplinas, rotation=45, ha='right', va='top')
352
- ax1.set_ylabel('Notas', fontsize=10, labelpad=10)
353
-
354
- # Adicionar linha de média mínima
355
- ax1.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.3)
356
- ax1.text(0.02, LIMITE_APROVACAO_NOTA + 0.1, 'Média mínima (5,0)',
357
- transform=ax1.get_yaxis_transform(), color='r', alpha=0.7)
358
-
359
- # Valores nas barras
360
- for barra in barras_notas:
361
- altura = barra.get_height()
362
- ax1.text(barra.get_x() + barra.get_width()/2., altura,
363
- f'{altura:.1f}',
364
- ha='center', va='bottom', fontsize=8)
365
-
366
- # Gráfico de frequências
367
- barras_freq = ax2.bar(disciplinas, medias_freq, color=cores_freq)
368
- ax2.set_title('Frequência Média por Disciplina', pad=20, fontsize=12, fontweight='bold')
369
- ax2.set_ylim(0, 110)
370
- ax2.grid(True, axis='y', alpha=0.3, linestyle='--')
371
-
372
- # Melhorar a apresentação dos rótulos
373
- ax2.set_xticklabels(disciplinas, rotation=45, ha='right', va='top')
374
- ax2.set_ylabel('Frequência (%)', fontsize=10, labelpad=10)
375
-
376
- # Adicionar linha de frequência mínima
377
- ax2.axhline(y=LIMITE_APROVACAO_FREQ, color='r', linestyle='--', alpha=0.3)
378
- ax2.text(0.02, LIMITE_APROVACAO_FREQ + 1, 'Frequência mínima (75%)',
379
- transform=ax2.get_yaxis_transform(), color='r', alpha=0.7)
380
-
381
- # Valores nas barras
382
- for barra in barras_freq:
383
- altura = barra.get_height()
384
- ax2.text(barra.get_x() + barra.get_width()/2., altura,
385
- f'{altura:.1f}%',
386
- ha='center', va='bottom', fontsize=8)
387
-
388
- # Título global com informações de média
389
- plt.suptitle(
390
- f'Desempenho Geral\nMédia Global: {media_global:.1f} | Frequência Global: {freq_global:.1f}%',
391
- y=0.98, fontsize=14, fontweight='bold'
392
- )
393
-
394
- if freq_global < LIMITE_APROVACAO_FREQ:
395
- plt.figtext(0.5, 0.02,
396
- "Atenção: Risco de Reprovação por Baixa Frequência",
397
- ha="center", fontsize=11, color="red", weight='bold')
398
-
399
- plt.tight_layout()
400
-
401
- plot_path = os.path.join(temp_dir, 'medias_frequencias.png')
402
- plt.savefig(plot_path, bbox_inches='tight', dpi=300)
403
- plt.close()
404
- return plot_path
405
-
406
- def gerar_relatorio_pdf(df, disciplinas_dados, grafico1_path, grafico2_path):
407
  """Gera relatório PDF com os gráficos e análises."""
408
  pdf = FPDF()
409
  pdf.set_auto_page_break(auto=True, margin=15)
410
- pdf.add_page()
411
 
412
- # Cabeçalho
 
413
  pdf.set_font('Helvetica', 'B', 18)
414
  pdf.cell(0, 10, 'Relatório de Desempenho Escolar', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
415
- pdf.ln(15) # Aumentar espaço após título
416
 
417
  # Informações do aluno
418
  pdf.set_font('Helvetica', 'B', 12)
419
  pdf.cell(0, 10, 'Informações do Aluno', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
420
- pdf.line(10, pdf.get_y(), 200, pdf.get_y()) # Adicionar linha divisória
421
  pdf.ln(5)
422
 
423
  pdf.set_font('Helvetica', '', 11)
@@ -439,18 +387,31 @@ def gerar_relatorio_pdf(df, disciplinas_dados, grafico1_path, grafico2_path):
439
  pdf.cell(0, 5, f'Data de geração: {data_atual}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='R')
440
  pdf.ln(15)
441
 
442
- # Seção de gráficos
443
  pdf.set_font('Helvetica', 'B', 14)
444
- pdf.cell(0, 10, 'Análise Gráfica', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
445
  pdf.line(10, pdf.get_y(), 200, pdf.get_y())
446
  pdf.ln(10)
 
447
 
448
- pdf.image(grafico1_path, x=10, w=190)
449
- pdf.ln(15)
450
- pdf.image(grafico2_path, x=10, w=190)
451
- pdf.ln(15)
 
 
 
 
 
 
 
 
 
 
 
452
 
453
- # Seção de Análise
 
454
  pdf.set_font('Helvetica', 'B', 14)
455
  pdf.cell(0, 10, 'Análise Detalhada', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
456
  pdf.line(10, pdf.get_y(), 200, pdf.get_y())
@@ -552,15 +513,33 @@ def processar_boletim(file):
552
  if not disciplinas_dados:
553
  return None, "Nenhuma disciplina válida encontrada no boletim."
554
 
 
 
 
 
 
555
  # Gerar gráficos
556
  print("Gerando gráficos...")
557
- grafico1_path = plotar_evolucao_bimestres(disciplinas_dados, temp_dir)
558
- grafico2_path = plotar_graficos_destacados(disciplinas_dados, temp_dir)
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  print("Gráficos gerados")
560
 
561
  # Gerar PDF
562
  print("Gerando relatório PDF...")
563
- pdf_path = gerar_relatorio_pdf(df, disciplinas_dados, grafico1_path, grafico2_path)
564
  print("Relatório PDF gerado")
565
 
566
  # Criar arquivo de retorno
 
14
  matplotlib.use('Agg')
15
 
16
  # Configurações globais
17
+ ESCALA_MAXIMA_NOTAS = 12
18
  LIMITE_APROVACAO_NOTA = 5
19
  LIMITE_APROVACAO_FREQ = 75
20
  BIMESTRES = ['1º Bimestre', '2º Bimestre', '3º Bimestre', '4º Bimestre']
21
+ CONCEITOS_VALIDOS = ['ES', 'EP', 'ET']
22
+
23
+ # Definição das disciplinas de formação básica
24
+ FORMACAO_BASICA = {
25
+ 'fundamental': {
26
+ 'LINGUA PORTUGUESA',
27
+ 'MATEMATICA',
28
+ 'HISTORIA',
29
+ 'GEOGRAFIA',
30
+ 'CIENCIAS',
31
+ 'LINGUA ESTRANGEIRA INGLES',
32
+ 'ARTE',
33
+ 'EDUCACAO FISICA'
34
+ },
35
+ 'medio': {
36
+ 'LINGUA PORTUGUESA',
37
+ 'MATEMATICA',
38
+ 'HISTORIA',
39
+ 'GEOGRAFIA',
40
+ 'BIOLOGIA',
41
+ 'FISICA',
42
+ 'QUIMICA',
43
+ 'INGLES',
44
+ 'FILOSOFIA',
45
+ 'SOCIOLOGIA',
46
+ 'ARTE',
47
+ 'EDUCACAO FISICA'
48
+ }
49
+ }
50
+
51
+ def detectar_nivel_ensino(disciplinas):
52
+ """Detecta se é ensino fundamental ou médio baseado nas disciplinas presentes."""
53
+ disciplinas_set = set(disciplinas)
54
+ disciplinas_exclusivas_medio = {'BIOLOGIA', 'FISICA', 'QUIMICA', 'FILOSOFIA', 'SOCIOLOGIA'}
55
+ return 'medio' if any(d in disciplinas_set for d in disciplinas_exclusivas_medio) else 'fundamental'
56
+
57
+ def separar_disciplinas_por_categoria(disciplinas_dados):
58
+ """Separa as disciplinas em formação básica e diversificada."""
59
+ disciplinas = [d['disciplina'] for d in disciplinas_dados]
60
+ nivel = detectar_nivel_ensino(disciplinas)
61
+
62
+ formacao_basica = []
63
+ diversificada = []
64
+
65
+ for disc_data in disciplinas_dados:
66
+ if disc_data['disciplina'] in FORMACAO_BASICA[nivel]:
67
+ formacao_basica.append(disc_data)
68
+ else:
69
+ diversificada.append(disc_data)
70
+
71
+ return {
72
+ 'nivel': nivel,
73
+ 'formacao_basica': formacao_basica,
74
+ 'diversificada': diversificada
75
+ }
76
 
77
  def converter_nota(valor):
78
  """Converte valor de nota para float, tratando casos especiais e conceitos."""
79
  if pd.isna(valor) or valor == '-' or valor == 'N' or valor == '' or valor == 'None':
80
  return None
81
 
 
82
  if isinstance(valor, str):
83
  valor_limpo = valor.strip().upper()
84
  if valor_limpo in CONCEITOS_VALIDOS:
 
85
  conceitos_map = {'ET': 10, 'ES': 8, 'EP': 6}
86
  return conceitos_map.get(valor_limpo)
87
 
 
88
  try:
89
  return float(valor_limpo.replace(',', '.'))
90
  except:
91
  return None
92
 
 
93
  if isinstance(valor, (int, float)):
94
  return float(valor)
95
 
 
107
  freq_validas = []
108
  for freq in frequencias:
109
  try:
 
110
  if isinstance(freq, str):
111
  freq = freq.strip().replace('%', '').replace(',', '.')
112
  if freq and freq != '-':
113
  valor = float(freq)
114
+ if valor > 0:
115
  freq_validas.append(valor)
116
  except:
117
  continue
 
129
  if len(tables) == 0:
130
  raise ValueError("Nenhuma tabela foi extraída do PDF.")
131
 
 
132
  df = tables[0].df
133
 
 
134
  info_aluno = {}
135
  for i, row in df.iterrows():
136
  if 'Nome do Aluno' in str(row[0]):
 
142
  elif 'Turma' in str(row[0]):
143
  info_aluno['turma'] = row[1].strip() if len(row) > 1 else ''
144
 
 
145
  for i, table in enumerate(tables):
146
  df_temp = table.df
 
147
  if any('Disciplina' in str(col) for col in df_temp.iloc[0]) or \
148
  any('Bimestre' in str(col) for col in df_temp.iloc[0]):
149
  df = df_temp
 
150
  df = df.rename(columns={
151
  0: 'Disciplina',
152
  1: 'Nota B1', 2: 'Freq B1', 3: '%Freq B1', 4: 'AC B1',
 
160
  if df.empty:
161
  raise ValueError("A tabela extraída está vazia.")
162
 
 
 
 
 
 
 
 
 
 
 
 
163
  for key, value in info_aluno.items():
164
  df.attrs[key] = value
165
 
 
168
  except Exception as e:
169
  print(f"Erro na extração das tabelas: {str(e)}")
170
  raise
 
171
  def obter_disciplinas_validas(df):
172
  """Identifica disciplinas válidas no boletim com seus dados."""
173
  colunas_notas = ['Nota B1', 'Nota B2', 'Nota B3', 'Nota B4']
 
180
  if pd.isna(disciplina) or disciplina == '':
181
  continue
182
 
 
183
  notas = []
184
  freqs = []
185
  bimestres_cursados = []
 
196
  notas.append(None)
197
  freqs.append(None)
198
 
 
199
  if bimestres_cursados:
200
  media_notas = calcular_media_bimestres(notas)
201
  media_freq = calcular_frequencia_media(freqs)
 
210
  })
211
 
212
  return disciplinas_dados
213
+
214
  def gerar_paleta_cores(n_cores):
215
  """Gera uma paleta de cores distintas para o número de disciplinas."""
216
  cores_base = [
 
227
 
228
  return cores_base[:n_cores]
229
 
230
+ def plotar_evolucao_bimestres(disciplinas_dados, temp_dir, titulo=None, nome_arquivo=None):
231
  """Plota gráfico de evolução das notas por bimestre com visualização refinada."""
232
  n_disciplinas = len(disciplinas_dados)
233
 
 
282
  plt.plot(bimestres_deslocados, notas_validas,
283
  color=cores[idx % len(cores)],
284
  marker=marcadores[idx % len(marcadores)],
285
+ markersize=7,
286
+ linewidth=1.5,
287
  label=disc_data['disciplina'],
288
  linestyle=estilos_linha[idx % len(estilos_linha)],
289
  alpha=0.8)
 
321
 
322
  anotacoes_usadas[bim_orig].append((nota + y_offset/20, texto))
323
 
324
+ # Usar título personalizado se fornecido
325
+ titulo_grafico = titulo or 'Evolução das Médias por Disciplina ao Longo dos Bimestres'
326
+ plt.title(titulo_grafico, pad=20, fontsize=12, fontweight='bold')
327
+
328
  plt.xlabel('Bimestres', fontsize=10)
329
  plt.ylabel('Notas', fontsize=10)
330
  plt.xticks([1, 2, 3, 4], ['1º Bim', '2º Bim', '3º Bim', '4º Bim'])
 
344
 
345
  plt.tight_layout()
346
 
347
+ # Usar nome de arquivo personalizado se fornecido
348
+ nome_arquivo = nome_arquivo or 'evolucao_notas.png'
349
+ plot_path = os.path.join(temp_dir, nome_arquivo)
350
  plt.savefig(plot_path, bbox_inches='tight', dpi=300)
351
  plt.close()
352
  return plot_path
353
 
354
+ def gerar_relatorio_pdf(df, disciplinas_dados, grafico_basica, grafico_diversificada, grafico_medias):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  """Gera relatório PDF com os gráficos e análises."""
356
  pdf = FPDF()
357
  pdf.set_auto_page_break(auto=True, margin=15)
 
358
 
359
+ # Primeira página - Informações e Formação Básica
360
+ pdf.add_page()
361
  pdf.set_font('Helvetica', 'B', 18)
362
  pdf.cell(0, 10, 'Relatório de Desempenho Escolar', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
363
+ pdf.ln(15)
364
 
365
  # Informações do aluno
366
  pdf.set_font('Helvetica', 'B', 12)
367
  pdf.cell(0, 10, 'Informações do Aluno', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
368
+ pdf.line(10, pdf.get_y(), 200, pdf.get_y())
369
  pdf.ln(5)
370
 
371
  pdf.set_font('Helvetica', '', 11)
 
387
  pdf.cell(0, 5, f'Data de geração: {data_atual}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='R')
388
  pdf.ln(15)
389
 
390
+ # Gráfico de evolução da formação básica
391
  pdf.set_font('Helvetica', 'B', 14)
392
+ pdf.cell(0, 10, 'Evolução das Notas - Formação Geral Básica', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
393
  pdf.line(10, pdf.get_y(), 200, pdf.get_y())
394
  pdf.ln(10)
395
+ pdf.image(grafico_basica, x=10, w=190)
396
 
397
+ # Segunda página - Parte Diversificada
398
+ pdf.add_page()
399
+ pdf.set_font('Helvetica', 'B', 14)
400
+ pdf.cell(0, 10, 'Evolução das Notas - Parte Diversificada', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
401
+ pdf.line(10, pdf.get_y(), 200, pdf.get_y())
402
+ pdf.ln(10)
403
+ pdf.image(grafico_diversificada, x=10, w=190)
404
+
405
+ # Terceira página - Médias e Frequências
406
+ pdf.add_page()
407
+ pdf.set_font('Helvetica', 'B', 14)
408
+ pdf.cell(0, 10, 'Análise de Médias e Frequências', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
409
+ pdf.line(10, pdf.get_y(), 200, pdf.get_y())
410
+ pdf.ln(10)
411
+ pdf.image(grafico_medias, x=10, w=190)
412
 
413
+ # Quarta página - Análise Detalhada
414
+ pdf.add_page()
415
  pdf.set_font('Helvetica', 'B', 14)
416
  pdf.cell(0, 10, 'Análise Detalhada', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
417
  pdf.line(10, pdf.get_y(), 200, pdf.get_y())
 
513
  if not disciplinas_dados:
514
  return None, "Nenhuma disciplina válida encontrada no boletim."
515
 
516
+ # Separar disciplinas por categoria
517
+ categorias = separar_disciplinas_por_categoria(disciplinas_dados)
518
+ nivel = categorias['nivel']
519
+ nivel_texto = "Ensino Médio" if nivel == "medio" else "Ensino Fundamental"
520
+
521
  # Gerar gráficos
522
  print("Gerando gráficos...")
523
+ grafico_basica = plotar_evolucao_bimestres(
524
+ categorias['formacao_basica'],
525
+ temp_dir,
526
+ titulo=f"Evolução das Médias - Formação Geral Básica ({nivel_texto})",
527
+ nome_arquivo='evolucao_basica.png'
528
+ )
529
+
530
+ grafico_diversificada = plotar_evolucao_bimestres(
531
+ categorias['diversificada'],
532
+ temp_dir,
533
+ titulo=f"Evolução das Médias - Parte Diversificada ({nivel_texto})",
534
+ nome_arquivo='evolucao_diversificada.png'
535
+ )
536
+
537
+ grafico_medias = plotar_graficos_destacados(disciplinas_dados, temp_dir)
538
  print("Gráficos gerados")
539
 
540
  # Gerar PDF
541
  print("Gerando relatório PDF...")
542
+ pdf_path = gerar_relatorio_pdf(df, disciplinas_dados, grafico_basica, grafico_diversificada, grafico_medias)
543
  print("Relatório PDF gerado")
544
 
545
  # Criar arquivo de retorno