Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -272,68 +272,77 @@ def plotar_graficos_destacados(disciplinas_dados, temp_dir):
|
|
272 |
if not n_disciplinas:
|
273 |
raise ValueError("Nenhuma disciplina válida encontrada no boletim.")
|
274 |
|
275 |
-
#
|
276 |
-
plt.figure(figsize=(
|
277 |
|
278 |
disciplinas = [d['disciplina'] for d in disciplinas_dados]
|
279 |
medias_notas = [d['media_notas'] for d in disciplinas_dados]
|
280 |
medias_freq = [d['media_freq'] for d in disciplinas_dados]
|
281 |
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
|
|
|
|
286 |
|
287 |
media_global = np.mean(medias_notas)
|
288 |
freq_global = np.mean(medias_freq)
|
289 |
|
290 |
# Gráfico de notas
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
|
|
|
|
|
|
296 |
|
297 |
# Adicionar linha de média mínima
|
298 |
-
|
299 |
-
|
300 |
-
transform=
|
301 |
|
302 |
# Valores nas barras
|
303 |
for barra in barras_notas:
|
304 |
altura = barra.get_height()
|
305 |
-
|
306 |
f'{altura:.1f}',
|
307 |
ha='center', va='bottom', fontsize=8)
|
308 |
|
309 |
# Gráfico de frequências
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
|
|
|
|
|
|
315 |
|
316 |
# Adicionar linha de frequência mínima
|
317 |
-
|
318 |
-
|
319 |
-
transform=
|
320 |
|
321 |
# Valores nas barras
|
322 |
for barra in barras_freq:
|
323 |
altura = barra.get_height()
|
324 |
-
|
325 |
f'{altura:.1f}%',
|
326 |
ha='center', va='bottom', fontsize=8)
|
327 |
|
|
|
328 |
plt.suptitle(
|
329 |
-
f'
|
330 |
-
y=0.
|
331 |
)
|
332 |
|
333 |
if freq_global < LIMITE_APROVACAO_FREQ:
|
334 |
plt.figtext(0.5, 0.02,
|
335 |
-
"Atenção: Risco de Reprovação por Baixa Frequência",
|
336 |
-
ha="center", fontsize=
|
337 |
|
338 |
plt.tight_layout()
|
339 |
|
@@ -349,15 +358,20 @@ def gerar_relatorio_pdf(df, disciplinas_dados, grafico1_path, grafico2_path):
|
|
349 |
pdf.add_page()
|
350 |
|
351 |
# Cabeçalho
|
352 |
-
pdf.set_font('Helvetica', 'B',
|
353 |
pdf.cell(0, 10, 'Relatório de Desempenho Escolar', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
|
354 |
-
pdf.ln(
|
355 |
|
356 |
# Informações do aluno
|
|
|
|
|
|
|
|
|
|
|
357 |
pdf.set_font('Helvetica', '', 11)
|
358 |
if hasattr(df, 'attrs'):
|
359 |
if 'nome' in df.attrs:
|
360 |
-
pdf.cell(0, 7, f'
|
361 |
if 'ra' in df.attrs:
|
362 |
pdf.cell(0, 7, f'RA: {df.attrs["ra"]}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
363 |
if 'escola' in df.attrs:
|
@@ -365,24 +379,30 @@ def gerar_relatorio_pdf(df, disciplinas_dados, grafico1_path, grafico2_path):
|
|
365 |
if 'turma' in df.attrs:
|
366 |
pdf.cell(0, 7, f'Turma: {df.attrs["turma"]}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
367 |
|
368 |
-
pdf.ln(
|
369 |
|
370 |
# Data do relatório
|
371 |
data_atual = datetime.now().strftime('%d/%m/%Y')
|
372 |
pdf.set_font('Helvetica', 'I', 10)
|
373 |
-
pdf.cell(0, 5, f'
|
|
|
|
|
|
|
|
|
|
|
|
|
374 |
pdf.ln(10)
|
375 |
|
376 |
-
# Gráficos
|
377 |
pdf.image(grafico1_path, x=10, w=190)
|
378 |
-
pdf.ln(
|
379 |
pdf.image(grafico2_path, x=10, w=190)
|
380 |
-
pdf.ln(
|
381 |
|
382 |
# Seção de Análise
|
383 |
pdf.set_font('Helvetica', 'B', 14)
|
384 |
-
pdf.cell(0, 10, 'Análise
|
385 |
-
pdf.
|
|
|
386 |
|
387 |
# Calcular médias globais
|
388 |
medias_notas = [d['media_notas'] for d in disciplinas_dados]
|
@@ -391,15 +411,19 @@ def gerar_relatorio_pdf(df, disciplinas_dados, grafico1_path, grafico2_path):
|
|
391 |
freq_global = np.mean(medias_freq)
|
392 |
|
393 |
# Resumo geral
|
|
|
|
|
|
|
|
|
394 |
pdf.set_font('Helvetica', '', 11)
|
395 |
pdf.cell(0, 7, f'Média Global: {media_global:.1f}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
396 |
pdf.cell(0, 7, f'Frequência Global: {freq_global:.1f}%', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
397 |
-
pdf.ln(
|
398 |
|
399 |
# Avisos Importantes
|
400 |
pdf.set_font('Helvetica', 'B', 12)
|
401 |
-
pdf.cell(0, 10, '
|
402 |
-
pdf.ln(
|
403 |
|
404 |
pdf.set_font('Helvetica', '', 10)
|
405 |
|
@@ -417,15 +441,19 @@ def gerar_relatorio_pdf(df, disciplinas_dados, grafico1_path, grafico2_path):
|
|
417 |
|
418 |
if disciplinas_risco:
|
419 |
for disc, avisos in disciplinas_risco:
|
|
|
|
|
|
|
420 |
for aviso in avisos:
|
421 |
-
pdf.cell(
|
|
|
422 |
else:
|
423 |
pdf.cell(0, 7, 'Nenhum problema identificado.', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
424 |
|
425 |
-
pdf.ln(5)
|
426 |
-
|
427 |
# Rodapé
|
428 |
pdf.set_y(-30)
|
|
|
|
|
429 |
pdf.set_font('Helvetica', 'I', 8)
|
430 |
pdf.cell(0, 10, 'Este relatório é uma análise automática e deve ser validado junto à secretaria da escola.',
|
431 |
0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
|
|
|
272 |
if not n_disciplinas:
|
273 |
raise ValueError("Nenhuma disciplina válida encontrada no boletim.")
|
274 |
|
275 |
+
# Aumentar a figura para melhor visualização
|
276 |
+
plt.figure(figsize=(12, 10))
|
277 |
|
278 |
disciplinas = [d['disciplina'] for d in disciplinas_dados]
|
279 |
medias_notas = [d['media_notas'] for d in disciplinas_dados]
|
280 |
medias_freq = [d['media_freq'] for d in disciplinas_dados]
|
281 |
|
282 |
+
# Criar subplot com mais espaço entre os gráficos
|
283 |
+
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[1, 1])
|
284 |
+
plt.subplots_adjust(hspace=0.5) # Aumentar espaço entre os gráficos
|
285 |
+
|
286 |
+
cores_notas = ['red' if media < LIMITE_APROVACAO_NOTA else '#2ecc71' for media in medias_notas]
|
287 |
+
cores_freq = ['red' if media < LIMITE_APROVACAO_FREQ else '#2ecc71' for media in medias_freq]
|
288 |
|
289 |
media_global = np.mean(medias_notas)
|
290 |
freq_global = np.mean(medias_freq)
|
291 |
|
292 |
# Gráfico de notas
|
293 |
+
barras_notas = ax1.bar(disciplinas, medias_notas, color=cores_notas)
|
294 |
+
ax1.set_title('Média de Notas por Disciplina', pad=20, fontsize=12, fontweight='bold')
|
295 |
+
ax1.set_ylim(0, ESCALA_MAXIMA_NOTAS)
|
296 |
+
ax1.grid(True, axis='y', alpha=0.3, linestyle='--')
|
297 |
+
|
298 |
+
# Melhorar a apresentação dos rótulos
|
299 |
+
ax1.set_xticklabels(disciplinas, rotation=45, ha='right', va='top')
|
300 |
+
ax1.set_ylabel('Notas', fontsize=10, labelpad=10)
|
301 |
|
302 |
# Adicionar linha de média mínima
|
303 |
+
ax1.axhline(y=LIMITE_APROVACAO_NOTA, color='r', linestyle='--', alpha=0.3)
|
304 |
+
ax1.text(0.02, LIMITE_APROVACAO_NOTA + 0.1, 'Média mínima (5,0)',
|
305 |
+
transform=ax1.get_yaxis_transform(), color='r', alpha=0.7)
|
306 |
|
307 |
# Valores nas barras
|
308 |
for barra in barras_notas:
|
309 |
altura = barra.get_height()
|
310 |
+
ax1.text(barra.get_x() + barra.get_width()/2., altura,
|
311 |
f'{altura:.1f}',
|
312 |
ha='center', va='bottom', fontsize=8)
|
313 |
|
314 |
# Gráfico de frequências
|
315 |
+
barras_freq = ax2.bar(disciplinas, medias_freq, color=cores_freq)
|
316 |
+
ax2.set_title('Frequência Média por Disciplina', pad=20, fontsize=12, fontweight='bold')
|
317 |
+
ax2.set_ylim(0, 110)
|
318 |
+
ax2.grid(True, axis='y', alpha=0.3, linestyle='--')
|
319 |
+
|
320 |
+
# Melhorar a apresentação dos rótulos
|
321 |
+
ax2.set_xticklabels(disciplinas, rotation=45, ha='right', va='top')
|
322 |
+
ax2.set_ylabel('Frequência (%)', fontsize=10, labelpad=10)
|
323 |
|
324 |
# Adicionar linha de frequência mínima
|
325 |
+
ax2.axhline(y=LIMITE_APROVACAO_FREQ, color='r', linestyle='--', alpha=0.3)
|
326 |
+
ax2.text(0.02, LIMITE_APROVACAO_FREQ + 1, 'Frequência mínima (75%)',
|
327 |
+
transform=ax2.get_yaxis_transform(), color='r', alpha=0.7)
|
328 |
|
329 |
# Valores nas barras
|
330 |
for barra in barras_freq:
|
331 |
altura = barra.get_height()
|
332 |
+
ax2.text(barra.get_x() + barra.get_width()/2., altura,
|
333 |
f'{altura:.1f}%',
|
334 |
ha='center', va='bottom', fontsize=8)
|
335 |
|
336 |
+
# Título global com informações de média
|
337 |
plt.suptitle(
|
338 |
+
f'Desempenho Geral\nMédia Global: {media_global:.1f} | Frequência Global: {freq_global:.1f}%',
|
339 |
+
y=0.98, fontsize=14, fontweight='bold'
|
340 |
)
|
341 |
|
342 |
if freq_global < LIMITE_APROVACAO_FREQ:
|
343 |
plt.figtext(0.5, 0.02,
|
344 |
+
"⚠️ Atenção: Risco de Reprovação por Baixa Frequência",
|
345 |
+
ha="center", fontsize=11, color="red", weight='bold')
|
346 |
|
347 |
plt.tight_layout()
|
348 |
|
|
|
358 |
pdf.add_page()
|
359 |
|
360 |
# Cabeçalho
|
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) # Aumentar espaço após título
|
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()) # Adicionar linha divisória
|
369 |
+
pdf.ln(5)
|
370 |
+
|
371 |
pdf.set_font('Helvetica', '', 11)
|
372 |
if hasattr(df, 'attrs'):
|
373 |
if 'nome' in df.attrs:
|
374 |
+
pdf.cell(0, 7, f'Nome: {df.attrs["nome"]}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
375 |
if 'ra' in df.attrs:
|
376 |
pdf.cell(0, 7, f'RA: {df.attrs["ra"]}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
377 |
if 'escola' in df.attrs:
|
|
|
379 |
if 'turma' in df.attrs:
|
380 |
pdf.cell(0, 7, f'Turma: {df.attrs["turma"]}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
381 |
|
382 |
+
pdf.ln(10)
|
383 |
|
384 |
# Data do relatório
|
385 |
data_atual = datetime.now().strftime('%d/%m/%Y')
|
386 |
pdf.set_font('Helvetica', 'I', 10)
|
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 |
+
# Seção de gráficos
|
391 |
+
pdf.set_font('Helvetica', 'B', 14)
|
392 |
+
pdf.cell(0, 10, 'Análise Gráfica', 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 |
|
|
|
396 |
pdf.image(grafico1_path, x=10, w=190)
|
397 |
+
pdf.ln(15)
|
398 |
pdf.image(grafico2_path, x=10, w=190)
|
399 |
+
pdf.ln(15)
|
400 |
|
401 |
# Seção de Análise
|
402 |
pdf.set_font('Helvetica', 'B', 14)
|
403 |
+
pdf.cell(0, 10, 'Análise Detalhada', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
404 |
+
pdf.line(10, pdf.get_y(), 200, pdf.get_y())
|
405 |
+
pdf.ln(10)
|
406 |
|
407 |
# Calcular médias globais
|
408 |
medias_notas = [d['media_notas'] for d in disciplinas_dados]
|
|
|
411 |
freq_global = np.mean(medias_freq)
|
412 |
|
413 |
# Resumo geral
|
414 |
+
pdf.set_font('Helvetica', 'B', 12)
|
415 |
+
pdf.cell(0, 7, 'Resumo Geral:', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
416 |
+
pdf.ln(5)
|
417 |
+
|
418 |
pdf.set_font('Helvetica', '', 11)
|
419 |
pdf.cell(0, 7, f'Média Global: {media_global:.1f}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
420 |
pdf.cell(0, 7, f'Frequência Global: {freq_global:.1f}%', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
421 |
+
pdf.ln(10)
|
422 |
|
423 |
# Avisos Importantes
|
424 |
pdf.set_font('Helvetica', 'B', 12)
|
425 |
+
pdf.cell(0, 10, 'Pontos de Atenção:', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
426 |
+
pdf.ln(5)
|
427 |
|
428 |
pdf.set_font('Helvetica', '', 10)
|
429 |
|
|
|
441 |
|
442 |
if disciplinas_risco:
|
443 |
for disc, avisos in disciplinas_risco:
|
444 |
+
pdf.set_font('Helvetica', 'B', 10)
|
445 |
+
pdf.cell(0, 7, f'• {disc}:', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
446 |
+
pdf.set_font('Helvetica', '', 10)
|
447 |
for aviso in avisos:
|
448 |
+
pdf.cell(10) # Indentação
|
449 |
+
pdf.cell(0, 7, f'- {aviso}', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
450 |
else:
|
451 |
pdf.cell(0, 7, 'Nenhum problema identificado.', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L')
|
452 |
|
|
|
|
|
453 |
# Rodapé
|
454 |
pdf.set_y(-30)
|
455 |
+
pdf.line(10, pdf.get_y(), 200, pdf.get_y())
|
456 |
+
pdf.ln(5)
|
457 |
pdf.set_font('Helvetica', 'I', 8)
|
458 |
pdf.cell(0, 10, 'Este relatório é uma análise automática e deve ser validado junto à secretaria da escola.',
|
459 |
0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C')
|