geracao_documentos / utils.py
gui-sparim's picture
Upload 13 files
bbbd911 verified
"""
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)\d{5}(?![,\d])", m)[-1] for m in pattern_setor.findall(text)]
if setores:
dados["setor_imovel"] = list(set(setores))[-1]
# QUARTEIRÃO
pattern_quadra = re.compile(r"Setor(.*?)Quadra", re.DOTALL)
quadras = [re.findall(r"(?<!\d)\d{4}(?![,\d])", m)[-1] for m in pattern_quadra.findall(text)]
if quadras:
dados["quarteirao_imovel"] = list(set(quadras))[-1]
# LOTE
pattern_lote = re.compile(r"Lote Fiscal(.*?)Quarteir", re.DOTALL)
lotes = [re.findall(r"(\d{1}\.\d{8}\.\d{4})", m)[-1] for m in pattern_lote.findall(text)]
if lotes:
dados["lote_imovel"] = list(set(lotes))[-1]
# FINALIDADE
pattern_finalidade = re.compile(r"Uso(.*?)Finalidad", re.DOTALL)
fins = [f.strip() for f in pattern_finalidade.findall(text)]
if fins:
dados["finalidade_imovel"] = list(set(fins))[-1]
# VALOR VENAL
pattern_valor = re.compile(r'Tributação(.*?)Planta', re.DOTALL)
matches_valor = pattern_valor.findall(text)
valores_ano = []
for m in matches_valor:
try:
ano = re.search(r'(\d{4})', m).group(1)
valores = re.findall(r'(\d{1,3}(?:\.\d{3})*,\d{2})', m)
valor_venal = valores[2]
valores_ano.append(f"{ano}: R$ {valor_venal}")
except:
continue
if valores_ano:
dados["valores_venais"] = " | ".join(valores_ano)
# ÁREA TERRITORIAL
match_area_real = re.search(r"Área real total\s+(\d{1,5},\d{2})\s*m²", text, re.IGNORECASE)
area_real = match_area_real.group(1) if match_area_real else "0,00"
matches_corr = re.findall(r'territorial corrigida(.*?)Última', text, re.DOTALL | re.IGNORECASE)
corrigidas = []
for m in matches_corr:
valores = re.findall(r"(\d{1,5},\d{2})", m)
if valores:
corrigidas.extend(valores)
corrigidas_unicas = list(set(corrigidas))
area_corrigida = corrigidas_unicas[-1] if corrigidas_unicas else "0,00"
dados["area_territorial"] = f"{area_real} m² / {area_corrigida} m²"
# ÁREA CONSTRUÍDA
pattern_area_construida = re.compile(
r"Área Construída(.*?)Documento de Origem",
re.DOTALL | re.IGNORECASE
)
match_area = pattern_area_construida.search(text)
construcoes = []
if match_area:
bloco = match_area.group(1)
pattern_const = re.compile(
r"^\s*\d+\s+(?P<area>\d{1,4},\d{2})\s+(?P<ano>\d{4})\s+\d{4}\s+(?P<tipo>.*?)\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", []))
]