As três fontes
Cada fonte tinha sido criada em um momento diferente da empresa, por equipes diferentes, com convenções diferentes.
A API interna era o sistema operacional — registrava contratos ativos. Mas não registrava contratos encerrados antes de 2021, que tinham sido migrados para um sistema legado e depois "arquivados".
O bucket S3 tinha os PDFs de todos os contratos, incluindo os legados. Mas o nome do arquivo seguia uma convenção que mudou três vezes. E alguns PDFs foram renomeados manualmente por alguém que saiu da empresa.
A planilha era a tentativa humana de reconciliar as duas fontes anteriores. Incompleta, com filtros salvos que excluíam linhas sem o operador perceber.
A abordagem: reconciliação por hash
A tentativa inicial foi cruzar por número de contrato. Não funcionou — a API usava um formato, o S3 usava outro, e a planilha tinha uma coluna de "número legado" que não batia com nenhum dos dois.
A solução foi mais bruta e mais robusta: extraímos os metadados de cada PDF via pdfplumber, normalizamos CPF/CNPJ do tomador, data de emissão e valor do contrato, e geramos um hash composto. O mesmo hash foi gerado para cada linha da API e cada linha da planilha.
Qualquer registro que compartilhasse o mesmo hash era o mesmo contrato. 94% dos registros bateram. Os 6% restantes foram auditados manualmente.
import hashlib
from pdfplumber import open as open_pdf
def extract_contract_hash(pdf_path: str) -> str | None:
try:
with open_pdf(pdf_path) as pdf:
text = pdf.pages[0].extract_text() or ""
cpf_cnpj = normalize_document(extract_field(text, r"CPF/CNPJ[:\s]+([\d\.\-\/]+)"))
date = normalize_date(extract_field(text, r"Data de Emissão[:\s]+(\d{2}/\d{2}/\d{4})"))
value = normalize_currency(extract_field(text, r"Valor[:\s]+R\$\s?([\d\.\,]+)"))
if not all([cpf_cnpj, date, value]):
return None # metadados insuficientes — vai para revisão manual
raw = f"{cpf_cnpj}|{date}|{value}"
return hashlib.sha256(raw.encode()).hexdigest()[:16]
except Exception:
return None
Extração e normalização de metadados do PDF para geração do hash de reconciliação
O que os 6% revelaram
Os contratos que não bateram não eram todos erros. Três categorias distintas:
- Contratos renegociados — existiam como dois registros diferentes (contrato original + aditivo), mas o sistema os tratava como um só.
- PDFs corrompidos — alguns arquivos no S3 tinham os primeiros 4 bytes danificados, o que quebrava o parser mas não impedia a leitura humana.
- Contratos genuinamente duplicados — o mesmo contrato foi digitado duas vezes na planilha por pessoas diferentes, em datas diferentes, com pequenas variações de valor (provavelmente erro de arredondamento na digitação manual).
Essa terceira categoria foi a descoberta mais importante para o cliente. Havia R$ 2.4M em contratos duplicados no sistema.
- 40k+ · contratos processados
- 94% · match rate automático
- R$2.4M · duplicatas identificadas
- 100% · python — zero power query
Dado "completo" é uma ilusão operacional. Toda empresa que tem mais de uma fonte de dados tem inconsistências — a questão é se alguém já mediu o tamanho do problema. A maioria não mediu porque medir dói.



