Deixar a base limpa, reprodutível e documentada, sem nenhum scoring ainda, e montar a infraestrutura “todo número sai de script”: config, fmt, clean, facts e docs, mais os testes que comparam o texto dos documentos com os arquivos gerados. Decidir, com número e critério definido antes de olhar o resultado, as investigações 2a (win rate de 82% no 1º trimestre de 2017), 2b (vendedores sem deals) e 2c (close_value contra preço de tabela). Zerar também as pendências que o bloco 1 deixou para este bloco.
Claude Sonnet (subagente executor). Execução da especificação E3 de process-log/plano_execucao_opus.md, planejada pelo Claude Opus em modo somente leitura. Nenhuma pergunta foi feita; as decisões abaixo são do executor (decisão autônoma do Claude).
clean.py, config.py, fmt.py, facts.json e docs.py serão usados; nada dessas etapas foi implementado.CLAUDE.md (no contexto), docs/briefing.md, docs/SCOPE.md, PROMPTS.md (bloco 2), PROMPT_AUTONOMO.md, process-log/00_fase0_exploracao.md, process-log/01_bloco-1.md (seção Pendências), docs/DECISIONS.md, process-log/00_bloco-0.md (primeiras 60 linhas, para o formato).process-log/scripts/{eda,eda2,snapshot}.py e as saídas process-log/outputs/bloco0_*.txt (só leitura).solution/data/raw.explore2.py calculou os meses de fechamento, as coortes, o cruzamento de trimestres, os vendedores sem deals e a razão close_value / tabela, para eu escolher a forma dos critérios. Nada foi gravado em $SUB.solution/scorer/: __init__.py, config.py, fmt.py, clean.py, facts.py, docs.py. Funções puras, com docstring curta; clean.py não altera DataFrames de entrada (testado).Set-Location $SOL; & $PY -B -m scorer.clean. Inspecionei o facts.json inteiro e os blocos AUTO renderizados.docs/DATA_QUALITY.md (tabela de 11 linhas, seções 2a, 2b e 2c, vazamento por conta ausente; 8 blocos AUTO) e 10 linhas “Bloco 2” no docs/DECISIONS.md (sem apagar nenhuma). docs/SCOPE.md não foi alterado: todos os seus números casaram.test_clean.py (12), test_docs_numbers.py (11), test_facts.py (6) e test_fmt.py (11). Os dois últimos não estavam na lista da E3; entraram porque os números de facts.json e a formatação são o contrato dos blocos seguintes.python -m scorer.clean duas vezes: SHA-256 de deals.csv e de facts.json idênticos nas duas.--reference-date 2017-09-30 --out-dir <scratchpad>: roda sem erro com outra data (não toca em data/processed).unmatched_numbers acusou as 6 (ver “Resultados”)..gitkeep de scorer, tests e data/processed (as pastas ganharam arquivos). Rodei Python com -B ou PYTHONDONTWRITEBYTECODE=1 e o pytest com -p no:cacheprovider; conferi no fim que não há __pycache__ nem .pytest_cache no projeto.solution/scorer/{__init__,config,fmt,clean,facts,docs}.py; solution/tests/{test_clean,test_docs_numbers,test_facts,test_fmt}.py; solution/data/processed/{deals.csv,facts.json}; docs/DATA_QUALITY.md; este log.docs/DECISIONS.md (34 linhas de dados no total; as 10 últimas são “Bloco 2”)..gitkeep (aparecem como exclusões no gitw.py status).$SOL)| Comando | Resultado |
|---|---|
& $PY -m scorer.clean |
exit 0; “deals.csv: 8800 linhas, 23 colunas”; deals abertos 2.089 |
& $PY -m scorer.docs |
exit 0; DATA_QUALITY.md e SCOPE.md “sem mudanças” (na primeira vez, DATA_QUALITY.md foi “atualizado”: os blocos AUTO estavam vazios) |
& $PY -m pytest -p no:cacheprovider |
exit 0; 40 passed in 1.71s |
& $PY -m scorer.docs --check |
exit 0 |
(Import-Csv "$SOL\data\processed\deals.csv").Count |
8800 |
& $PY "$PM\tools\gitw.py" status |
“FORA da pasta do projeto: nada”; 4 não preparados (DECISIONS.md e os 3 .gitkeep removidos) |
solution/data/processed/facts.json)| Item | Esperado (plano ou briefing) | Calculado | Chave em facts.json |
|---|---|---|---|
| Estágios | 4.238 / 2.473 / 1.589 / 500 | 4.238 / 2.473 / 1.589 / 500 | pipeline.deals_por_estagio |
| Win rate dos fechados | 63,2% | 0,6315 | pipeline.win_rate_fechados |
| Linhas GTXPro | 1.480 | 1.480 | qualidade.linhas_gtxpro |
| Setor com erro | “technolgy” | 12 contas, 1.165 deals | qualidade.contas_com_setor_technolgy |
| Sem conta | 1.425 = 1.088 + 337; 0 fechados | 1.425 = 1.088 + 337; 0 fechados; 68,2% dos abertos | qualidade.sem_conta |
| Prospecting sem data | 500 | 500 | qualidade.prospecting_sem_engage_date |
| Vendedores | 35 / 30 / 5 | 35 / 30 / 5 | evidencia_2b_vendedores |
| Ciclo máximo; medianas ganho / perdido | 138; 57 / 14 | 138; 57 / 14 | ciclos |
| Perdidos em até 14 dias | 1.249 | 1.249 | ciclos.perdidos_ciclo_rapido |
| Qui-quadrado produto / vendedor | 0,37 / 0,26 | 0,3719 / 0,2639 | atributos.qui_quadrado_p |
| Correlação do vendedor H1 x H2 | 0,57 | 0,5727 | atributos.correlacao_vendedor_h1_h2 |
| AUC das numéricas | cerca de 0,50 | 0,493 a 0,498 | atributos.auc_numericas |
| Faixas de idade em 31/12 | 110 / 188 / 1.291 | 110 / 188 / 1.291 | idade_hoje.faixas |
| Valor dos 1.291 / do Engaging | 3.198.664 / 3.892.229 | 3.198.664 / 3.892.229 | idade_hoje |
| Prospecting | US$ 1,07 mi | 1.073.986 | prospecting.valor_tabela |
| Concentração top 3 | 83,5% | 0,8352 | produtos.receita_ganha_top3_share |
Snapshot 30/09, pct_ganhos |
0,502 / 0,343 / 0,067 / 0 / 0 | 0,502 / 0,343 / 0,067 / 0 / 0 | fotos_do_briefing.2017-09-30 |
Snapshot 30/06, pct_ganhos |
0,529 / 0,445 / 0,216 / 0 | 0,529 / 0,445 / 0,216 / 0 | fotos_do_briefing.2017-06-30 |
Pendências herdadas do bloco 1, todas cobertas em facts.json:
| Número do SCOPE | Chave | Valor |
|---|---|---|
| 0,51 e 0,54 (AUC de target encoding na própria amostra, inflado) | atributos.auc_categoricas_target_encoding_na_amostra_inflado |
0,5068 (região) a 0,5401 (vendedor) |
| 63% | pipeline.win_rate_fechados |
0,6315 |
| 66%, 134%, 100% | evidencia_2c_close_value.geral |
mín. 0,6573; máx. 1,3418; média 0,9965 |
| cerca de 61% | evidencia_2a_q1.win_rate_depois_do_primeiro_trimestre |
0,6113 |
| US$ 1,07 mi | prospecting.valor_tabela |
1.073.986 |
| 25 fechados do GTK 500 | produtos.deals_fechados_do_produto_maior_preco |
25 |
| 83,5% | produtos.receita_ganha_top3_share |
0,8352 |
| GTX Basic acima de MG Special em deals | produtos.deals_por_produto e deals_fechados_por_produto |
1.866 contra 1.651 (todos) e 1.436 contra 1.223 (fechados) |
| Investigação | Critério (definido antes de olhar o resultado) | Número medido | Decisão |
|---|---|---|---|
| 2a (i) | O 1º trimestre só tem fechamentos em março | 0 fechamentos fora de março; 647 no trimestre | Vale |
| 2a (ii) | Win rate de março a até 5 pp da média de jun, set e dez | 82,1% contra 80,2% (+1,9 pp) | Vale |
| 2a (iii) | Ciclo mínimo de cada coorte engajada antes do 1º fechamento a até 7 dias do piso do recorte | Folgas de 2, 5, 3, 7 e 4 dias (ciclos mínimos 123, 96, 63, 36 e 5); maior folga 7 | Vale no limite (ver “O que deu errado”, item 3) |
| 2a, veredito | As três condições | Artefato de recorte confirmado | (a) win rate por trimestre só com ressalva; (b) fotos de treino a partir de 31/03/2017; (c) 28/02 só como sensibilidade no bloco 3; (d) artefatos do gerador documentados e não explorados |
| 2b | Zero deals em qualquer estágio = “cadastro sem pipeline” | 35 no cadastro, 30 com deals, 5 sem; 27 com deal aberto; 3 só com fechados | Os 5 aparecem no filtro sob o manager com “(sem deals no CRM)”, na “Carteira do time” com zeros e alerta, sem brief (o índice de briefs os lista); os 3 só com fechados mostram “sem deals abertos” |
| 2c | Média por produto em [0,95; 1,05] e variação entre trimestres de até 3 pp | Média por produto de 0,985 a 1,004; variação de 0,46 pp (0,5 pp) | Vale: o valor do deal aberto é sales_price; close_value só como desfecho no backtest |
| Vazamento | Sem conta = nunca fechou? | 1.425 sem conta, todos abertos | has_account e qualquer categoria “desconhecido” de setor ou porte ficam proibidos como variável |
Evidências completas (mês a mês, coortes, cruzamento, posição no trimestre, razão por produto e trimestre, lista dos vendedores) estão nos blocos AUTO de docs/DATA_QUALITY.md.
facts.json (fechados_fora_da_regra_q_q1) e no DATA_QUALITY.process-log/outputs/bloco0_eda.txt).Nos documentos reais, 0 números sem fonte: DATA_QUALITY.md com 50 tokens conferidos e SCOPE.md com 38. Troquei um número de cada vez, em cópia na memória, para ver se o verificador acusa:
| Troca | Acusado? |
|---|---|
| DATA_QUALITY: “1.480” por “1.840” | sim (linha 9) |
| DATA_QUALITY: “82,1%” por “82,3%” | sim |
| DATA_QUALITY: “US$ 3,2 mi” por “US$ 3,3 mi” | sim |
| DATA_QUALITY: “1.088” por “1.089” | sim |
| SCOPE: “83,5%” por “84,5%” | sim |
| SCOPE: “US$ 1,07 mi” por “US$ 1,7 mi” | sim |
Limites do verificador (decisão registrada no DECISIONS): só confere inteiros a partir de 100 (ou com ponto de milhar), US$, porcentagens e decimais com vírgula; aceita o token se algum valor de facts.json ou de ALLOWED_CONSTANTS, arredondado à precisão exibida, bater. Isso prova que o número existe nos fatos, não que está no contexto certo. Contagens pequenas como “25 deals” ou “30 vendedores” não são conferidas pelo texto; os fatos por trás delas estão em test_facts.py.
O PROMPTS manda mostrar o resumo de 2a, 2b e 2c e esperar o ok do Rafael; no modo autônomo, o resumo está na tabela acima e as perguntas abaixo foram respondidas por mim.
scorer.clean, scorer.docs, pytest e scorer.docs --check com exit 0.Import-Csv deals.csv com 8.800 linhas.facts.json com as chaves evidencia_2a_q1, evidencia_2b_vendedores e evidencia_2c_close_value.gitw.py status: “FORA da pasta do projeto: nada”.facts.json para mostrar que é ruído) e nenhuma métrica dentro da amostra como resultado (decisão 6): os AUCs de target encoding saem com a chave ..._na_amostra_inflado.facts.json (produto_com_mais_deals: GTX Basic). O briefing continua sem edição.explore2.py usei c["eq"] (trimestre de engajamento) e depois chamei c.eq, que é o método DataFrame.eq; o cruzamento saiu com o cabeçalho <bound method DataFrame.eq ...>. No código final as colunas são variáveis locais com outros nomes (engage_quarter, close_quarter).facts.json traz um reforço sem tolerância (0% de ciclos de até 14 dias nas coortes com piso de 14 dias ou mais, contra no mínimo 28% nas coortes sem recorte) e as condições (i) e (ii) sustentam o veredito sozinhas. Registrado no DATA_QUALITY e no DECISIONS.facts.json gerado: o ciclo máximo por trimestre de engajamento saía como 138.0 (float por causa do NaN) e passou a inteiro; os quartis de porte tinham rótulos de intervalo ((8.999, 1238.0]) e ganharam q1_menores a q4_maiores. Os demais ciclos e os preços já tinham sido convertidos para inteiro antes da primeira geração.--reference-date 2017-09-30 as três faixas de idade somavam menos que engaging_total (751 + 193 + 530 contra 1.589), porque 115 deals foram engajados depois dessa data e ficam sem idade. idade_hoje passou a contar só os Engaging com idade definida (na data padrão, nada muda: 1.589).Dez linhas “Bloco 2” em docs/DECISIONS.md, todas marcadas “(decisão autônoma do Claude)”:
sales_price.has_account e categorias “desconhecido” proibidos como variável.facts.json.age_days e cycle_days como float com NaN; age_days NaN com engajamento posterior à referência.Escolhas de código sem linha própria: docs.py guarda o verificador (unmatched_numbers) além do inject, em vez de um módulo à parte; os renderizadores AUTO se registram com @docs.block("nome"); join_all compara com o número de linhas da entrada (a conferência de 8.800 está nos testes); o ALLOWED_CONSTANTS é um dicionário número para comentário, ligado às constantes de config.py em vez de números soltos; o arredondamento de fmt.round_half_up é metade para cima, depois de descartar o ruído de ponto flutuante (14.499999999999998 vale 14,5), e o verificador usa a mesma função.
.gitignore do repositório (precisam de git add -f, que o gitw.py já faz). Os 3 .gitkeep removidos aparecem como exclusões.deals.csv é gravado para a data de referência 2017-12-31; age_days só vale para essa data. O estado histórico deve ser reconstruído por as_of(T) a partir das datas.config.FIRST_SNAPSHOT já é 2017-03-31, e SENSITIVITY_FIRST_SNAPSHOT é 2017-02-28.has_account pode ir ao training_set só para diagnóstico.employees e year_established são float (NaN nos 1.425 sem conta).facts._auc é privado; a E5 pode reaproveitar ou mover a função para validate.py.config.COVERED_DOCS; o teste e o number_pool já leem backtest_metrics.json se existir. Novos renderizadores entram com @docs.block. Se o SCORING citar uma constante de desenho que não é fato, entra em config.ALLOWED_CONSTANTS com comentário.