Limpeza e reconciliação de dados
Ferramentas padrão: DuckDB 2.0 SQL ou API lazy do Polars 2.x (scan_csv, scan_parquet) para arquivos locais. pandas apenas quando uma biblioteca forçar. Python via uv, lint com ruff. Verifique qualquer detalhe de API que tenha mudado no Polars 2.0 ou DuckDB 2.0 com a documentação atual antes de confiar (veja references/landscape-2026-11.md).
Nunca corrigir silenciosamente
Cada coerção, remoção, valor padrão ou mesclagem é contado e relatado. Uma etapa de limpeza é (linhas_entrada, linhas_saída, linhas_alteradas, motivo) . Falhas de conversão se tornam NULL mais uma contagem, nunca uma linha removida e nunca um valor adivinhado. Mantenha os dados brutos intactos; escreva a saída limpa em um novo caminho. Se as contagens de uma etapa te surpreenderem, pare e relate em vez de continuar.
Procedimento
- Perfil (somente leitura). 2. Decidir chave, granularidade e tipos. 3. Limpar em um pipeline de pequenas etapas contadas. 4. Reconciliar se duas fontes existirem. 5. Emitir exceções mais um resumo da execução com todas as contagens.
1. Perfil antes de tocar
Leia cada coluna como texto primeiro (all_varchar=true no DuckDB, infer_schema=False no Polars), porque a inferência em uma amostra silenciosamente transforma 00123 em 123 e colunas mistas no tipo da primeira linha.
Em seguida, por coluna calcule: contagem de linhas, taxa de nulos/cadeias vazias, contagem de valores distintos, mínimo/máximo (como texto e como analisado), comprimento mais curto/mais longo e proporção de valores que correspondem a um padrão de tipo (inteiro, decimal, data ISO, outro).
SUMMARIZE do DuckDB faz a maior parte disso em uma chamada.
Registre antes da limpeza: total de linhas, contagem de chaves candidatas distintas, linhas por chave (máximo) e os cabeçalhos exatamente como encontrados (espaços à direita, BOM, nomes duplicados). Uma chave candidata só é uma chave se a contagem de valores distintos for igual à contagem de linhas após a normalização; teste-a, não presuma.
Verifique: duplicatas de linha inteira, cabeçalho repetido no meio do arquivo, linhas de rodapé/total dentro dos dados, valores sentinela ("N/A", "-", "0000-00-00", 9999-12-31), codificações mistas e colunas numéricas que são realmente identificadores.
2. Tipos e parsing
- IDs, CEPs/códigos postais, números de conta e telefone são strings, sempre. Armazene a largura com preenchimento zero como regra (
lpadpara a largura conhecida) apenas quando a largura for documentada; caso contrário, mantenha como lido. - Datas: analise com um formato explícito por coluna (
try_strptime/str.to_date(format, strict=False)). Nunca deixe o mecanismo adivinhar a ordem dia/mês. Valores ambíguos (03/04/2026) sem ordem documentada são uma exceção, não um cara ou coroa. Mantenha uma decisão de fuso horário explícita. - Dinheiro: analise para unidades menores inteiras ou
DECIMAL(p,s), nunca float. Remova símbolos de moeda e separadores de milhares por regra de localidade:1.234,56e1,234.56são localidades diferentes, então decida por fonte e afirme que todo valor corresponde ao padrão escolhido. Parênteses e sinal de menos à direita são negativos. Registre a moeda por linha; não some entre moedas. - Booleanos e enums: mapeie a partir de uma tabela explícita; valores não mapeados vão para exceções.
- Após cada conversão, conte
não-nulo antesmenosnão-nulo depois. Esse número é a contagem de falhas de coerção e vai para o relatório.
3. Normalização
Aplique a uma chave de correspondência derivada, não sobre a coluna original, para que o valor bruto permaneça disponível:
Unicode NFKC, trim, colapsar espaços internos, casefold, remover espaços de largura zero e espaços inquebráveis.
NFKC é com perda (ligaduras, formas de largura total, sobrescritos), que é o ponto para correspondência e errado para exibição.
Para identificadores, também remova separadores (-, /, espaços) e decida explicitamente o tratamento de O/0 e I/1.
NFKC não está no SQL principal do DuckDB; faça em Polars/Python ou registre uma UDF (verifique o suporte atual).
4. Desduplicação
- Duplicatas exatas: remova com uma contagem; mantenha o número da linha de origem da linha sobrevivente.
- Duplicatas por chave: primeiro decida se são repetições verdadeiras ou eventos distintos (mesmo número de fatura, valor diferente é um conflito, não uma duplicata).
- A escolha do sobrevivente deve ser determinística e declarada:
ROW_NUMBER() OVER (PARTITION BY key ORDER BY updated_at DESC NULLS LAST, completeness DESC, source_row DESC) = 1(DuckDBQUALIFY). Sempre termine o ORDER BY com um desempate único, como o número da linha de origem, porque empates, de outra forma, são resolvidos por ordem de varredura arbitrária. No Polars usesort(...)depoisunique(subset, keep="first", maintain_order=True). - Desduplicação fuzzy é um gerador de candidatos, nunca uma mesclagem automática: bloqueie por uma chave barata (CEP normalizado, primeiras 3 letras, ano), pontue dentro dos blocos, aceite automaticamente apenas acima de um limite alto com um campo secundário exato concordando, e envie a faixa intermediária para um humano. Relate o limite e as contagens.
5. Auditoria de reconciliação
Fonte da verdade = A, derivada = B (ex.: exportação do sistema vs. documentos OCRizados). Nunca diga "B está errado" antes de verificar se a extração ou a chave estão erradas.
- Totais de controle primeiro. Contagem de linhas e soma de cada coluna de dinheiro/quantidade em A e B, mais contagens de chaves distintas. Uma incompatibilidade indica se você deve esperar linhas faltantes, linhas extras ou desvio de valor. Totais que empatam enquanto as linhas diferem significam erros compensadores; continue.
- Correspondência de chaves. Construa a mesma chave normalizada em ambos os lados (seção 3). Corresponda exatamente primeiro, depois em passagens explícitas de rigor decrescente (ex.: apenas chave, depois chave mais janela de data), rotulando cada correspondência com a passagem que a produziu. Nunca misture passagens sem rótulo.
- Anti-joins em ambas as direções. A-não-em-B (faltante em derivado) e B-não-em-A (inesperado em derivado, frequentemente erros de digitação de chave OCR ou duplicatas). Relate ambos; ler apenas uma direção esconde metade dos erros. Antes de chamar uma linha de faltante, verifique se sua chave colide com duplicatas em qualquer um dos lados (joins muitos-para-muitos inflam totais).
- Comparação de campos em pares correspondentes. Compare por campo após a mesma normalização.
Tolerância de dinheiro: compare em unidades menores,
abs(a - b) <= tolonde tol é declarada antecipadamente (0 para livros contábeis; uma tolerância de arredondamento como 1 unidade menor por linha apenas quando as fontes arredondam de forma diferente, e nunca uma porcentagem em valores pequenos). Datas: exata ou uma janela de dias declarada. Texto: igualdade normalizada, depois uma pontuação de similaridade relatada como uma pontuação, não como um passe. - Classifique, não apenas diferencie. Cada exceção recebe um código de motivo:
MISSING_IN_B,EXTRA_IN_B,DUP_KEY_A,DUP_KEY_B,AMOUNT_MISMATCH,FIELD_MISMATCH,UNPARSEABLE_A,UNPARSEABLE_B,AMBIGUOUS_MATCH. Linhas não analisáveis são exceções, nunca excluídas silenciosamente dos totais. - Feche o ciclo. A identidade final deve ser válida:
|A| = correspondidas + faltantes_em_B + excesso_dup_A + não_analisáveis_A(e o equivalente para B). Se não fechar, a auditoria não está concluída.
Padrões SQL para passagens, anti-joins, tolerância e verificação de balanço estão em references/reconciliation-recipes.md.
Relatório de exceções
Uma linha por exceção, legível sem as ferramentas:
reason_code, key, arquivo de origem A e número da linha, arquivo de origem B e número da linha (e página para documentos escaneados), field, a_value, b_value, delta, suggested_action.
Ordene por impacto monetário decrescente, depois por motivo.
Coloque um resumo da execução no topo: entradas com contagens de linhas e checksums, cada etapa de limpeza com contagens, totais de controle de ambos os lados, a identidade do balanço, tolerâncias usadas.
Escreva CSV para a máquina e uma visão curta em markdown ou xlsx para o humano; não despeje milhares de linhas no chat, resuma contagens por motivo e mostre as primeiras.
Guardrails
- Afirme, não confie na vista: contagens de linhas após joins, unicidade de chave após desduplicação, nenhuma coluna float para dinheiro.
- Left joins em chaves não únicas multiplicam linhas; verifique a contagem antes e depois de cada join.
- Não sobrescreva A. Correções na fonte da verdade são uma decisão humana listada no relatório.
- Senhas para bancos de dados são lidas de
/run/secrets/<name>, não de variáveis de ambiente ou linhas de comando. - Verificações declarativas (soda-core, great_expectations) valem a pena adicionar uma vez que as regras estejam estáveis; o perfil e as contagens acima vêm primeiro.
Lições de uso real se acumulam em references/gotchas.md.