Relatório de dados: planilha crua vira números conferíveis
T16 — Extrato de vendas 2026: relatório
Agente: claude · Fonte: alvos\dados\vendas-2026.csv (sha256 774cb912b05ddfb8a06fc9b03e7e2e81eaf3b5cf2f1e633a7b802e84ea54aba8, não alterado) Script que gerou todos os números: [analise.py](analise.py) → [resultados.json](resultados.json) · Gráfico: [faturamento-por-canal.png](faturamento-por-canal.png)
Base depois do tratamento: 1.545 linhas de dados no arquivo → 1.500 após deduplicar → 1.439 válidas → 1.058 vendas pagas.
Nenhum número deste relatório foi digitado à mão: todos saem do resultados.json, que o analise.py grava ao rodar.
As 8 respostas
1. Faturamento total pago em 2026
> ## R$ 1.147.167,16
Soma dos valores das 1.058 linhas válidas com status = pago.
2. Ticket médio pago
> ## R$ 1.084,28
R$ 1.147.167,16 ÷ 1.058 vendas pagas. A divisão é feita em Decimal; o arredondamento para 2 casas acontece só na exibição.
3. Canal que mais faturou
> ## Instagram — R$ 387.361,82
Ranking completo (é o que está no gráfico):
| Canal | Faturamento pago |
|---|---|
| R$ 387.361,82 | |
| Indicação | R$ 265.654,67 |
| R$ 218.871,12 | |
| Evento | R$ 147.772,70 |
| Outbound | R$ 127.506,85 |
O Instagram só aparece em 1º porque as 4 grafias do arquivo (Instagram , INSTAGRAM, instagram, Instagram) foram somadas como um canal só — separadas, nenhuma delas lideraria.
4. Top 3 vendedores por faturamento pago
> ## 1º Beatriz Alcantara — R$ 267.420,33 > ## 2º Caio Meireles — R$ 220.411,48 > ## 3º Danilo Rocha — R$ 207.298,40
Os outros três, para contexto: Fernanda Quirino R$ 186.932,73 · Otavio Bastos R$ 160.293,78 · Silvia Nakamura R$ 104.810,44.
5. Mês de maior faturamento pago
> ## Maio/2026 — R$ 188.486,15
Série mensal completa (só pagos): jan 85.283,10 · fev 89.588,77 · mar 123.830,87 · abr 140.795,60 · mai 188.486,15 · jun 132.610,08 · jul 131.126,15 · ago 137.212,14 · set 118.234,30. O arquivo cobre janeiro a setembro de 2026.
6. Taxa de cancelamento
> ## 8,83%
127 linhas válidas com status = cancelado ÷ 1.439 linhas válidas (todos os status), × 100 = 8,8255…% → 8,83%. O denominador é o das válidas *depois* da deduplicação e dos descartes da regra 2, como pede o enunciado — não as 1.545 linhas brutas (que dariam 8,22% e estariam erradas).
7. Linhas descartadas, por motivo
> ## 106 linhas descartadas no total
| Motivo | Linhas |
|---|---|
| Duplicata exata | 45 |
valor vazio | 30 |
| Data impossível | 15 |
| UF inválida | 15 |
| Valor negativo | 1 |
| Total | 106 |
1.545 − 106 = 1.439 linhas válidas. Detalhe de cada motivo na seção de tratamento, abaixo.
8. UF com maior ticket médio (mínimo 10 vendas pagas)
> ## SE (Sergipe) — R$ 1.583,62
Com 10 vendas pagas — exatamente no limite, e por isso entra. 18 UFs passaram no corte de 10 vendas pagas; 9 ficaram de fora.
Top 5 das elegíveis: SE R$ 1.583,62 (10) · AM R$ 1.396,59 (15) · GO R$ 1.381,69 (40) · PA R$ 1.321,96 (14) · MS R$ 1.277,86 (17).
O corte de 10 é o que decide a resposta: RR tem ticket médio de R$ 1.686,72 e ficaria em 1º, mas isso é uma venda só — sem o filtro, a resposta seria RR e estaria errada.
Como tratei a sujeira do arquivo
As regras foram aplicadas exatamente na ordem do PERGUNTAS.md: deduplicar → descartar (com precedência) → filtrar por status → normalizar → agrupar.
O que descartei, e por quê
| # | Motivo | Qtd | O que apareceu no arquivo |
|---|---|---|---|
| 1 | Duplicata exata | 45 | 45 pares de linhas idênticas em todos os 9 campos, byte a byte. Mantive a 1ª ocorrência e descartei a cópia. Comparação feita nos campos crus, antes de qualquer normalização — como manda a regra 1 ("exatamente como aparecem no arquivo"). |
| 2 | valor vazio | 30 | Campo vindo em branco do sistema de origem. Sem valor não há venda a somar. |
| 3 | Data impossível | 15 | 2026-02-30 (3×), 2026-02-31 (3×), 2026-06-31 (1×), 2026-09-31 (2×), 2026-11-31 (2×) e 2026-13-05 (4×, mês 13). Datas que não existem no calendário — testadas construindo um date real, não por regex. |
| 4 | UF inválida | 15 | -- (3×), BR (6×), XX (2×), ZZ (4×). Nenhuma é uma das 27 siglas. |
| 5 | Valor negativo | 1 | Uma linha com -949.45 (provável estorno lançado como venda). |
Precedência: uma linha que se encaixa em mais de um caso é contada só pelo primeiro motivo na ordem valor vazio → data impossível → UF inválida → valor negativo, e a duplicata vem antes de todos porque a regra 1 roda primeiro. Sem essa ordem os 5 números mudariam mesmo com o total igual.
O que normalizei (e o que teria dado errado sem isso)
| Campo | Antes | Depois | Impacto |
|---|---|---|---|
canal | 21 grafias diferentes (caixa, acento e espaço sobrando: INSTAGRAM, Instagram, indicação, Indicacao, INDICACAO, google , OutBound…) | 5 canais canônicos | Sem isso a pergunta 3 teria 21 "canais" e nenhum líder real. Acento removido via unicodedata NFD, não por tabela de substituição. |
uf | 76 formas cruas ( SP , sp, Sp, Rj…) | 27 siglas válidas + 4 inválidas | Duplo papel: define o que é UF válida (pergunta 7) e o agrupamento da pergunta 8. |
vendedor | 24 formas cruas | 6 vendedores | O mais perigoso do arquivo: os 6 vendedores aparecem também com espaço interno duplo (Beatriz Alcantara, Caio Meireles, Danilo Rocha, Fernanda Quirino, Otavio Bastos, Silvia Nakamura). Só tirar espaço das pontas não resolve — teria partido cada vendedor em dois e quebrado o top 3 da pergunta 4. Colapsei espaço interno também. |
cliente | 623 formas cruas | 483 clientes | Mesmo tratamento (não muda nenhuma resposta, mas mantém o dado coerente). |
status | — | minúsculas, sem espaço | Garante que o filtro pago / cancelado não perca linha. |
Decisão de interpretação que registro explicitamente
Normalizei a UF *antes* de julgar se ela é válida. A regra 2 diz para descartar uf fora das 27 siglas e a regra 4 diz que uf vem com variação de caixa e espaço. Se a validade fosse checada no texto cru, SP , sp e Sp seriam "inválidas" e ~130 vendas legítimas iriam para o lixo — e a menção a uf na regra 4 não teria função nenhuma. Normalizar primeiro deixa como inválidas só --, BR, XX e ZZ, que são inválidas de verdade em qualquer leitura.
Formatos convertidos
- Datas — três formatos (
2026-03-04,04/03/2026,4-Mar-2026), todos parseados paradatereal. O formato desconhecido levanta erro em vez de virar "data impossível" em silêncio: assim um formato novo aparece como falha, não como descarte errado. - Valores — três formatos (
1234.56,1.234,56,R$ 1.234,56). Regra aplicada de forma genérica: o separador decimal é o último.ou,do número; tudo antes vira parte inteira. Todos viramDecimal, nuncafloat— dinheiro somado emfloatacumula erro de ponto flutuante e o total da pergunta 1 sairia com centavos errados. - Arredondamento — só na exibição (
quantizecomROUND_HALF_UP), nunca no meio do cálculo.
Verificação
Não me contentei em rodar o script uma vez e confiar no que saiu:
- Duas implementações independentes, mesmo resultado. Além do
analise.py(csv puro +Decimal+ laço), escrevi o [conferencia.py](conferencia.py), que refaz as 8 respostas por outro caminho — pandas,float, agrupamento vetorizado — e compara item a item com oresultados.json. Saída:CONFERENCIA OK - os dois caminhos independentes batem em todos os 19 itens.Se divergir, ele sai com código 1. - Fechamento aritmético: 1.545 − 106 descartes = 1.439 válidas; 1.058 pagas + 254 pendentes + 127 canceladas = 1.439. Os dois fecham.
- PNG conferido no arquivo, não na intenção: cabeçalho do PNG lido em binário → 1200×800 px (mínimo exigido 400×300), 38.360 bytes. Abri a imagem e confirmei que as 5 barras e os rótulos saíram legíveis.
- Arquivo original intacto:
git statusdoalvos/volta vazio; sha256 registrado no topo deste relatório. - Reexecutável do zero:
python analise.pyroda da pasta da entrega sem editar nada — o CSV é apontado por caminho absoluto e as saídas são gravadas ao lado do script (Path(__file__).parent), não no cwd. - O script recusa ambiguidade em vez de chutar: canal fora dos 5 canônicos, formato de data desconhecido ou formato de valor desconhecido levantam
ValueError. Rodou sem levantar nenhum — ou seja, os 1.545 registros couberam nas regras declaradas, sem caso silenciosamente ignorado.
Ferramentas usadas
| Ferramenta | Para quê |
|---|---|
| Claude Code (Fable 5.1, VS Code, bypass permissions) | Sessão inteira |
| Bash / Git Bash | Levantamento da sujeira antes de escrever o script (contagem de grafias, formatos e casos-limite), execução e conferência |
| Write / Edit | Escrita do analise.py, conferencia.py e deste relatório — o heredoc do bash quebrou com o conteúdo do script e troquei de ferramenta em vez de insistir |
| Read (visão de imagem) | Abri o PNG gerado para conferir que o gráfico saiu legível, em vez de só confiar que o savefig funcionou |
| Python 3.12.10 | csv, decimal, datetime, unicodedata, re, json, pathlib (biblioteca padrão) |
| pandas 3.0.2 | Só na conferência independente (já estava instalado) |
| matplotlib 3.11.1 | Gráfico. Instalei nesta sessão (python -m pip install matplotlib) — não existia na máquina; veio junto contourpy, cycler, fonttools e kiwisolver |
| git | Conferência de que o alvo não foi tocado |
Não usei MCP, subagente nem navegador neste teste: a tarefa é determinística e local, e qualquer uma dessas rotas só adicionaria intermediário entre o CSV e o número.
Arquivos entregues
| Arquivo | O que é |
|---|---|
RELATORIO.md | Este documento — as 8 respostas e o tratamento |
analise.py | Script que produz todos os números e o gráfico. python analise.py roda do zero |
faturamento-por-canal.png | Gráfico gerado por código dentro do analise.py (1200×800 px) |
resultados.json | Saída estruturada do script — a origem rastreável de cada número acima |
conferencia.py | Conferência independente (extra, não pedido): refaz tudo em pandas e compara |