""" utils.py Módulo de utilitários e funções acessórias. Contém: - Formatadores de texto e moeda - Extrator de dados do PDF do SIAT """ import re import json from typing import Tuple, List, Dict from pypdf import PdfReader # ============================================================================ # FORMATADORES (Antigo formatters.py) # ============================================================================ def numero_para_extenso(valor: float) -> str: """Converte um valor numérico para sua representação por extenso em português.""" if valor == 0: return "zero reais" unidades = ['', 'um', 'dois', 'três', 'quatro', 'cinco', 'seis', 'sete', 'oito', 'nove'] especiais = ['dez', 'onze', 'doze', 'treze', 'quatorze', 'quinze', 'dezesseis', 'dezessete', 'dezoito', 'dezenove'] dezenas = ['', '', 'vinte', 'trinta', 'quarenta', 'cinquenta', 'sessenta', 'setenta', 'oitenta', 'noventa'] centenas = ['', 'cento', 'duzentos', 'trezentos', 'quatrocentos', 'quinhentos', 'seiscentos', 'setecentos', 'oitocentos', 'novecentos'] def converter_grupo(n): if n == 0: return '' if n == 100: return 'cem' resultado = '' if n >= 100: resultado += centenas[n // 100] n %= 100 if n > 0: resultado += ' e ' if n >= 20: resultado += dezenas[n // 10] n %= 10 if n > 0: resultado += ' e ' + unidades[n] elif n >= 10: resultado += especiais[n - 10] elif n > 0: resultado += unidades[n] return resultado parte_inteira = int(valor) centavos = round((valor - parte_inteira) * 100) resultado = '' if parte_inteira >= 1000000: milhoes = parte_inteira // 1000000 if milhoes == 1: resultado += 'um milhão' else: resultado += converter_grupo(milhoes) + ' milhões' parte_inteira %= 1000000 if parte_inteira > 0: resultado += ' e ' if parte_inteira >= 1000: milhares = parte_inteira // 1000 if milhares == 1: resultado += 'mil' else: resultado += converter_grupo(milhares) + ' mil' parte_inteira %= 1000 if parte_inteira > 0: resultado += ' e ' if parte_inteira > 0: resultado += converter_grupo(parte_inteira) if resultado: if int(valor) == 1: resultado += ' real' else: resultado += ' reais' if centavos > 0: if resultado: resultado += ' e ' resultado += converter_grupo(centavos) if centavos == 1: resultado += ' centavo' else: resultado += ' centavos' return resultado def parse_valor_monetario(valor_str: str) -> float: """Converte string de valor monetário para float.""" if not valor_str: return 0.0 valor_limpo = re.sub(r'[^\d,.]', '', valor_str) valor_limpo = valor_limpo.replace('.', '').replace(',', '.') try: return float(valor_limpo) except ValueError: return 0.0 def formatar_valor_monetario(valor_str: str) -> Tuple[str, str]: """Formata valor monetário e retorna tupla (valor_formatado, extenso).""" valor_float = parse_valor_monetario(valor_str) if valor_float == 0: return valor_str, "" valor_formatado = f"R$ {valor_float:,.2f}".replace(',', 'X').replace('.', ',').replace('X', '.') extenso = numero_para_extenso(valor_float) return valor_formatado, extenso def aplicar_mascara_monetaria(valor: str) -> str: """Aplica máscara monetária brasileira a um valor.""" if not valor: return "" valor = valor.strip() if re.match(r'^R\$\s*[\d\.]+,\d{2}$', valor): return valor if re.search(r'[a-zA-ZáéíóúãõâêîôûàèìòùçÁÉÍÓÚÃÕÂÊÎÔÛÀÈÌÒÙÇ]', valor): return valor valor_limpo = re.sub(r'R\$\s*', '', valor) if ',' in valor_limpo and '.' in valor_limpo: valor_limpo = valor_limpo.replace('.', '').replace(',', '.') elif ',' in valor_limpo: valor_limpo = valor_limpo.replace(',', '.') try: valor_float = float(valor_limpo) except ValueError: return valor return f"R$ {valor_float:,.2f}".replace(',', 'X').replace('.', ',').replace('X', '.') def formatar_motivos_para_documento(motivos: List[str]) -> str: """Formata lista de motivos para inclusão no documento.""" motivos_limpos = [m.strip() for m in motivos if m and m.strip()] if not motivos_limpos: return "" return "\n".join([f"- {m};" for m in motivos_limpos]) def formatar_documentacao_para_documento(docs: List[str]) -> str: """Formata lista de documentos para inclusão no documento final.""" docs_limpos = [d.strip() for d in docs if d and d.strip()] if not docs_limpos: return "" return "\n".join([f"- {d};" for d in docs_limpos]) def formatar_valores_mercado_para_documento(anos: List[str], valores: List[str], com_extenso: bool = True) -> str: """Formata lista de valores de mercado por ano para inclusão no documento.""" linhas = [] for ano, valor in zip(anos, valores): if ano and valor: try: ano_str = str(int(float(ano))) if ano else "" except (ValueError, TypeError): ano_str = str(ano) if ano else "" if com_extenso: valor_float = parse_valor_monetario(valor) if valor_float > 0: extenso = numero_para_extenso(valor_float) linhas.append(f"{ano_str} - {valor} ({extenso})") else: linhas.append(f"{ano_str} - {valor}") else: linhas.append(f"{ano_str}: {valor}") if not linhas: return "" return "\n".join(linhas) def formatar_motivos_desvalorizantes(motivos: List[Dict]) -> Dict: """Formata a lista unificada de motivos desvalorizantes.""" if not motivos: return { 'alegados': [], 'confirmados': [], 'todos': [], 'alegados_texto': '', 'confirmados_texto': '', 'secoes': [] } alegados = [] confirmados = [] todos = [] secoes = [] for i, motivo in enumerate(motivos, 1): descricao = motivo.get('descricao', '').strip() foi_alegado = motivo.get('alegado', False) foi_confirmado = motivo.get('confirmado', False) if not descricao: continue item = { 'numero': i, 'descricao': descricao, 'alegado': foi_alegado, 'confirmado': foi_confirmado } todos.append(item) if foi_alegado: alegados.append(descricao) if foi_confirmado: confirmados.append(descricao) status = [] if foi_alegado: status.append("alegado pelo contribuinte") else: status.append("não alegado pelo contribuinte") if foi_confirmado: status.append("e confirmado na análise") else: status.append("e não confirmado na análise") secoes.append({ 'numero': i, 'titulo': descricao, 'status': ", ".join(status) if status else "identificado na vistoria", 'confirmado': foi_confirmado }) alegados_texto = formatar_motivos_para_documento(alegados) confirmados_texto = formatar_motivos_para_documento(confirmados) return { 'alegados': alegados, 'confirmados': confirmados, 'todos': todos, 'alegados_texto': alegados_texto, 'confirmados_texto': confirmados_texto, 'secoes': secoes } # ============================================================================ # EXTRATOR DE PDF (Antigo pdf_extractor.py) # ============================================================================ def extrair_dados_pdf(pdf_file) -> dict: """Extrai dados do PDF do SIAT.""" if pdf_file is None: return None try: reader = PdfReader(pdf_file) text = "" for page in reader.pages: page_text = page.extract_text() if page_text: text += page_text dados = { "inscricao_imovel": None, "endereco_imovel": None, "bairro_imovel": None, "setor_imovel": None, "quarteirao_imovel": None, "lote_imovel": None, "finalidade_imovel": None, "area_territorial": None, "construcoes_imovel": "Sem construções cadastradas", "valores_venais": None } # INSCRIÇÃO m = re.search(r'(\d{0,10})Inscriç', text) if m: dados["inscricao_imovel"] = m.group(1) # BAIRRO pattern_bairro = re.compile(r"Quadra \/ Lote(.*?)Bairro", re.DOTALL) bairros = [b.strip() for b in pattern_bairro.findall(text)] if bairros: dados["bairro_imovel"] = list(set(bairros))[-1] # ENDEREÇO pattern_endereco = re.compile( r"([^\n]*? - Porto Alegre/RS - \d{5}-\d{3})", re.IGNORECASE ) matches_end = pattern_endereco.findall(text) if matches_end: endereco_completo = matches_end[-1] if dados["bairro_imovel"]: dados["endereco_imovel"] = endereco_completo.split(f" - {dados['bairro_imovel']}")[0].strip() else: dados["endereco_imovel"] = re.split(r"\s-\s(?:PORTO ALEGRE|Porto Alegre)/RS", endereco_completo)[0].strip() # SETOR pattern_setor = re.compile(r"Ratei(.*?)Setor", re.DOTALL) setores = [re.findall(r"(?\d{1,4},\d{2})\s+(?P\d{4})\s+\d{4}\s+(?P.*?)\s+\d{1,2}-\d{1,2}%", re.MULTILINE ) for m in pattern_const.finditer(bloco): area = m.group("area") ano = m.group("ano") tipo = m.group("tipo").strip() construcoes.append(f"{area} m² / {ano} / {tipo}") if construcoes: dados["construcoes_imovel"] = "\n".join(construcoes) return dados except Exception as e: print(f"Erro ao extrair dados do PDF: {e}") return None def processar_upload_pdf(pdf_file): """Processa upload de PDF e retorna valores para preencher formulário.""" if pdf_file is None: return [""] * 10 dados = extrair_dados_pdf(pdf_file) if dados is None: return [""] * 10 setor_quarteirao = f"{dados['setor_imovel']} / {dados['quarteirao_imovel']}" return [ dados["inscricao_imovel"], dados["endereco_imovel"], dados["bairro_imovel"], setor_quarteirao, dados["lote_imovel"], dados["finalidade_imovel"], dados["area_territorial"], dados["construcoes_imovel"], dados["valores_venais"], json.dumps(dados.get("valores_mercado_extraidos", [])) ]