Spaces:
Sleeping
Sleeping
import gradio as gr | |
import camelot | |
import pandas as pd | |
import matplotlib.pyplot as plt | |
import numpy as np | |
from fpdf import FPDF | |
from fpdf.enums import XPos, YPos | |
import tempfile | |
import os | |
import matplotlib | |
import shutil | |
matplotlib.use('Agg') | |
def extrair_tabelas_pdf(pdf_path): | |
"""Extrai tabelas do PDF e retorna um DataFrame processado.""" | |
try: | |
# Extrair tabelas do PDF usando o método 'lattice' | |
tables = camelot.read_pdf(pdf_path, pages='all', flavor='lattice') | |
print(f"Tabelas extraídas: {len(tables)}") | |
if len(tables) == 0: | |
raise ValueError("Nenhuma tabela foi extraída do PDF.") | |
# Processar a primeira tabela | |
df = tables[0].df | |
# Verificar se a tabela tem conteúdo | |
if df.empty: | |
raise ValueError("A tabela extraída está vazia.") | |
# Salvar todas as tabelas extraídas em CSV (para debug) | |
temp_dir = os.path.dirname(pdf_path) | |
for i, table in enumerate(tables): | |
csv_path = os.path.join(temp_dir, f'boletim_extraido_{i+1}.csv') | |
table.to_csv(csv_path) | |
print(f"Tabela {i+1} salva como CSV em {csv_path}") | |
return df | |
except Exception as e: | |
print(f"Erro na extração das tabelas: {str(e)}") | |
raise | |
def converter_nota(valor): | |
"""Converte valor de nota para float, tratando casos especiais.""" | |
if pd.isna(valor) or valor == '-' or valor == 'N': | |
return 0 | |
try: | |
return float(str(valor).replace(',', '.')) # Tratar decimal com vírgula | |
except: | |
return 0 | |
def plotar_evolucao_bimestres(df_filtrado, temp_dir): | |
"""Plota gráfico de evolução das notas por bimestre.""" | |
plt.figure(figsize=(12, 6)) | |
disciplinas_basicas = ['LINGUA PORTUGUESA', 'ARTE', 'LINGUA ESTRANGEIRA INGLES', | |
'GEOGRAFIA', 'CIENCIAS', 'HISTORIA', 'MATEMATICA'] | |
estilos = { | |
'LINGUA PORTUGUESA': {'cor': '#DC143C', 'marcador': 'p', 'zorder': 1, 'linestyle': '-', 'desloc': 0.1}, | |
'ARTE': {'cor': '#4169E1', 'marcador': 'D', 'zorder': 2, 'linestyle': '--', 'desloc': 0.08}, | |
'LINGUA ESTRANGEIRA INGLES': {'cor': '#9370DB', 'marcador': 'h', 'zorder': 3, 'linestyle': '-.', 'desloc': 0.06}, | |
'GEOGRAFIA': {'cor': '#32CD32', 'marcador': '^', 'zorder': 4, 'linestyle': ':', 'desloc': 0.04}, | |
'CIENCIAS': {'cor': '#FF8C00', 'marcador': 's', 'zorder': 5, 'linestyle': '-', 'desloc': 0.02}, | |
'HISTORIA': {'cor': '#00CED1', 'marcador': '*', 'zorder': 6, 'linestyle': '--', 'desloc': -0.02}, | |
'MATEMATICA': {'cor': '#FF69B4', 'marcador': 'o', 'zorder': 7, 'linestyle': '-.', 'desloc': -0.04} | |
} | |
plt.grid(True, linestyle='--', alpha=0.3, zorder=0) | |
colunas_notas = ['Nota B1', 'Nota B2', 'Nota B3', 'Nota B4'] | |
for disciplina in disciplinas_basicas: | |
dados_disciplina = df_filtrado[df_filtrado['Disciplina'] == disciplina] | |
if not dados_disciplina.empty: | |
notas = dados_disciplina[colunas_notas].values[0] | |
notas_validas = notas > 0 | |
if any(notas_validas): | |
bimestres = np.arange(1, len(colunas_notas) + 1)[notas_validas] | |
notas_filtradas = notas[notas_validas] | |
estilo = estilos[disciplina] | |
notas_deslocadas = notas_filtradas + estilo['desloc'] | |
plt.plot(bimestres, notas_deslocadas, | |
color=estilo['cor'], | |
marker=estilo['marcador'], | |
markersize=10, | |
linewidth=2.5, | |
label=disciplina, | |
zorder=estilo['zorder'], | |
linestyle=estilo['linestyle'], | |
alpha=0.8) | |
for x, y in zip(bimestres, notas_filtradas): | |
plt.annotate(f"{y:.1f}", (x, y), textcoords="offset points", xytext=(0, 10), ha='center') | |
plt.title('Evolução das Médias por Disciplina ao Longo dos Bimestres') | |
plt.xlabel('Bimestres') | |
plt.ylabel('Média de Notas') | |
plt.xticks([1, 2, 3, 4], ['B1', 'B2', 'B3', 'B4']) | |
plt.ylim(0, 10) | |
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') | |
plt.tight_layout() | |
plot_path = os.path.join(temp_dir, 'evolucao_notas.png') | |
plt.savefig(plot_path, bbox_inches='tight', dpi=300) | |
plt.close() | |
return plot_path | |
def plotar_graficos_destacados(df_boletim_clean, temp_dir): | |
"""Plota gráficos de médias e frequências com destaques.""" | |
plt.figure(figsize=(12, 6)) | |
disciplinas = df_boletim_clean['Disciplina'].astype(str) | |
# Processar frequências (remover % e converter para número) | |
colunas_freq = ['%Freq B1', '%Freq B2', '%Freq B3', '%Freq B4'] | |
freq_data = df_boletim_clean[colunas_freq].replace('%', '', regex=True) | |
medias_frequencia = freq_data.apply(pd.to_numeric, errors='coerce').mean(axis=1) | |
medias_notas = df_boletim_clean[['Nota B1', 'Nota B2', 'Nota B3', 'Nota B4']].apply(pd.to_numeric, errors='coerce').mean(axis=1) | |
cores_notas = ['red' if media < 5 else 'blue' for media in medias_notas] | |
cores_frequencias = ['red' if media < 75 else 'green' for media in medias_frequencia] | |
frequencia_global_media = medias_frequencia.mean() | |
plt.subplot(1, 2, 1) | |
plt.bar(disciplinas, medias_notas, color=cores_notas) | |
plt.title('Média de Notas por Disciplina (Vermelho: < 5)') | |
plt.xticks(rotation=90) | |
plt.ylim(0, 10) | |
plt.subplot(1, 2, 2) | |
plt.bar(disciplinas, medias_frequencia, color=cores_frequencias) | |
plt.title('Média de Frequência por Disciplina (Vermelho: < 75%)') | |
plt.xticks(rotation=90) | |
plt.ylim(0, 100) | |
plt.suptitle(f"Frequência Global Média: {frequencia_global_media:.2f}%") | |
if frequencia_global_media < 75: | |
plt.figtext(0.5, 0.01, "Cuidado: Risco de Reprovação por Baixa Frequência", ha="center", fontsize=12, color="red") | |
plt.tight_layout() | |
plot_path = os.path.join(temp_dir, 'medias_frequencias.png') | |
plt.savefig(plot_path, bbox_inches='tight', dpi=300) | |
plt.close() | |
return plot_path | |
def gerar_relatorio_pdf(df, grafico1_path, grafico2_path): | |
"""Gera relatório PDF com os gráficos e análises.""" | |
pdf = FPDF() | |
pdf.add_page() | |
pdf.set_font('Helvetica', 'B', 16) | |
pdf.cell(0, 10, 'Relatório de Desempenho Escolar', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='C') | |
pdf.ln(10) | |
pdf.image(grafico1_path, x=10, w=190) | |
pdf.ln(10) | |
pdf.image(grafico2_path, x=10, w=190) | |
pdf.ln(10) | |
pdf.set_font('Helvetica', 'B', 12) | |
pdf.cell(0, 10, 'Avisos Importantes:', 0, new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L') | |
pdf.set_font('Helvetica', '', 10) | |
# Calcular médias | |
medias_notas = df[['Nota B1', 'Nota B2', 'Nota B3', 'Nota B4']].apply(pd.to_numeric, errors='coerce').mean(axis=1) | |
# Processar frequências | |
colunas_freq = ['%Freq B1', '%Freq B2', '%Freq B3', '%Freq B4'] | |
freq_data = df[colunas_freq].replace('%', '', regex=True) | |
medias_freq = freq_data.apply(pd.to_numeric, errors='coerce').mean(axis=1) | |
for idx, (disciplina, media_nota, media_freq) in enumerate(zip(df['Disciplina'], medias_notas, medias_freq)): | |
if media_nota < 5: | |
pdf.cell(0, 10, f'- {disciplina}: Média de notas abaixo de 5 ({media_nota:.1f})', 0, | |
new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L') | |
if media_freq < 75: | |
pdf.cell(0, 10, f'- {disciplina}: Frequência abaixo de 75% ({media_freq:.1f}%)', 0, | |
new_x=XPos.LMARGIN, new_y=YPos.NEXT, align='L') | |
temp_pdf = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') | |
pdf_path = temp_pdf.name | |
pdf.output(pdf_path) | |
return pdf_path | |
def processar_boletim(file): | |
"""Função principal que processa o boletim e gera o relatório.""" | |
temp_dir = None | |
try: | |
# Verificar se o arquivo é válido | |
if file is None: | |
return None, "Nenhum arquivo foi fornecido." | |
# Criar diretório temporário | |
temp_dir = tempfile.mkdtemp() | |
print(f"Diretório temporário criado: {temp_dir}") | |
# Verificar se o arquivo tem conteúdo | |
if not hasattr(file, 'name') or not os.path.exists(file.name): | |
return None, "Arquivo inválido ou corrompido." | |
if os.path.getsize(file.name) == 0: | |
return None, "O arquivo está vazio." | |
# Copiar o arquivo para o diretório temporário | |
temp_pdf = os.path.join(temp_dir, 'boletim.pdf') | |
shutil.copy2(file.name, temp_pdf) | |
print(f"PDF copiado para: {temp_pdf}") | |
# Verificar se a cópia foi bem sucedida | |
if not os.path.exists(temp_pdf) or os.path.getsize(temp_pdf) == 0: | |
return None, "Erro ao copiar o arquivo." | |
# Extrair tabelas do PDF | |
print("Iniciando extração das tabelas...") | |
df = extrair_tabelas_pdf(temp_pdf) | |
print("Tabelas extraídas com sucesso") | |
if df is None or df.empty: | |
return None, "Não foi possível extrair dados do PDF." | |
# Renomear colunas para o formato esperado | |
try: | |
df.columns = ['Disciplina', 'Nota B1', 'Freq B1', '%Freq B1', 'AC B1', | |
'Nota B2', 'Freq B2', '%Freq B2', 'AC B2', | |
'Nota B3', 'Freq B3', '%Freq B3', 'AC B3', | |
'Nota B4', 'Freq B4', '%Freq B4', 'AC B4', | |
'CF', 'Nota Final', 'Freq Final', 'AC Final'] | |
except: | |
return None, "O formato do PDF não corresponde ao esperado." | |
# Processar notas | |
colunas_notas = ['Nota B1', 'Nota B2', 'Nota B3', 'Nota B4'] | |
for col in colunas_notas: | |
df[col] = df[col].apply(converter_nota) | |
print("Notas processadas") | |
# Gerar gráficos | |
print("Gerando gráficos...") | |
grafico1_path = plotar_evolucao_bimestres(df, temp_dir) | |
grafico2_path = plotar_graficos_destacados(df, temp_dir) | |
print("Gráficos gerados") | |
# Gerar PDF | |
print("Gerando relatório PDF...") | |
pdf_path = gerar_relatorio_pdf(df, grafico1_path, grafico2_path) | |
print("Relatório PDF gerado") | |
# Criar arquivo temporário para retorno | |
output_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') | |
output_path = output_file.name | |
shutil.copy2(pdf_path, output_path) | |
return output_path, "Relatório gerado com sucesso!" | |
except Exception as e: | |
print(f"Erro durante o processamento: {str(e)}") | |
return None, f"Erro ao processar o boletim: {str(e)}" | |
finally: | |
# Limpar arquivos temporários | |
if temp_dir and os.path.exists(temp_dir): | |
try: | |
shutil.rmtree(temp_dir) | |
print("Arquivos temporários limpos") | |
except Exception as e: | |
print(f"Erro ao limpar arquivos temporários: {str(e)}") | |
# Interface Gradio | |
iface = gr.Interface( | |
fn=processar_boletim, | |
inputs=gr.File(label="Upload do Boletim (PDF)", type="filepath"), | |
outputs=[ | |
gr.File(label="Relatório (PDF)"), | |
gr.Textbox(label="Status") | |
], | |
title="Análise de Boletim Escolar", | |
description="Faça upload do boletim em PDF para gerar um relatório com análises e visualizações.", | |
allow_flagging="never" | |
) | |
if __name__ == "__main__": | |
iface.launch(server_name="0.0.0.0") |