Qualidade dos dados, tratamento e limpeza
O diagnóstico da base operacional e a prestação de contas do tratamento aplicado.
1.1Escopo e método
Este documento descreve o diagnóstico de qualidade da base operacional e a auditoria do tratamento aplicado na camada Silver. Ele é o par técnico do Relatório Executivo: os dois contam os mesmos fatos, com os mesmos números, em profundidades diferentes. Todo número do executivo é rastreável até uma seção daqui.
Arquitetura em camadas
O dado percorre três camadas, e a separação entre elas não é organizacional, é o que torna a auditoria possível:
| Camada | O que é | O que nunca faz |
|---|---|---|
| Bronze | Cópia fiel da origem, sem uma única transformação. Preserva até o defeito. | Nunca corrige nada. Se corrigisse, não haveria com o que comparar. |
| Silver | O Bronze conformado, no mesmo grão. Uma tabela entra, a mesma tabela sai. | Nunca agrega, nunca descarta, nunca imputa. |
| Gold | Transformação de negócio: fases em colunas, durações, régua de prazo. | Nunca volta ao Bronze direto. |
Por que o Bronze é intocável. O defeito mais caro deste projeto foi encontrado comparando o Silver com o Bronze, não lendo código. Sem a cópia original preservada, ele teria chegado ao relatório final. A seção 1.6.4 conta o caso com nome e número.
Evidência de origem
notebooks/operacional/00_eda_qualidade_dados_ope.ipynb· o diagnóstico, em 12 dimensões de qualidadenotebooks/operacional/01_validacao_silver_ope.ipynb· a auditoria do tratamento, célula a célulasrc/logistica_otif_mlops/transformacoes.py· as regras puras, com 89 testes unitáriossrc/logistica_otif_mlops/pipelines/silver.py· a aplicação das regras e o manifesto
1.2A fotografia da base
Toda avaliação de qualidade descreve um instante. Sem dizer qual, a conclusão perde validade: a base muda todo dia, e um defeito corrigido ontem continuaria sendo reportado. Os dados abaixo são lidos do manifesto de ingestão, gerado na mesma execução que gravou os arquivos.
| Tabela | Linhas | Colunas |
|---|---|---|
pedido_fase | 14.604.666 | 5 |
pedido_item | 7.732.685 | 4 |
ordem_coleta | 3.666.054 | 6 |
entrega | 2.670.239 | 11 |
pedido | 1.778.062 | 17 |
retirada_base | 363.622 | 5 |
minuta | 215.277 | 8 |
estoque_snapshot | 57.060 | 8 |
ocorrencia | 48.546 | 7 |
endereco | 19.697 | 12 |
item | 6.804 | 10 |
organizacao | 240 | 13 |
veiculo | 78 | 5 |
rota | 28 | 4 |
transportador | 19 | 5 |
fase | 10 | 5 |
tipo_ocorrencia | 8 | 4 |
modalidade | 2 | 4 |
1.3Dimensões verificadas
O diagnóstico cobriu doze dimensões. Registrar as que passaram é tão importante quanto registrar as que falharam: sem elas, o leitor não sabe se a dimensão foi verificada e passou ou se simplesmente não foi olhada.
| Dimensão | O que procura | Resultado |
|---|---|---|
| Perfil e tipagem | tipo coerente com o rótulo da coluna | sem divergência |
| Completude | campo crítico vazio | achados Q4 e Q6 |
| Unicidade | chave de negócio duplicada | nenhuma duplicata |
| Domínio | categoria fora do previsto | sem categoria estranha |
| Texto | caixa, espaço e acentuação inconsistentes | achados Q1, Q2, Q5 e Q7 |
| Consistência temporal | evento que termina antes de começar | nenhuma inversão em 14,6 mi de passagens |
| Integridade referencial | referência para registro inexistente | nenhum órfão |
| Números impossíveis | peso, volume ou quantidade negativos | nenhum |
| Duplicidade de cadastro | mesmo endereço em registros distintos | achado Q3 |
| Vazamento temporal | campo que só existe depois do instante da previsão | 2 vazamentos corrigidos na geração |
| Coerência entre tabelas | contradição entre fontes do mesmo fato | sem contradição |
| Coerência com o negócio | número plausível para a operação descrita | 21 indicadores dentro da banda |
1.4Achados, com a leitura técnica
A mesma cidade cadastrada de quatro jeitos
alta tratado65 cidades e 56 nomes de local em endereco apareciam em mais de uma grafia, por variação de caixa e de acentuação (SAO PAULO, Sao Paulo, SÃO PAULO, São Paulo). A capitalização sozinha não resolve, porque não devolve acento não digitado; foi preciso eleger uma grafia canônica por grupo.
Evidência: 121 valores em grafia múltipla reduzidos a 0.
Correção na origem: O cadastro de endereço aceita texto livre no campo de cidade. Enquanto não houver validação ou lista de municípios na entrada, o problema volta na próxima carga.
Endereço abreviado e por extenso na mesma base
alta tratado10.097 dos 19.697 logradouros usavam abreviação (R., Av., Rod.), em caixa variável. A expansão usa uma tabela de abreviações em transformacoes.py, que cresce por evidência: abreviação nova encontrada entra lá com teste próprio.
Evidência: 10.097 abreviados reduzidos a 0.
Correção na origem: O campo de logradouro é texto livre. Vale avaliar preenchimento assistido por CEP na tela de cadastro.
O mesmo endereço cadastrado mais de uma vez
alta marcadoA coluna derivada chave_endereco agrupa logradouro, cidade e UF em forma comparável e marca os grupos. O Silver marca e não funde: fusão é irreversível depois que o Bronze rotaciona, e os pedidos antigos referenciam o registro que seria eliminado.
Evidência: 333 grupos, 676 cadastros, 343 registros seriam eliminados numa fusão.
Correção na origem: Conferir os 333 grupos e decidir quais cadastros são o mesmo lugar. A decisão precisa de registro de quem decidiu e quando, porque é irreversível.
Transferência para base não registra quem recebeu
alta preservadoentrega.recebedor nulo em 100% das linhas com tipo_perna = TRANSFERENCIA_BASE, contra 0,03% nas pernas de última milha. O Silver não preenche esse campo: a ausência é o próprio achado, e imputá-la apagaria do relatório o problema que precisa ser visto.
Evidência: 1.241.334 ausências no Bronze e as mesmas 1.241.334 no Silver.
Correção na origem: Definir se a conferência na chegada à base passa a ser obrigatória. É decisão de operação, com impacto em responsabilidade sobre avaria e extravio.
Nome de quem recebeu digitado de formas diferentes
média tratadoentrega.recebedor com 101.291 ocorrências em caixa alta e 42.241 com espaço nas pontas. Foi a coluna mais ajustada do pipeline: 168.899 células.
Evidência: 56 nomes distintos reduzidos a 32.
Correção na origem: O aplicativo do motorista aceita texto livre onde poderia haver seleção ou validação. É a origem do ruído.
Itens sem valor unitário
média preservadoitem.valor_unitario nulo em 345 de 6.804 registros. Qualquer imputação depende de regra definida pelo cliente (média da categoria, último valor praticado, valor de nota) e precisa ser aplicada no Gold, com a decisão registrada.
Evidência: 345 ausências no Bronze e as mesmas 345 no Silver.
Correção na origem: Definir a regra de valor para item sem preço cadastrado, ou completar o cadastro.
Observação de ocorrência preenchida com texto sem conteúdo
baixa tratadoocorrencia.observacao com marcadores de vazio. O tratamento converte esses textos em ausência, que é o inverso do erro de imputar: converter revela a falta, preencher esconde a falta.
Evidência: 717 registros convertidos para ausência.
1.5A camada Silver: o que foi aplicado
Sete tabelas têm regra de tratamento; as outras 11 são copiadas como vieram, porque no Bronze já estavam conformes e transformar o que não precisa é criar diferença sem motivo.
1.5.1 As regras
As funções de transformação vivem em transformacoes.py, isoladas de banco, arquivo e
DataFrame. Cada uma faz uma coisa, recebe e devolve valor simples, e tem teste unitário com o
caso fácil e o caso que costuma quebrar. A regra que governa todas é
normalizar para comparar, preservar para exibir.
| Função | O que faz | O caso difícil que ela trata |
|---|---|---|
chave_comparacao | reduz o texto à forma comparável | é o instrumento de medida, nunca vira valor de negócio |
normalizar_espacos | tira espaço das pontas e colapsa o do meio | string só com espaço vira ausência, não texto vazio |
capitalizar_nome | capitaliza respeitando partículas e siglas | str.title() transformaria "Rua das Flores" em "Rua Das Flores" e "CD SP" em "Cd Sp" |
normalizar_logradouro | expande a abreviação do tipo | capitaliza o texto inteiro de uma vez, senão a partícula do meio vira início de frase |
normalizar_documento | mantém só os dígitos | preserva zero à esquerda |
normalizar_cep | mantém oito dígitos | o zero à esquerda é a razão de CEP ser texto: como número, 01001000 vira 1001000 |
canonizar_variantes | elege uma grafia oficial por grupo | capitalizar não devolve acento não digitado; a eleição prefere quem tem acento, depois caixa de nome próprio, depois frequência, depois ordem alfabética (para ser reprodutível) |
1.5.2 Onde o tratamento tocou
190.991 células alteradas em 5 colunas de 3 tabelas. O denominador correto aqui é a célula, não a linha: o tratamento age em campo, e dividir por linha inflaria a proporção em tantas vezes quantas forem as colunas. Sobre as 190.194.916 células do domínio operacional, a proporção é 0.1004%, ou 1 célula em cada 995.
| Tabela | Coluna | Células | % do total |
|---|---|---|---|
entrega | recebedor |
168.899 | 88.4% |
endereco | logradouro |
9.317 | 4.9% |
endereco | nome_local |
6.447 | 3.4% |
endereco | cidade |
5.492 | 2.9% |
ocorrencia | observacao |
836 | 0.4% |
1.5.3 As regras que rodaram e não corrigiram nada
13 colunas passaram por regra de tratamento sem ter uma única célula alterada, porque já vinham conformes da origem. Isso é registrado por honestidade metodológica (o pipeline não infla o número de correções) e porque essas regras são defesa, não conserto: existem para o dia em que a origem mudar de formato, e são o que impede que essa mudança quebre o Gold sem aviso.
| Tabela | Coluna | Linhas verificadas |
|---|---|---|
endereco | documento |
19.697 |
endereco | bairro |
19.697 |
endereco | uf |
19.697 |
endereco | cep |
19.697 |
organizacao | sigla |
240 |
organizacao | razao_social |
240 |
organizacao | nome_fantasia |
240 |
organizacao | cnpj |
240 |
item | codigo |
6.804 |
item | descricao |
6.804 |
transportador | nome |
19 |
transportador | cnpj |
19 |
veiculo | placa |
78 |
1.6Auditoria do tratamento
Um pipeline de limpeza falha de dois jeitos, e o segundo é muito pior. Não corrigir o que devia é visível: o defeito continua lá, alguém reclama. Corrigir o que não devia é invisível: o número fica bonito, o relatório fecha, e o dado está errado. Esta seção foi desenhada para pegar o segundo caso.
1.6.1 O grão foi preservado?
18 de 18 tabelas mantiveram a contagem exata de
linhas. 0 colunas foram perdidas. As únicas colunas novas são
_processado_em, técnica, e chave_endereco, derivada e aditiva.
Grão preservado é o que garante que qualquer número do Gold pode ser aberto até o registro individual. É também o que separa uma camada Silver de uma camada de relatório: se o tratamento já entregasse o dado agregado, todo relatório novo voltaria ao Bronze e a rastreabilidade morreria no meio do caminho.
1.6.2 Alguma ausência foi criada ou destruída?
| Tabela | Coluna | Bronze | Silver | Diferença |
|---|---|---|---|---|
ocorrencia | observacao |
0 | 719 | +719 |
A única divergência é deliberada, e vale como exemplo do critério. Em
ocorrencia.observacao havia texto livre com -, N/A e
sem informacao, que é ausência disfarçada de conteúdo: mantê-los faria qualquer
agrupamento exibir - como se fosse uma categoria real de ocorrência.
Converter para ausência revela a falta; preencher a ausência esconde a falta.
Verificação complementar: 0 valores de ausência
disfarçados de texto (nan, none, null, -) sobraram
nas 18 tabelas do Silver.
1.6.3 Os achados que precisavam sobreviver intactos
| Achado | Bronze | Silver | Situação |
|---|---|---|---|
| Transferência para base sem recebedor (Q4) | 1.241.334 | 1.241.334 | preservado |
| Item sem valor unitário (Q6) | 345 | 345 | preservado |
1.6.4 O incidente que originou esta seção
Registro de defeito do próprio pipeline. Durante o desenvolvimento, a aplicação das funções de texto converteu 1.241.334 ausências em um recebedor chamado "Nan".
A causa: o pandas, ao aplicar uma função sobre coluna de texto, passa o valor ausente para a
função em vez de pulá-lo, e no caminho o ausente vira a string "nan". A função de
capitalizar recebeu "nan" e devolveu "Nan", corretamente. O erro não
estava na função, estava na chamada.
Por que é grave: é silencioso (a coluna fica com aparência melhor que antes), é irreversível dentro do Silver (a ausência não se recupera de um texto) e destrói justamente o achado mais valioso do diagnóstico. Correção aplicada: instrução explícita ao pandas para pular os ausentes, em todas as chamadas.
Como foi encontrado: não por leitura de código, mas por comparação com a camada anterior. É o argumento prático a favor do Bronze intocável, e a razão de esta auditoria ser etapa do processo e não um extra.
1.6.5 O tratamento é idempotente?
As 7 regras foram reaplicadas sobre o próprio Silver: nenhuma célula muda. Sem essa propriedade, reprocessar uma carga (o que acontece o tempo todo, por falha de rede, correção de defeito ou dado atrasado) produziria resultado diferente do anterior, e ninguém saberia qual dos dois é o certo.
A propriedade não é automática. A eleição da grafia canônica poderia facilmente não ser idempotente: se o desempate fosse a ordem de leitura do arquivo, ou se houvesse qualquer sorteio, duas execuções elegeriam vencedores diferentes e o mesmo endereço mudaria de nome a cada carga. Por isso o desempate final é alfabético, que é ordem estável e independe de como o dado chegou.
1.7Antes e depois, por campo
| Campo | Medida | Bronze | Silver |
|---|---|---|---|
endereco.cidade | valores em grafia múltipla | 65 | 0 |
endereco.nome_local | valores em grafia múltipla | 56 | 0 |
endereco.logradouro | tipo abreviado | 5.928 | 0 |
entrega.recebedor | nomes distintos | 56 | 32 |
O caso de São Paulo ilustra o mecanismo: no Bronze existiam as grafias
SAO PAULO, Sao Paulo, SÃO PAULO, São Paulo;
no Silver existe uma só, com acento.
Duplicidade de endereço: marcada, não fundida
O pipeline tem informação suficiente para fundir os cadastros e escolhe não fazê-lo. Saber que dois cadastros são o mesmo lugar é diferente de ter autoridade para eliminar um deles. Marcar entrega ao cliente as duas coisas: o problema quantificado e a liberdade de decidir. Fundir entregaria só o resultado, sem volta, porque os pedidos antigos referenciam o registro que seria eliminado.
1.8Limitações e como reproduzir
Limitações declaradas
- O diagnóstico descreve a carga de 2026-08-27. Cargas posteriores podem trazer defeitos novos, e os defeitos de origem (Q1, Q2, Q3, Q5) voltam a cada carga enquanto não houver validação na entrada do sistema.
- A eleição de grafia canônica usa apenas os próprios dados, sem lista externa de municípios. Se a base inteira escrever uma cidade errada, ela continua errada, só que uniforme.
- A duplicidade de endereço é detectada por logradouro, cidade e UF. Duplicata com logradouro escrito de forma substancialmente diferente não é capturada.
- O tratamento cobre texto e formato. Não cobre veracidade: um CEP válido no formato mas errado para o endereço não é detectável sem fonte externa.
Reprodução
Todo número deste documento sai da leitura das camadas no momento da emissão. Para reproduzir:
uv run python -m logistica_otif_mlops.pipelines.bronzeuv run python -m logistica_otif_mlops.pipelines.silveruv run pytest· 89 testesuv run python -m logistica_otif_mlops.relatorios· regera estes dois documentos
O que os dados dizem sobre a operação
A leitura analítica do histórico: prazo por etapa, concentração, sazonalidade e as heurísticas do negócio confrontadas com o que o dado mostra.
Este capítulo entra aqui quando a etapa for entregue. Aparece desde já, de propósito: o caminho inteiro do projeto fica visível desde a primeira reunião, e não só o trecho pronto.
Previsão de atraso e nova data prevista
O modelo de previsão de OTIF e a projeção de nova data de entrega, com o desempenho medido e as limitações declaradas.
Este capítulo entra aqui quando a etapa for entregue. Aparece desde já, de propósito: o caminho inteiro do projeto fica visível desde a primeira reunião, e não só o trecho pronto.
Operação do modelo e monitoramento
Como o modelo entra em produção, como é acompanhado e o que dispara uma reavaliação.
Este capítulo entra aqui quando a etapa for entregue. Aparece desde já, de propósito: o caminho inteiro do projeto fica visível desde a primeira reunião, e não só o trecho pronto.