CUB_PDF / app.py
ESJL's picture
Update app.py
6d76bf1 verified
import re
import os
import tempfile
from datetime import datetime
from pypdf import PdfReader
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
import gradio as gr
# --- CONFIGURAÇÕES SINDUSCON ---
LINHAS = [
"R 1-B (Res. Unifamiliar)", "R 1-N (Res. Unifamiliar)", "R 1-A (Res. Unifamiliar)",
"PP 4-B (Prédio Popular)", "PP 4-N (Prédio Popular)", "R 8-B (Res. Multifamiliar)",
"R 8-N (Res. Multifamiliar)", "R 8-A (Res. Multifamiliar)", "R 16-N (Res. Multifamiliar)",
"R 16-A (Res. Multifamiliar)", "PIS (Projeto Inter. Social)", "RP1Q (Residência Popular)",
"CAL 8-N (Com. Andar Livres)", "CAL 8-A (Com. Andar Livres)", "CSL 8-N (Com.Salas e Lojas)",
"CSL 8-A (Com.Salas e Lojas)", "CSL 16-N (Com.Salas e Lojas)", "CSL 16-A (Com.Salas e Lojas)",
"GI (Galpão Industrial)"
]
NUM_LINHAS = len(LINHAS)
MESES_PT = {i: m for i, m in enumerate(["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"], 1)}
CINZA = PatternFill(start_color="D9D9D9", end_color="D9D9D9", fill_type="solid")
def limpar_e_converter(valor_str):
"""Converte string '1.234,56' ou '-0,45' em float."""
try:
# Remove pontos de milhar e troca vírgula por ponto
return float(valor_str.replace(".", "").replace(",", "."))
except:
return None
def processar_pdf_universal(arquivo_pdf, anos_retroativos):
if arquivo_pdf is None: return None, "Aguardando arquivo..."
log = []
dados_globais = {}
eh_porcentagem = False # Flag para detectar tipo de dado
try:
reader = PdfReader(arquivo_pdf.name)
ano_limite = datetime.now().year - anos_retroativos
for i, page in enumerate(reader.pages):
text = page.extract_text()
# Busca o ano na página
ano_match = re.search(r"(?:20)\d{2}", text)
if not ano_match: continue
ano_pag = int(ano_match.group(0))
if ano_pag < ano_limite: continue
# Detecta se é PDF de variação (geralmente contém o símbolo % no texto)
if "%" in text: eh_porcentagem = True
# REGEX UNIVERSAL: Pega números como 2.500,00 | 900,00 | 0,45 | -0,10
# Explicação: Sinal opcional | dígitos com pontos opcionais | vírgula | 2 decimais
padrao = r"-?\d{1,3}(?:\.\d{3})*,\d{2}"
valores_encontrados = re.findall(padrao, text)
valores_float = [limpar_e_converter(v) for v in valores_encontrados]
if not valores_float: continue
num_meses = len(valores_float) // NUM_LINHAS
if num_meses == 0: continue
for linha_idx in range(NUM_LINHAS):
for mes_off in range(num_meses):
pos = (linha_idx * num_meses) + mes_off
if pos < len(valores_float):
mes_idx = mes_off + 1
dados_globais[(linha_idx, f"{ano_pag}-{mes_idx:02d}")] = valores_float[pos]
log.append(f"Página {i+1} (Ano {ano_pag}) processada.")
if not dados_globais: return None, "Nenhum dado extraído."
# Gerar Excel
colunas = sorted(set(c for _, c in dados_globais.keys()))
wb = Workbook()
ws = wb.active
ws.title = "Dados CUB-RS"
# Cabeçalho
ws.append(["PROJETO-PADRÃO"] + [f"{MESES_PT[int(c.split('-')[1])]}/{c.split('-')[0]}" for c in colunas])
for l_idx, nome in enumerate(LINHAS):
row = [nome] + [dados_globais.get((l_idx, c)) for c in colunas]
ws.append(row)
if (l_idx // 3) % 2 == 0:
for c_idx in range(1, len(colunas) + 2):
ws.cell(row=ws.max_row, column=c_idx).fill = CINZA
# Formatação de Células
for r in range(2, ws.max_row + 1):
for c in range(2, ws.max_column + 1):
cell = ws.cell(r, c)
if eh_porcentagem:
cell.number_format = '0.00"%"'
else:
cell.number_format = '#,##0.00'
ws.column_dimensions['A'].width = 30
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
wb.save(temp_file.name)
tipo = "Variação (%)" if eh_porcentagem else "Valores (R$)"
return temp_file.name, f"Tipo detectado: {tipo}\n" + "\n".join(log)
except Exception as e:
return None, f"Erro: {str(e)}"
# Interface Gradio permanece a mesma...
demo = gr.Interface(
fn=processar_pdf_universal,
inputs=[gr.File(label="Suba o PDF (Valores ou Variação)"), gr.Slider(1, 25, 5, label="Anos")],
outputs=[gr.File(label="Download"), gr.Textbox(label="Log")],
title="🏗️ Extrator CUB-RS"
)
demo.launch()