Agregador ponderado · modelo probabilístico · Eleição presidencial 2026

Placar 2026

Parte 3 de 3 · Bastidores técnicos. Como o modelo funciona, a lista de pesquisas e pesos, o método, os vieses de cada instituto, um glossário, as bases completas e o código. Os resultados estão na página inicial; as análises, em O que move.
Atualizado em
25 out 2026, 13h23
1º turno 04 out · 2º turno 25 out
Daniel Pessini Sobreira · modelo, dados e painel
3
Bastidores técnicos
Para quem quer conferir: como o modelo funciona, a lista de pesquisas e pesos, o método, os vieses de cada instituto e um glossário.
Avaliação do modelo

O modelo errou a margem por 1,2 ponto, contra 4,6 das pesquisas: o que acertou e o que falhou

Em uma frase: o modelo errou a margem por 1,2 ponto, contra 4,6 das pesquisas que ele agregava, e o resultado caiu dentro do intervalo central; a parte que mais ajudou foi o desconto do erro histórico, e a que mais atrapalhou foi a correção de casa ancorada nos institutos tradicionais.

Previsão congelada na véspera × resultado oficial

A previsão final foi gravada na noite de 3/out em previsoes_1turno_congeladas.json e não foi mais mexida. Abaixo, cada número dela contra a totalização do TSE: a barra é a faixa em que o modelo punha 80% de chance, o traço é a mediana prevista e o ponto é o resultado (escuro se caiu dentro da faixa, terracota se caiu fora).
Perguntas da véspera
4 de 4
desfechos que o modelo apontava como mais prováveis e que aconteceram · Brier médio 0,108 (moeda: 0,250)
Dentro da faixa de 80%
7 de 8
margem e 7 candidatos; o esperado, se a incerteza estivesse bem calibrada, seria 6 ou 7 de 8
Erro médio por candidato
0,35 p.p.
pesquisas sem desconto: 1,22 · o modelo errou menos em 7 dos 7
Ganho sobre a moeda
32%
redução do Brier de 'quem fica em 1º' contra 50%: o modelo dava 59% ao desfecho certo, não 99%
MARGEM LULA − FLÁVIO (P.P.) −8 −6 −4 −2 0 +2 +4 +6 Margem Margem: faixa de 80% da previsão congelada, −5,3 a 3,8 mediana prevista: −0,7 Margem: apurado −1,87 (previsto −0,7, erro +1,2) −1,87dentro LULA E FLÁVIO (% DOS VÁLIDOS) 41 42 43 44 45 46 47 48 49 Lula (PT) Lula (PT): faixa de 80% da previsão congelada, 42,3 a 47,6 mediana prevista: 45,0 Lula (PT): apurado 45,16 (previsto 45,0, erro −0,2) 45,16dentro Flávio Bolsonaro (PL) Flávio Bolsonaro (PL): faixa de 80% da previsão congelada, 42,9 a 48,5 mediana prevista: 45,7 Flávio Bolsonaro (PL): apurado 47,03 (previsto 45,7, erro −1,3) 47,03dentro DEMAIS CANDIDATOS (% DOS VÁLIDOS) 0 1 2 3 4 5 Augusto Cury (Avante) Augusto Cury (Avante): faixa de 80% da previsão congelada, 1,7 a 4,5 mediana prevista: 2,8 Augusto Cury (Avante): apurado 2,89 (previsto 2,8, erro −0,1) 2,89dentro Renan Santos (Missão) Renan Santos (Missão): faixa de 80% da previsão congelada, 1,4 a 3,9 mediana prevista: 2,4 Renan Santos (Missão): apurado 2,24 (previsto 2,4, erro +0,2) 2,24dentro Ronaldo Caiado (PSD) Ronaldo Caiado (PSD): faixa de 80% da previsão congelada, 1,3 a 3,7 mediana prevista: 2,3 Ronaldo Caiado (PSD): apurado 2,18 (previsto 2,3, erro +0,1) 2,18dentro Romeu Zema (Novo) Romeu Zema (Novo): faixa de 80% da previsão congelada, 0,1 a 1,1 mediana prevista: 0,4 Romeu Zema (Novo): apurado 0,27 (previsto 0,4, erro +0,1) 0,27dentro Outros (6 candidatos) Outros (6 candidatos): faixa de 80% da previsão congelada, 0,3 a 1,6 mediana prevista: 0,7 Outros (6 candidatos): apurado 0,22 (previsto 0,7, erro +0,5) 0,22fora
faixa de 80% previstamediana previstaresultado dentro da faixafora da faixana margem, a faixa mais escura é a de 50%
Pergunta da vésperaO que o modelo diziaO que aconteceuProb. do que aconteceuBrier
Quem termina em 1º?Flávio, com 59%Flávio, por 1,87 pontos59%0,171
Haverá 2º turno?sim, com 98,7%sim98,7%0,000
Flávio vai ao 2º turno?sim, com 100%sim100%0,000
Quem fica em 3º?Cury, com 49% (o mais provável)Augusto Cury, com 2,89%49%0,263
CandidatoApuradoModelo (mediana)Erro do modeloPesquisas (média sem desconto)Erro das pesquisasQuem chegou mais perto
Lula (PT)45,1645,0−0,245,5+0,3modelo
Flávio Bolsonaro (PL)47,0345,7−1,342,8−4,2modelo
Augusto Cury (Avante)2,892,8−0,13,8+0,9modelo
Renan Santos (Missão)2,242,4+0,23,2+1,0modelo
Ronaldo Caiado (PSD)2,182,3+0,13,0+0,8modelo
Romeu Zema (Novo)0,270,4+0,10,7+0,4modelo
Outros (6 candidatos)0,220,7+0,51,1+0,9modelo
Erro médio absoluto0,351,22modelo

Erro = previsto menos apurado, em pontos dos votos válidos. Brier = (1 − probabilidade dada ao que aconteceu)²; 0 é acerto com certeza total e 0,25 é o que se ganha chutando 50% numa pergunta de sim ou não; na pergunta do 3º lugar, com quatro nomes, o chute uniforme (25% para cada) daria 0,563. Uma eleição só não basta para dizer se a incerteza do modelo está calibrada: os números acima dizem se ele foi útil desta vez, não se acertará da próxima.

Ler a análise completa≈ 4 min de leitura

A margem e as alternativas de método

Erro da margem
+1,2 modelo
pesquisas sem desconto: +4,6 · média simples das últimas: +3,5
Percentil do resultado
37º
na distribuição congelada da margem; 50º seria a mediana exata, e entre 25º e 75º é o intervalo de 50%
Brier de 'Lula em 1º'
0,171
0 = certeza correta; 0,25 = moeda; sem desconto seria 0,834
Candidatos dentro dos 80%
6 de 7
só "outros" ficou fora (0,22 contra piso de 0,3)
PrevisãoMargem previstaErroP(Lula em 1º)BrierLog-perda
Pesquisas sem desconto (agregado do modelo)+2,7+4,691%0,8342,44
Média simples das últimas pesquisas+1,6+3,5———
Modelo publicado (desconto +3,4, 60% do histórico)−0,7+1,241%0,1710,53
Desconto histórico cheio (+5,7)−3,0−1,122%0,0480,25

Brier = (probabilidade dada a Lula liderar − 0)²; log-perda = −ln(probabilidade dada ao desfecho real). Menor é melhor nos dois. Margem real: −1,87.

Componente por componente

ComponenteAvaliaçãoEvidência
Desconto do erro histórico do 1º turnofuncionouMoveu a margem de +2,7 para −0,7, na direção certa, e eliminou 74% do erro das pesquisas.
Tamanho do desconto (60% da média histórica)conservadorO erro real da âncora (+4,7) ficou entre o desconto usado (+3,4) e o histórico cheio (+5,7). Com o histórico cheio, a mediana seria −3,0 e a pontuação de Brier, menor.
Largura da incertezaadequadaO resultado caiu no percentil 37, dentro do intervalo de 50%; 6 dos 7 candidatos ficaram dentro da faixa de 80%.
Efeitos de casa ancorados nos institutos tradicionaisfalhouA correção afastou 11 das 14 últimas pesquisas do resultado: ela penalizava quem acertou (Palver, Gerp, Futura) por divergir da âncora, e a âncora errou em bloco. O desconto de viés compensou o nível, mas a informação desses institutos foi descartada.
Nível de LulaacertouMediana 45,0 contra 45,16 apurado.
Nível de Flávio e colapso da terceira viasubestimouMediana 45,7 contra 47,03: o colapso sorteado só redistribuía votos sem deslocar a margem, e o colapso real foi maior e todo para Flávio.
3º lugar e decisão no 1º turnoacertouCury em 3º era o desfecho mais provável (49%); a chance de decisão no 1º turno era de 1,3%.
Leitura estadualfalhou no SudesteAs estaduais previam ganho de Lula no Sudeste e houve perda de 4,3 pontos; a camada pesava 6% no nível e saiu do modelo no regime pós-1º turno.
Validação de origem móvelnão detectava o problemaCobertura de 85% no intervalo de 80%, mas medida contra a própria média das pesquisas: um erro comum a todos os institutos é invisível a ela por construção.
Regime pós-1º turno (2º turno)falha corrigida hojeCom o resultado, sem nenhuma pesquisa nova, o desconto das pesquisas de 2º turno caía de +3,4 para +2,0 e a chance de Lula subia de 28% para 37%. As ondas de antes de 4/out passaram a receber o erro observado (+4,7); com isso a chance de Lula é de 17%, e pesquisas e voto contado convergem.

Como a previsão andou nos últimos dias

RodadaVersãoLula em 1ºFlávio em 1º
30/setv3358%42%
1º/out, tardev3650%50%
1º/out, noitev3852%48%
2/outv3946%54%
3/out, manhãv4044%56%
3/out, tarde (auditoria)v4156%44%
3/out, tarde (MDA, Palver)v4238%62%
3/out, noite (2ª auditoria)v4345%55%
3/out, noite · congeladav4441%59%

A chance oscilou entre 38% e 58% para Lula com as pesquisas e as correções de método da última semana, sem nunca dar a Flávio menos de 42%. O ruído entre rodadas foi do mesmo tamanho que o efeito das auditorias: um sinal de que a previsão estava, corretamente, perto de 50%.

O que muda no modelo para o 2º turno

(1) As pesquisas de 2º turno feitas antes de 4/out recebem o erro observado da âncora no 1º turno (+4,7), e as feitas depois, o desconto das rodadas finais de 2º turno mais 25% desse erro; a mistura segue o peso que as pesquisas novas ganham no filtro. (2) O voto contado entra no placar com o erro total das pesquisas, não só o amostral, e hoje pesa 21%. (3) O erro de cada instituto no 1º turno, medido contra a âncora, vira prior do efeito de casa no 2º turno. (4) O mercado volta a pesar 25%, com preço posterior ao resultado. Pendente: rever a âncora e o encolhimento do desconto com a série de 1º turno atualizada (2010–2026: +5,2; +10,5; +2,4; +5,8; +4,7).

Resumo técnico

Todos os números do painel — atualizado em 25/10/2026, 13h23

  1. 1º turno (4/out). Nas 75 pesquisas de 1º turno desde agosto (17 institutos), ancoradas nos institutos calibrados: Lula 45,5% × Flávio 42,8% dos válidos; Cury 3,8, Renan 3,2, Caiado 3,0, Zema 0,7. Descontado o erro histórico do 1º turno (+3,4 ± 3,8 na margem PT − principal adversário; 60% da média de 2010/14/18/22), margem esperada +2,7 (80% entre +0,1 e +5,2): Lula em 1º em 91% das simulações, Flávio em 9%. Decisão no 1º turno: 1,3%. 2º turno Lula × Flávio: 100%.
  2. 2º turno, placar de hoje. Espaço de estados único (221 pesquisas de 22 institutos; janela de campanha desde 1/jun com 165), efeitos de casa ancorados em AtlasIntel, Datafolha, Ipespe, CNT/MDA, Paraná Pesquisas, PoderData, Quaest: Lula − Flávio = -1,3 p.p. em válidos (±2,3); há duas semanas -2,0 na série das pesquisas; tendência da última semana a favor de Lula, desacelerando. Sob a identificação antiga (média de todos os institutos) o nível seria -2,6: a troca da âncora vale +1,7 p.p.
  3. Chance de vitória. Modelo de pesquisas com desconto de +0,8 ± 2,0 (erro observado das pesquisas no 1º turno, nas ondas de antes de 4/out, e rodadas finais de 2º turno de 2006–2022 mais 25% desse erro, nas ondas novas): Lula 17% · Flávio 83%; sem desconto, Lula 27%; com desconto de +2,1, Lula 6%. Mercados (Polymarket 25/out, 8h): Lula 20%. Fundamentos (só referência, peso 0): Lula 53%. Ensemble (75%/25%/0%): Lula 18% · Flávio 82%; 30% de chance de margem inferior a 1,5 p.p.
  4. Ponto de equilíbrio. Placar de +0,8 para Lula nas pesquisas finais dá 50% para cada um; +1,9 dá 70% a Lula, +3,5 dá 90%; os mesmos 70% e 90% para Flávio vêm com -0,3 e -1,9.
  5. Quatro cenários (do ensemble): Lula por >4 p.p. 2% · Lula por 0–4 16% · Flávio por 0–4 57% · Flávio por >4 25%.
  6. Validação. Origem móvel (últimos 60 dias, horizonte de 7 dias, 233 previsões): cobertura 85% do intervalo de 80% e 94% do de 95%; erro absoluto médio 1,9 p.p.; viés médio -0,3. σ_rw = 0,31 p.p./dia (MAP; ML 0,32); deriva total 0,0 p.p. até 25/out (entre turnos: 0,95 p.p./dia, calibrado em 2014/18/22).
  7. Viés das pesquisas. O erro típico de uma pesquisa isolada neste ciclo é de 3,9 p.p. na margem, contra 2,3 p.p. previstos pela amostragem — razão de 1,72×. As margens brutas variam 2,0 p.p. entre institutos por efeitos de casa reais (medem Flávio melhor que a âncora: Gerp (-7,7), Veritá (-5,3), Palver (-4,4), Futura (-3,8), PoderData (-2,4), Paraná Pesquisas (-2,0); medem Lula melhor: CNT/MDA (+3,9)); tirado o efeito de casa, a dispersão é 0,78× a esperada pelo erro amostral. Ver a seção "Viés".
  8. Indicadores estruturais. Rejeição alta dos dois lados, variando conforme o instituto (seção Rejeição); estoque de indecisos/brancos/nulos variando por instituto (seção Indecisos); ótimo + bom do governo 34,0% (Datafolha 22–23/set).
Sumário
  1. 1 · Os resultados
  2. 1º turno · 4 de outubro
  3. 2º turno · 25 de outubro
  4. Apuração ao vivo: contagem do TSE e projeção
  5. Os números do dia
  6. Em quatro frases
  7. Como ler este painel
  8. Histórico das simulações: a chance de Lula em cada rodada do modelo
  9. Pesquisas depois do 1º turno: evolução e linha-síntese
  10. Ponteiro da tendência: para onde as pesquisas do 2º turno estão indo
  11. Painel de mudança: diferenças dentro de cada instituto
  12. Votos em disputa: quem ainda decide e como cada candidato vai nos segmentos
  13. Quatro cenários para 25 de outubro
  14. 2 · O que move a disputa
  15. O que move o 2º turno: temas, notícias e manifestações
  16. Temas do 1º turno: por que Flávio virou e pesquisas × urnas
  17. Como o placar evoluiu desde dezembro
  18. Os estados: quem lidera onde e o que isso diz sobre o placar nacional
  19. Os municípios: o que mudou entre o 2º turno de 2022 e o 1º turno de 2026
  20. O voto no exterior: cidades, locais de votação e onde agir
  21. Gerações: há uma nova geração de eleitores mais à direita?
  22. Variáveis decisivas: o que move a margem
  23. Monitoramento da rejeição: Lula × Flávio Bolsonaro
  24. Evolução do estoque de indecisos, brancos e nulos
  25. Abstenção: quem falta às urnas e a quem isso custa
  26. Migração de ausentes, terceira via e brancos/nulos: cadeia de Markov + Monte Carlo
  27. Escândalos e fatos da reta final: STF, Master, Dark Horse, bets, Aparecida e Carbono Oculto
  28. Outros adversários testados
  29. 3 · Bastidores técnicos
  30. Acurácia da previsão do 1º turno
  31. Modelo probabilístico: quem vence e sob que condições
  32. Pesquisas individuais e pesos
  33. Metodologia de agregação
  34. Histórico de mudanças e ajustes
  35. Viés, vícios de amostra e o que a margem de erro não cobre
  36. Glossário
  37. Transparência e conformidade eleitoral
  38. Fundamentos: aprovação, economia e sentimento
  39. Mercados de previsão e casas de análise
  40. Eventos e riscos políticos de 2026
  41. Deriva histórica: pesquisas de setembro × resultado
  42. Calibração por instituto (2006 a 2022)
  43. Apêndice A — base completa de pesquisas
  44. Apêndice B — base completa de pesquisas de 1º turno
  45. Apêndice C — código, dados e reprodução
  46. Referências
Simulação

Lula precisa de +0,8 nas pesquisas finais para ter 50% de chance; hoje o modelo lhe dá 17%

Em uma frase: o modelo simula a eleição 400 mil vezes a partir da tendência de hoje, somando a incerteza do que ainda pode mudar até 25/out (deriva) e o erro histórico das pesquisas.

Como ler. No gráfico, o eixo horizontal é a margem final (Lula − Flávio). A altura de cada barra mostra em quantas simulações o resultado caiu naquela faixa: ardósia (à direita do zero) = Lula vence; terracota = Flávio vence. Quanto mais larga a "montanha", maior a incerteza. A tabela de break-even mais abaixo responde: "se a última pesquisa der Lula +X, qual a chance real dele?"
Distribuição simulada da margem final (modelo de pesquisas com o desconto do erro histórico). A tabela de ponto de equilíbrio traduz o resultado em regra prática: dado um placar ajustado nas pesquisas da última semana, qual a chance de cada um — considerando que as pesquisas finais superestimaram o candidato do PT em 3 dos 5 últimos 2ºs turnos e o subestimaram em 2.
margem -14,0 a -13,0 p.p. · Flávio vence nessa faixa · densidade 0% do pico margem -13,0 a -12,0 p.p. · Flávio vence nessa faixa · densidade 0% do pico margem -12,0 a -11,0 p.p. · Flávio vence nessa faixa · densidade 0% do pico margem -11,0 a -10,0 p.p. · Flávio vence nessa faixa · densidade 1% do pico margem -10,0 a -9,0 p.p. · Flávio vence nessa faixa · densidade 1% do pico margem -9,0 a -8,0 p.p. · Flávio vence nessa faixa · densidade 2% do pico margem -8,0 a -7,0 p.p. · Flávio vence nessa faixa · densidade 5% do pico margem -7,0 a -6,0 p.p. · Flávio vence nessa faixa · densidade 12% do pico margem -6,0 a -5,0 p.p. · Flávio vence nessa faixa · densidade 25% do pico margem -5,0 a -4,0 p.p. · Flávio vence nessa faixa · densidade 49% do pico margem -4,0 a -3,0 p.p. · Flávio vence nessa faixa · densidade 78% do pico margem -3,0 a -2,0 p.p. · Flávio vence nessa faixa · densidade 100% do pico margem -2,0 a -1,0 p.p. · Flávio vence nessa faixa · densidade 99% do pico margem -1,0 a 0,0 p.p. · Flávio vence nessa faixa · densidade 76% do pico margem 0,0 a 1,0 p.p. · Lula vence nessa faixa · densidade 46% do pico margem 1,0 a 2,0 p.p. · Lula vence nessa faixa · densidade 23% do pico margem 2,0 a 3,0 p.p. · Lula vence nessa faixa · densidade 11% do pico margem 3,0 a 4,0 p.p. · Lula vence nessa faixa · densidade 5% do pico margem 4,0 a 5,0 p.p. · Lula vence nessa faixa · densidade 2% do pico margem 5,0 a 6,0 p.p. · Lula vence nessa faixa · densidade 1% do pico margem 6,0 a 7,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 7,0 a 8,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 8,0 a 9,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 9,0 a 10,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 10,0 a 11,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 11,0 a 12,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 12,0 a 13,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 13,0 a 14,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 14,0 a 15,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 15,0 a 16,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 16,0 a 17,0 p.p. · Lula vence nessa faixa · densidade 0% do pico margem 17,0 a 18,0 p.p. · Lula vence nessa faixa · densidade 0% do pico -12 -8 -4 0 +4 +8 +12 +16 margem Lula − Flávio em 25/out (p.p. de votos válidos) Lula vence · 17% Flávio vence · 83% faixa clara = |margem| < 1,5 p.p. (resultado contestável): 34%
Modelo de pesquisas (75%)
Lula 17% · Flávio 83%
mediana da margem -2,0 · 80% entre -4,8 e +0,7
Mercados de previsão (25%)
Lula 20% · Flávio 80%
Polymarket 25/out, 8h: Flávio 79,5% × Lula 20,5% (US$ 197 mi). Desde 8/set: 59% → 40% (21/set) → 41% (30/set) → 38% (1º/out) → 36% (3/out) → 15% (4/out, após o resultado) → 16% (5/out, noite) → 12% (10/out, após Datafolha, AtlasIntel e PoderData) → 12% (11/out, estável após Vox Brasil e Ipespe). → 17% (15/out) → 22% (16/out, após Datafolha 47 x 45, Quaest 46 x 44 e PoderData 50 x 50). → 23% (17/out, noite) → 20% (18/out, antes do debate da Band). → 23,5% (19/out, depois de PoderData, AtlasIntel, BTG/Nexus e Futura). → 21,5% (20/out, 13h, depois de Real Time Big Data, Alfa e França). → 20,5% (21/out, 7h20, depois de Meio/Ideia; 19,5% de madrugada). → 21,5% (21/out, 16h34, depois de Quaest, Palver e Futura; 22,5% às 12h03). → 21,5% (22/out, 9h, depois de Gerp, Veritá e PoderData 51 x 49; 20,5% na noite de 21/out). → 22,5% (22/out, 17h, depois de Indexa 50,6 x 49,4; 21,5% a 22,5% ao longo do dia). → 22,5% (23/out, 3h, depois de Datafolha 50 x 50 e Vox 50,6 x 49,4; estável desde a tarde de 22/out). → 23,5% (23/out, 11h, depois de Real Time Big Data 52 x 48, Alfa 52 x 48 e Ipespe 50,5 x 49,5). → 23,5% (23/out, 15h, estável à tarde, depois de Vox, Real Time Big Data, Alfa, Ipespe e Veritá). → 20,5% (24/out, 3h, depois do debate da Globo e da Veritá 51,3 x 48,7). → 18% (24/out, 11h, antes de Datafolha, AtlasIntel e Quaest; só Instituto França 51,4 x 48,6 e DataTrends 51,9 x 48,1 nos válidos). → 17,5% (24/out, 18h, depois de CNT/MDA 50,7 x 49,3, Gerp 52 x 48 e Palver 50 x 49; 83,7% para Flávio às 17h; ainda sem Datafolha, AtlasIntel e Quaest). → 20,5% (25/out, 8h, dia da votação, depois de Datafolha 51 x 49, Quaest 51 x 49, AtlasIntel 50,8 x 49,2, BTG/Nexus 50,5 x 49,5, Futura 50,1 x 49,9, Veritá 51,6 x 48,4, PoderData 51 x 49 e Colectta 49,7 x 50,3; 83,7% para Flávio na tarde de 24/out).
Fundamentos (0%)
Lula 53% · Flávio 47%
Ótimo + bom do governo hoje: 34% (Datafolha 22–23/set). Mesma medida nos três últimos presidentes candidatos (Datafolha, durante a campanha): o empate ficou em torno de 33,7% de ótimo + bom (2006: 47,0 → +21,7; 2014: 39,0 → +3,3; 2022: 31,0 → -1,8). Margem esperada: +0,4.
Ensemble (central)
Lula 18% · Flávio 82%
mediana -2,3 · 80% entre -6,1 e +1,0 · |margem| < 1,5: 30%
Dois modelos em paralelo: Monte Carlo e MCMC. O modelo principal estima cada parâmetro no seu valor mais provável (velocidade de mudança do placar, desvio e ruído de cada instituto) e depois sorteia por fora a deriva e o erro das pesquisas. O modelo paralelo usa as mesmas pesquisas, as mesmas hipóteses e as mesmas fontes de incerteza, mas sorteia tudo junto por MCMC (4 cadeias × 4.000 sorteios), de modo que a incerteza sobre os próprios parâmetros também chega à previsão. Se os dois concordam, a manchete não depende do atalho do modelo principal; se divergem, a tabela mostra de onde vem a diferença. A manchete continua sendo o modelo principal.
margem -20,0 a -19,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem -19,0 a -18,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem -18,0 a -17,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem -17,0 a -16,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem -16,0 a -15,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem -15,0 a -14,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem -14,0 a -13,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem -13,0 a -12,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem -12,0 a -11,0 p.p. · principal 0.1% · MCMC 0.0% das simulações margem -11,0 a -10,0 p.p. · principal 0.1% · MCMC 0.0% das simulações margem -10,0 a -9,0 p.p. · principal 0.2% · MCMC 0.0% das simulações margem -9,0 a -8,0 p.p. · principal 0.4% · MCMC 0.1% das simulações margem -8,0 a -7,0 p.p. · principal 0.9% · MCMC 0.2% das simulações margem -7,0 a -6,0 p.p. · principal 2.2% · MCMC 0.6% das simulações margem -6,0 a -5,0 p.p. · principal 4.7% · MCMC 1.8% das simulações margem -5,0 a -4,0 p.p. · principal 9.2% · MCMC 4.9% das simulações margem -4,0 a -3,0 p.p. · principal 14.6% · MCMC 11.2% das simulações margem -3,0 a -2,0 p.p. · principal 18.6% · MCMC 18.1% das simulações margem -2,0 a -1,0 p.p. · principal 18.4% · MCMC 21.5% das simulações margem -1,0 a 0,0 p.p. · principal 14.1% · MCMC 18.5% das simulações margem 0,0 a 1,0 p.p. · principal 8.6% · MCMC 12.3% das simulações margem 1,0 a 2,0 p.p. · principal 4.3% · MCMC 6.5% das simulações margem 2,0 a 3,0 p.p. · principal 2.0% · MCMC 2.8% das simulações margem 3,0 a 4,0 p.p. · principal 0.9% · MCMC 1.1% das simulações margem 4,0 a 5,0 p.p. · principal 0.4% · MCMC 0.5% das simulações margem 5,0 a 6,0 p.p. · principal 0.2% · MCMC 0.1% das simulações margem 6,0 a 7,0 p.p. · principal 0.1% · MCMC 0.0% das simulações margem 7,0 a 8,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 8,0 a 9,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 9,0 a 10,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 10,0 a 11,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 11,0 a 12,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 12,0 a 13,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 13,0 a 14,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 14,0 a 15,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 15,0 a 16,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 16,0 a 17,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 17,0 a 18,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 18,0 a 19,0 p.p. · principal 0.0% · MCMC 0.0% das simulações margem 19,0 a 20,0 p.p. · principal 0.0% · MCMC 0.0% das simulações -20 -16 -12 -8 -4 0 +4 +8 +12 +16 +20 margem Lula − Flávio em 25/out (p.p. de votos válidos; caudas além de ±20 somadas nas pontas) principal (Monte Carlo): Lula 17% · mediana -2,0 · 80% de -4,8 a 0,7 MCMC (posterior completo): Lula 23% · mediana -1,4 · 80% de -3,7 a 1,1
QuantidadePrincipal (Monte Carlo)MCMC (posterior completo)
Chance de Lula · só pesquisas17%23%
Chance de Lula · ensemble18%23%
Margem mediana em 25/out-2,0-1,4
Faixa de 80% da margem final-4,8 a +0,7-3,7 a +1,1
Placar verdadeiro hoje (já sem o erro das pesquisas)-2,0 ± 4,5-1,3 ± 3,8
Deriva até 25/out (desvio)0,00,0
σrw: mudança diária até o 1º turno (p.p.)0,31 (fixo)0,31 (80%: 0,22 a 0,43)
σentre: mudança diária entre os turnos (p.p.)0,95 (fixo)0,63 (80%: 0,39 a 0,95)
Erro comum das pesquisas β, além do desconto históricosorteado: t5(0; 2,5)-0,6 (80%: -2,7 a 1,5)
Convergência—R̂ máx. 1,014 · ESS mín. 589

Diferença na chance de Lula só pelas pesquisas: +6,7 p.p. (MCMC menos principal). O placar verdadeiro de hoje difere em +0,7 p.p. nos dois modelos. Com pesquisas novas, as duas leituras tendem a se aproximar.

O que mudou no modelo (v3, auditoria de 30/set). A tabela parte do modelo anterior e liga uma correção de cada vez, mostrando a chance de cada um só pelas pesquisas depois de cada passo. Todas usam a mesma semente de sorteio, então as diferenças vêm do fator, não do acaso. A maior mudança é a identificação dos efeitos de casa: antes, o "consenso" era a média de todos os institutos (inclusive os que mais destoam); agora é a média dos institutos com histórico verificado, o mesmo conjunto em que o erro histórico é medido, o que elimina uma dupla contagem.
PassoLula · só pesquisasFlávio · só pesquisasEfeito do passo
Modelo anterior (v2): âncora em todos os institutos, viés +2,1, deriva 4,321,6%78,4%
+ âncora nos institutos calibrados (identificação dos efeitos de casa)30,4%69,6%+8,8 p.p. para Lula
+ viés recalibrado (+0,9 ± 2,0)38,9%61,1%+8,5 p.p. para Lula
+ deriva em duas fases calibrada (0,0)24,8%75,2%-14,1 p.p. para Lula
+ P(Flávio no 2º turno) do modelo do 1º turno (1,00)15,7%84,3%-9,1 p.p. para Lula
+ viés das pesquisas de 2º turno anteriores ao 1º turno (+0,8 ± 1,98)16,5%83,5%+0,8 p.p. para Lula
Placar ajustado das pesquisas na última semana (Lula − Flávio, p.p. válidos)-2-10+1+2+3+4+5+6+7+8
Chance de Lula10%20%36%54%72%85%94%98%99%100%100%
Chance de Flávio90%80%64%46%28%15%6%2%1%0%0%

Ponto de equilíbrio (50% para cada um): placar de +0,8 para Lula nas pesquisas finais, que é o erro histórico descontado. Lula chega a 70% com +1,9 e a 90% com +3,5; Flávio chega aos mesmos 70% e 90% com placares de -0,3 e -1,9 (simetria em torno do equilíbrio). Hoje o placar ajustado é -1,3.

Lula vence se…

  • Ficar com pelo menos 62% dos 7,8% que votaram nos demais candidatos (a 2ª opção da Quaest lhe dava cerca de 25%; a PoderData pós-1º turno, de 30% a 43% conforme o candidato), ou compensar a diferença trazendo eleitores que se abstiveram no 1º turno
  • Recuperar no Sudeste (40,7% dos válidos, onde perdeu 4,3 pontos de margem contra 2022) o terreno perdido: cada 3 pontos ali valem cerca de 1,2 no nacional
  • As pesquisas feitas depois de 4/out mostrarem Lula à frente por mais que o erro de 1º turno da âncora (+4,7)
  • A rejeição de Flávio passar à frente da de Lula, que hoje estão empatadas

Flávio vence se…

  • Ficar com pelo menos 38% dos votos dos demais candidatos (a 2ª opção da Quaest lhe dava de 36% a 59%; a PoderData pós-1º turno, de 38% a 57%)
  • Manter Minas Gerais e ampliar a frente no Sul e no Centro-Oeste
  • O comparecimento repetir o do 1º turno, sem mobilização extra no Nordeste
  • As pesquisas repetirem no 2º turno o erro do 1º
Base de dados

Pesquisas individuais e pesos

Em uma frase: a última pesquisa de cada instituto, o peso que ela recebe no cálculo e o desvio típico (efeito de casa) que o painel corrige.

Última onda de cada instituto no cenário Lula × Flávio, todas de institutos profissionais com plano amostral e pesquisas registradas na Justiça Eleitoral — nenhuma enquete informal (sondagem sem metodologia estatística, vedada desde 16/ago/2026) entra na base. A margem é calculada sobre votos válidos (Lula ÷ (Lula + Flávio)), como no resultado oficial. "Contratante" é quem encomendou a pesquisa; "Margem de erro" é a margem amostral padrão a 95% de confiança (p=0,5), calculada a partir de n — para o registro oficial no PesqEle/TSE de cada rodada, consulte a fonte de cada pesquisa (links na versão completa). "Efeito de casa" é o desvio sistemático do instituto em 2026 em relação à âncora, os institutos com histórico presidencial verificado (positivo = mede Lula melhor que a âncora; negativo = mede Flávio melhor); σp é a variância extra do instituto além do erro amostral, estimada nos dados, e "peso" é a precisão relativa de cada pesquisa no modelo (1 = a mais precisa); "MAE 2006–22" é o erro absoluto médio na margem nas últimas pesquisas antes de cada turno das eleições de 2006 a 2022 em que o instituto aparece.
InstitutoContratanteCamponMétodoMargem de erro (95%)Lula × FlávioMargem válidaEfeito de casaMargem ajustadaVariância extra e pesoMAE 2006–22
Futura (escolaridade pela PNAD) · 21 ondas Apex/100% Cidades 24/10 2.000 telefônico ±2,2 p.p. 47,9 × 48,2 -0,3 -0,4 +0,1 σp = 0,8 · peso 0,55 2,4
PoderData · 17 ondas Aya 24/10 4.000 telefônico ±1,5 p.p. 47,0 × 49,0 -2,1 -1,2 -0,9 σp = 0,8 · peso 1,00 · âncora 4,5
BTG/Nexus · 19 ondas BTG Pactual 24/10 2.000 telefônico ±2,2 p.p. 46,0 × 47,0 -1,1 -0,6 -0,5 σp = 0,8 · peso 0,54 —
Quaest · 19 ondas Globo/O Globo 24/10 3.702 presencial ±1,6 p.p. 45,0 × 46,0 -1,1 -0,5 -0,6 σp = 0,8 · peso 0,90 · âncora 4,0
Datafolha · 18 ondas Folha/Globo 24/10 4.050 presencial ±1,5 p.p. 47,0 × 48,0 -1,1 0,0 -1,1 σp = 0,8 · peso 1,00 · âncora 3,4
Palver (calibração v3) · 9 ondas próprio 24/10 5.000 online ±2,3 p.p. 49,0 × 50,0 -1,0 -0,3 -0,7 σp = 0,8 · peso 0,51 —
Gerp · 20 ondas próprio 24/10 2.400 telefônico ±2,0 p.p. 46,0 × 49,0 -3,2 -3,8 +0,7 σp = 0,8 · peso 0,64 —
Instituto França · 3 ondas próprio 23/10 1.537 não informado ±2,5 p.p. 43,1 × 45,6 -2,8 -0,2 -2,6 σp = 0,8 · peso 0,40 —
Colectta · 1 onda Brazil Economy 23/10 3.000 presencial ±1,8 p.p. 46,5 × 46,0 +0,5 +0,2 +0,3 σp = 0,8 · peso 0,76 —
CNT/MDA (ponderada pelo 1º turno) · 7 ondas CNT 23/10 2.032 presencial ±2,2 p.p. 45,0 × 46,3 -1,4 -0,3 -1,1 σp = 0,8 · peso 0,54 3,9
Veritá · 9 ondas próprio 23/10 15.554 telefônico ±1,4 p.p. 46,5 × 49,4 -3,1 -2,7 -0,4 σp = 1,3 · peso 0,83 3,8
AtlasIntel · 17 ondas Bloomberg 23/10 4.990 online ±2,0 p.p. 48,4 × 49,9 -1,5 -0,2 -1,3 σp = 0,8 · peso 0,69 · âncora 4,5
Ipespe · 3 ondas Abrapel 22/10 1.500 telefônico ±2,5 p.p. 47,0 × 48,0 -1,1 -0,9 -0,2 σp = 0,8 · peso 0,42 · âncora 4,9
Alfa Inteligência · 6 ondas próprio 22/10 2.700 presencial ±1,9 p.p. 46,0 × 49,9 -4,1 +0,1 -4,2 σp = 1,0 · peso 0,65 —
Real Time Big Data · 9 ondas próprio 22/10 2.000 telefônico ±2,2 p.p. 45,0 × 49,0 -4,3 -0,8 -3,5 σp = 0,8 · peso 0,54 —
Vox Brasil · 11 ondas próprio 22/10 2.100 presencial ±2,1 p.p. 45,4 × 44,3 +1,2 -0,7 +1,9 σp = 1,4 · peso 0,45 —
Indexa · 8 ondas Broadcast/Estadão 21/10 2.000 telefônico ±2,2 p.p. 44,1 × 45,2 -1,2 +0,5 -1,8 σp = 0,8 · peso 0,52 —
DataTrends · 5 ondas próprio 21/10 2.000 telefônico ±2,2 p.p. 46,0 × 50,0 -4,2 -0,9 -3,2 σp = 0,8 · peso 0,55 —
Meio/Ideia · 11 ondas Canal Meio 19/10 1.500 telefônico ±2,5 p.p. 46,0 × 48,0 -2,1 -0,4 -1,7 σp = 0,8 · peso 0,42 —
American Analytics · 2 ondas Times Brasil/CNBC 20/09 2.000 não informado ±2,2 p.p. 43,0 × 43,0 0,0 0,0 0,0 σp = 0,8 · peso 0,50 —
Vetor/Arrow · 2 ondas próprio 18/05 9.000 não informado ±1,4 p.p. 50,7 × 38,3 +13,9 -0,4 +14,3 σp = 6,0 · peso 0,08 —
Paraná Pesquisas · 4 ondas próprio 28/03 2.080 presencial ±2,1 p.p. 44,1 × 45,2 -1,2 -2,0 +0,8 σp = 0,8 · peso 0,54 · âncora 3,8
Metodologia

Metodologia de agregação (versão 3)

Em uma frase: um único modelo estatístico estima ao mesmo tempo a tendência, o desvio de cada instituto e quanto cada um erra além do declarado; a tendência é ancorada nos institutos com histórico verificado, e o erro histórico desse mesmo conjunto é descontado na simulação.

Ler a análise completa≈ 20 min de leitura
Quatro camadas, cada uma corrigindo uma fonte de erro distinta: erro de instituto (efeitos de casa e variância extra), erro de momento (tendência diária), erro sistemático do conjunto das pesquisas (calibração histórica 2006–2022) e o que ainda pode mudar até a eleição (deriva). Tudo reprodutível no script Python anexo. A auditoria de 30/set/2026 substituiu a versão anterior; a tabela "o que mudou" na seção Modelo mostra o efeito de cada correção.

1 · Tendência, efeitos de casa e variância por instituto: um só modelo

yi = xt(i) + hp(i) + εi · εi ~ N(0, EPamostral,i² + σp(i)²)
xt = xt−1 + ηt, η ~ N(0, σrw²) · hp ~ N(0, 1,5²) · Σp ∈ âncora qp·hp = 0

É o núcleo dos agregadores de referência (Jackman 2005; Linzer 2013; Heidemanns, Gelman e Morris para a Economist): o placar verdadeiro xt anda um pouco a cada dia e cada pesquisa é uma medição ruidosa dele, deslocada pelo efeito de casa hp do instituto. Os três blocos são estimados em conjunto, por iteração: suavizador de Kalman para xt, média posterior encolhida para hp e, para σp, a variância que sobra nos resíduos de cada instituto além do erro amostral, encolhida para a mediana. Institutos erráticos (Veritá, Vetor, Gerp, CNT/MDA) recebem σp alto e pesam menos, sem nenhum peso arbitrário. σrw = 0,31 p.p./dia na janela de campanha (desde 1/jun, 165 pesquisas), estimado com um prior centrado em 0,25 (sem o prior: 0,32).

2 · A âncora: quem define o "zero" dos efeitos de casa

Efeito de casa é um desvio em relação a alguma referência, e a escolha da referência define o nível de toda a curva. A versão anterior usava a média de todos os institutos, ponderada pelo número de ondas, o que deixava o "consenso" nas mãos de quem publica mais (Gerp, Futura, Palver e Veritá, os que mais destoam para Flávio). Agora a referência é a âncora: AtlasIntel, Datafolha, Ipespe, CNT/MDA, Paraná Pesquisas, PoderData, Quaest, os institutos com histórico presidencial verificado em 2014–2022, ponderados pela qualidade histórica. Só a média deles é fixada em zero; cada um pode desviar. O motivo é consistência: o erro histórico descontado no passo 3 foi medido nesses mesmos institutos, então nível e desconto falam da mesma coisa. Trocar a âncora desloca o nível de hoje em +1,7 p.p. (ver "o que mudou").

2b · Pesquisas estaduais

swinguf = margem2026,uf − margem2022,uf ~ N(μ, τ²) · nacional = +1,80 + Σ pesouf·swinguf

Cada pesquisa estadual recente vira um swing em relação ao 2º turno de 2022 no estado, corrigida pelo efeito de casa nacional do instituto e pelo movimento do placar desde o fim do campo. Encolhimento em dois níveis, estado → região → país (DerSimonian-Laird, τ = 7,2); a soma ponderada pelo eleitorado dá uma segunda leitura do placar nacional (+3,6), combinada com a nacional assumindo correlação de 0,3 entre os erros (peso atual: 0%), desde que as UFs com pesquisa somem ao menos 30% do eleitorado.

2c · Validação do próprio modelo (origem móvel)

Para cada um dos últimos 60 dias, o modelo foi reajustado só com as pesquisas até aquele dia e previu as pesquisas dos 7 dias seguintes. Em 233 previsões: intervalo de 80% cobriu 85% dos casos e o de 95% cobriu 94% (calibração adequada); erro absoluto médio de 1,9 p.p.; viés médio de -0,3 p.p. (as pesquisas vieram, em média, mais favoráveis a Flávio do que o previsto uma semana antes, reflexo da queda de agosto–setembro, que um passeio aleatório não extrapola). Separando: -0,3 p.p. nos institutos que o ajuste já conhecia (217 previsões) e -0,8 p.p. nos que entraram na base depois (16), que estreiam com efeito de casa zero. Esse viés é informação, não é corrigido: extrapolar tendência foi o defeito da versão anterior.

De onde vem o movimento. Quanto o placar de hoje mudaria se cada pesquisa dos últimos 7 dias fosse retirada (positivo = a pesquisa puxa a favor de Lula). Em série que termina hoje, a pesquisa mais recente pesa mais.

PesquisaCampo atéEfeito no placar do 2º turnoEfeito na margem do 1º turno
Alfa Inteligência22/10-0,09—
Gerp24/10+0,09—
PoderData21/10+0,06—
Vox Brasil22/10+0,06—
Real Time Big Data22/10-0,06—
Colectta23/10+0,06—
Veritá22/10+0,05—
DataTrends21/10-0,05—

3 · Erro sistemático: o desconto histórico

viés ~ t5(+0,8, 2,0) · ondas de antes de 4/out: o erro observado da âncora no 1º turno; ondas depois de 4/out: rodadas finais de 2º turno (sem herdar o erro do 1º turno desde 22/out, porque ele foi voto útil); mistura pela fração do placar que vem de ondas novas, só sobre a parcela do placar que vem de pesquisas

Nos cinco 2ºs turnos de 2006 a 2022, as pesquisas finais dos institutos nacionais erraram a margem PT − direita em +0,3, -0,1, +1,1, -1,2 e +2,9 pontos (tabela abaixo; positivo = superestimaram o PT). A média com recência suave é +0,9; a média simples, +0,6. A versão anterior usava +2,1, com peso 2,5× no ciclo de 2022. O desvio entre ciclos (σ = 2,0) entra como incerteza. Depois do 1º turno. As pesquisas da âncora erraram a margem Lula − Flávio do 1º turno em +4,7 p.p. (positivo = superestimaram Lula). As ondas de 2º turno feitas antes de 4/out são as mesmas que carregavam esse erro e recebem o desconto observado (+4,7); as feitas depois recebem só o desconto das rodadas finais de 2º turno (+0,9). Até 22/out elas recebiam também 25% do erro do 1º turno; a revisão de 22/out (v56) tirou essa herança porque o erro do 1º turno teve a assinatura de voto útil: a média final das pesquisas errou Lula em -0,3 p.p. e Flávio em +4,2, enquanto os demais candidatos perderam 4,0 pontos, que foram quase todos para Flávio, e esse mecanismo não existe num 2º turno com dois nomes. O erro próprio de 2º turno continua no desconto: nas rodadas finais de 2006 a 2022 ele foi +0,3 (2006), -0,1 (2010), +1,1 (2014), -1,2 (2018), +2,9 (2022), com mais peso nos ciclos recentes. A herança tirava cerca de 8 pontos da chance de Lula no modelo principal (12,8% contra 20,6% com os dados de 22/out). Hoje 57 ondas pós-1º turno respondem por 100% do placar das pesquisas. O resultado oficial, com a transferência de votos dos demais candidatos, dá um placar de partida de -2,7 ± 2,5 em 4/out, que vira ± 5,0 hoje somada a deriva do eleitorado desde então; as pesquisas dão -0,9 ± 0,7 antes do desconto. O painel combina os dois pelo erro total de cada um (nas pesquisas, amostral e sistemático): hoje o resultado oficial pesa 21% e perde peso a cada dia, porque a foto de 4/out envelhece enquanto chegam pesquisas novas. (Revisão de 4/out: a regra anterior aplicava o desconto de pesquisas novas às ondas antigas e comparava o voto contado só com o erro amostral das pesquisas; com o resultado na mão, isso levava a chance de Lula de 28% para 37%.) A sensibilidade a essa hipótese está na tabela ao lado: é a escolha que mais muda o resultado, por isso fica à vista. Após o 1º turno, as pesquisas de 2º turno já incorporam o resultado e volta a valer o desconto das pesquisas finais; o erro de cada instituto no 1º turno vira prior, com metade do peso, do seu efeito de casa no 2º.

4 · Simulação até 25/out

margemfinal = xhoje + N(0, 1,2) + deriva t5(0, 0,0) − erro das pesquisas t5(+0,8, 2,0)
com P(adversário = Flávio) = 100% (modelo do 1º turno); senão margem ~ N(+7, 4)

400 mil simulações. Deriva em duas fases: até 4/out, σrw = 0,31 p.p./dia; entre os turnos, 0,95 p.p./dia, calibrado no movimento das pesquisas de 2º turno de Datafolha e Ibope/Ipec entre a primeira e a última rodada em 2014, 2018 e 2022 (tabela abaixo; descontado o erro amostral e encolhido 50% para σrw, porque são só três ciclos). Total: 0,0 p.p. até 25/out. Deriva e erro das pesquisas têm caudas t de Student (5 graus de liberdade, mesmo desvio): com três a cinco ciclos de calibração, a normal subestima surpresas. Os choques regionais saíram da simulação na auditoria de 3/out: o erro histórico já foi medido no resultado nacional, que inclui os erros regionais. P(adversário = Flávio) vem da simulação do 1º turno, com 0% de risco de impugnação.

5 · Ensemble

P = 75% · modelo + 25% · mercados (Polymarket 25/out, 8h: Lula 20% × Flávio 80%) + 0% · fundamentos (ótimo + bom 34,0% · margem = 1,47·(ótimo + bom − 33,7))

Mistura de distribuições, não só de probabilidades, por isso os cenários são coerentes com o ensemble. Os fundamentos (uma reta ajustada em três eleições, com erro de previsão acima de 8 pontos) ficam com peso zero desde a auditoria de 3/out. O mercado só entra com preço de até 5 dias e, depois do 1º turno, posterior a 4/out; sem isso, o ensemble é o próprio modelo. Depois do 1º turno, o resultado oficial passa a informar o modelo diretamente. Mercados reagem rápido a notícias, mas não são independentes das pesquisas.

5b · Leituras alternativas do herói: só as pesquisas feitas depois do 1º turno

Os botões no alto da página trocam a manchete e a barra de chance por rodadas extras do mesmo modelo em que a base de 2º turno fica só com as 57 pesquisas com campo depois de 4/out (19 institutos; 19 ondas de 5 institutos com histórico calibrado: AtlasIntel, Datafolha, Ipespe, PoderData, Quaest), de modo que o desvio de cada instituto é estimado apenas nelas, sem a persistência de metade do desvio medido antes do 1º turno (λ = 0,5). Tudo o mais é igual: âncora, prior do efeito de casa vindo do erro de cada instituto no 1º turno, deriva até 25/out e ensemble com os mercados. Modelo completo hoje: placar -1,3 ± 1,17, chance de Lula 17% só pelas pesquisas e 18% no ensemble, com 21% do nível vindo do voto contado (-2,7). Na leitura pesquisas pós-1º turno + voto contado o nível é combinado ao placar de partida como no modelo completo (peso 21%): placar -1,9 ± 1,19, chance de Lula 11% só pelas pesquisas e 13% no ensemble; a diferença para o modelo completo isola o efeito de estimar os desvios de instituto só nas ondas novas. Na leitura só pesquisas pós-1º turno, sem o voto contado o nível não é combinado ao placar de partida e o desconto do erro histórico incide inteiro sobre as pesquisas (+0,9 ± 2,5, contra +0,8 efetivos no modelo completo): placar -1,7 ± 0,69, chance de Lula 13% só pelas pesquisas e 15% no ensemble.

O que essas leituras medem bem: não dependem da hipótese de que metade do desvio medido em cada instituto antes do 1º turno persiste depois dele (mal identificada na auditoria de 16/out) e, na versão sem voto contado, tampouco da transferência declarada de votos. O que medem mal: com 18 dias de dados a velocidade diária do placar fica pouco identificada (σrw 0,29 contra 0,31 na série longa); institutos com uma só onda ficam perto do prior; a incerteza da média da âncora sobe (0,17 contra 0,10); e não há validação de origem móvel possível (herdam a calibração do modelo principal). Por isso a página inteira, o compartilhamento e a versão sem JavaScript seguem o modelo completo; as variantes são leituras de sensibilidade, gravadas em resultados_pos1t_estrut.json e resultados_pos1t.json.

5c · Leitura com a direção das últimas ondas (momentum)

O quarto botão mantém o modelo completo inteiro (nível, efeitos de casa, voto contado, desconto) e muda uma coisa: a deriva até 25/out, que no modelo principal é sorteada com média zero, ganha uma média. Ela vem do painel de mudança: o agregado das diferenças entre ondas consecutivas do mesmo instituto, em p.p. por dia, ponderado pelo erro de cada diferença e pela recência (meia-vida de 3 dias), vale hoje +0,40 ± 0,93 p.p. por semana para Lula. O modelo usa metade dele (encolhimento de 50%, porque a diferença entre duas ondas soma o erro amostral das duas e não há validação histórica desta leitura) vezes os 0 dias que faltam (zero no dia da votação, quando a variante coincide com o modelo principal), com desvio igual ao erro-padrão do agregado (piso de 0,1 p.p. por dia): média da deriva 0,00 ± 0,00 p.p. O MCMC paralelo recebe a mesma prior (sorteada a cada iteração, sem verossimilhança própria). Resultado: chance de Lula 17% só pelas pesquisas e 18% no ensemble (MCMC 23% e 23%), contra 17% e 18% no modelo completo. Limites: o agregado é dominado pelas 3 ou 4 diferenças mais novas, muda a cada pesquisa publicada e não distingue movimento real de ruído quando os institutos discordam; por isso é uma leitura de sensibilidade, gravada em resultados_momentum.json, e não a manchete.

6 · 1º turno

estados: margem Lula − Flávio, soma dos dois e um por candidato menor (votos válidos) · efeitos de casa por instituto · viés ~ t5(+3,4; 3,8) na margem · colapso tardio da 3ª via ~ Beta(2, 8), com efeito médio nulo na margem

Mesmo modelo do passo 1, aplicado à margem Lula − Flávio, à soma dos dois e a cada candidato menor nas 75 pesquisas de 1º turno desde agosto, com a mesma âncora. Até 3/out Lula e Flávio eram duas séries independentes, o que ignorava que disputam o mesmo voto e deixava o placar sensível demais à última pesquisa. Na simulação (200 mil), os candidatos são sorteados e renormalizados a 100%. O desconto histórico do 1º turno vem dos quatro ciclos verificáveis (+5,2, +10,5, +2,4, +5,8: as finais superestimaram a margem do PT sobre o principal adversário), encolhido a 60% da média com recência, com o desvio preditivo entre ciclos (± 3,8) e caudas t de Student. Esse erro já contém a migração tardia da 3ª via para o candidato de direita (2014, 2022), por isso o colapso sorteado só redistribui votos e acrescenta incerteza, sem deslocar de novo a margem média (deslocaria -0,5 p.p.). Os candidatos menores usam um passeio aleatório comum (0,17 p.p./dia) e só as ondas das últimas 4 semanas: com o passeio próprio de cada um, a queda de Cury no início de setembro fazia o nível copiar a última pesquisa e a chance de 3º lugar oscilar a cada onda. A previsão é congelada na última rodada antes de 4/out e comparada com o resultado oficial.

7 · Modelo paralelo por MCMC

yi = xt(i) + μregime + β + hp(i) + εi · β ~ t5(0, 2,5) · nível estrutural = x5/out + N(0, 2,5)
log σrw ~ N(log 0,25; 0,6) · log σentre ~ N(log 0,95; 0,5) · log σp ~ N(m, 0,5²), σp ≥ 0,8 · margemfinal = x25/out

O mesmo modelo dos passos 1 a 4, escrito como um único modelo bayesiano e estimado por MCMC (mcmc_2turno.py, sem bibliotecas além de numpy e scipy). Cada pesquisa mede o placar verdadeiro mais o desconto histórico do seu regime (μ: ondas até 4/out +4,7; ondas depois +0,9) e um erro comum β; o resultado oficial do 1º turno com a transferência entra como uma medida do placar sem erro de pesquisa. A cada passo da cadeia, σrw e σentre são sorteados com a trajetória integrada (dado o resto, o modelo é linear e gaussiano), e a trajetória, β e os efeitos de casa saem juntos de uma normal conjunta exata; σp e o centro m vêm por Metropolis. Diagnósticos: R̂ dividido e tamanho efetivo de amostra multicadeia. A conferência no próprio script mostra que, com os parâmetros fixos, a normal conjunta coincide com o suavizador de Kalman do passo 1. A previsão dos dois modelos é congelada na véspera de 25/out em previsoes_2turno_congeladas.json.

8 · Migração por cadeia de Markov

destinosu = nu · Mu · Mu[o, ·] ~ Dirichlet(κo · mo) · m3ª via = 2ª opção + resíduo decidido (d3, s3) · mausente = (rinπu, rin(1−πu), ·, 1−rin)
campanha: xt+1 = xt · Pp,t sobre S0…S4 · alcance Dp = Pp(1 − e−N/Pp) · conversão qp por contato, fases do guia · margem = 100(ΣL − ΣF)/(ΣL + ΣF)

Análise paralela (migracao_markov.py), condicional e sem efeito na manchete: a partir do resultado oficial por UF, uma cadeia de Markov leva cada eleitor do 1º turno (Lula, Flávio, cada candidato da 3ª via, branco/nulo, ausente) a um dos quatro destinos do 2º turno por uma matriz sorteada linha a linha (Dirichlet em torno da 2ª opção declarada da Quaest, dos fluxos brutos de comparecimento e de uma inclinação de perfil). Uma segunda cadeia, diária e não homogênea, segue a campanha de conversa pessoal de 5 a 24/out (alcance por voluntários e recrutamento em cadeia; conversão por contato tomada dos experimentos de Gerber & Green, Nickerson & Rogers e Kalla & Broockman; reversão; confirmação) e aplica o estoque convertido como fluxo entre destinos, com teto na massa disponível. 20 mil sorteios por célula, em 4 cenários de alcance × 2 respostas de Flávio, mais a referência "metas do guia cumpridas". Sem campanha dirigida a margem mediana é −2,7; com a forte, −2,7. A chance "com deriva" soma a mesma deriva t5 do passo 4, só para comparar com o modelo principal.

Ciclo2º turnoPesquisas finais dos institutos de referência (válidos)ResultadoErro médio da margem PT − direita
2006Lula × AlckminDatafolha 61×3960,8 × 39,2+0,3 (peso 0,25)
2010Dilma × SerraDatafolha 56×4456,0 × 44,0-0,1 (peso 0,35)
2014Dilma × AécioDatafolha 52×48 · Ibope 53×47 · CNT/MDA 49,7×50,3 · Vox Populi 54×4651,6 × 48,4+1,1 (peso 0,50)
2018Haddad × BolsonaroDatafolha 45×55 · Ibope 46×54 · CNT/MDA 43,2×56,8 · Paraná Pesquisas 39,4×60,6 · Vox Populi 50×50 · Ipespe 42×5844,9 × 55,1-1,2 (peso 0,71)
2022Lula × BolsonaroDatafolha 52×48 · Ipec 54×46 · Quaest 52×48 · AtlasIntel 53,4×46,6 · PoderData 53×47 · Ipespe 53×47 · CNT/MDA 51,1×48,9 · Paraná Pesquisas 50,4×49,650,9 × 49,1+2,9 (peso 1,00)
Prior 2026média por ciclo dos institutos de referência; ciclos ponderados por recência (meia-vida de dois ciclos); Veritá, Futura e Brasmarket fora da referência+0,9 ± 2,0
Desconto depois do 1º turnoondas pré-1º turno: erro observado +4,7; ondas pós: +0,9; peso das pós: 100%; na parcela de pesquisas (79%)+0,8 ± 2,0
Ciclo1º turnoMargem PT − direita nas finais (válidos)ResultadoErro médio
2010Dilma × SerraDatafolha +19,0 · Ibope +20,0+14,3+5,2
2014Dilma × AécioDatafolha +18,0 · Ibope +19,0 · CNT/MDA +18,6+8,0+10,5
2018Haddad × BolsonaroDatafolha -15,0 · Ibope -16,0 · CNT/MDA -14,8 · Paraná Pesquisas -15,3 · Vox Populi -9,0 · Ipespe -16,0-16,8+2,4
2022Lula × BolsonaroDatafolha +14,0 · Ipec +14,0 · Quaest +11,0 · AtlasIntel +9,2 · PoderData +10,0 · Ipespe +14,0 · CNT/MDA +8,6 · Paraná Pesquisas +7,1+5,2+5,8
Prior 2026 (1º turno)60% da média com recência (+5,7)+3,4 ± 3,8
CicloInstitutoPrimeira pesquisa do 2º turno (esq. × dir.)Última antes da votaçãoMovimento da margem
2014Datafolha49 × 51 (09/10)52 × 48 (25/10)+6 em 16 dias
2014Ibope49 × 51 (08/10)53 × 47 (25/10)+8 em 17 dias
2018Datafolha42 × 58 (10/10)45 × 55 (27/10)+6 em 17 dias
2018Ibope41 × 59 (14/10)46 × 54 (27/10)+10 em 13 dias
2022Datafolha53 × 47 (07/10)52 × 48 (29/10)-2 em 22 dias
2022Ipec55 × 45 (05/10)54 × 46 (29/10)-2 em 24 dias

Movimento entre turnos: raiz da média dos quadrados = 6,4 p.p.; descontados dois erros amostrais por par de pesquisas e dividido pelos dias, 1,31 p.p./dia, encolhido para 0,95 p.p./dia.

Decomposição da incerteza (variância da margem final)

Erro sistemático das pesquisas74%
Agregado hoje (amostral+casas)26%
Deriva até 25/out0%

Hoje, o erro sistemático das pesquisas responde por 74% da incerteza sobre a margem final e a imprecisão do agregado de hoje (amostra e efeitos de casa) por 26%; o que ainda pode mudar até 25/out pesa 0%. No dia da votação a deriva é zero: resta o que as pesquisas erram em bloco, a única parcela que não se dissipa com mais pesquisas.

Sensibilidade: a chance de cada um sob hipóteses alternativas

NívelErro históricoLulaFlávio
nowcast combinadoviés recalibrado17%83%
nowcast combinadosem correção de viés27%73%
média ponderada últimasviés recalibrado20%80%
média ponderada últimassem correção de viés31%69%
Desconto aplicadoLulaFlávio
+0,0 p.p.27%73%
+0,8 p.p.17%83%
+0,9 p.p.15%85%
+2,1 p.p.6%94%

A leitura mais favorável a Lula (sem desconto) chega a 27%; a mais favorável a Flávio (desconto de +2,1) dá Lula 6%. Os mercados (Lula 20%) ficam dentro dessa faixa.

Metodologia

Histórico de mudanças e ajustes

Em uma frase: todas as 60 versões do painel, de 8/set a hoje, com o que mudou no método, nos dados e na apresentação e, quando medido, o efeito de cada mudança na chance de cada candidato.

Regras desde o início: cada mudança de método é registrada com data e efeito; a previsão do 1º turno foi congelada antes da votação e nunca reescrita; correções feitas depois do resultado ficam identificadas como tais. O registro vive em historico_versoes.json, no repositório, e o painel é gerado a partir dele.
Ler a análise completa≈ 56 min de leitura

Marcos de método (18)

VersãoDataMudançaEfeito medido
v5622/out/2026O desconto das pesquisas de 2º turno deixa de herdar o erro do 1º turno, que foi voto útil; fica só o erro histórico das rodadas finais de 2º turnomanchete de Lula 15% para 21% (modelo principal 12,8% → 20,6%; MCMC 17,7% → 27,1%), com os mesmos dados de 22/out; desconto aplicado de +1,65 para +0,74; variantes do herói: só pesquisas pós-1º turno 11,7% → 16,9%, com voto contado 12,1% → 16,4%, com a direção das últimas ondas 16,7% → 23,4% (MCMC 21,2% → 30,5%)
v5521/out/2026Painel de mudança (diferenças dentro de cada instituto), ponteiro com efeitos fixos, leitura do herói com a direção das últimas ondas, e correções de consistêncianenhum na manchete (12,3% / 14,4%, placar −1,73); a quarta leitura do herói dá Lula 14,7% (ensemble 16,2%; MCMC 17,1% / 17,9%) com deriva de média +0,33 ± 0,31; ponteiro de +1,1 para +1,9 p.p./semana ao trocar a reta pooled por efeitos fixos; Futura 19–20/out como série nova (v2) desde o commit 5d0551f
v53.318/out/2026Peso do voto contado decai com a deriva; λ só nas séries com ondas pré-4/out; validação roda o modelo do painelpeso do nível estrutural de ~53% para 27% (cai sozinho); cobertura 80% da validação de 82% para 85% (commit ec672a3, registrado em 21/out)
v53.216/out/2026λ = 0,5 no efeito de casa pós-1º turno; n efetivo declarado; Palver v3 como série nova; teto de 2.500 na AtlasIntelchance de Lula 14,6% (modelo), 16,5% (ensemble); com λ = 1 seria 18,5%, com λ = 0, 15,0% (commit 5f3d59c, registrado em 21/out)
v536/out/2026Migração de ausentes, terceira via e brancos/nulos: cadeia de Markov + Monte Carlo, ao lado do modelo principalnenhum na manchete; análise condicional: campanha forte +130 mil votos de saldo, as metas do guia exigiriam conversão 7 a 29× a da literatura
v515/out/2026Modelo paralelo do 2º turno estimado por MCMC (posterior completo), ao lado do modelo principalnenhum na manchete; MCMC: Lula 21% × 20% no principal
v454/out/2026Resultado oficial; correção do desconto pós-1º turno; análise da virada, das pesquisas e da acurácia do modeloLula (só pesquisas) 28,4% na véspera → 36,9% com a regra antiga → 18,1% corrigido; ensemble 17,4%
v443/out/2026Previsão do 1º turno congelada antes da votação—
v433/out/2026Segunda auditoria: regime pós-1º turno, caudas t, fundamentos com peso zero, teste de manadaensemble 34,2% → 31,9%; Lula em 1º no 1º turno 38% → 45%
v413/out/2026Auditoria: 3ª via sem dupla contagem, desconto pré-1º turno no 2º turno, fundamentos homogêneosLula (só pesquisas) 50% → 31%; ensemble 48% → 35%
v392/out/2026Leitura estadual: mapa por UF e pesquisas estaduais no placar nacional—
v371º/out/2026Abstenção entra no modelo (cenários de comparecimento por UF e perfil)—
v3330/set/2026Auditoria e modelo v3: espaço de estados ancorado, viés recalibrado, previsão do 1º turnoLula (ensemble) 37,7% → 47%
v2828/set/2026Termômetro de tendência das duas últimas semanas—
v2524/set/2026Modelo v2: pesquisas estaduais, filtro de Kalman e novos fatoresver seção "O que mudou no modelo"
v1617/set/2026Seção de viés: vícios de amostra, margem de erro e calibração histórica—
v28/set/2026Ponderação por recência, tamanho da amostra e qualidade; base de 41 ondas—
v18/set/2026Primeira versão: média simples das últimas pesquisas de cinco institutos—
Registro completo, da versão mais recente à primeira (18 de método, 32 de dados, 10 de painel)
  • v56 — 22/out/2026 · desconto sem herança do 1º turno [método] Crítica externa (22/out): o erro do 1º turno foi antecipação de voto útil e não deveria ser descontado no 2º. A regra de 4/out somava ao desconto das rodadas finais de 2º turno (+0,95, média com recência dos ciclos 2006–2022) 25% do erro comum da âncora no 1º turno (0,25 × 4,74 = +1,19), aplicado sobre a parcela do placar que vem de pesquisas. A decomposição do erro de 2026 por candidato confirma a leitura de voto útil: a média final das pesquisas tinha Lula 45,8 (urna 45,16, −0,6), Flávio 43,1 (urna 47,03, +3,9) e os demais 11,0 (urna 7,8, −3,2). As pesquisas acertaram Lula; o que os candidatos menores perderam foi quase todo para Flávio, um mecanismo que não existe num 2º turno com dois nomes. O coeficiente 0,25 vinha de três ciclos em que o erro do 2º turno acompanhou o do 1º, sem separar a parte de voto útil. Agora RHO_1T_2T = 0: as ondas pós-1º turno recebem só o desconto das rodadas finais de 2º turno, cujo sinal varia (2018 −1,2; 2022 +2,9), com σ = 2,5 mantido. As ondas de 2º turno feitas antes de 4/out continuam com o erro observado, mas pesam menos de 1% do placar. Não mudou: o embalo continua só como variante (quarto botão do herói, deriva com metade do agregado das diferenças intra-instituto), porque o ritmo caiu de +3,0 para +1,8 p.p./semana e, em 2014 e 2018, estender a tendência além da última pesquisa teria errado mais. Na mesma crítica, o embalo integral daria 26,3% (MCMC 35,0%). Ressalva: é uma mudança de método três dias antes da eleição, motivada por uma crítica feita depois de ver os dados. A justificativa é a decomposição do erro do 1º turno, que é anterior a esta semana e independe da direção das pesquisas de 2º turno.
  • v55 — 21/out/2026 · painel de mudança e momentum [método] Análise crítica do agregador (analise-modelo-agregacao-2026-10-21.md) a partir da crítica de que o modelo carrega inércia do passado e deveria ler o "drive do presente" em diferenças intra-instituto. (1) Nova seção #mudanca (mudanca_2turno.py): para cada série com duas ondas consecutivas depois de 4/out, a diferença da margem em válidos, ponderada pelo erro da diferença (soma dos dois erros amostrais) e pela recência (meia-vida 3 dias); a versão literal (só a última diferença de cada casa) ao lado; séries v2/v3 não pareiam com a antiga; tracking da Gerp só a 5 dias ou mais; rejeição e espaço disponível (100 − voto − rejeição) na mesma máquina quando houver ondas pós-1º turno em rejeicao_2026.csv. Hoje: +1,17 ± 1,08 p.p./semana para Lula, desacelerando (janela longa +1,55). (2) Ponteiro: inclinação com intercepto por série (efeitos fixos), que cancela o efeito de casa sem estimá-lo e deixa de confundir composição com tendência; pesos com σinst em vez do RMS dos resíduos (que já continha o erro amostral); ritmo para virar parte do nível das pesquisas do modelo; "Virada em curso" só quando o ritmo alcança. (3) Variante ELEICAO_MOMENTUM=1 (resultados_momentum.json, quarto botão do herói, passo 5c): a deriva até 25/out ganha média = metade do agregado × dias restantes, com desvio encolhido na mesma proporção (piso 0,1/dia); o MCMC recebe a mesma prior. (4) Consistência: erro da série do termômetro e de serie_pos1t só das pesquisas (antes era o do nível mesclado); bias_prior_sd volta a ser o prior; o congelado grava nivel_pesquisas e nivel_ajustado; fórmula do MCMC com o SD estrutural de 4/out (2,5); notas para Palver v3, MDA v2 e Futura v2; prior do erro do 1º turno declaradamente não aplicado às séries novas. Registradas também, nesta data, as versões v53.1 a v53.4 (13–19/out), que só existiam nos commits.
  • v54 — 19/out/2026 · toggle do herói [painel] Rodadas extras de modelo_eleicao_2026.py (variável ELEICAO_SO_POS1T: estrut grava resultados_pos1t_estrut.json, com o voto contado combinado como no modelo completo; 1 grava resultados_pos1t.json, sem ele): a base de 2º turno fica só com as ondas de campo depois de 4/out, o efeito de casa de cada instituto é estimado apenas nelas (sem a persistência de 50% do desvio medido antes do 1º turno) e, na versão sem voto contado, o nível não é combinado ao placar de partida e o desconto do erro histórico incide inteiro sobre as pesquisas. Âncora, priors do erro do 1º turno, deriva e ensemble são os mesmos. O herói ganha três botões; o JS troca manchete, subtítulo, barra e legenda, e uma nota explica de onde vem a diferença. Passo 5b da metodologia registra o que a leitura mede bem (independe de λ e da transferência declarada) e mal (12 dias não identificam σrw; institutos de onda única ficam no prior; sem validação de origem móvel). A rodada principal não muda: zero diferença em resultados.json além dos carimbos de tempo.
  • v53.4 — 19/out/2026 · gráfico evolutivo [painel] Nova seção #evolucao (evolucao_pesquisas.py): pontos de Lula e Flávio por pesquisa, leitura por ponto e por dia, tabela com fontes; linha-síntese = placar ajustado do modelo dia a dia (não muda com o seletor); com seleção parcial, média simples tracejada das escolhidas (meia-vida 4 dias, janela 14).
  • v53.3 — 18/out/2026 · auditoria de lógica [método] A foto de 4/out (resultado + transferência) era tratada como medida de hoje com SD 2,5 e ficava presa em ~53% do nível; passa a somar a deriva σentre por dia, como o MCMC já fazia. λ aplica-se só a séries com ondas antes de 4/out (Palver v3 e MDA v2 identificam h inteiro). A validação de origem móvel passa a rodar o mesmo modelo do painel (prior do erro do 1º turno e λ). Ponteiro: efeito de casa aplicado (λ·h) e n efetivo, como no modelo.
  • v53.2 — 16/out/2026 · auditoria das pesquisas pós-1º turno [método] Auditoria das ondas pós-4/out contra os relatórios técnicos: a Palver trocou a calibração (v3, não comparável) e vira série nova; todas as ondas passam a usar o n efetivo declarado (coluna n_efetivo); Ipespe é CATI. No modelo, o efeito de casa medido antes do 1º turno vale metade (λ = 0,5) nas ondas novas, porque a inclinação observada era +0,58 ± 0,68 e a dispersão entre institutos subia com o efeito inteiro; AtlasIntel com n efetivo máximo de 2.500.
  • v53.1 — 13/out/2026 · ponteiro da tendência [painel] Nova seção #ponteiro (ponteiro_tendencia.py): inclinação da margem Lula − Flávio nas pesquisas com campo depois de 4/out, corrigida pelo efeito de casa, com prior de ±3 p.p./semana enquanto há poucas pesquisas; medidor com ponteiro de hoje, de 3 dias atrás e marca do ritmo necessário para virar.
  • v53 — 6/out/2026 · migração por cadeia de Markov [método] Novo script migracao_markov.py e seção Migração na camada 2, entre Abstenção e Escândalos. A partir do resultado oficial por UF, uma cadeia de Markov leva cada eleitor do 1º turno (Lula, Flávio, cada candidato da 3ª via, branco/nulo, ausente) a um dos quatro destinos do 2º turno por uma matriz 8 × 4 sorteada linha a linha (Dirichlet em torno da 2ª opção declarada da Quaest, dos fluxos brutos de comparecimento e de uma inclinação de perfil); uma segunda cadeia, diária e não homogênea, segue a campanha de conversa pessoal de 5 a 24/out (voluntários e recrutamento em cadeia, conversão por contato tomada de Gerber & Green, Nickerson & Rogers e Kalla & Broockman, reversão, confirmação), com resposta simétrica de Flávio e chamado aberto que sai pela culatra nas UFs dele. 20 mil sorteios por célula, 4 cenários de alcance × 2 respostas mais a referência "metas do guia cumpridas". Resultado: sem campanha dirigida a margem vai a −3,85 (80% entre −4,7 e −3,0; reproduz o modelo de transferência); a campanha forte (500 mil voluntários iniciais, 1,3 mi ao fim, 3,5 mi de nomes) rende +130 mil votos de saldo e move a margem 0,12 ponto; a máxima, +250 mil; com resposta simétrica de Flávio, +50 mil. As metas do guia (+5,1 mi) levariam a margem a +0,4, mas exigiriam, com esse alcance, conversão por contato de 7× (ausentes) a 29× (eleitores de Flávio) a da literatura. Chance de Lula "com deriva" (mesma deriva t5 do modelo principal, só para comparar): 14,6% sem campanha, 15,2% com a forte. Autoverificações no script: massa conservada, linhas somam 1, cadeia em [0, 1], reprodução do estrutural (−3,85 = −3,85), escada monotônica, semente fixa. Passo 8 da metodologia e dois termos novos no glossário (cadeia de Markov, Dirichlet). A manchete continua sendo o modelo principal.
  • v52 — 5/out/2026 · dia seguinte ao 1º turno [dados] Varredura do dia seguinte ao 1º turno: nenhuma pesquisa nacional de 2º turno com campo depois de 4/out foi divulgada (Datafolha e PoderData saem em 8/out; AtlasIntel e Vox em 9/out; Ipespe em 10/out). Polymarket lido em três espelhos na noite de 5/out: Lula 16% (era 15% na noite da apuração). Novo quadro em Escândalos com os apoios do 2º turno (Zema declara voto em Flávio; Cury e Renan neutros; PSD libera e Caiado decide com Kassab), o calendário de pesquisas e os debates (Band/Estadão e Record em 11/out, Globo em 23/out). Recoleta dos municípios no TSE para fechar os que tinham ficado com arquivo parcial na noite de 4/out.
  • v51 — 5/out/2026 · modelo paralelo por MCMC [método] Novo script mcmc_2turno.py: o mesmo modelo do 2º turno (pesquisas da janela, efeitos de casa ancorados com o prior do erro de cada instituto no 1º turno, quebra de 4/out, nível estrutural do voto contado, desconto de viés por regime com caudas t, deriva em duas fases), escrito como um único modelo bayesiano e estimado por MCMC, com 4 cadeias de 4.000 sorteios. σrw e σentre são sorteados com a trajetória integrada; trajetória, erro comum e efeitos de casa saem de uma normal conjunta exata; σp vem por Metropolis. Diagnósticos: R̂ e tamanho efetivo de amostra. Na estreia, o placar verdadeiro de hoje é o mesmo nos dois (−3,9); a chance de Lula fica em 21% no MCMC contra 20% no principal, porque a velocidade de mudança entre os turnos deixa de ser fixa e alarga a deriva (desvio 5,4 × 4,2). A previsão dos dois passa a ser congelada até a véspera de 25/out em previsoes_2turno_congeladas.json. A manchete continua sendo o modelo principal; nenhum número dele mudou.
  • v50 — 5/out/2026 · seção Gerações [painel] Nova seção Gerações (#geracoes): há uma nova geração de eleitores mais à direita? Junta as pesquisas Datafolha de véspera com recorte por idade e perfil (2010–2022; em 2026, média das simulações Lula × Flávio das rodadas de 17/set a 3/out, mais Quaest e AtlasIntel), o cadastro do TSE (perfil do eleitorado 2010–2026 e do comparecimento 2014–2022) e a variação do voto por município 2010–2026. Resposta: em parte. Os de 16 a 24 anos seguem à esquerda do país desde 2018 (−5, −5, −2); a faixa à direita é a dos 25 a 44 anos (+3 e +2 em 2026), e os de 60+ foram de +1 para −4. Novos scripts: coletar_eleitorado_idade.py, coletar_votos_historico.py, coletar_datafolha_2026.py, analisar_geracoes.py, geracoes.py.
  • v49 — 5/out/2026 · seletor no mapa municipal [painel] Mapa municipal ganha seletor com quatro leituras: quem venceu e onde virou (as setas da v48); variação 2022 → 2026, com a seta apontando para onde o município andou (esquerda, vermelha: Lula melhorou a margem; direita, azul: Flávio foi melhor que Bolsonaro) e comprimento proporcional ao tamanho da variação, teto em 30 pontos; vencedor em 2026 e vencedor em 2022, municípios pintados com a cor de quem venceu, em quatro intensidades pela margem. A variação usa a margem de 2026 só entre Lula e Flávio, comparável ao 2º turno de 2022: 5.389 municípios andaram para a direita e 181 para a esquerda; mediana −8,5 pontos. A coluna "Variação" das tabelas passou a usar essa mesma conta.
  • v48 — 5/out/2026 · mapa municipal e previsão congelada × resultado [painel] Mapa municipal (nova seção "Municípios"): os 5.570 municípios com uma seta cada, ← vermelha onde Lula venceu o 1º turno de 2026 e → azul onde Flávio venceu; cheia se o município virou em relação ao 2º turno de 2022, clara se manteve; tamanho pela margem de 2026. Zoom por UF e tooltip com as margens das duas eleições. Lula venceu em 3.123 municípios em 2022 e em 2.663 em 2026; 459 viraram para Flávio (5,96 milhões de eleitores) e 1 para Lula. Dados: TSE (arquivos municipais da divulgação de 2026, coletar_municipios.py; Dados Abertos de 2022 lidos por HTTP Range, coletar_2022_2t.py) e malha mínima do IBGE (gerar_geo_mun.py). 11 municípios ainda aparecem com totalização parcial no arquivo municipal do TSE. Acurácia: novo bloco "Previsão congelada na véspera × resultado oficial", com faixas de 80% contra o resultado (7 de 8 dentro; só "outros" fora), as quatro perguntas da véspera (4 de 4 no desfecho mais provável; Brier médio 0,108) e o erro por candidato do modelo contra as pesquisas sem desconto (0,35 × 1,22 p.p.; o modelo chegou mais perto nos 7).
  • v47 — 5/out/2026 · totalização final do TSE [dados] Resultado do TSE com 100% das seções totalizadas (02h58 de 5/out): Flávio 47,03% × Lula 45,16% dos válidos (56.104.503 × 53.879.538; diferença de 2.224.965), Cury 2,89, Renan 2,24, Caiado 2,18, Zema 0,27, outros 0,22; abstenção 21,08%; soma das 27 UFs e do exterior confere com o total nacional. Margem −1,87 (inalterada na 2ª casa); erro comum da âncora +4,74 e placar de partida do 2º turno −3,85, ambos inalterados. Com a data fixada em 4/out, os números novos deixam Lula no 2º turno em 18,3% (ensemble 17,5%), como antes. A rodada de 5/out é a primeira depois do 1º turno: o filtro aplica a quebra prevista de ±6 pontos no nível das pesquisas, o voto contado passa a pesar 87% (antes 52%) e a incerteza do nível de hoje sobe de ±1,4 para ±2,3; o centro quase não se move (−3,9), e Lula vai a 19,7% (ensemble 18,6%). Conclusões das seções de análise inalteradas.
  • v46 — 4/out/2026 (23h) · totalização em 99,99% [dados] Resultado do TSE atualizado para 99,99% das seções (23h12): Flávio 47,03% × Lula 45,16% dos válidos (56.102.011 × 53.865.927; diferença de 2.236.084), Cury 2,89, Renan 2,24, Caiado 2,18, Zema 0,27, outros 0,22; abstenção 21,08%. Margem −1,90 → −1,87; erro comum da âncora +4,77 → +4,74; placar de partida do 2º turno −3,88 → −3,85; Lula no 2º turno 18,1% → 18,3% (ensemble 17,4% → 17,5%). Conclusões das seções de análise inalteradas.
  • v45 — 4/out/2026 (noite) · resultado oficial do 1º turno [método] Resultado: TSE às 22h42 de 4/out, 99,94% das seções: Flávio 47,04% × Lula 45,14% dos válidos (56.090.744 × 53.825.990), Cury 2,89, Renan 2,24, Caiado 2,18, Zema 0,27, outros 0,22; abstenção 21,08%. Gravados resultado_1turno.csv (aciona o regime pós-1º turno) e resultado_1turno_uf.csv (27 UFs e exterior). Método: (1) com o resultado e sem nenhuma pesquisa nova, o desconto das pesquisas de 2º turno caía de +3,4 para +2,0 e a chance de Lula subia de 28% para 37%, no sentido contrário ao erro que o resultado mostrou. As ondas de 2º turno feitas antes de 4/out passam a receber o erro observado da âncora no 1º turno (+4,8); as feitas depois, a regra pós-1º turno (finais de 2º turno + 25% do erro, +2,1), misturadas pela fração do placar que vem de ondas novas no filtro (F = w_pós ÷ (w_pós + 1/(P + 6²))); (2) o voto contado com transferência (−3,9) passa a ser combinado com o erro total das pesquisas (amostral e sistemático), não só o amostral: peso de 8% para 52%; pesquisas corrigidas e voto contado agora convergem (−3,9 e −3,9); (3) mercado lido na noite da apuração vale no ensemble se marcado como posterior ao resultado (Polymarket: Lula 15%, US$ 170 mi, dois espelhos). Testado em cópia com 0, 1 e 3 ondas fictícias pós-1º turno (transição suave). Painel: topo do 1º turno com o resultado ao lado da previsão congelada; novas seções "Por que Flávio virou", "Pesquisas × urnas" (erro por instituto, método, região e UF; correção de casa contra a urna; teste de manada) e "Acurácia" (erro, percentil, Brier e log-perda, componente por componente); condições de vitória do 2º turno refeitas a partir do resultado; histórico de versões passa a viver em historico_versoes.json e vira a seção metodológica "Histórico de mudanças e ajustes", no painel e no site. Avaliação: margem prevista −0,7 contra −1,90 (erro 1,2; pesquisas sem desconto: 4,6); resultado no percentil 37; o desconto histórico funcionou e foi conservador; a correção de casa ancorada afastou 11 de 14 institutos do resultado; leitura estadual errou o Sudeste.
  • v44 — 3/out/2026 (noite) · previsão do 1º turno congelada [método] Datafolha 2–3/out (2º turno Lula 47 × Flávio 46; 1º turno 42 × 40), Quaest 2–3/out (2º turno Flávio 44 × Lula 42; 1º turno Lula 40 × Flávio 38) e Futura 2–3/out (2º turno Flávio 48 × Lula 45,1; 1º turno só em válidos, fora da base). AtlasIntel nacional de 27/set–2/out não localizada. Polymarket: Lula 36%. Modelo: nível +0,39; Lula 28,4% só pelas pesquisas; ensemble 30,3%; Lula em 1º no 1º turno 41,3%; Cury em 3º 49%. Esta é a rodada gravada em previsoes_1turno_congeladas.json para comparar com o resultado de 4/out.
  • v43 — 3/out/2026 · segunda auditoria (estatística e ciência política) [método] Regime pós-1º turno: (1) a troca de desconto deixava de +3,5 para +0,95 só pela data, em 4/out, sem resultado nem pesquisa nova; agora depende do resultado oficial; (2) o erro comum das pesquisas no 1º turno passa a entrar no desconto do 2º turno (25% dele), em vez de ser descartado; (3) o passeio aleatório recebe uma quebra de 6 p.p. no dia seguinte ao 1º turno, para o placar seguir as pesquisas novas, e as estaduais anteriores a 4/out saem da leitura; (4) o resultado oficial, com a transferência de votos, entra no nível do 2º turno, combinado por precisão; (5) o mercado só pesa com preço recente e posterior ao 1º turno; (6) a matriz de transferência saiu do código para transferencia_2turno.csv. 1º turno: (7) Lula e Flávio deixam de ser duas séries independentes: o modelo estima a margem e a soma dos dois; (8) desconto histórico com 2010 na base e desvio preditivo entre ciclos (± 3,8 em vez de ± 3,0). Especificação: (9) caudas t de Student no erro das pesquisas e na deriva; (10) qualidade do instituto medida pelo erro em relação à média do ciclo, não pelo erro bruto; (11) incerteza da âncora somada ao erro do nível; (12) fundamentos com peso zero (reta de três eleições); (13) choques regionais retirados; (14) teste de manada refeito nos resíduos; (15) erro amostral da margem com a fórmula exata; (16) validação separada entre institutos conhecidos e novos, e tabela de influência de cada pesquisa. Efeito, com as mesmas pesquisas: Lula só pelas pesquisas 29,8% → 29,9%; ensemble 34,2% → 31,9% (sai o componente de fundamentos, que dizia 53%); Lula em 1º no 1º turno 38% → 45%. Painel: hora da atualização no cabeçalho; rótulos do desconto corrigidos (o painel chamava de "2º turnos de 2006–22" o desconto do 1º turno); links vazios das estaduais; textos com números fixos passam a vir dos dados.
  • v42 — 3/out/2026 (tarde) [dados] CNT/MDA 30/set–2/out (2º turno Lula 47,3 × Flávio 43,1; 1º turno 43,1 × 38,0), Palver 30/set–3/out (2º turno Flávio 49 × Lula 44; 1º turno Flávio 47 × Lula 43) e DataTrends 28–30/set (2º turno 45 × 45; 1º turno 41 × 39). Modelo: nível +0,56; Lula 29,8% só pelas pesquisas; ensemble 34,2%; Lula em 1º no 1º turno 38%.
  • v41 — 3/out/2026 · auditoria [método] Auditoria de código, dados e textos, com correções de método e de apresentação. Modelo: (1) o desconto do erro histórico do 1º turno deixa de ser somado ao colapso tardio da 3ª via, que já está dentro desse erro (o colapso passa a só redistribuir votos, sem deslocar a margem média); (2) candidatos menores com passeio aleatório comum e nível estimado nas ondas das últimas 4 semanas, o que estabiliza a chance de 3º lugar (antes ela copiava a última pesquisa) e faz as chances de 3º somarem 100% (o agregado "outros" saiu da disputa); (3) os choques regionais passam a repartir o erro sistemático das pesquisas em vez de somar-se a ele; (4) teto de 5 mil entrevistas no peso de uma pesquisa; (5) a previsão do 1º turno nunca é regravada a partir de 4/out; (6) regime pós-1º turno implementado (erro de cada instituto no 1º turno vira prior do efeito de casa no 2º); (7) até 4/out, o 2º turno recebe o mesmo desconto do 1º turno (+3,5 ± 3,0, antes +0,9 ± 2,0): as pesquisas de 2º turno de hoje ainda não passaram pelo resultado do 1º, e o erro do 1º turno chega quase inteiro ao ponto de partida do 2º; nas rodadas de 2º turno publicadas antes do 1º turno em 2014, 2018 e 2022 o erro foi ainda maior (+8, +7 e +15); é a mudança que mais move o painel (com os dados estaduais novos, chance de Lula no 2º turno pelas pesquisas de 50% para 31%; ensemble de 48% para 35%); (8) fundamentos com série homogênea (ótimo + bom do Datafolha na campanha): o 77,7 atribuído a 2006 era a aprovação pessoal de Lula na CNT/Sensus de 2008. Dados: PoderData de 30/ago–2/set corrigida (Flávio 45 × Lula 44, não 46 × 45); PoderData de 13–16/set com brancos/nulos 8 (não 4); Datafolha de 8–10/set com fim de campo em 10/set nos dois turnos; Indexa de 27–29/set com brancos e indecisos do 1º turno. Pesquisas estaduais: 85 ondas novas de 1º turno e 75 de 2º turno (Datafolha em SP, RJ, MG, PE e DF; AtlasIntel em 18 UFs; Real Time Big Data em 12; Veritá em 26; DataTempo em MG), coletadas e conferidas em 3/out; com elas a leitura estadual do placar nacional passa de +3,1 para +0,8. Mapa: seletor de instituto e de turno. Painel: separador de milhar, sinais duplicados ("++"), "−0,0", cenários que somavam 101%, frase de sensibilidade com o cenário errado, textos de institutos, mercado, debate cancelado e rejeição/indecisos (agora gerados das bases) atualizados.
  • v40 — 3/out/2026 (manhã) [dados] PoderData/Aya 30/set–2/out (2º turno 46 × 46; 1º turno Lula 42 × Flávio 41) e Gerp 30/set–2/out (2º turno Flávio 49 × Lula 44). Modelo: nível +0,9; Lula 50,1% só pelas pesquisas; ensemble 48,1%.
  • v39 — 2/out/2026 [método] Mapa do 1º turno por UF (rodada Quaest de 20–28/set em 26 UFs), 14 pesquisas estaduais novas de 2º turno no modelo e a onda Vox Brasil de 29/set–1º/out (2º turno Flávio 48,2 × Lula 45,2; 1º turno Flávio 41,2 × Lula 40,4). Modelo: nível +0,56; ensemble Lula 46%; Lula em 1º no 1º turno em 46%.
  • v38 — 1º/out/2026 (19h) [dados] Nova onda Datafolha (campo 28/set–1º/out, TSE BR-08039/2026, Folha/Globo, n=2.506, presencial, divulgada às 19h05): 2º turno Lula 48% × Flávio 45% (era 47 × 45), brancos/nulos 6%, indecisos 1%; 1º turno Lula 42 × Flávio 38 (ambos +2), Cury 4, Caiado 3, Renan 3, Zema 1. Números lidos nas duas matérias do g1 (grupo contratante); registro, amostra e período conferidos em segunda fonte. O g1 traz o campo ora como 28–30/set, ora como 29/set–1º/out; a base usa 1º/out como fim. Base: 153 ondas de 2º turno. Modelo recalculado: nível +0,6 p.p.; Lula 48% · Flávio 52% só pelas pesquisas; ensemble Lula 47% · Flávio 53%; 1º turno: margem esperada +0,2, Lula em 1º em 52%.
  • v37 — 1º/out/2026 (noite) [método] Nova seção "Abstenção: quem falta às urnas e a quem isso custa", na camada 2. Modelo: bloco 4b calcula a margem nacional sob cenários de comparecimento, com dois canais: geográfico (comparecimento por UF de 2022, eleitorado de 2026 e margem estadual de 2026, alinhada ao placar nacional) e de perfil (K = 0,25 ponto de margem por ponto de abstenção, faixa 0–0,4, calibrado na estimativa de Adriano Laureno). Os cenários medem o desvio em relação a 2022, porque o padrão daquele ano já está no desconto histórico. Resultado: cada ponto a mais de abstenção custa a Lula 0,25 ponto de margem (0,19 se for só no Nordeste); abstenção de 25% levaria Lula a 37%; mobilização no Nordeste (−2 pontos) a 48%. O item "Abstenção diferencial" do tornado deixa de ser a faixa fixa de −1,5 a 0 e passa a vir desses cenários. Nova base abstencao_2022_uf.csv (abstenção por UF nos dois turnos de 2022, CNN Brasil a partir do TSE, fonte única; Amapá e Roraima a conferir). Checagem: a margem de 2022 reconstruída por região coincide com o resultado oficial. Sem pesquisa nacional nova até 18h40; a Datafolha de 28/set–1º/out (n=4.006) foi anunciada para hoje, mas ainda não havia sido publicada. Modelo: nível +0,3 p.p.; Lula 45% · Flávio 55% só pelas pesquisas; ensemble Lula 45% · Flávio 55%.
  • v36 — 1º/out/2026 (tarde) [dados] Sem pesquisa nacional nova: as dez ondas divulgadas de 27/set a 1º/out (Quaest, BTG/Nexus, AtlasIntel, Meio/Ideia, Gerp, Palver, Vox Brasil, Indexa, Futura e Real Time Big Data) já estavam na base. Uma varredura automática havia listado também um "Datafolha de 28/set–1º/out" (n=2.506, 45 × 47); a verificação nas matérias não achou essa pesquisa, e o Datafolha mais recente segue sendo o de 22–23/set (47 × 45), que foi mantido. A mesma varredura trocou os lados e usou votos válidos na Real Time Big Data (49 × 51, quando os totais são Lula 45 × Flávio 46) e inverteu a ordem da AtlasIntel (Flávio 47,7 × Lula 47,6); nada disso entrou na base. Mercado: Polymarket em Lula 38% × Flávio 62% (1º/out, US$ 158,9 milhões, lido na página do mercado), 3 pontos abaixo de 30/set — a menor leitura da série. A leitura de 43% de 26/set que consta nas notas anteriores veio de um resumo de busca e não foi corroborada na verificação. Notícias checadas em duas fontes: jingle "Foi o Flávio" suspenso pelo TSE; perícia da PF no material do Dark Horse (R$ 750 mil à produtora Go Up, via emendas de Mario Frias); pedido de ANJL e IBJR ao STF contra a MP das bets, sem liminar; viagem de Lulinha à Europa (R$ 1,8 milhão) paga por lobista; Zema e Caiado recusam o pedido de Flávio para desistir. Dois itens que circulavam não resistiram à checagem: a delação de "Mineiro" não é fato da semana (homologada em 9/set) e o valor de R$ 69 milhões não existe nas fontes. Modelo recalculado: nível +0,3 p.p.; Lula 45% · Flávio 55% só pelas pesquisas; ensemble Lula 45% · Flávio 55%. Orquestração: coleta com Haiku, verificação com Sonnet.
  • v35 — 1º/out/2026 [dados] Nova onda Real Time Big Data (campo 26–30/set, TSE BR-09503/2026, n=2.000, telefônica, divulgada 1º/out): 2º turno Flávio 46% × Lula 45%, brancos/nulos 5%, NS 4%; 1º turno Lula 43 × Flávio 39 (Renan 4, Cury 3, Caiado 3, Zema 1). Confirmada em Transmissão Política, AC24horas e Gazeta do Povo (esta em válidos: 46×41; 2º turno 51×49). Notícia: Lula confirmou ausência no debate da Globo de 1º/out; Flávio vai com ou sem ele. Polymarket sem leitura nova de 30/set (Lula 41%). Modelo recalculado: nível +0,3 p.p. (há 2 semanas +0,4); Lula 45% · Flávio 55% só pelas pesquisas; ensemble Lula 46% · Flávio 54%; 1º turno: margem esperada -0,0, Lula em 1º em 50%; termômetro: estável.
  • v34 — 30/set/2026 (noite) [dados] Nova onda Futura/100% Cidades (campo 25–29/set, TSE BR-01122/2026, n=2.000, telefônica, divulgada 30/set 14h42): 2º turno Flávio 49% × Lula 43,5%, brancos/nulos 6,2%, NS 1,3%; 1º turno Flávio 42,2 × Lula 39,4 (Caiado 4,0, Cury 3,8, Renan 3,2, Zema 1,6); rejeição Lula 48,9 × Flávio 45,6. Confirmada em CNN Brasil e Itatiaia. Mercados: Polymarket Lula 41% (30/set). Palver 24–28/set (47×45) já estava na base (campo 24–27). Nenhuma outra pesquisa nacional divulgada em 30/set até as 20h. A v33 acima foi gerada com os valores vigentes em 30/set (manhã): nível +0,5; Lula 47% × Flávio 53%. Modelo recalculado: nível +0,2 p.p.; Lula 45% · Flávio 55% só pelas pesquisas; ensemble Lula 46% · Flávio 54%; 1º turno: margem esperada -0,1, Lula em 1º em 49%.
  • v33 — 30/set/2026 · auditoria e modelo v3 [método] Auditoria completa do agregador com as práticas de referência (Jackman; Linzer; Economist; Silver; Shirani-Mehr et al.). Achados corrigidos: (1) dupla contagem do erro contra o PT: os efeitos de casa eram identificados pela média de todos os institutos ponderada pelo nº de ondas, o que deslocava o "consenso" ~1,4 p.p. para Flávio, e sobre esse nível ainda se descontava um viés calibrado nos grandes institutos; agora a âncora são os institutos com histórico verificado (AtlasIntel, Datafolha, CNT/MDA, Paraná Pesquisas, PoderData, Quaest), o mesmo conjunto da calibração; (2) prior de viés recalculado com os números verificados de 2006–2022 em fonte primária (`calibracao_historica.csv`, 39 finais): +0,9 ± 2,0 em vez de +2,1 (que pesava 2,5× o ciclo de 2022 e usava valores errados, como Quaest 2022 53×47 em vez de 52×48); (3) modelo de tendência único (espaço de estados com efeitos de casa e variância por instituto estimados em conjunto) no lugar da P-spline + Kalman costurados; σ_rw cai de 0,67 para 0,32 p.p./dia; (4) deriva entre turnos calibrada no movimento observado em 2014/18/22 (0,95 p.p./dia) em vez do multiplicador 1,4; (5) ensemble com pesos por calendário (65/25/10 → 75/25/0 após o 1º turno); (6) 1º turno: base própria de 65 pesquisas desde agosto (duas fontes cada), um estado por candidato, desconto do erro histórico do 1º turno e previsão congelada; (7) regime pós-1º turno: o resultado oficial recalibra pesos, efeitos de casa e viés do 2º turno; (8) validação de origem móvel publicada; (9) 7 ondas de 2º turno que faltavam (Gerp, Futura, Palver ×2, Vox, Indexa, Alfa). Apresentação reformulada para mostrar a chance dos dois candidatos lado a lado em todas as seções. Resultado: nível +0,5 p.p. (v2: -1,3); Lula 47% · Flávio 53% só pelas pesquisas (v2: 24,4%); ensemble Lula 47% · Flávio 53% (v2: 37,7%). 1º turno: Lula 45,1 × Flávio 39,8 nas pesquisas; margem esperada +0,6; Lula em 1º em 58% das simulações.
  • v32 — 30/set/2026 [dados] Nova onda Meio/Ideia (campo 25–28/set, TSE BR-08706/2026, n=2.000, telefônica, Canal Meio): Lula 48,5% × Flávio 48%, brancos/nulos 2,1%, NS 1,5%; 1º turno Lula 39,4 × Flávio 38,4. Confirmada em CNN Brasil e Gazeta do Povo. Nenhuma outra pesquisa nacional nova até a manhã de 30/set. Modelo recalculado: nível -1,3 p.p. (há 2 semanas -0,7); P(Lula) modelo 24,4%; ensemble 37,7%; termômetro: estável.
  • v31 — 29/set/2026 (tarde) [dados] Nenhuma pesquisa nacional nova além da AtlasIntel até o início da tarde de 29/set. Incluída uma onda que faltava na base: American Analytics/Times Brasil-CNBC (campo 15–20/set, TSE BR-032587/26, n=2.000, divulgada 23/set): Lula 43% × Flávio 43%, brancos/nulos 6%, NS 8%; aprovação 45/51 (entra na média dos fundamentos). Confirmada em Poder360, O Povo e CartaCapital. Modelo recalculado: nível -1,2 p.p. (há 2 semanas -0,4); P(Lula) modelo 25,3%; ensemble 38,1%; termômetro: estável.
  • v30 — 29/set/2026 [dados] Nova onda AtlasIntel/Bloomberg (campo 23–28/set, TSE BR-04391/2026, n=5.005, online): Flávio 47,7% × Lula 47,6%, brancos/nulos/NS 4,8%; 1º turno Lula 45,3 × Flávio 42,2 (substitui a onda de 4–9/set na tabela do 1º turno). Confirmada em Exame e Misto Brasil. Mercados: Polymarket 42% (29/set, Flávio 58,1). Seção "Escândalos": bloco "28 e 29 de setembro" com a escalada do caso Aparecida (remoção de posts por Mendonça no TSE, suspensa por Dino no STF; reunião convocada por Fachin; 977,6 mil publicações em 55 h) e a leitura das três pesquisas com campo após o boato (Quaest, Nexus, Atlas), sem efeito mensurável. Modelo recalculado: nível -1,3 p.p. (há 2 semanas -0,5); P(Lula) modelo 24,9%; ensemble 37,9%; termômetro: estável.
  • v29 — 28/set/2026 (tarde) [dados] Nova onda BTG/Nexus (campo 25–27/set, TSE BR-07557/2026, n=2.000, telefônica): Lula 46% × Flávio 44%, brancos/nulos 8%, NS 1%; 1º turno Lula 42 × Flávio 37; rejeição Flávio 51 × Lula 48; aprovação 46/49. Confirmada no site do instituto e no Poder360. Rejeição da Genial/Quaest 24–27/set incluída (Flávio 56 × Lula 55, CNN Brasil). Modelo recalculado: nível -1,0 p.p. (há 2 semanas -0,2); P(Lula) modelo 27,1%; ensemble 39,0%; termômetro: estável.
  • v28 — 28/set/2026 [método] Novo termômetro de tendência na seção "A corrida no tempo": o placar ajustado recalculado dia a dia nas últimas duas semanas (mesma combinação P-spline × Kalman × leitura estadual do número principal), com faixa de 95%, variação em 7 e 14 dias, classificação da direção (estável se |Δ7d| < 0,5 p.p.; "desacelerando" se a semana ficou abaixo disso mas as duas semanas somam 1 p.p. ou mais) e a contagem de pesquisas que andaram para cada lado em relação à onda anterior do mesmo instituto. Correção de consistência: o número "há 2 semanas" passa a incluir a mesma leitura estadual do número de hoje (era só nacional: -0,4 agora, contra −0,6 antes), então a variação de 14 dias deixa de misturar bases diferentes. Nenhuma probabilidade mudou.
  • v27 — 28/set/2026 [dados] Nova onda Genial/Quaest (campo 24–27/set, TSE BR-06520/2026, n=2.004, presencial, Globo/O Globo): Lula 42% × Flávio 42%, brancos/nulos 13%, indecisos 3% — primeira pesquisa com campo inteiro após a MP das bets e o boato de Aparecida; 1º turno Lula 39 × Flávio 34 (substitui a onda de 10–13/set na tabela do 1º turno). Confirmada em O Tempo, Correio Braziliense e Imirante. Nenhuma outra pesquisa nacional localizada em 25–28/set. Mercados: Polymarket 42% (28/set, Flávio 57,6). Seção "Escândalos" ganhou o bloco "26 a 28 de setembro". Modelo recalculado: nível -1,5 p.p. (há 2 semanas -0,6); P(Lula) modelo 24,4%; ensemble 37,7%; empate técnico 22,0%.
  • v26 — 26/set/2026 [dados] Nova onda Real Time Big Data (campo 19–23/set, TSE BR-04202/2026, n=2.000, telefone e recrutamento digital, divulgada 24/set): Flávio 45% × Lula 44%, brancos/nulos 8%, indecisos 3%; rejeição Lula 50 × Flávio 49; aprovação 45/52 (incluída na média dos fundamentos). Confirmada em CNN Brasil, Poder360 e Terra. Nenhuma outra pesquisa nacional divulgada em 25–26/set. Base: 138 ondas. Mercados: Polymarket 43% (26/set, Flávio 56), terceira alta seguida; o mesmo mercado precifica em 97% Lula e Flávio no 2º turno, o que corrobora a correção da v25 (P(Flávio no 2º turno) de 0,88 para 0,99). Seção "Escândalos" ampliada com três fatos da reta final: (1) a MP que proíbe as bets (25/set), com 73% de apoio na AtlasIntel (17–22/set); (2) a polêmica de Nossa Senhora Aparecida — informação falsa ligando Flávio a um projeto de 2007 arquivado, corrigida pela GloboNews, com risco de voltar em 12/out; (3) a 3ª fase da Operação Carbono Oculto (24/set), que alcança o operador de Vorcaro cuja empresa transferiu US$ 12,3 milhões ao fundo do filme Dark Horse. Nenhuma pesquisa publicada tem campo posterior a eles; nenhum altera parâmetros do modelo. Modelo recalculado: nível -1,4 p.p. (há 2 semanas -0,6); P(Lula) modelo 25,4%; ensemble 38,4%; empate técnico 22,1%.
  • v25 — 24/set/2026 (madrugada de 25) [método] Modelo probabilístico revisado, com sete mudanças e o efeito de cada uma publicado na seção "Modelo". (1) Espaço de estados: filtro de Kalman com σ do passeio estimado por máxima verossimilhança (0,67 p.p./dia), combinado com a P-spline. Diagnóstico: a P-spline prolongava em setembro a inclinação de queda além do que as pesquisas mostram (último nó em 1º/ago), deixando o placar cerca de 1 ponto mais pró-Flávio que as pesquisas corrigidas das últimas duas semanas. (2) Pesquisas estaduais: 18 pesquisas de 11 UFs (76% do eleitorado), confirmadas em duas fontes (quatro descartadas), convertidas em swing sobre 2022, corrigidas por efeito de casa e tendência nacional, encolhidas estado → região → país; leitura nacional +0,5, com peso de 18% no nível final. (3) P(Flávio no 2º turno) passa de 0,88 para 0,99: o 0,88 era o mercado "Flávio em 2º lugar", que exclui o caso de ele terminar em 1º; agora sai da simulação do 1º turno, com 1% de risco de impugnação. (4) Viés por metodologia (aplicado +2,0). (5) Deriva por fase: 0,61 p.p./dia, ×1,4 entre os turnos (hipótese). (6) Choques regionais no lugar do ruído único. (7) Fundamentos dinâmicos: regressão margem × aprovação em 2006, 2014 e 2022 (empate em 43,2% de aprovação), aplicada à aprovação média de 44,9% → 60%. Checagem da coleta: o "22% de ótimo/bom" de Bolsonaro em set/2022 que circula na Wikipédia em português está errado (o Datafolha publicou 30%), e o preprint citado sobre "44% de aprovação garante reeleição" trata de prefeitos, não de presidentes; nenhum dos dois entrou. Orquestração: coleta com Haiku, verificação com Sonnet, modelagem com Opus. Resultado: nível -1,2 p.p.; P(Lula) modelo 27%; ensemble 39%; empate técnico 22%.
  • v24 — 24/set/2026 (noite) [painel] Reformulação do storytelling, sem mudança de dados nem de modelo. A página passa a seguir a lógica de pirâmide invertida, em três camadas: 1 · O resultado (manchete em linguagem comum gerada a partir do modelo, barra de probabilidade com as quatro faixas de resultado, quadro "o que mudou", números do dia com rótulos leigos, resumo em quatro frases, guia "como ler este painel" e os quatro cenários, que antes eram a última seção); 2 · O que move a disputa (evolução do placar em seção própria, variáveis decisivas, rejeição, indecisos, escândalos e adversários); e 3 · Bastidores técnicos (modelo, pesquisas e pesos, metodologia, viés, glossário novo e a nota de conformidade). Cada seção abre com uma frase-resumo, os gráficos ganharam caixas "como ler", e 18 termos técnicos têm definição ao tocar ou passar o mouse, com os mesmos textos reunidos no glossário. Tooltips passam a abrir ao toque no celular, como folha fixa no rodapé da tela. O número principal do painel é o ensemble; o do modelo de pesquisas aparece como "só pelas pesquisas". Neste documento, o resumo técnico completo foi para o início da camada 3.
  • v23 — 24/set/2026 (noite) [dados] Nova onda Datafolha (campo 22–23/set, TSE BR-00304/2026, Folha/Globo, n=2.002, presencial, divulgada 24/set): 2º turno Lula 47% × Flávio 45%, brancos/nulos 7%, indecisos 1% — Lula e Flávio sobem 1 p.p. cada sobre a onda de 15–16/set, primeira pesquisa do instituto depois do anúncio do reajuste de 15% no Bolsa Família. Cenários alternativos: Caiado 46×44, Zema 48×39, Cury 46×43, Renan Santos 48×39. Confirmada no g1 (matéria de 24/09, 18h41) e no SBT News. Rejeição: Lula 45% (era 47%) e Flávio 45% (era 47%), empate; aprovação do governo 47% × desaprovação 50% (era 48/50); 1º turno Lula 40 × Flávio 36 × Cury 5 × Caiado 4 × Renan 3 × Zema 1; 80% dos eleitores totalmente decididos. Base: 137 ondas de 19 institutos. Interface: painel e standalone receberam ajustes de responsividade (breakpoints de notebook, tablet e celular, tabelas e gráficos com rolagem horizontal legível, navegação com áreas de toque maiores). Modelo recalculado por inteiro: nowcast -2,1 p.p. (há 2 semanas -0,4); P(Lula) modelo 24,2%; ensemble 37,1%; empate técnico 21,9%.
  • v22 — 24/set/2026 [dados] Nova onda PoderData/Aya (campo 20–23/set, TSE BR-01739/2026, n=3.000, telefônica, divulgada 24/set): 2º turno Flávio 46% × Lula 45% (margem de 1 p.p.; Lula +1 p.p. sobre a onda anterior, Flávio estável); 1º turno Lula 41 × Flávio 39. Cenário alternativo relevante: Cury 45 × Lula 42 — único teste em que Lula perde por mais que Flávio; incluído na tabela de adversários (a AtlasIntel, no mesmo período, deu Lula 46,2 × Cury 39,4). Branco/nulo do 2º turno não foi divulgado nas fontes consultadas. Base: 136 ondas de 19 institutos. Mercados: Polymarket 42% (24/set, Flávio 57), segunda alta seguida; componente de mercados do ensemble revisado para 0,42. Modelo recalculado por inteiro: nowcast -2,4 p.p. (há 2 semanas -0,5); P(Lula) modelo 22,6%; ensemble 36,3%; empate técnico 21,3%.
  • v21 — 23/set/2026 [dados] Nova onda AtlasIntel/Bloomberg (campo 17–22/set, TSE BR-04739/2026, n=5.015, online): 2º turno Lula 47,7% × Flávio 47,4%, brancos/nulos 4,9% — empate técnico, revertendo a sequência de ondas favoráveis a Flávio (Gerp, Futura, Palver) e destoando também da leitura quase empatada mas ligeiramente pró-Flávio do Genial/Quaest de 20/set. Nenhum outro instituto nacional (Datafolha, PoderData, Real Time Big Data) divulgou pesquisa no dia. Base: 135 ondas de 19 institutos. Cenários alternativos da mesma pesquisa (Lula sempre à frente): vs. Zema 47,6%×42,2%; vs. Caiado 46,6%×44%; vs. Cury 46,2%×39,4%; vs. Renan Santos 46,5%×31,2% — tabela de "Outros adversários testados" ajustada para refletir a faixa mais alta de Caiado. Mercados: Polymarket 23/set em Flávio 59 × Lula 41 (US$ 154,0 mi) — primeira leitura sem queda para Lula desde o início da crise do STF em 8/set (59%→49%→45%→43%→40%→41%), embora a estabilização ainda pareça anterior à divulgação da AtlasIntel do mesmo dia. Componente de mercados do ensemble revisado para 0,41. Modelo recalculado por inteiro: nowcast -2,4 p.p. (há 2 semanas -0,5); P(Lula) modelo 22,4%; ensemble 36,0%; empate técnico 21,1%.
  • v20 — 21/set/2026 (noite) [dados] Duas ondas novas divulgadas ainda em 21/set: Palver (campo 15–18/set, TSE BR-00860/2026, autofinanciada, n=5.000, online): 2º turno Flávio 47% × Lula 43%, indecisos+brancos 10% — reforça a série de leituras mais favoráveis a Flávio do próprio instituto. E Genial/Quaest (campo 17–20/set, TSE BR-06004/2026, contratante Globo/O Globo, n=2.004, presencial): Flávio 42% × Lula 41%, brancos/nulos 14%, indecisos 3% — a mesma margem numérica da onda anterior, mas com Lula subindo 1 p.p.; rejeição dos dois na máxima da série (Flávio 56%, Lula 55%, ambos +1 p.p. no mês). 79% dos que já decidiram dizem que o voto é definitivo. Base: 134 ondas de 19 institutos. Mercados seguiram caindo no mesmo dia: Polymarket foi de 41% (manhã) para 40% (tarde) — sexto pregão seguido de queda, mais provavelmente reação ao acúmulo de pesquisas do dia (todas com Flávio à frente) do que a qualquer novidade do STF. Componente de mercados do ensemble revisado para 0,40. Modelo recalculado por inteiro: nowcast -2,3 p.p. (há 2 semanas -0,2); P(Lula) modelo 23,6%; ensemble 36,3%; empate técnico 21,4%.
  • v19 — 21/set/2026 [dados] Nova onda BTG/Nexus (campo 18–20/set, TSE BR-00485/2026, contratante BTG Pactual, n=2.006, telefônica): 2º turno Lula 46% × Flávio 45%, brancos/nulos 7%, indecisos 1% — depois de duas ondas seguidas (Gerp, Futura) puxando para Flávio, este instituto volta a um resultado quase empatado com leve vantagem de Flávio na rejeição (50% × 49% de Lula) e aprovação do governo em 46/50. Confirmado que só essa pesquisa saiu no período (varredura de 19 a 21/set); Quaest, Datafolha e AtlasIntel têm ondas previstas para a semana de 21–24/set, ainda não divulgadas. Base: 132 ondas de 19 institutos. Mercados: Polymarket caiu pela quinta vez seguida, para 41% (21/set manhã, ante 43% em 18/set) — o julgamento do STF sobre Moraes–Vorcaro segue sem data de retomada. Componente de mercados do ensemble revisado para 0,41. Modelo recalculado por inteiro: nowcast −2,0 p.p. (há 2 semanas −0,2); P(Lula) modelo 25,1%; ensemble 37,3%; empate técnico 21,9%.
  • v18 — 18/set/2026 [dados] Duas ondas novas: Gerp (campo 14–16/set, TSE BR-00535/2026, autofinanciada, n=2.400, método não informado): 2º turno Flávio 50% × Lula 43% — a leitura mais favorável a Flávio da base neste ciclo —, com rejeição invertida em relação ao padrão do mês (Lula 49% × Flávio 40%, Lula +9 p.p.), lida no painel como reflexo do mesmo efeito de casa pró-Flávio que a seção "Viés" já identificava no instituto, não como virada real. E Futura/Apex (campo 11–15/set, TSE BR-00749/2026, contratante Apex/100% Cidades, n=2.000, telefônica): Flávio 48,1% × Lula 43,7%, com aprovação do governo em 44,1% e desaprovação em 53,1% (ótimo/bom 36,5%, o mais alto do ciclo para essa pergunta). Uma pesquisa Veritá de campo "4–12/set" circulando em agregadores (Lula 41,5% × Flávio 43,9%) não entrou na base por falta de confirmação independente — nem o site do instituto nem outra fonte teve os números. Base: 131 ondas de 19 institutos. Mercados seguem caindo para Lula mesmo com o julgamento do STF suspenso: Polymarket 43% (18/set), ante 45% em 16–17/set — o componente de mercados do ensemble foi revisado para 0,43. Modelo recalculado por inteiro: nowcast −1,9 p.p. (há 2 semanas −0,2); P(Lula) modelo 26,4%; ensemble 38,5%; empate técnico 21,9%.
  • v17 — 17/set/2026 (noite) [dados] Nova onda Datafolha (campo 15–16/set, TSE BR-04029/2026, contratante Folha/Globo, n=2.002, presencial): 2º turno Lula 46% × Flávio 44%, brancos/nulos 8%, indecisos 1% — quarta leitura seguida do instituto travada em 46×44, mas a rejeição mútua encostou (47% para os dois) e a desaprovação do governo estabilizou em 50% (aprovação 48%, ante 47% na onda de 11/set). Base: 129 ondas de 19 institutos. Modelo recalculado por inteiro: nowcast −1,4 p.p. (há 2 semanas +0,4); P(Lula) modelo 29,1%; ensemble 40,4%; empate técnico 23,0%.
  • v16 — 17/set/2026 (tarde) [método] Nova seção "Viés, vícios de amostra e o que a margem de erro não cobre" (id vies), entre "Metodologia" e "Indecisos". Ela mede, na própria base, que o erro típico de uma pesquisa é 4,4 p.p. contra 2,2 p.p. previstos pela amostragem (1,97×, convergindo com Shirani-Mehr et al./JASA 2018); mostra que a margem declarada se refere a cada candidato e que a diferença entre os dois já carrega ±4,4 p.p.; aplica um teste de manada (dispersão observada 1,43× a esperada — o oposto do padrão americano de 2024); traz um forest plot dos efeitos de casa com intervalo de 95%; uma radiografia dos 19 institutos (método, contratante, viés, erro típico vs. teórico, histórico); a série de erros das finais de 2010 a 2022 com casos exemplares (Vox Populi e Paraná em 2018, o erro de ~8,8 p.p. contra Bolsonaro no 1º turno de 2022, SP 2020 e 2024); o quadro regulatório (PesqEle, multas, PL 2.558/2022) e os episódios de 2026 (Áltica, as duas ações contra a AtlasIntel, a suspensão do Veritá); e uma conclusão classificando os institutos por fidedignidade. Correção de calibração decorrente da pesquisa: o histórico do painel registrava só a atuação de 2022 da Paraná Pesquisas, que por isso recebia o melhor peso de qualidade — mas o instituto errou o 2º turno de 2018 por ~11 p.p. Foram acrescentadas as finais verificadas de Paraná (2018), Veritá e Futura (2022), e o peso de qualidade passou a ser encolhido em direção ao valor neutro conforme o número de ciclos que o sustenta. Efeito: nowcast de −1,5 para −1,7 p.p., P(Lula) no modelo de 28,6% para 27,7% e ensemble de 40,1% para 39,6%.
  • v15 — 17/set/2026 [dados] Duas ondas novas: PoderData/Aya (13–16/set, TSE BR-00360/2026, n=3.000 por URA, Flávio 46% × Lula 44%, brancos/nulos 4%, NS 2%; rejeição Lula 50% × Flávio 46%) e AtlasIntel/Bloomberg (11–16/set, TSE BR-06221/2026, n=5.018 online, votos totais Flávio 47,2% × Lula 46,8%, brancos/NS 6%; desaprovação de Lula 53,6% × aprovação 45,4%) — a Atlas mostra a distância encolhendo de 6 para ≈2 p.p. em relação à leitura de 4–9/set. Mercados estáveis (Polymarket Flávio 55 × Lula 45, P(Lula) normalizada 45%, igual a 16/set) — a sessão do STF de 15/set não gerou nova oscilação. Nenhuma pesquisa publicou ainda pergunta dedicada ao desfecho do STF (vista de Dino). Modelo recalculado por inteiro.
  • v14 — 16/set/2026 [dados] Nova onda incorporada: DataTrends (instituto inédito na base; campo 12–14/set por telefone automatizado nos 27 estados, n=2.000, TSE BR-08691/2026, divulgada em 16/set): 2º turno Flávio 45% × Lula 44%, brancos/nulos 8%, não responderam 3% — entra com peso de qualidade neutro (0,8), por não ter histórico presidencial verificável. Sessão do STF de 15/set: terminou sem decisão de mérito — questão de ordem de Gilmar Mendes para unir as petições, placar provisório 4×3 pela separação, pedido de vista de Flávio Dino (cujo voto empataria em 4×4), com até 90 dias para devolução; a validade do relatório da PF e a eventual investigação sobre Moraes seguem indefinidas e atravessam o 1º turno. Repercussão de 16/set incorporada à seção "Escândalos": Lula passou a defender Moraes nominalmente e a ligar Flávio a Vorcaro; Flávio consolidou o mote "Lula é Moraes e Moraes é Lula". Mercados: Polymarket em Flávio 54 × Lula 45 (P(Lula) normalizada 45%, ante 46% em 15/set) — sem repique após a sessão. Modelo recalculado por inteiro com data-base 16/set; cenários, tornado, break-even e decomposição de incerteza regenerados.
  • v13 — 15/set/2026 (tarde) [dados] Varredura das pesquisas presidenciais divulgadas em 15/set: a única nacional é a CNT/MDA (170ª rodada, campo 9–13/set, n=2.002 presencial, TSE BR-06902/2026), já presente na base desde a v12 — agora completada com brancos/nulos (10,2%) e indecisos (2,5%) do 2º turno, 1º turno com brancos 6% e indecisos 7,5% publicados (antes estimados) e aprovação pessoal de Lula 47×48 (ótimo/bom 35,3%). Incluídos também brancos/indecisos da Real Time Big Data 27–31/ago (9/3) e a aprovação da Quaest 10–13/set (43 aprova × 50 desaprova; ótimo/bom 32%). Uma onda Veritá de 4–12/set (n=40.500) aparece só como 1º turno em agregadores e não tem 2º turno nacional verificável — não entrou. Sessão do STF sobre o relatório Moraes–Vorcaro em andamento, sem resultado no fechamento. Modelo recalculado.
  • v12 — 15/set/2026 [dados] Base de pesquisas reconstruída: conferida onda a onda contra a tabela consolidada da Wikipédia (com link-fonte primário por pesquisa), a base passou de 54 ondas para a base completa então disponível. Entraram as ondas novas de 14–15/set — CNT/MDA (9–13/set, Lula 47,3 × Flávio 40) e Indexa/Broadcast (10–13/set, Lula 43 × Flávio 42) — e ondas anteriores que faltavam (entre elas Nexus/BTG de mar–ago, Gerp, Futura/Apex, Meio/Ideia e Vox Brasil de jan–ago, CNT/MDA de abr/jun/ago, Indexa de mai–ago, Palver de ago, Alfa Inteligência, Vetor/Arrow e American Analytics). Ondas que antes tinham data aproximada (PoderData, Real Time Big Data) foram substituídas pelas datas e números exatos das fontes; os valores de branco/indecisos já apurados foram preservados. CNT/MDA entra com o peso de qualidade de 1,5 (MDA teve o menor erro histórico entre os grandes institutos em 2018/2022). Modelo — com a base completa, os efeitos de casa passam a identificar institutos sistematicamente pró-Flávio (Gerp, Veritá, Futura) e pró-Lula (CNT/MDA), e o nowcast fica menos pró-Flávio que na v11 (−1,8). Mercados revisados para 46% (Polymarket 15/set: Flávio 52 × Lula 45). Tabela do 1º turno do modelo e do Apêndice B atualizada com as ondas de 4–13/set (incluindo CNT/MDA). Escândalos: a linha da crise do STF foi corrigida e ampliada — o centro da crise é o relatório da PF (218 p., 187 interações) sobre Vorcaro e o ministro Alexandre de Moraes, com pedido de anulação da PGR e julgamento no plenário em 15/set (resultado pendente); o inquérito sobre Flávio foi autorizado em 22/jul e divulgado em 10/set (não 9/set); lançamento do filme suspenso em 11/set; inquérito sobre Lulinha autorizado em 30/jul. Leitura comparada reescrita: o inquérito de Flávio ainda não aparece no placar das pesquisas pós-10/set. Outras seções: indecisos (Atlas caiu de 10,3% para 7,4% com migração para Flávio; Quaest 18%), rejeição (Datafolha 8–10/set empata em 46%; Palver 52×51), debate de 14/set cancelado, Marçal indeferido e substituído por Avalanche. Painel: rodapé de metodologia com contagem de ondas por instituto gerada automaticamente; "nowcast há 2 semanas" e σ residual passam a vir do modelo.
  • v11 — 14/set/2026 [dados] Atualização de três dias de cobertura (data-base movida de 11 para 14/set). Pesquisas novas: BTG/Nexus 11–13/set (TSE BR-04076/2026, n=2.003, Lula 47 × Flávio 46 — atualiza a onda de 4–7/set do mesmo instituto); primeira onda do instituto Palver na base (4–9/set, n=5.000, Flávio 46 × Lula 44); duas ondas do instituto Veritá, inédito na base (16–20/ago, n=3.840, Flávio 47,3 × Lula 42; e 1–4/set, TSE BR-04006/2026, n=3.804, Flávio 48,5 × Lula 43,1) — total de 53 ondas de 12 institutos. Nowcast, probabilidades e cenários recalculados (ver KPIs no topo do painel). Seção "Escândalos" ampliada para "STF, Master, INSS e Dark Horse": nova linha sobre a crise institucional do STF (disputa Mendonça×Dino×Fachin pelo comando da PF, 8–9/set) e a quebra parcial de sigilo do caso Master na noite de 9/set, que tornou pública a investigação da PF confirmando Flávio Bolsonaro como intermediário de Vorcaro no financiamento do Dark Horse — um salto de exposição pessoal (de "áudio vazado" para "investigado nominalmente"), ainda sem pesquisa de opinião dedicada. Modelo probabilístico revisado: componente de mercados do ensemble atualizado de 57% para 48% (Polymarket/Kalshi viraram para Flávio favorito entre 8 e 13/set, precificando a crise do STF) — o ensemble já reflete esse ajuste, e o texto de "Mercados e analistas"/"Modelo" foi revisado por inteiro para refletir a virada, mantendo as leituras de 8/set como referência histórica.
  • v10 — 11/set/2026 (fim do dia) [dados] Nova onda da Datafolha incorporada (campo 8–10/set, divulgada 11/set às 18h45, TSE BR-01833/2026, n=2.002): 1º turno Lula 39% × Flávio 35% × Cury 6% × Caiado 4% × Renan Santos 3% × Zema 2%; 2º turno Lula 46% × Flávio 44% — número idêntico ao da onda anterior (1–3/set), lido aqui como estabilidade do 2º turno mesmo com o 1º turno oscilando — total de 49 ondas de 10 institutos. Nowcast ajustado passa de -1,5 para -1,0 p.p. (Flávio segue à frente, mas a distância encolhe); P(Lula) do modelo de pesquisas sobe de 29,5% para 32,4%; P(Lula) do ensemble sobe de 43,5% para 45%. Revisão de consistência: conferidos os textos de "Leitura executiva", "Variáveis decisivas", "Cenários" e do rodapé de metodologia contra os números atualizados — os trechos analíticos já eram gerados por substituição de token (nowcast, probabilidades, cenários, contagens de pesquisas/institutos) e não continham valores desatualizados além da contagem fixa de ondas da Datafolha no rodapé (7→8), corrigida nesta versão. Buscada também a existência de outras pesquisas divulgadas no mesmo dia (11/set) além da Datafolha — nenhuma localizada até o momento desta atualização.
  • v9 — 11/set/2026 (noite) [dados] Duas novas ondas de 2º turno incorporadas: segunda onda da Futura Inteligência (4–10/set, Flávio 45,4 × Lula 45 — atualiza a leitura de 27/ago–1/set) e primeira onda do instituto Meio/Ideia na base (Canal Meio, 4–7/set, Lula 46 × Flávio 46, TSE BR-07935/26 — mesma pesquisa já citada na seção "Escândalos" para a pergunta sobre o caso Vorcaro/Dark Horse, agora também incorporado o resultado de intenção de voto) — total de 48 ondas de 10 institutos. Nowcast ajustado em -1,5 p.p. (Flávio ainda ligeiramente à frente; a data de referência do modelo também avançou de 10 para 11/set, o que por si só adiciona um dia de deriva à extrapolação). Corrigida também uma contagem defasada no rodapé (AtlasIntel/Bloomberg tinha 9 ondas, não 8, desde a atualização de 10/set).
  • v8 — 11/set/2026 (tarde) [painel] Adicionada a seção "Pesquisas qualitativas e exposição a escândalos: Master, INSS e Dark Horse", reunindo achados de pesquisa qualitativa (grupos focais contínuos do Monitor do Debate Público, LEMEP/UERJ) e pesquisas dedicadas de exposição/dano eleitoral para os três casos com maior tração em set/2026 — com setembro de 2026 como baseline cronológico (eventos anteriores tratados como origem, não como medição corrente).
  • v7 — 11/set/2026 (manhã) [painel] Removida a seção "O que falta para Lula vencer no 1º turno" do painel, a pedido do usuário — junto com o link de navegação correspondente e os cálculos exclusivos a ela (grade de viabilidade, tabela por instituto, reservatório da 3ª via). Permaneceram as menções pontuais a P(1º turno)/distância até 50%/válidos-hoje que já existiam na Leitura executiva e no grid de KPIs, e o Apêndice B (tabela bruta do 1º turno por instituto), por serem conteúdo distinto.
  • v6 — 10/set/2026 (noite) [dados] Incorporada onda AtlasIntel/Bloomberg de 4–9/set (empate técnico Flávio 46,4 × Lula 46,2) — total de 46 ondas de 9 institutos. Removido o botão de download/impressão em PDF da barra de ferramentas, a pedido do usuário.
  • v5 — 10/set/2026 (tarde) [painel] Adicionada a seção "Transparência e conformidade eleitoral": esclarece que o painel é um agregador de pesquisas já registradas na Justiça Eleitoral (não uma pesquisa própria), que nenhuma enquete informal compõe a base e que a metodologia de consolidação é auditável e não usa IA generativa no cálculo das projeções. As tabelas de pesquisas (painel e Apêndice A) passam a exibir contratante e margem de erro amostral (95% de confiança) de cada pesquisa — dados de transparência associados às Res. TSE nº 23.747/2026 e nº 23.755/2026 (ver Referências). Tema claro fixado como padrão (não segue mais o modo escuro do sistema operacional do visitante).
  • v4 — 10/set/2026 [dados] Nova onda PoderData/Aya (6–9/set, Flávio 47 × Lula 45, substitui a leitura de 2/set como mais recente do instituto) e primeira onda do instituto Futura Inteligência na base (27/ago–1/set, Lula 45,6 × Flávio 45,2 — publicada em 3/set e incorporada agora). Nowcast praticamente estável (a onda quase empatada da Futura Inteligência compensa o reforço pró-Flávio da PoderData).
  • v3 — 9/set/2026 [dados] Incorporadas as duas ondas de 2º turno mais recentes com campo até 9/set: BTG/Nexus (4–7/set, Flávio 46 × Lula 45) e a primeira onda do instituto Gerp na base (3–8/set, Flávio 47 × Lula 40) — total de 43 ondas de 8 institutos. O novo instituto entra com peso de qualidade neutro (sem histórico de acurácia verificável) e efeito de casa fortemente encolhido. Removida a seção "Se Flávio vencer: o que diz o plano de governo" do painel, a pedido do usuário. Nowcast ajustado recuou de +0,2 para -1,4 p.p. (ambas as novas ondas favorecem Flávio); P(Lula) no modelo caiu de 41,4% para 30,5% e o ensemble de 49,5% para 44,1%.
  • v2 — 8/set/2026 (tarde) [método] Base ampliada para 41 ondas de 7 institutos (dez/25–set/26); pesos de recência × tamanho × qualidade; P-spline com efeitos de casa; calibração do viés em cinco ciclos; Monte Carlo (400 mil) com deriva, viés e adversário alternativo; ensemble com mercados e fundamentos; tornado das variáveis decisivas e DAG; quatro cenários derivados da distribuição; estudo do 1º turno (aritmética, reservatório, grade de viabilidade, simulação, histórico); coleta de fundamentos (aprovação, economia, mercados, eventos). Esta versão standalone acrescenta sumário executivo, fundamentos, mercados, eventos, deriva histórica, calibração por instituto, apêndices de dados e código, e referências.
  • v1 — 8/set/2026 (manhã) [método] Agregação simples (média não ponderada) das últimas pesquisas de 5 institutos; três cenários qualitativos (vitória apertada de Lula ~50%, virada de Flávio ~30%, empate técnico ~20%); seção sobre o plano de governo de Flávio; avaliação do estoque de indecisos/brancos/nulos; monitoramento da rejeição (cenários revistos para 45/27/28 após a rejeição).
Viés

O erro real de uma pesquisa é 1,72 vezes o teórico; a margem de erro publicada cobre só parte dele

Em uma frase: a margem de erro divulgada cobre só parte do erro real — neste ciclo, o erro típico de uma pesquisa isolada é 1,72 vezes o que a margem promete, e é por isso que o painel nunca confia numa pesquisa só.

Toda pesquisa publica um número — "margem de erro de 2 pontos" — que o leitor entende como o limite do erro possível. Não é. Essa margem cobre apenas uma das fontes de erro, a menos importante delas, e se refere a cada candidato isoladamente, não à diferença entre os dois. Esta seção mede, na própria base deste painel, quanto do erro fica de fora da margem declarada; mostra o viés de cada instituto em 2026 com intervalo de confiança; recupera o histórico de acertos e erros de 2010 a 2022; e conclui avaliando quais pesquisas deste ciclo merecem mais crédito — e por quê.

1 · O que a margem declarada realmente cobre

margem declarada = 1,96 × √(p(1−p)/n) · com p = 0,5 → ±2,0 p.p. para n ≈ 2.000

Ela supõe amostragem aleatória simples, lista completa da população e resposta de todo mundo que é sorteado — três condições que nenhuma pesquisa eleitoral brasileira cumpre. E há uma armadilha de leitura: a margem vale para cada percentual, não para a diferença. Quando Lula tem 46% e Flávio 45% com "margem de 2 pontos", a diferença de 1 ponto carrega, só de erro amostral, um intervalo de ±4,4 p.p. — porque o erro dos dois percentuais se soma. Empates técnicos são, portanto, muito mais frequentes do que a leitura usual sugere.

2 · O que ela não cobre

Efeito de desenho: cotas e conglomerados inflam a variância; nos EUA, o erro-padrão real é ≈1,3× o da fórmula simples (mediana; Shirani-Mehr et al., JASA 2018), e amostras por cotas em ponto de fluxo tendem a fator maior. Não resposta: taxas acima de 90% em pesquisas eleitorais americanas — e a não resposta é diferencial: quem apoia o candidato que está perdendo responde menos, viés que nenhuma ponderação demográfica corrige. Cobertura: telefone exclui quem não tem linha (~4% dos adultos), painel online exclui o eleitor offline e de menor escolaridade, presencial tropeça em condomínios fechados. Ponderação: calibrar por voto declarado na eleição anterior importa o erro de memória do entrevistado. Eleitor provável e indecisos: critérios de comparecimento e a redistribuição de indecisos são hipóteses, não medições — e institutos diferentes usam hipóteses parecidas, o que faz os erros andarem juntos.

Medição na base deste painel
Ler a análise completa≈ 25 min de leitura

O erro real é o dobro do teórico — e a margem publicada cobre um quarto dele

Para cada uma das 221 pesquisas da base, dá para comparar o desvio observado em relação à tendência comum estimada pelo agregador com o desvio que a teoria amostral prevê para aquela amostra. O erro típico observado é de 3,9 p.p. na margem em votos válidos, contra 2,3 p.p. previstos — uma razão de 1,72×. Traduzindo para intervalo de 95%: o intervalo honesto de uma pesquisa isolada, neste ciclo, é de cerca de ±7,6 p.p. na margem, e não os ±2,0 que aparecem na ficha técnica. A coincidência com a literatura internacional é notável: analisando 4.221 pesquisas de 608 eleições americanas, Shirani-Mehr, Rothschild, Goel e Gelman encontraram erro médio de 3,5 p.p. — também cerca do dobro do implícito nos intervalos publicados.

Decomposição do erro observado (p.p. na margem)

Erro típico total (vs. tendência)3,9
Depois de remover o efeito de casa2,4
Previsto pela amostragem2,3

A parcela sistemática — o viés de cada instituto — é a maior fatia e é corrigível: é exatamente o que o agregador faz ao estimar efeitos de casa. O que sobra depois disso ainda é 1,1× o teórico: é o excesso de ruído que nenhuma ficha técnica declara.

Teste de manada (herding): sem sinal de manada

Manada é a convergência artificial de institutos perto da eleição — publicar o que o consenso já diz, para não errar sozinho. Nos EUA de 2024, Nate Silver mostrou que 78% das pesquisas dos estados-pêndulo caíam dentro de ±2,5 p.p. quando a teoria previa 55%: probabilidade de acontecer por acaso, 1 em 9,5 trilhões. Aqui o teste é feito nos resíduos: tirados a tendência e o efeito de casa de cada instituto, as 57 ondas dos últimos 21 dias se afastam em média 1,7 p.p., contra 2,2 p.p. esperados só por erro amostral — razão de 0,78×. Perto de 1: a dispersão é a que o acaso explica, sem sinal de manada (parte da distância para 1 vem de o próprio ajuste absorver ruído). Já as margens brutas variam 2,0 p.p. entre institutos: a divergência brasileira vem de efeitos de casa metodológicos reais e persistentes, não de ruído. (Até 3/out este teste comparava as margens brutas com o erro amostral, o que dava sempre razão acima de 1 e não media manada.) A consequência prática é que a média simples de pesquisas (usada pela maioria dos agregadores da imprensa) é um estimador ruim: sem corrigir viés de casa, ela reflete quem publicou mais naquela semana.

Efeito de casa por instituto em 2026 — desvio sistemático em relação à âncora (◆ = instituto da âncora)
Como ler. Cada ponto é o desvio médio de um instituto em relação à âncora; a barra é a faixa de 95% de confiança. À direita do zero, o instituto mede Lula melhor que a âncora; à esquerda, mede Flávio melhor. Barras que não cruzam o zero indicam desvio consistente.
mede mais Flávio que a âncora mede mais Lula que a âncora -6 -4 -2 0 +2 +4 +6 Gerp Gerp: efeito de casa -7,7 p.p. (IC 95%: -8,9 a -6,4) · 20 ondas · variância extra estimada σ=0,8 · erro típico 7,4 p.p. vs. 2,2 esperado pela amostragem -7,7* Veritá Veritá: efeito de casa -5,3 p.p. (IC 95%: -7,2 a -3,5) · 9 ondas · variância extra estimada σ=1,3 · erro típico 6,5 p.p. vs. 1,6 esperado pela amostragem -5,3* Palver Palver: efeito de casa -4,4 p.p. (IC 95%: -6,2 a -2,6) · 6 ondas · variância extra estimada σ=0,8 · erro típico 4,2 p.p. vs. 2,8 esperado pela amostragem -4,4* Futura Inteligência Futura Inteligência: efeito de casa -3,8 p.p. (IC 95%: -5,2 a -2,4) · 19 ondas · variância extra estimada σ=0,8 · erro típico 6,0 p.p. vs. 2,3 esperado pela amostragem -3,8* PoderData ◆ PoderData ◆: efeito de casa -2,4 p.p. (IC 95%: -3,6 a -1,3) · 17 ondas · variância extra estimada σ=0,8 · erro típico 2,0 p.p. vs. 1,9 esperado pela amostragem -2,4* Paraná Pesquisas ◆ Paraná Pesquisas ◆: efeito de casa -2,0 p.p. (IC 95%: -3,9 a -0,1) · 4 ondas · variância extra estimada σ=0,8 · erro típico 3,2 p.p. vs. 2,4 esperado pela amostragem -2,0* DataTrends DataTrends: efeito de casa -1,8 p.p. (IC 95%: -3,9 a 0,2) · 5 ondas · variância extra estimada σ=0,8 · erro típico 2,2 p.p. vs. 2,3 esperado pela amostragem -1,8 Real Time Big Data Real Time Big Data: efeito de casa -1,6 p.p. (IC 95%: -3,4 a 0,3) · 9 ondas · variância extra estimada σ=0,8 · erro típico 1,9 p.p. vs. 2,4 esperado pela amostragem -1,6 Vox Brasil Vox Brasil: efeito de casa -1,4 p.p. (IC 95%: -3,0 a 0,3) · 11 ondas · variância extra estimada σ=1,4 · erro típico 3,8 p.p. vs. 2,3 esperado pela amostragem -1,4 BTG/Nexus BTG/Nexus: efeito de casa -1,1 p.p. (IC 95%: -2,3 a 0,1) · 19 ondas · variância extra estimada σ=0,8 · erro típico 1,5 p.p. vs. 2,3 esperado pela amostragem -1,1 Quaest ◆ Quaest ◆: efeito de casa -1,0 p.p. (IC 95%: -2,3 a 0,3) · 19 ondas · variância extra estimada σ=0,8 · erro típico 1,8 p.p. vs. 2,4 esperado pela amostragem -1,0 Meio/Ideia Meio/Ideia: efeito de casa -0,9 p.p. (IC 95%: -2,8 a 1,0) · 11 ondas · variância extra estimada σ=0,8 · erro típico 1,7 p.p. vs. 2,6 esperado pela amostragem -0,9 Ipespe ◆ Ipespe ◆: efeito de casa -0,9 p.p. (IC 95%: -3,0 a 1,3) · 3 ondas · variância extra estimada σ=0,8 · erro típico 1,3 p.p. vs. 2,7 esperado pela amostragem -0,9 AtlasIntel ◆ AtlasIntel ◆: efeito de casa -0,5 p.p. (IC 95%: -1,8 a 0,8) · 17 ondas · variância extra estimada σ=0,8 · erro típico 1,8 p.p. vs. 2,1 esperado pela amostragem -0,5 Instituto França Instituto França: efeito de casa -0,5 p.p. (IC 95%: -2,8 a 1,9) · 3 ondas · variância extra estimada σ=0,8 · erro típico 0,9 p.p. vs. 2,4 esperado pela amostragem -0,5 Palver (calibração v3) Palver (calibração v3): efeito de casa -0,3 p.p. (IC 95%: -2,4 a 1,7) · 3 ondas · variância extra estimada σ=0,8 · erro típico 0,8 p.p. vs. 2,4 esperado pela amostragem -0,3 Datafolha ◆ Datafolha ◆: efeito de casa 0,0 p.p. (IC 95%: -1,3 a 1,3) · 18 ondas · variância extra estimada σ=0,8 · erro típico 2,1 p.p. vs. 2,2 esperado pela amostragem 0,0 Alfa Inteligência Alfa Inteligência: efeito de casa 0,3 p.p. (IC 95%: -1,7 a 2,2) · 6 ondas · variância extra estimada σ=1,0 · erro típico 2,1 p.p. vs. 2,4 esperado pela amostragem 0,3 Indexa Indexa: efeito de casa 1,1 p.p. (IC 95%: -0,7 a 2,8) · 8 ondas · variância extra estimada σ=0,8 · erro típico 1,6 p.p. vs. 2,4 esperado pela amostragem 1,1 CNT/MDA ◆ CNT/MDA ◆: efeito de casa 3,9 p.p. (IC 95%: 2,0 a 5,8) · 5 ondas · variância extra estimada σ=0,8 · erro típico 6,4 p.p. vs. 2,4 esperado pela amostragem 3,9*

Atenção à interpretação: efeito de casa não é sinônimo de viés político. Ele mede distância da âncora, não distância do resultado — e costuma refletir escolhas de método (quem é presencial, quem é online, quem redistribui indeciso). O consenso também erra: em 2022, os dois institutos mais próximos do resultado final do 2º turno foram justamente dois que destoavam da média.

Depois do 1º turno, o efeito de casa vale metade. Nas pesquisas feitas depois de 4/out, a margem observada acompanha só em parte o efeito de casa estimado antes (inclinação de +0,58, com intervalo de ±0,68, onde 1 seria persistência total e 0, nenhuma; sem a Gerp, perto de zero), e a dispersão entre institutos aumenta quando o efeito antigo é aplicado por inteiro. Vários institutos passaram a pesar a amostra pelo voto declarado no 1º turno, o que apaga parte das diferenças. O modelo aplica 50% do efeito de casa histórico às pesquisas posteriores a 4/out (a coluna "Efeito de casa" acima mostra o valor aplicado). A Palver trocou o método de calibração na onda de 10 a 13/out (v3, com o voto no 1º turno por região) e o próprio instituto diz que a onda não é comparável às anteriores: entra como série nova, sem o efeito de casa das ondas anteriores, e com o n efetivo de Kish informado por ela (1.783 em vez de 5.000). A AtlasIntel tem o n efetivo limitado a 2.500, porque a margem declarada (±1,0) é menor que a de uma amostra aleatória simples do mesmo tamanho (±1,4) e o efeito de desenho não é informado.

InstitutoMétodon médioEfeito de casaErro típicoErro teóricoRazãoMAE 2006–22Leitura
Gerp · 20 ondas · última 24/10 telefônico 2.220 -7,7 * 7,4 2,2 3,31× — Exigem cautela
Futura Inteligência · 19 ondas · última 17/10 telefônico 2.000 -3,8 * 6,0 2,3 2,58× 2,4 Exigem cautela
Quaest · 19 ondas · última 24/10 presencial 2.198 -1,0 1,8 2,4 0,75× 4,0 Confiáveis com ressalva
BTG/Nexus · 19 ondas · última 24/10 telefônico 2.007 -1,1 1,5 2,3 0,65× — Mais fidedignas
Datafolha · 18 ondas · última 24/10 presencial 2.345 0,0 2,1 2,2 0,93× 3,4 Mais fidedignas
AtlasIntel · 17 ondas · última 23/10 online 5.215 -0,5 1,8 2,1 0,85× 4,5 Confiáveis com ressalva
PoderData · 17 ondas · última 24/10 telefônico 2.964 -2,4 * 2,0 1,9 1,04× 4,5 Confiáveis com ressalva
Meio/Ideia · 11 ondas · última 19/10 telefônico 1.636 -0,9 1,7 2,6 0,64× — Confiáveis com ressalva
Vox Brasil · 11 ondas · última 22/10 presencial 2.100 -1,4 3,8 2,3 1,62× — Exigem cautela
Veritá · 9 ondas · última 23/10 telefônico 12.912 -5,3 * 6,5 1,6 3,99× 3,8 Exigem cautela
Real Time Big Data · 9 ondas · última 22/10 telefônico 2.000 -1,6 1,9 2,4 0,78× — Mais fidedignas
Indexa · 8 ondas · última 21/10 telefônico 2.000 +1,1 1,6 2,4 0,68× — Confiáveis com ressalva
Alfa Inteligência · 6 ondas · última 22/10 presencial 2.200 +0,3 2,1 2,4 0,86× — Exigem cautela
Palver · 6 ondas · última 03/10 online 5.000 -4,4 * 4,2 2,8 1,54× — Exigem cautela
CNT/MDA · 5 ondas · última 02/10 presencial 2.003 +3,9 * 6,4 2,4 2,69× 3,9 Exigem cautela
DataTrends · 5 ondas · última 21/10 telefônico 2.000 -1,8 2,2 2,3 0,94× — Série insuficiente
Paraná Pesquisas · 4 ondas · última 28/03 presencial 2.069 -2,0 * 3,2 2,4 1,37× 3,8 Confiáveis com ressalva
Instituto França · 3 ondas · última 23/10 não informado 2.179 -0,5 0,9 2,4 0,40× — Série insuficiente
Ipespe · 3 ondas · última 22/10 telefônico 1.500 -0,9 1,3 2,7 0,48× 4,9 Exigem cautela
Palver (calibração v3) · 3 ondas · última 24/10 online 5.000 -0,3 0,8 2,4 0,32× — Exigem cautela
Vetor/Arrow · 2 ondas · última 18/05 não informado 9.000 -0,4 10,4 1,5 7,02× — Série insuficiente
American Analytics · 2 ondas · última 20/09 não informado 2.000 0,0 0,4 2,4 0,16× — Série insuficiente
CNT/MDA (ponderada pelo 1º turno) · 2 ondas · última 23/10 presencial 2.023 -0,3 0,5 2,3 0,23× — Exigem cautela
Futura (escolaridade pela PNAD) · 2 ondas · última 24/10 telefônico 2.000 -0,4 0,8 2,3 0,33× — Exigem cautela
Colectta · 1 onda · última 23/10 presencial 3.000 +0,2 1,8 1,9 0,94× — Série insuficiente

"Efeito de casa" positivo = mede Lula melhor que a âncora; negativo = mede Flávio melhor; * marca os estatisticamente significativos (95%). "Erro típico" é o desvio observado em relação à tendência comum; "erro teórico" é o previsto pela amostragem para aquele n. "Razão" acima de 2 significa que o instituto erra o dobro do que sua ficha técnica sugere — e amostra grande não resolve: Veritá e Vetor/Arrow declaram as menores margens da base (amostras de 3,8 mil a 40 mil) e têm as maiores razões, porque amostra grande reduz o erro amostral sem tocar no viés.

O método explica boa parte da divergência

Não é opinião: a PollingData mediu, sobre 4.400 pesquisas registradas no TSE, que em 2022 a vantagem média de Lula era de 14,1 p.p. em pesquisas presenciais contra 9,3 p.p. em telefônicas no 1º turno (4,8 p.p. de diferença), e 17,6 contra 13,8 no 2º turno. A direção se repete em 2026 nas variáveis de percepção: Datafolha (presencial, ponto de fluxo) mede rejeição de 46% para Flávio e 45% para Lula; a Quaest (presencial domiciliar), 54% e 52% no mesmo mês — 7 a 8 p.p. de diferença na variável que define o teto de cada candidato. Na aprovação do governo, PoderData (URA) e Quaest chegaram a divergir 10 p.p. em dezembro de 2025. Métodos autoadministrados (online, URA) tendem a medir mais voto à direita que os métodos com entrevistador — compatível com menor desejabilidade social.

Viés de contratante: o que a evidência mostra

Nos EUA, pesquisas patrocinadas por campanhas e partidos exibem viés médio de 4 a 5 p.p. a favor do patrocinador (FiveThirtyEight). No Brasil, o JOTA estima efeitos de casa por instituto e já registrou desvios da ordem de 4 p.p. em pesquisas de institutos ligados a contratantes corporativos. Na base deste painel, porém, o padrão não se organiza por contratante. Os maiores desvios em relação à âncora são: Gerp (-7,7, pró-Flávio); Veritá (-5,3, pró-Flávio); Palver (-4,4, pró-Flávio); CNT/MDA (+3,9, pró-Lula). Há entre eles institutos autofinanciados e institutos com contratante. Ou seja: no ciclo 2026 o viés está mais associado a método e ausência de escrutínio do que a quem paga a conta.

CicloO que as finais projetaramResultadoErro na margemLição
2010 · 2º turno Datafolha e Ibope: Dilma 56 × 44 56,05 × 43,95 ≈0,2 O ciclo mais preciso da série — quando a disputa não é polarizada por identidade, a pesquisa funciona bem
2014 · 2º turno Vox Populi 48×41 · Datafolha 47×43 · Ibope 49×43 · CNT/MDA deu Aécio à frente Dilma 51,6 × 48,4 0,6 a 3,9 Primeiro caso recente de vencedor errado por um instituto tradicional; 1º turno subestimou Aécio em ~9 p.p.
2018 · 2º turno Datafolha 55×45 · Ibope 54×46 · Vox Populi 50×50 · Paraná Pesquisas 60,6×39,4 Bolsonaro 55,1 × 44,9 0,3 (Datafolha) a 10,9 (Paraná) O mesmo ciclo produziu o melhor e o pior desempenho da série — média de institutos esconde dispersão brutal
2022 · 1º turno Datafolha e Ipec: Lula +14 em válidos Lula +5,2 ≈8,8 Erro sistemático contra Bolsonaro, com desvios de até 20 p.p. em estados — a origem do prior de viés deste painel
2022 · 2º turno CNT/MDA 51,1×48,9 · Paraná 50,4×49,6 · Datafolha e Quaest 52×48 · Ipec 54×46 · Veritá, Brasmarket e Futura deram Bolsonaro Lula 50,9 × 49,1 0,4 a 9,0 Os dois mais precisos estavam fora do consenso; três institutos erraram o vencedor — dois deles seguem na base de 2026
Municipais 2020/2024 SP 2020: Covas 38 (Ibope final) · SP 2024: Boulos 27 × Nunes 24 × Marçal 24 (Datafolha) Covas 32,9 · Nunes 29,5 × Boulos 29,1 × Marçal 28,1 4 a 5,5 Em 2024 a ordem dos três primeiros foi invertida — erro de ordenação, não só de magnitude

Convenção: erro na margem = (margem projetada esquerda−direita) − (margem oficial), em pontos percentuais de votos válidos. É essa série que alimenta o desconto de erro sistemático usado na simulação (+0,8 ± 2,0 p.p. para o 2º turno — erro observado das pesquisas no 1º turno, nas ondas de antes de 4/out, e rodadas finais de 2º turno de 2006–2022 mais 25% desse erro, nas ondas novas; +3,4 ± 3,8 para o 1º). Note a assimetria entre turnos: o viés grande e sistemático aparece sobretudo nos 1ºs turnos (2014 e 2022), enquanto os 2ºs turnos têm viés menor e mais disperso.

Regulação: o que o TSE exige — e o que não garante

Pela Lei 9.504/1997 (art. 33) toda pesquisa deve ser registrada no PesqEle até 5 dias antes da divulgação, com contratante, valor pago, plano amostral, período de campo e margem de erro; depois, é obrigatório complementar com a composição final da amostra. Divulgar sem registro custa multa de R$ 53 mil a R$ 106 mil, inclusive para quem apenas compartilha. O registro, porém, é uma formalidade de transparência, não um selo de qualidade: não há auditoria prévia de método nem sanção por errar. O projeto que puniria com 8 anos de inabilitação o instituto que errasse além da margem (PL 2.558/2022) não avançou, e a CPI das pesquisas pedida em 2022 nunca foi instalada.

O que já aconteceu neste ciclo

Três episódios de 2026 mostram o tamanho da zona cinzenta. Fevereiro: o TSE suspendeu pesquisa da Áltica Research divulgada sem registro prévio. Junho e julho: a campanha de Flávio Bolsonaro acionou o TSE duas vezes contra a AtlasIntel — na primeira, alegando indução ao reproduzir áudio do caso Vorcaro antes das perguntas de imagem, obteve liminar do presidente do TSE suspendendo a divulgação; na segunda, contestou a documentação complementar de uma onda que dava Lula +7. Agosto: pesquisas do Veritá foram suspensas pela Justiça Eleitoral após questionamentos sobre metodologia, composição da amostra e cronograma de coleta — o mesmo instituto que registrou 93 pesquisas autofinanciadas no ano. Judicialização de pesquisa é, ela própria, um instrumento de campanha: convém separar o mérito metodológico do uso político da contestação.

Conclusão

Quais pesquisas deste ciclo merecem mais crédito

Não existe instituto infalível, e a pergunta certa não é "quem está certo hoje", e sim quem erra de forma menor e mais previsível. Cruzei quatro critérios verificáveis: acurácia histórica em eleições presidenciais reais (2018 e 2022), tamanho e significância estatística do efeito de casa em 2026, excesso de erro sobre o teórico, e transparência de método, contratante e registro. O resultado abaixo é um julgamento explicitado — não um índice automático — e cada nome vem com sua ressalva.

Faixa 1 — as mais fidedignas da base

BTG/Nexus · 19 ondas · efeito de casa -1,1 · erro 0,65× o teórico

Em 2026: 19 ondas, efeito de casa de -1,1 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. A série mais densa do ciclo, com ondas quinzenais desde março, e um dos menores erros típicos da base. Método telefônico e contratante declarados em toda onda. Ressalvas: não tem histórico presidencial próprio para aferir acurácia, e uma dispersão tão baixa também é compatível com suavização entre ondas.

Datafolha · 18 ondas · efeito de casa 0,0 · erro 0,93× o teórico

Em 2026: 18 ondas, efeito de casa de 0,0 p.p., indistinguível de zero, erro típico próximo do teórico. A série mais longa do país, método presencial em ponto de fluxo e a melhor acurácia histórica entre os grandes (MAE 3,4 p.p. na margem, 2006–22, com o acerto quase exato do 2º turno de 2018).

Real Time Big Data · 9 ondas · efeito de casa -1,6 · erro 0,78× o teórico

Em 2026: 9 ondas, efeito de casa de -1,6 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. Método telefônico e registro declarados, erro típico abaixo do teórico. Ressalvas: sem histórico presidencial verificável e série curta.

Faixa 2 — confiáveis, com ressalva conhecida

Quaest · 19 ondas · efeito de casa -1,0 · erro 0,75× o teórico

Em 2026: 19 ondas, efeito de casa de -1,0 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. Série longa e presencial domiciliar, o método mais caro e, em tese, o de melhor cobertura, com registro e contratante declarados. Contra: o pior MAE histórico entre os grandes (4,0 p.p.). Mede rejeição acima do Datafolha, diferença que é de pergunta e método, não de momento.

AtlasIntel · 17 ondas · efeito de casa -0,5 · erro 0,85× o teórico

Em 2026: 17 ondas, efeito de casa de -0,5 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. Amostras de 5 mil, alta cadência e o melhor desempenho no 1º turno de 2022. Contra: é online não probabilística (recrutamento digital) e foi alvo de duas ações do PL em 2026, uma delas com liminar de suspensão. Amostra grande encolhe a margem declarada sem encolher o erro real.

PoderData · 17 ondas · efeito de casa -2,4 · erro 1,04× o teórico

Em 2026: 17 ondas, efeito de casa de -2,4 p.p. (mede Flávio melhor que a âncora, desvio estatisticamente significativo), erro típico próximo do teórico. Cadência fixa, método (URA) e registro TSE publicados em toda onda. Histórico verificável, ainda que mediano (MAE 4,5 p.p.).

Meio/Ideia · 11 ondas · efeito de casa -0,9 · erro 0,64× o teórico

Em 2026: 11 ondas, efeito de casa de -0,9 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. Ondas telefônicas com registro TSE e contratante declarado, boa cadência. Contra: amostra menor que a média da base e nenhum histórico presidencial próprio.

Indexa · 8 ondas · efeito de casa +1,1 · erro 0,68× o teórico

Em 2026: 8 ondas, efeito de casa de +1,1 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. Telefônica, contratante jornalístico (Broadcast/Estadão) e registro divulgado. Contra: série curta e sem histórico presidencial verificável.

Paraná Pesquisas · 4 ondas · efeito de casa -2,0 · erro 1,37× o teórico

Em 2026: 4 ondas, efeito de casa de -2,0 p.p. (mede Flávio melhor que a âncora, desvio estatisticamente significativo), erro típico 1,4× o teórico. Caso instrutivo: acertou o 2º turno de 2022 quase na mosca quando o consenso errou, mas havia errado o de 2018 por ~11 p.p. (MAE combinado de 3,8 p.p.). A última onda é de março: está fora da leitura atual.

Faixa 3 — exigem cautela ou têm série insuficiente

Gerp · 20 ondas · efeito de casa -7,7 · erro 3,31× o teórico

Em 2026: 20 ondas, efeito de casa de -7,7 p.p. (mede Flávio melhor que a âncora, desvio estatisticamente significativo), erro típico 3,3× o teórico. Autofinanciado e sem histórico presidencial verificável. Não implica má-fé: pode ser uma escolha metodológica que capta melhor o eleitor de direita, mas não há como auditar.

Futura Inteligência · 19 ondas · efeito de casa -3,8 · erro 2,58× o teórico

Em 2026: 19 ondas, efeito de casa de -3,8 p.p. (mede Flávio melhor que a âncora, desvio estatisticamente significativo), erro típico 2,6× o teórico. Projetou o vencedor errado no 2º turno de 2022, na mesma direção do desvio atual: o viés não é novo.

Vox Brasil · 11 ondas · efeito de casa -1,4 · erro 1,62× o teórico

Em 2026: 11 ondas, efeito de casa de -1,4 p.p., indistinguível de zero, erro típico 1,6× o teórico. Presencial domiciliar e autofinanciada nas ondas recentes (registro no TSE divulgado); as ondas até maio não informavam o método. Sem histórico presidencial verificável.

Veritá · 9 ondas · efeito de casa -5,3 · erro 3,99× o teórico

Em 2026: 9 ondas, efeito de casa de -5,3 p.p. (mede Flávio melhor que a âncora, desvio estatisticamente significativo), erro típico 4,0× o teórico. Amostras enormes (até 40 mil) e margem declarada entre as menores da base, com erro observado várias vezes o teórico: a ilustração mais clara de falsa precisão. Teve pesquisas suspensas pela Justiça Eleitoral em agosto de 2026 após questionamentos sobre metodologia e composição de amostra, e projetou o vencedor errado em 2022.

Alfa Inteligência · 6 ondas · efeito de casa +0,3 · erro 0,86× o teórico

Em 2026: 6 ondas, efeito de casa de +0,3 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. Amostras menores que o padrão da base, método não informado e sem histórico verificável.

Palver · 6 ondas · efeito de casa -4,4 · erro 1,54× o teórico

Em 2026: 6 ondas, efeito de casa de -4,4 p.p. (mede Flávio melhor que a âncora, desvio estatisticamente significativo), erro típico 1,5× o teórico. Coleta por WhatsApp com recrutamento por anúncios e calibração por raking. Em compensação, tem a ficha técnica mais completa do ciclo e prometeu abrir os microdados após o 2º turno.

CNT/MDA · 5 ondas · efeito de casa +3,9 · erro 2,69× o teórico

Em 2026: 5 ondas, efeito de casa de +3,9 p.p. (mede Lula melhor que a âncora, desvio estatisticamente significativo), erro típico 2,7× o teórico. O caso mais difícil da base. Tem boa acurácia histórica (MAE 3,9 p.p.; foi o mais próximo do resultado no 2º turno de 2022) e, em 2026, é o instituto da âncora que mais mede Lula à frente. Ou capta algo que os demais perdem, como em 2022, ou destoou neste ciclo. Poucas ondas, todas presenciais, contratante declarado (CNT).

DataTrends · 5 ondas · efeito de casa -1,8 · erro 0,94× o teórico

Em 2026: 5 ondas, efeito de casa de -1,8 p.p., indistinguível de zero, erro típico próximo do teórico. Estreia na base por URA. Sem histórico e com série curta para medir efeito de casa (a nota não muda o peso no modelo: cada onda pesa pelo seu erro amostral e pelo ruído extra da casa).

Instituto França · 3 ondas · efeito de casa -0,5 · erro 0,40× o teórico

Em 2026: 3 ondas, efeito de casa de -0,5 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. Telefônica (CATI), autofinanciada, sem histórico verificável. O relatório de 16–20/out diz que "consolida levantamentos" e o total geral não fecha com os recortes: nenhum recorte tem mais de 4,1% de "não sabe" por idade (o total diz 8,84%), e o voto por região, ponderado pelos pesos do próprio relatório, dá Lula ~41,6 × Flávio ~39,3, o inverso do total.

Ipespe · 3 ondas · efeito de casa -0,9 · erro 0,48× o teórico

Em 2026: 3 ondas, efeito de casa de -0,9 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. Volta à base no 2º turno de 2026 com histórico presidencial verificável (as séries XP/Ipespe de 2018 e 2022), mas mediano: errou a margem do 2º turno por 5,7 p.p. em 2018 e 4,2 em 2022, em direções opostas. Amostra pequena (1.500) e divulgações que divergem sobre o método (recrutamento digital × telefone CATI).

Palver (calibração v3) · 3 ondas · efeito de casa -0,3 · erro 0,32× o teórico

Em 2026: 3 ondas, efeito de casa de -0,3 p.p., indistinguível de zero, erro típico abaixo do esperado pela amostra. A série da Palver depois da mudança de ponderação (v3, 10–13/out em diante): mesma casa e mesmo método, mas efeito de casa estimado só nas ondas novas, por isso aparece à parte. O n efetivo declarado (Kish) é o que entra no erro.

Vetor/Arrow · 2 ondas · efeito de casa -0,4 · erro 7,02× o teórico

Com 2 ondas, não há série para medir efeito de casa. Duas ondas com amostras de 9 mil que se moveram mais de 10 p.p. entre si em dois meses, o maior descolamento da base em relação ao que a amostra permitiria. Sem histórico e sem método declarado.

American Analytics · 2 ondas · efeito de casa 0,0 · erro 0,16× o teórico

Com 2 ondas, não há série para medir efeito de casa. Série curta, sem histórico verificável no Brasil e sem método declarado.

CNT/MDA (ponderada pelo 1º turno) · 2 ondas · efeito de casa -0,3 · erro 0,23× o teórico

Com 2 ondas, não há série para medir efeito de casa. A série da CNT/MDA depois da mudança de ponderação (v2, 13–15/out em diante): mesma casa, efeito de casa estimado só nas ondas novas, fora da âncora enquanto não tiver série.

Futura (escolaridade pela PNAD) · 2 ondas · efeito de casa -0,4 · erro 0,33× o teórico

Com 2 ondas, não há série para medir efeito de casa. A série da Futura depois da troca da ponderação por escolaridade (cadastro do TSE → PNAD Contínua, onda de 19–20/out em diante): mesma casa, efeito de casa estimado só nas ondas novas.

Colectta · 1 onda · efeito de casa +0,2 · erro 0,94× o teórico

Com 1 onda, não há série para medir efeito de casa. Estreia na base na véspera do 2º turno: presencial, um dia de campo (23/out), contratante declarado (Brazil Economy) e registro TSE BR-05601/2026. Sem histórico verificável e com uma onda só, não há série para medir efeito de casa.

Regra prática

Como ler qualquer pesquisa de 2º turno

  • Dobre mentalmente a margem publicada — e lembre que, para a diferença entre dois candidatos, ela já vale o dobro antes de qualquer correção de viés
  • Desconfie de amostra grande com margem pequena: n alto reduz o erro amostral, que é a menor parte do problema, e não toca no viés
  • Compare cada instituto com ele mesmo: a tendência dentro de uma série é mais informativa que o nível entre séries diferentes
  • Pergunte quem pagou e qual o método antes de olhar o número — e trate "método não informado" como informação relevante sobre o instituto
  • Uma pesquisa fora da curva não é necessariamente errada: em 2022, os dois institutos mais próximos do resultado estavam fora do consenso, e o consenso é que errou
  • Nenhuma pesquisa isolada decide nada: é por isso que este painel agrega, corrige efeito de casa e ainda assim projeta com intervalo largo

Correção feita nesta atualização. Ao levantar o histórico para esta seção, encontrei uma falha na calibração do próprio painel: a Paraná Pesquisas entrava com o melhor peso de qualidade da base (menor erro histórico) porque só sua atuação em 2022 estava registrada — mas o instituto errou o 2º turno de 2018 por cerca de 11 p.p., o maior desvio individual da série. O histórico foi completado (Paraná em 2018; Veritá e Futura em 2022), e o peso de qualidade passou a ser encolhido conforme o número de ciclos que o sustenta: com uma única eleição verificada, o instituto fica perto do peso neutro. O nowcast e as probabilidades desta versão já refletem a correção.

Fontes desta seção. Erro total vs. margem declarada: Shirani-Mehr, Rothschild, Goel & Gelman, "Disentangling Bias and Variance in Election Polls", JASA 113(522), 2018. Manada: Nate Silver, "There's more herding in swing state polls", 2024. Método presencial × telefônico: PollingData (base de 4.400 pesquisas registradas no TSE). Efeito de casa e viés de contratante: FiveThirtyEight e JOTA (agregador de house effects). Séries históricas: Congresso em Foco, O Povo, Poder360, Metrópoles, CartaCapital, O Janelão e Wikipédia (pesquisas presidenciais 2014/2018/2022). Regulação e episódios de 2026: TSE (PesqEle, Res. 23.747/2026 e decisões de fev., jun. e jul./2026), Senado (PL 2.558/2022), JOTA, Brasil de Fato e Correio da Manhã. Transparência: AAPOR Transparency Initiative, ABEP e ABRAPEL.

Glossário

Os termos do painel, em linguagem comum

Em uma frase: cada termo técnico usado no painel, explicado sem jargão. Os mesmos textos aparecem ao tocar nas palavras sublinhadas.

Abstenção
Eleitores aptos que não comparecem para votar. O voto é obrigatório dos 18 aos 69 anos (falta sem justificativa gera multa) e facultativo para quem tem 16–17 anos, 70 ou mais e para analfabetos. Em 2022, faltaram 20,95% no 1º turno e 20,59% no 2º.
Aprovação do governo
Percentual que aprova a forma de governar do presidente. É o fator que mais pesa na reeleição: cada ponto a mais de aprovação vale cerca de 0,8 ponto de margem para Lula.
Cadeia de Markov
Modelo em que cada eleitor está num estado (votou em X, não votou, foi alcançado pela campanha, fez um plano de voto…) e passa para outro com probabilidades que dependem só do estado atual, não do caminho até ele. Multiplicando a população pela matriz de transição, obtém-se a população em cada estado no passo seguinte; repetindo o passo dia a dia, a campanha inteira.
Choques regionais
Erros que atingem uma região inteira de uma vez (por exemplo, pesquisas subestimando o voto de direita no Sul, como em 2018 e 2022). O erro histórico nacional já contém esses erros regionais; o sorteio separado por região saiu da simulação na auditoria de 3/out, porque não alterava nenhum resultado nacional.
Deriva
Quanto o placar ainda pode andar até o dia da eleição. Quanto mais campanha pela frente, maior ela é; no dia da votação, é zero.
Distribuição Dirichlet
Sorteio de um conjunto de proporções que somam 100% (por exemplo, a parcela dos eleitores de Cury que vai para Lula, para Flávio, para branco/nulo ou fica em casa), em torno de um centro e com uma concentração κ que diz quanto elas podem se afastar dele: quanto menor o κ, mais incerta a divisão.
Efeito de casa
Tendência sistemática de um instituto a mostrar um candidato melhor ou pior que a média dos demais, por causa do método (telefone, internet, entrevista presencial, forma da pergunta). O painel mede e desconta esse desvio.
Empate técnico
Quando a diferença entre os candidatos é menor que a incerteza da medição: não dá para afirmar quem está à frente.
Ensemble
Combinação de duas fontes: o modelo de pesquisas (75%) e os mercados de apostas (25%, só enquanto houver preço recente; sem ele, vale só o modelo). O histórico de presidentes candidatos à reeleição aparece no painel, mas não pesa: três eleições não sustentam uma previsão. É o número principal do painel.
Erro histórico do 1º turno
Em 2010, 2014, 2018 e 2022, as pesquisas finais de 1º turno superestimaram a margem do candidato do PT sobre o principal adversário de direita, em 2 a 10 pontos de votos válidos. O modelo desconta parte (60%) da média recente desses erros na projeção do 1º turno e mostra o resultado sem desconto e com o desconto inteiro.
Erro sistemático das pesquisas
Erro que empurra todas as pesquisas para o mesmo lado. Nos 2ºs turnos de 2006 a 2022, as pesquisas finais superestimaram o candidato do PT em três ciclos e o subestimaram em dois; a simulação desconta hoje +0,8 ponto da margem de Lula, com a incerteza correspondente.
Espaço de estados (filtro de Kalman)
Método que trata o placar verdadeiro como algo que anda um pouco a cada dia e cada pesquisa como uma medição ruidosa dele. A partir dos dados, estima quanto o placar costuma andar por dia, o desvio de cada instituto e quanto cada um erra além do declarado.
Fundamentos
Indicadores que costumam antecipar eleições independentemente das pesquisas, como aprovação do governo e economia. Presidentes com aprovação perto de 45% costumam se reeleger.
Intervalo de 80% / 95%
Faixa onde o resultado cai em 80% (ou 95%) das simulações. Quanto mais larga, maior a incerteza.
Margem de erro
Faixa em torno do número divulgado onde o valor real provavelmente está (95% de confiança). Vale para cada candidato isoladamente; para a diferença entre os dois, a faixa é quase o dobro — e ela não cobre erros de método.
MCMC (Markov chain Monte Carlo)
Método de simulação que sorteia, juntos e muitas vezes, todos os parâmetros do modelo (tendência, desvio de cada instituto, erro das pesquisas, velocidade de mudança do placar) de acordo com o quanto os dados os sustentam. Em vez de fixar cada parâmetro no seu valor mais provável, carrega a incerteza de todos até a previsão.
Mercados de previsão
Plataformas de apostas (como Polymarket e Kalshi) em que o preço de cada contrato reflete a chance que os apostadores dão a cada resultado. Reagem rápido a notícias — e a boatos.
Placar ajustado (nowcast)
Estimativa de como estaria a eleição se fosse hoje, combinando todas as pesquisas e corrigindo o desvio típico de cada instituto. É um retrato, não uma previsão para o dia da eleição.
Ponto de equilíbrio
O placar das pesquisas finais em que os dois teriam 50% de chance, já descontado o erro histórico das pesquisas. Hoje fica em +0,8 para Lula: um empate nas pesquisas corresponde a vantagem de Flávio.
Ponto percentual (p.p.)
Diferença direta entre dois percentuais: de 45% para 47% são 2 pontos percentuais — não 2%.
Rejeição
Percentual de eleitores que dizem que não votariam no candidato de jeito nenhum. Funciona como um teto: quem rejeita dificilmente muda de ideia.
Simulação (Monte Carlo)
O computador roda a eleição 400 mil vezes, sorteando em cada uma quanto o placar ainda pode mudar e quanto as pesquisas podem errar. A chance de vitória é a fração das simulações em que cada candidato vence.
Swing
Quanto a margem de um estado mudou em relação à eleição anterior: margem de hoje nas pesquisas menos a margem do 2º turno de 2022. Se todos os estados andam parecido, o swing médio diz quanto o país inteiro andou.
Tendência suavizada
Curva que atravessa a nuvem de pesquisas sem seguir cada sobe-e-desce, para separar o movimento real do ruído de cada pesquisa. No modelo v3 ela vem do suavizador de Kalman.
Terceira via
Candidatos que não são Lula nem Flávio no 1º turno (Cury, Caiado, Zema, Renan Santos…). Para onde vão seus eleitores no 2º turno ajuda a decidir a eleição.
Variância extra do instituto
Quanto as pesquisas de um instituto oscilam além do que a amostra explica. Estimada nos dados; institutos mais erráticos pesam menos.
Votos válidos
Votos dados a candidatos, sem contar brancos e nulos. É assim que o TSE decide a eleição: vence quem tiver mais da metade dos válidos.
Âncora
Conjunto de institutos com histórico presidencial verificado (AtlasIntel, Datafolha, Ipespe, CNT/MDA, Paraná Pesquisas, PoderData, Quaest) cuja média define o zero dos efeitos de casa. É o mesmo conjunto em que o erro histórico é medido.
Conformidade · nota ao leitor

Transparência e conformidade eleitoral

Fundamentos: aprovação, economia e sentimento

A aprovação do governo é o mediador central do modelo (Lavareda: "aprovação ≈ voto no 2º turno"; Eurasia: incumbentes com ~45% vencem ~3/4 das vezes). A série mostra recuperação de fev/2025 (24% ótimo/bom, pior dos três mandatos) até jul/2026 e deterioração de 3–4 p.p. desde então — coincidente com a queda da vantagem no 2º turno.
Datafolha (campo)Ótimo/bomRegularRuim/péssimoAprovaDesaprova
dez/2024352934——
10–11 fev/2025243241——
set/202533—3848—
2–4 dez/202532—374948
mai–jun/20263228384948
22–23 jul/20263228384948
18–20 ago/20263325414750
1–2 set/20263125424551
Genial/Quaest (mês)jan/25mar/25mai/25jul/25set/25out/25jan/26abr/26mai/26jun/26jul/26ago/261 set/266 set/26
Aprova47414043464847434647484645—
Desaprova——575351494952494847484850
Outros institutosCampoAprovaDesaprovaÓt-bom / reg / ruim-pésObservação
AtlasIntel/Bloomberg10–15 dez/25 → jun/26 → 22–27 jul → 25–30 ago48,8 → 46 → 47,6 → 45,450,7 → 52 → 51,2 → 52,937,0 / 11,9 / 51,1 (ago)Desaprovação: evangélicos 76%, jovens 16–24 77%, Sul 68%. Aprovação: NE 55–67%, 60+ 63%, católicos 55%
PoderData/Ayamai → jun → jul → set/2644 → 43 → 42 → 4250 → 52 → 51 → 5134 / 15 / 48 (set)Série mais baixa para Lula, coerente com o efeito de casa de -2,4 no 2º turno
Ipsos-Ipecset/25 → dez/25 → mar/26 → jun/2644 → 42 → 43 → 4451 → 52 → 51 → 5032 / 28 / 38 (jun)Confiança em Lula 40% / desconfiança 56% (mar)
BTG/Nexus · RTBD · CNT/MDAago/26 · ago/26 · nov/2548 · 45 · 48,149 · 51 · 48,835/—/43 · 27/33/39 · 34/29/36—
Indicador econômicoÚltimo dadoLeitura eleitoral
IPCAjul/26: +0,07% m/m; 4,44% em 12 m (abaixo do teto após 2 meses); alimentação no domicílio −1,14%. IPCA-15 ago: −0,40% (4,24% em 12 m); alimentos −0,57%; energia −6,25% (bônus Itaipu)Deflação de alimentos jun–ago ainda não convertida em percepção (49% dizem que a economia piorou). Alimentos acumulavam 7,4% em ago/25
Desemprego (PNAD)mai–jul/26: 5,3% (menor da série); ocupados 103,3 mi; CLT privado 39,4 mi (recordes); informalidade 37,5%Ativo do incumbente — mas Nunes: 64% dizem que a renda subiu e só 10% que superou o custo de vida
Rendimento realR$ 3.762; +3,3% a/a; −0,7% no trimestre; massa de renda R$ 383,5 bi (+4,1%)Ganho real modesto e desacelerando
Selic15% → cortes de 0,25 desde mar/26 → 14,00% (5/ago); Copom 15–16/set; Focus 13,75% fim-2026Ciclo de corte lento; efeito sobre crédito das famílias ainda pequeno
PIB2025: +2,3%. 2T26: +0,5% t/t, +2,0% a/a; consumo das famílias −0,4% t/t; Focus 2026: 1,92% (em queda)Desaceleração do consumo no 2º semestre é o principal risco econômico para o incumbente
Confiança do consumidor (FGV)ago/26: 84,7 (−3,6), menor desde nov/2022; 4ª queda seguida; expectativas 86,1Famílias endividadas e inadimplentes (FGV) — o "sentido" descola do "medido"
Mercado / câmbioUSD/BRL R$ 5,09 (8/set); Ibovespa ≈188 mil; saída de R$ 15,6 bi de estrangeiros da B3 em ago; JPMorgan rebaixou Brasil a neutro (11/ago)"Trade eleitoral": bolsa sobe e real se valoriza quando Flávio avança — Eurasia e Morgan Stanley leem como subestimação de risco
FiscalDívida bruta 81,9% do PIB (jun/26); déficit primário 1,19%; nominal 9,99% (Morgan Stanley)Morgan Stanley: o decisivo é a credibilidade fiscal pós-2026, não o vencedor
SentimentoQuaest ago: piorou 49 / igual 30 / melhorou 19; próximos 12 m: melhora 42. Datafolha jun: piorou 45 / melhorou 22; pessimistas 26% (eram 35 em mar). Problema nº 1: violência 33%, economia 15%, saúde 14%, corrupção 14%Segurança supera economia como preocupação — terreno de Flávio (Garman: Lula tem "baixa credibilidade em segurança e corrupção")

Mercados de previsão e casas de análise

Os mercados precificaram Lula como favorito (56–59%) até início de setembro, mas viraram para Flávio a partir de 8–10/set e seguiram caindo para Lula até 15/set (45%), dia da sessão do STF sobre o relatório da PF com as interações Moraes–Vorcaro — em paralelo à disputa pelo comando da PF e à divulgação do inquérito sobre Flávio no caso Dark Horse. Nenhum banco publicou probabilidade após a virada; Eurasia é a única consultoria com número pré-crise (≈60%) e já alertava que "o mercado subestima Flávio". O ensemble do painel usa 20% (mercados, 25/out, 8h) e 53% (fundamentos), com pesos de 25% e 0%.
FonteDataP(Lula)P(Flávio)Observação
Polymarket17/set/2645%55%Estável desde a sessão do STF de 15/set; volume US$ 150,7 mi; P(Lula) normalizada ≈ 45%
Polymarket16/set/2645%54%Depois da sessão do STF terminar sem decisão de mérito (pedido de vista de Dino); volume US$ 150,6 mi; P(Lula) normalizada ≈ 45%
Polymarket15/set/2645%52%Dia da sessão extraordinária do STF sobre o relatório Moraes–Vorcaro; volume acumulado US$ 150,1 mi; P(Lula) normalizada ≈ 46%
Polymarket13/set/2649%50,2%Virada em relação a 8/set (Lula 59%); volume acumulado US$ 149,4 mi desde a abertura do mercado (set/2025); reprecificação atribuída à crise do STF/comando da PF e à quebra parcial de sigilo do Master (9–10/set)
Kalshi10/set/26≈49%≈51%Manchete do mercado: "Flávio Bolsonaro Hits 51% as Supreme Court Clash Rattles Lula" — atribui a virada ao "judiciary standoff" e a cortes emergenciais de combustível. Antes de 8/set: Lula 56%; vencedor do 1º turno (leitura de 8/set): Lula 89%; 2º lugar: Flávio 88%; margem de Lula 0–5 pts 44%, 5–10 pts 38%
Polymarket/Kalshi (8/set, pré-crise)8/set/2659% · 56%39,9% · 44%Leitura anterior à crise do STF de 8–10/set, mantida aqui para comparação histórica; Predict.fun/Prediction Hunt média 57–58%
Eurasia Group (Garman)set/25 → abr/26 → jul/26 → ago/2650-50 (viés Lula) → 55% → 60% → ~60%~40%Mai/26: 30% de chance de outro nome da direita no 2º turno. Ago/26: "mercado subestima Flávio"; clientes que veem Lula favorito passaram de 47% para 80%
XP Researchjul/26"leve favoritismo" Lula—Eleição decidida por 3–4% de indecisos, 69% mulheres; abstenção crescente penaliza a coalizão de Lula
Morgan Stanley (126 p.)25/ago/26sem P—Cenários 2027: base PIB 0,8% / Selic 12,75 / USD 5,25; pessimista 0,2% / 15,5% / 6,00 (Ibov −25%); otimista 1,5% / 9,75% / 4,50; riscos inclinados ao pessimista
BTG Pactual · JPMorganmar–ago/26sem P—BTG: carteiras por cenário (Lula: SUZB3, ITUB4, WEGE3; Flávio: BBAS3, XP, PETR4). JPM rebaixou ações brasileiras a neutro
"Brasil em Mapas" (nowcast Monte Carlo, 15 pesquisas)7/set/2674%26%Central 51,5 × 48,5 válidos; sem correção de viés histórico — compare com os 27% do nosso modelo sem correção
Painel de 6 modelos de IA (OddsShopper)6/ago/2668%23,8%"1 em 3 de chance de idade, virada de campanha ou consolidação mudar o vencedor"
Este painel25/10/2026modelo 17% · ensemble 18%83% · 82%Único com correção explícita do viés 2006–22 e com tendência estimada (suavizador de Kalman); Itaú, Bradesco BBI, Genial, Warren, Goldman, Barclays, Economist e FT não publicaram base case com probabilidade

Eventos e riscos políticos de 2026

Choques discretos que o modelo trata como deriva (0,31 p.p./dia até 4/out; 0,95 entre os turnos) — aqui listados para leitura qualitativa dos cenários. Quem é o candidato do PL: Flávio Bolsonaro, senador pelo Rio de Janeiro desde 2019, filho mais velho de Jair Bolsonaro, ex-vereador e ex-deputado estadual (RJ); assumiu a candidatura após a inelegibilidade do pai.

Jair Bolsonaro — situação jurídica

  • Inelegível até 2030 (TSE, jun/2023); condenado pelo STF a 27 anos e 3 meses pela trama golpista (11/set/2025); preso em 22/nov/2025; prisão domiciliar humanitária desde 27/mar/2026, prorrogada por tempo indeterminado em 3/jul (tornozeleira, sem redes sociais, visitas autorizadas).
  • Ago/2026: visitas suspensas após Flávio divulgar carta do pai; restrição até 11/out (7 dias após o 1º turno). 7/set: Flávio liderou ato na Paulista ("Fora Lula, Fora Moraes"); Lula no desfile em Brasília com discurso de soberania.
  • Eduardo Bolsonaro inelegível por 12 anos (16/jun/2026). Michelle: "quero ser primeira-dama" (18% no 1º turno, 61% de rejeição — Quaest set/25). Quaest jan/26: 43% acham que um opositor sem o sobrenome venceria Lula com mais facilidade.

Relação Brasil–EUA

  • Jul/2025: tarifa de 50% e Lei Magnitsky sobre Moraes; dez/2025: após três encontros Lula–Trump, Moraes retirado da lista e sobretaxas parcialmente removidas.
  • 22–23/jul/2026: novas tarifas de 25% + 12,5% (trabalho forçado) = 37,5% sobre ~16,5% das exportações; isentos café, carne, aeronaves, suco de laranja, celulose. 21/ago: ligação Lula–Trump (1h20); 31/ago: negociação com USTR "sem avanço concreto". Lula usa o tema como "soberania" na campanha.

Escândalos e vulnerabilidades pessoais

  • INSS: fraude estimada em R$ 6,3 bi (2019–24); R$ 3 bi devolvidos; Lupi saiu (mai/25); CPMI encerrada sem relatório (pedia 200+ indiciamentos, incl. Lulinha). Caso Lulinha: 3 inquéritos da PF autorizados pelo STF (tráfico de influência).
  • Caso Master: áudio Flávio–Vorcaro pedindo recursos para cinebiografia (Intercept, mai/26) — Flávio caiu 6 p.p. na Atlas; Flávio judicializou e o TSE suspendeu a pesquisa. Quaest (1/set): 40% dizem que Lulinha prejudica muito Lula; 42% que Master prejudica muito Flávio; explicações não convencem 54% (Lula) e 52% (Flávio).
  • Crise do STF (ago–set/2026): em 24/ago, Mendonça manda a PF aprofundar a análise do celular de Vorcaro; o relatório (218 p.) aponta 187 interações entre Vorcaro e Alexandre de Moraes; a PGR pede anulação. Em 8/set, Mendonça afasta a cúpula da PF alegando monitoramento ilegal; Dino reintegra em 9/set; Fachin suspende as duas decisões e procedimentos que envolvam ministros, e convoca sessão extraordinária para 15/set. A sessão não julgou o mérito: o STF parou numa questão de ordem sobre unir as petições (4×3 pela separação; o voto de Dino empataria em 4×4) e Dino pediu vista, com até 90 dias para devolver — a validade do relatório e a eventual investigação sobre Moraes ficam indefinidas, atravessando o 1º turno. Em 16/set, Lula defendeu Moraes publicamente e ligou Flávio a Vorcaro ("os R$ 130 milhões" do filme); Flávio mantém o mote "Lula é Moraes e Moraes é Lula". Em 10/set, Mendonça torna públicos 15 inquéritos do caso Master, confirmando Flávio como investigado (inquérito autorizado em 22/jul: lavagem, evasão de divisas, corrupção ativa e passiva; US$ 12,3 mi pagos de um contrato de US$ 24 mi); lançamento do Dark Horse suspenso em 11/set. Lula pede quebra total do sigilo; Caiado protocola pedido similar; Flávio fala em "tapetão". Lulinha: inquérito da PF por tráfico de influência autorizado em 30/jul.
  • Idade/saúde de Lula (80): 50% dizem que não é problema, 43% que é (PoderData jul/26; melhora de 24 p.p. em um ano). Desenrola 2.0: aprovação caiu de 50% para 43% (Nunes).

Alianças, TV e calendário

  • 13 candidaturas. Lula (PT) + Alckmin (PSB), com PCdoB, PV, PSOL, PDT. MDB, PP, União, Republicanos, Podemos, PSDB e Cidadania neutros; palanques estaduais fragmentados (SP: coalizão ampla pró-Tarcísio). Kassab (13/ago): o centro prefere Lula, Flávio vai "desabar"; Caiado: "em nenhuma hipótese ao lado de Lula"; Tarcísio "dosa" apoio de olho em 2030. Centrão (276/513) sem compromisso com Flávio.
  • Tempo de TV (desde 28/ago): Lula 5'31" e 434 inserções; Flávio 4'20" e 339; Caiado 2'02"; Cury 35"; Zema e Renan sem tempo. Marçal (PRTB): TSE vetou fundo, TV e debates (20/ago); registro indeferido pelo TSE em 11/set; PRTB o substituiu por Leonardo Avalanche em 14/set.
  • Calendário: campanha desde 16/ago; propaganda gratuita 28/ago–1/out; debates Band 23/ago (Lula e Flávio ausentes), pool SBT/CNN de 14/set cancelado em 8/set (nenhum dos dois confirmou presença), Record 27/set, Globo 1/out; sessão do STF sobre Moraes–Vorcaro 15/set (suspensa por pedido de vista); Copom 15–16/set; 1º turno 4/out; 2º turno 25/out.
SegmentoLulaFlávioFonte
Evangélicos (~30% do eleitorado)29%62%Datafolha 18–20/ago (2º turno); Atlas: 76% desaprovam Lula; Quaest 1º turno 43 × 24
Católicos54%36%Datafolha ago; Quaest 1º turno 47 × 27
Mulheres · Homens51% · 43%38% · 48%Datafolha ago; PoderData set: mulheres 48 × 41; desaprovação masculina 50 → 57 (Quaest jul→set)
Nordeste · Sudeste · Sul · CO/N61 · 43 · 38 · 4230 · 46 · 50 · 49Datafolha ago. Quaest estadual (abr): Flávio +26 RS, +20 PR, +13 GO/RJ, +12 SP; Lula +34 PE, +33 BA, +28 CE, +7 PA, +3 MG (empate)
Renda até 2 SM · 2–5 SM · >5 SMmaioria · 37 · 39— · 51 · 54Datafolha ago; Atlas: aprovação 55,6% até R$ 2 mil
Independentes / sem partido38,2%26%AtlasIntel ago (Cury 19,2%); Roman: "só 10% são de fato indecisos — desinteressados, classe C, mais mulheres, buscam o novo"
Base sólida ("voto definitivo")29–33%24%Nunes/Quaest 7/set; indecisos querem outsiders (20%) ou moderados (17%)

Deriva histórica: pesquisas de setembro × resultado do 2º turno

Em quatro dos cinco últimos ciclos, o candidato anti-PT superou as simulações de setembro; a margem do líder encolheu de 5 a 16 pontos (ou o líder nem chegou ao 2º turno). Parte disso é reversão de lideranças infladas — o que pouco se aplica a um empate — e parte é erro sistemático das pesquisas, que o modelo trata separadamente (prior +0,8 ± 2,0).
CicloSimulação de 2º turno em setembroResultadoDeriva da margemPesquisas da véspera × resultado
2006Lula ≈ +20 (Datafolha set apontava vitória no 1º turno com 56% válidos)Lula 60,8 × 39,2 (+21,7)≈ +2Datafolha 61 × 39: erro +0,2
2010Dilma 55 × Serra 38 (Datafolha 21–22/set; +17)Dilma 56,1 × 43,9 (+12,1)−5Datafolha 56 × 44 (0) · Ibope 55 × 45 (−1)
2014Marina 50 × Dilma 40 (ago); Dilma 49 × Aécio 41 (30/set; +8)Dilma 51,6 × Aécio 48,4 (+3,3)−5 (e a líder de ago nem chegou ao 2º turno)Datafolha 52 × 48 (+0,7) · Ibope 53 × 47 (+2,2)
2018Haddad 45 × Bolsonaro 39 (Datafolha 26–28/set; +6 Haddad)Bolsonaro 55,1 × 44,9 (+10,3)−16Datafolha 45 × 55 (+0,3) · Ibope 46 × 54 (+2,3) · MDA (−3,3) · Vox 50 × 50 (+10,3)
2022Lula 54 × Bolsonaro 38 (Datafolha 20–22/set; +16); 26/set–1/out: +5,3 a +18,3Lula 50,9 × 49,1 (+1,8)−14Paraná +0,8 · MDA +2,2 · Datafolha +4 · Quaest/Atlas/PoderData/Ipespe +6 · Ipec +8 → erro médio +3,1
2026Lula -1,3 (nowcast 25/10/2026) · -2,0 há duas semanas?modelo: deriva N(0, 0,0)Break-even de Lula na véspera: +0,8

Calibração por instituto (2006 a 2022)

Erro na margem esquerda − direita em votos válidos (positivo = superestimou o PT), nas últimas pesquisas de cada instituto antes de cada turno, verificadas em fonte primária em 30/set/2026 (`calibracao_historica.csv`, 43 finais). É a base do peso de qualidade (4,0 ÷ MAE, limitado a 0,6–1,5, encolhido para 0,8 conforme o número de ciclos) e do desconto de erro sistemático. Institutos sem histórico presidencial verificável recebem 0,8. Veritá, Futura e Brasmarket ficam fora do conjunto de referência que define o desconto.

Resumo por instituto

InstitutoMAE (p.p.)Viés médioAtivo em 2026Peso de qualidade
Futura Inteligencia2,4-2,4sim0,85
Datafolha3,4+3,4sim1,32
Verita3,8-3,8sim0,76
Paraná Pesquisas3,8-2,1sim0,78
MDA3,9+1,5sim1,32
Quaest4,0+4,0sim1,15
Ibope4,5+4,5não—
PoderData4,5+4,5sim1,12
AtlasIntel4,5+4,5sim0,93
Ipespe4,9+2,0sim0,93
Ipec7,5+7,5não—
Vox Populi7,6+7,6não—
Brasmarket7,8-7,8não—

Leitura

O erro dos 2ºs turnos não tem direção fixa: pró-PT em 2014, 2022 e, por pouco, 2006; contra o PT em 2010 e 2018 (média por ciclo: 2006: +0,3, 2010: -0,1, 2014: +1,1, 2018: -1,2, 2022: +2,9). Nos 1ºs turnos de 2010 a 2022, ao contrário, o erro foi sempre contra o principal adversário do PT (2010: +5,2, 2014: +10,5, 2018: +2,4, 2022: +5,8). Por isso o desconto das rodadas finais de 2º turno é pequeno (+0,9 ± 2,0) e o do 1º turno é maior (+3,4 ± 3,8); erro observado das pesquisas no 1º turno, nas ondas de antes de 4/out, e rodadas finais de 2º turno de 2006–2022 mais 25% desse erro, nas ondas novas. Em 2026, com a base completa (221 ondas) e a âncora nos institutos calibrados, os efeitos de casa estatisticamente distintos de zero são: medem Lula melhor que a âncora CNT/MDA (+3,9); medem Flávio melhor Gerp (-7,7), Veritá (-5,3), Palver (-4,4), Futura (-3,8), PoderData (-2,4), Paraná Pesquisas (-2,0).

InstitutoEleiçãoÚltima pesquisa (esq × dir, válidos)ResultadoErro da margemFonte · verificação
AtlasIntel2022 · 2º turno53,4 × 46,650,9 × 49,1+5,0fonte · sim
Brasmarket (fora da referência)2022 · 2º turno47 × 5350,9 × 49,1-7,8fonte · sim
Datafolha2022 · 2º turno52 × 4850,9 × 49,1+2,2fonte · sim
Futura Inteligencia (fora da referência)2022 · 2º turno49,7 × 50,350,9 × 49,1-2,4fonte · sim
Ipec2022 · 2º turno54 × 4650,9 × 49,1+6,2fonte · sim
Ipespe2022 · 2º turno53 × 4750,9 × 49,1+4,2fonte · sim
CNT/MDA2022 · 2º turno51,1 × 48,950,9 × 49,1+0,4fonte · sim
Paraná Pesquisas2022 · 2º turno50,4 × 49,650,9 × 49,1-1,0fonte · sim
PoderData2022 · 2º turno53 × 4750,9 × 49,1+4,2fonte · sim
Quaest2022 · 2º turno52 × 4850,9 × 49,1+2,2fonte · sim
Verita (fora da referência)2022 · 2º turno49 × 5150,9 × 49,1-3,8fonte · sim
AtlasIntel2022 · 1º turno50,3 × 41,148,4 × 43,2+4,0fonte · sim
Datafolha2022 · 1º turno50 × 3648,4 × 43,2+8,8fonte · sim
Ipec2022 · 1º turno51 × 3748,4 × 43,2+8,8fonte · sim
Ipespe2022 · 1º turno49 × 3548,4 × 43,2+8,8fonte · sim
CNT/MDA2022 · 1º turno48,3 × 39,748,4 × 43,2+3,4fonte · sim
Paraná Pesquisas2022 · 1º turno47,1 × 4048,4 × 43,2+1,9fonte · sim
PoderData2022 · 1º turno48 × 3848,4 × 43,2+4,8fonte · sim
Quaest2022 · 1º turno49 × 3848,4 × 43,2+5,8fonte · sim
Datafolha2018 · 2º turno45 × 5544,9 × 55,1+0,3fonte · sim
Ibope2018 · 2º turno46 × 5444,9 × 55,1+2,3fonte · sim
Ipespe2018 · 2º turno42 × 5844,9 × 55,1-5,7fonte · sim
CNT/MDA2018 · 2º turno43,2 × 56,844,9 × 55,1-3,3fonte · resumo de busca
Paraná Pesquisas2018 · 2º turno39,4 × 60,644,9 × 55,1-10,9fonte · sim
Vox Populi2018 · 2º turno50 × 5044,9 × 55,1+10,3fonte · sim
Datafolha2018 · 1º turno25 × 4029,3 × 46,0+1,8fonte · sim
Ibope2018 · 1º turno25 × 4129,3 × 46,0+0,8fonte · sim
Ipespe2018 · 1º turno25 × 4129,3 × 46,0+0,8fonte · sim
CNT/MDA2018 · 1º turno27,8 × 42,629,3 × 46,0+1,9fonte · resumo de busca
Paraná Pesquisas2018 · 1º turno25,6 × 40,929,3 × 46,0+1,4fonte · resumo de busca
Vox Populi2018 · 1º turno31 × 4029,3 × 46,0+7,8fonte · sim
Datafolha2014 · 2º turno52 × 4851,6 × 48,4+0,7fonte · sim
Ibope2014 · 2º turno53 × 4751,6 × 48,4+2,7fonte · sim
CNT/MDA2014 · 2º turno49,7 × 50,351,6 × 48,4-3,9fonte · sim
Vox Populi2014 · 2º turno54 × 4651,6 × 48,4+4,7fonte · sim
Datafolha2014 · 1º turno44 × 2641,6 × 33,5+10,0fonte · sim
Ibope2014 · 1º turno46 × 2741,6 × 33,5+11,0fonte · sim
CNT/MDA2014 · 1º turno45,6 × 2741,6 × 33,5+10,6fonte · sim; válidos calculados dos totais 40.6/24.0 com 11% de brancos+indecisos
Datafolha2010 · 2º turno56 × 4456,0 × 44,0-0,1fonte · wikipedia
Datafolha2010 · 1º turno50 × 3146,9 × 32,6+4,7fonte · sim; 2ª fonte: en.wikipedia Opinion polling for the 2010 Brazilian presidential election
Ibope2010 · 1º turno51 × 3146,9 × 32,6+5,7fonte · sim; 2ª fonte: en.wikipedia Opinion polling for the 2010 Brazilian presidential election
Datafolha2006 · 2º turno61 × 3960,8 × 39,2+0,3fonte · wikipedia
Datafolha (fora da referência)2006 · 1º turno50 × 3848,6 × 41,6+5,0fonte · fonte única (a Wikipédia traz 48 x 37 sem dizer se são válidos); fora do cálculo

Apêndice A — base completa de pesquisas (2º turno, Lula × Flávio)

221 ondas de 22 institutos, de dez/2025 a 25/10/2026, com margem em válidos e peso final (recência × precisão) na data-base. "≈" marca data de campo aproximada ao mês (fonte sem data exata). Branco/nulo e indecisos só quando publicados para o cenário de 2º turno. "Contratante" e "Margem de erro" (amostral, 95% de confiança, p=0,5) são os dados de transparência exigidos pelo TSE para cada pesquisa; o número de registro oficial no PesqEle está na fonte linkada de cada linha — ver seção "Conformidade" para o detalhe.
InstitutoContratanteCampo (fim)nMétodoMargem de erro (95%)LulaFlávioBr/nuloIndec.Margem válidaPesoFonte
Futura Inteligencia v2Apex/100% Cidades24/10/262.000telefônico±2,2 p.p.47,948,22,31,6-0,30,96fonte
PoderDataAya24/10/264.000telefônico±1,5 p.p.47,049,03,01,0-2,11,73fonte
BTG/NexusBTG Pactual24/10/262.000telefônico±2,2 p.p.46,047,05,21,8-1,10,93fonte
QuaestGlobo/O Globo24/10/263.702presencial±1,6 p.p.45,046,08,01,0-1,11,56fonte
DatafolhaFolha/Globo24/10/264.050presencial±1,5 p.p.47,048,03,02,0-1,11,73fonte
Palver v3próprio24/10/265.000online±1,4 p.p.49,050,01,00,0-1,00,89fonte
Gerppróprio24/10/262.400telefônico±2,0 p.p.46,049,01,03,0-3,21,12fonte
ColecttaBrazil Economy23/10/263.000presencial±1,8 p.p.46,546,04,43,1+0,51,28fonte
Veritapróprio23/10/2615.554telefônico±0,8 p.p.46,549,41,42,7-3,11,40fonte
AtlasIntelBloomberg23/10/264.990online±1,4 p.p.48,449,91,7—-1,51,15fonte
MDA v2CNT23/10/262.032presencial±2,2 p.p.45,046,35,82,9-1,40,90fonte
Instituto Francapróprio23/10/261.537não informado±2,5 p.p.43,145,67,43,9-2,80,68fonte
Alfa Inteligenciapróprio22/10/262.700presencial±1,9 p.p.46,049,92,02,1-4,11,06fonte
DatafolhaFolha/Globo22/10/262.520presencial±2,0 p.p.46,047,05,02,0-1,11,07fonte
Vox Brasilpróprio22/10/262.100presencial±2,1 p.p.45,444,36,04,3+1,20,73fonte
Real Time Big Datapróprio22/10/262.000telefônico±2,2 p.p.45,049,05,01,0-4,30,88fonte
Veritapróprio22/10/2615.554telefônico±0,8 p.p.47,550,21,40,9-2,81,37fonte
IpespeAbrapel22/10/261.500telefônico±2,5 p.p.47,048,03,02,0-1,10,69fonte
DataTrendspróprio21/10/262.000telefônico±2,2 p.p.46,050,04,01,0-4,20,87fonte
IndexaBroadcast/Estadão21/10/262.000telefônico±2,2 p.p.44,145,29,11,6-1,20,82fonte
PoderDataAya21/10/264.000telefônico±1,5 p.p.48,049,03,01,0-1,01,58fonte
Gerppróprio21/10/262.400telefônico±2,0 p.p.45,050,02,02,0-5,31,01fonte
Futura Inteligencia v2Apex/100% Cidades20/10/262.000telefônico±2,2 p.p.46,848,92,02,3-2,20,84fonte
QuaestGlobo/O Globo20/10/262.154presencial±2,1 p.p.44,045,09,02,0-1,10,84fonte
Palver v3próprio20/10/265.000online±1,4 p.p.48,050,01,00,0-2,00,77fonte
Instituto Francapróprio20/10/263.000telefônico±1,8 p.p.39,641,010,68,8-1,61,03fonte
Alfa Inteligenciapróprio19/10/262.700presencial±1,9 p.p.45,948,83,51,8-3,10,95fonte
Real Time Big Datapróprio19/10/262.000telefônico±2,2 p.p.45,048,05,02,0-3,20,79fonte
Gerppróprio19/10/262.400telefônico±2,0 p.p.45,050,02,03,0-5,30,95fonte
Meio/IdeiaCanal Meio19/10/261.500telefônico±2,5 p.p.46,048,03,42,6-2,10,62fonte
Veritapróprio19/10/2615.554telefônico±0,8 p.p.48,249,71,40,7-1,51,24fonte
AtlasIntelBloomberg18/10/264.998online±1,4 p.p.47,649,82,6—-2,30,97fonte
PoderDataAya18/10/263.000telefônico±1,8 p.p.46,049,04,02,0-3,21,11fonte
BTG/NexusBTG Pactual18/10/262.002telefônico±2,2 p.p.45,946,54,72,9-0,60,76fonte
DataTrendspróprio17/10/262.000telefônico±2,2 p.p.46,049,04,01,0-3,20,76fonte
Futura InteligenciaApex/100% Cidades17/10/262.000telefônico±2,2 p.p.47,548,32,41,9-0,80,76fonte
Vox Brasilpróprio17/10/262.100presencial±2,1 p.p.46,444,65,03,9+2,00,63fonte
Veritapróprio16/10/2615.554telefônico±0,8 p.p.47,749,71,51,1-2,11,12fonte
Gerppróprio16/10/262.400telefônico±2,0 p.p.45,050,02,03,0-5,30,86fonte
MDA v2CNT15/10/262.014presencial±2,2 p.p.45,946,84,92,4-1,00,70fonte
PoderDataAya15/10/263.000telefônico±1,8 p.p.47,047,04,02,00,01,00fonte
Futura InteligenciaApex/100% Cidades15/10/262.000telefônico±2,2 p.p.47,549,01,52,1-1,60,72fonte
IpespeAbrapel15/10/261.500telefônico±2,5 p.p.46,048,03,03,0-2,10,54fonte
IndexaBroadcast/Estadão14/10/262.000telefônico±2,2 p.p.42,044,012,02,0-2,30,63fonte
DatafolhaFolha/Globo14/10/262.520presencial±2,0 p.p.45,047,06,02,0-2,20,82fonte
Palver v3próprio13/10/265.000online±1,4 p.p.49,050,01,00,0-1,00,62fonte
QuaestGlobo/O Globo13/10/262.154presencial±2,1 p.p.44,046,09,01,0-2,20,67fonte
Gerppróprio12/10/262.400telefônico±2,0 p.p.44,053,01,03,0-9,30,77fonte
PoderDataAya12/10/263.000telefônico±1,8 p.p.46,048,05,02,0-2,10,90fonte
BTG/NexusBTG Pactual12/10/262.002telefônico±2,2 p.p.45,048,05,02,0-3,20,63fonte
Futura InteligenciaApex/100% Cidades10/10/262.000telefônico±2,2 p.p.46,650,01,71,7-3,50,61fonte
DataTrendspróprio09/10/262.000telefônico±2,2 p.p.44,050,05,01,0-6,40,58fonte
Ipespepróprio08/10/261.500telefônico±2,5 p.p.44,049,05,03,0-5,40,42fonte
AtlasIntelBloomberg08/10/265.026online±1,4 p.p.45,751,13,2—-5,60,70fonte
PoderDataAya07/10/263.000telefônico±1,8 p.p.44,049,05,02,0-5,40,76fonte
DatafolhaFolha/Globo07/10/262.520presencial±2,0 p.p.45,049,05,01,0-4,30,66fonte
Vox Brasilpróprio07/10/262.100presencial±2,1 p.p.44,242,73,49,7+1,70,43fonte
Futura InteligenciaApex/100% Cidades03/10/262.000telefônico±2,2 p.p.45,148,05,71,2-3,10,47fonte
QuaestGlobo/O Globo03/10/263.702presencial±1,6 p.p.42,044,013,01,0-2,30,74fonte
DatafolhaFolha/Globo03/10/264.006presencial±1,5 p.p.47,046,06,02,0+1,10,84fonte
Palverpróprio03/10/265.000online±1,4 p.p.44,049,06,01,0-5,40,40fonte
AtlasIntelpróprio02/10/264.945online±1,4 p.p.47,647,45,0—+0,20,56fonte
Instituto Francapróprio02/10/262.000não informado±2,2 p.p.45,945,03,55,6+1,00,44fonte
MDACNT02/10/262.007presencial±2,2 p.p.47,343,17,61,9+4,60,44fonte
PoderDataAya02/10/264.000telefônico±1,5 p.p.46,046,07,01,00,00,81fonte
Gerppróprio02/10/262.400telefônico±2,0 p.p.44,049,06,01,0-5,40,53fonte
DatafolhaFolha/Globo01/10/262.506presencial±2,0 p.p.48,045,06,01,0+3,20,53fonte
Vox Brasilpróprio01/10/262.100não informado±2,1 p.p.45,248,22,54,1-3,20,38fonte
Real Time Big DataCNN30/09/262.000telefônico±2,2 p.p.45,046,05,04,0-1,10,41fonte
DataTrendspróprio30/09/262.000telefônico±2,2 p.p.45,045,07,03,00,00,41fonte
Futura InteligenciaApex/100% Cidades29/09/262.000telefônico±2,2 p.p.43,549,06,21,3-5,90,41fonte
IndexaBroadcast/Estadão29/09/262.000telefônico±2,2 p.p.43,042,07,04,0+1,20,38fonte
Vox Brasilpróprio28/09/262.100presencial±2,1 p.p.44,745,26,83,3-0,60,33fonte
Gerppróprio28/09/262.400telefônico±2,0 p.p.43,050,05,01,0-7,50,47fonte
Meio/IdeiaCanal Meio28/09/262.000telefônico±2,2 p.p.48,548,02,11,5+0,50,41fonte
AtlasIntelBloomberg28/09/265.005online±1,4 p.p.47,647,74,8—-0,10,49fonte
Palverpróprio27/09/265.000online±1,4 p.p.45,047,07,01,0-2,20,31fonte
BTG/NexusBTG27/09/262.000telefônico±2,2 p.p.46,044,08,01,0+2,20,37fonte
QuaestGlobo/O Globo27/09/262.004presencial±2,2 p.p.42,042,013,03,00,00,35fonte
PoderDataAya23/09/263.000telefônico±1,8 p.p.45,046,0——-1,10,47fonte
Real Time Big Data—23/09/262.000telefônico±2,2 p.p.44,045,08,03,0-1,10,32fonte
DatafolhaFolha/Globo23/09/262.002presencial±2,2 p.p.47,045,07,01,0+2,20,33fonte
Futura InteligenciaApex23/09/262.000telefônico±2,2 p.p.43,749,45,61,3-6,10,34fonte
Palverpróprio23/09/265.000online±1,4 p.p.45,048,06,01,0-3,20,27fonte
Alfa InteligenciaTMC23/09/262.700presencial±1,9 p.p.43,043,07,07,00,00,37fonte
AtlasIntelBloomberg22/09/265.015online±1,4 p.p.47,747,44,9—+0,30,40fonte
American AnalyticsTimes Brasil/CNBC20/09/262.000não informado±2,2 p.p.43,043,06,08,00,00,28fonte
QuaestGlobo/O Globo20/09/262.004presencial±2,2 p.p.41,042,014,03,0-1,20,28fonte
BTG/NexusBTG20/09/262.006telefônico±2,2 p.p.46,045,07,01,0+1,10,30fonte
Palverpróprio18/09/265.000online±1,4 p.p.43,047,010,0—-4,40,18fonte
PoderDataAya16/09/263.000telefônico±1,8 p.p.44,046,08,02,0-2,20,37fonte
Gerppróprio16/09/262.400telefônico±2,0 p.p.43,050,06,01,0-7,50,32fonte
DatafolhaFolha/Globo16/09/262.002presencial±2,2 p.p.46,044,08,01,0+2,20,26fonte
AtlasIntelBloomberg16/09/265.018online±1,4 p.p.46,847,26,0—-0,40,33fonte
Futura InteligenciaApex15/09/262.000telefônico±2,2 p.p.43,748,16,51,7-4,80,26fonte
DataTrendspróprio14/09/262.000telefônico±2,2 p.p.44,045,08,03,0-1,10,24fonte
QuaestGlobo/O Globo13/09/262.004presencial±2,2 p.p.40,042,013,05,0-2,40,22fonte
MDACNT13/09/262.002presencial±2,2 p.p.47,340,010,22,5+8,40,23fonte
IndexaBroadcast/Estadão13/09/262.000telefônico±2,2 p.p.43,042,0——+1,20,22fonte
BTG/NexusBTG13/09/262.003telefônico±2,2 p.p.47,046,07,0—+1,10,24fonte
DatafolhaFolha/Globo10/09/262.002presencial±2,2 p.p.46,044,0——+2,20,21fonte
Futura InteligenciaApex10/09/262.000telefônico±2,2 p.p.45,045,47,12,5-0,40,21fonte
PoderDataAya09/09/263.000telefônico±1,8 p.p.45,047,0——-2,20,30fonte
AtlasIntelBloomberg09/09/265.000online±1,4 p.p.46,246,47,4—-0,20,26fonte
Gerppróprio08/09/262.400telefônico±2,0 p.p.40,047,011,03,0-8,00,23fonte
Palverpróprio07/09/265.000online±1,4 p.p.44,046,0——-2,20,15fonte
BTG/NexusBTG07/09/262.002telefônico±2,2 p.p.45,046,0——-1,10,19fonte
Meio/IdeiaCanal Meio07/09/261.500telefônico±2,5 p.p.46,046,03,54,50,00,15fonte
QuaestGenial06/09/262.004presencial±2,2 p.p.41,041,0——0,00,17fonte
Veritapróprio04/09/263.804não informado±1,6 p.p.43,148,5——-5,90,23fonte
PoderDataAya02/09/263.000telefônico±1,8 p.p.44,045,0——-1,10,23fonte
DatafolhaFolha/Globo02/09/262.002presencial±2,2 p.p.46,044,09,02,0+2,20,16fonte
QuaestGenial01/09/262.004presencial±2,2 p.p.42,041,013,04,0+1,20,15fonte
Futura InteligenciaApex01/09/262.000telefônico±2,2 p.p.45,645,27,22,0+0,40,16fonte
Real Time Big DataCNN31/08/262.000telefônico±2,2 p.p.44,044,09,03,00,00,15fonte
BTG/NexusBTG30/08/262.005telefônico±2,2 p.p.46,045,0——+1,10,15fonte
AtlasIntelBloomberg30/08/265.014online±1,4 p.p.47,142,610,3—+5,00,18fonte
Vox Brasilpróprio27/08/262.100presencial±2,1 p.p.44,545,1——-0,70,12fonte
PoderDataAya26/08/262.400telefônico±2,0 p.p.45,044,0——+1,10,15fonte
Gerppróprio25/08/262.400telefônico±2,0 p.p.42,047,0——-5,60,15fonte
BTG/NexusBTG23/08/262.006telefônico±2,2 p.p.46,045,0——+1,10,12fonte
IndexaBroadcast/Estadão23/08/262.000telefônico±2,2 p.p.46,041,0——+5,70,11fonte
DatafolhaFolha/Globo20/08/262.058presencial±2,2 p.p.47,043,09,02,0+4,40,11fonte
Veritapróprio20/08/263.840telefônico±1,6 p.p.42,047,36,14,6-5,90,14fonte
BTG/NexusBTG16/08/262.003telefônico±2,2 p.p.47,044,0——+3,30,09fonte
QuaestGenial13/08/262.004presencial±2,2 p.p.43,040,0——+3,60,08fonte
PoderDataAya12/08/262.400telefônico±2,0 p.p.46,045,0——+1,10,10fonte
Gerppróprio10/08/262.400telefônico±2,0 p.p.43,045,0——-2,30,09fonte
MDACNT09/08/262.002presencial±2,2 p.p.48,039,110,62,3+10,20,07fonte
BTG/NexusBTG09/08/262.001telefônico±2,2 p.p.47,044,0——+3,30,07fonte
Palverpróprio07/08/265.000online±1,4 p.p.46,046,0——0,00,04fonte
Futura InteligenciaApex07/08/262.000telefônico±2,2 p.p.46,544,0——+2,80,07fonte
Meio/IdeiaCanal Meio03/08/261.500telefônico±2,5 p.p.48,543,0——+6,00,05fonte
QuaestGenial03/08/262.004presencial±2,2 p.p.44,039,013,04,0+6,00,06fonte
BTG/NexusBTG02/08/262.002telefônico±2,2 p.p.46,045,0——+1,10,06fonte
Alfa InteligenciaTMC28/07/262.700não informado±1,9 p.p.48,041,0——+7,90,06fonte
PoderDataAya28/07/262.400telefônico±2,0 p.p.46,043,0——+3,40,06fonte
Vox Brasilpróprio28/07/262.100não informado±2,1 p.p.47,541,1——+7,20,04fonte
AtlasIntelBloomberg27/07/265.021online±1,4 p.p.49,242,97,9—+6,80,06fonte
BTG/NexusBTG26/07/262.004telefônico±2,2 p.p.47,043,0——+4,40,05fonte
DatafolhaFolha/Globo24/07/262.004presencial±2,2 p.p.48,043,0——+5,50,04fonte
Real Time Big DataCNN20/07/262.000telefônico±2,2 p.p.45,042,0——+3,40,04fonte
Indexapróprio19/07/262.000telefônico±2,2 p.p.46,039,0——+8,20,04fonte
Gerppróprio17/07/262.000não informado±2,2 p.p.45,046,0——-1,10,04fonte
PoderDataAya15/07/262.400telefônico±2,0 p.p.45,043,0——+2,30,04fonte
QuaestGenial13/07/262.004presencial±2,2 p.p.45,037,0——+9,80,03fonte
BTG/NexusBTG12/07/262.003telefônico±2,2 p.p.47,044,0——+3,30,03fonte
Futura InteligenciaApex11/07/262.000não informado±2,2 p.p.46,346,1——+0,20,03fonte
Gerppróprio07/07/262.000não informado±2,2 p.p.42,045,0——-3,40,02fonte
Meio/IdeiaCanal Meio06/07/262.000telefônico±2,2 p.p.45,040,0——+5,90,02fonte
AtlasIntelBloomberg30/06/264.999online±1,4 p.p.48,842,3——+7,10,02fonte
BTG/NexusBTG28/06/262.009telefônico±2,2 p.p.47,044,0——+3,30,02fonte
Vox Brasilpróprio25/06/262.100não informado±2,1 p.p.45,342,8——+2,80,01fonte
PoderDataAya24/06/262.400telefônico±2,0 p.p.46,043,0——+3,40,02fonte
Indexapróprio20/06/262.000telefônico±2,2 p.p.47,040,09,04,0+8,00,01fonte
Gerppróprio20/06/262.000não informado±2,2 p.p.40,042,0——-2,40,01fonte
DatafolhaFolha/Globo19/06/262.004presencial±2,2 p.p.47,043,0——+4,40,01fonte
American Analyticspróprio15/06/262.000não informado±2,2 p.p.45,040,0——+5,90,01fonte
MDACNT14/06/262.002presencial±2,2 p.p.49,336,8——+14,50,01fonte
BTG/NexusBTG14/06/262.017telefônico±2,2 p.p.49,043,0——+6,50,01fonte
Futura InteligenciaApex12/06/262.000não informado±2,2 p.p.48,142,9——+5,70,01fonte
Alfa Inteligenciapróprio10/06/261.400não informado±2,6 p.p.44,041,0——+3,50,01fonte
QuaestGenial08/06/262.004presencial±2,2 p.p.44,038,0——+7,30,01fonte
Vox Brasilpróprio06/06/262.100não informado±2,1 p.p.47,841,3——+7,30,01fonte
Gerppróprio05/06/262.000não informado±2,2 p.p.39,144,7——-6,70,01fonte
Real Time Big DataCNN30/05/262.000telefônico±2,2 p.p.45,040,0——+5,90,01fonte
PoderDataAya28/05/262.400telefônico±2,0 p.p.46,042,0——+4,50,01fonte
Meio/IdeiaCanal Meio27/05/261.500telefônico±2,5 p.p.46,541,4——+5,80,01fonte
Veritapróprio26/05/263.330não informado±1,7 p.p.46,853,2——-6,40,01fonte
Indexapróprio24/05/262.000telefônico±2,2 p.p.46,041,0——+5,70,01fonte
BTG/NexusBTG24/05/262.045telefônico±2,2 p.p.47,043,0——+4,40,01fonte
Gerppróprio21/05/262.000não informado±2,2 p.p.44,047,0——-3,30,01fonte
DatafolhaFolha/Globo21/05/262.004presencial±2,2 p.p.47,043,0——+4,40,01fonte
Futura InteligenciaApex20/05/262.000não informado±2,2 p.p.47,742,2——+6,10,01fonte
Vox Brasilpróprio19/05/262.100não informado±2,1 p.p.46,838,1——+10,20,00fonte
AtlasIntelBloomberg18/05/265.032online±1,4 p.p.48,941,8——+7,80,01fonte
Vetorpróprio18/05/269.000não informado±1,0 p.p.50,738,3——+13,90,00fonte
DatafolhaFolha/Globo14/05/262.004presencial±2,2 p.p.45,045,0——0,00,00fonte
Vox Brasilpróprio12/05/262.100não informado±2,1 p.p.40,243,8——-4,30,00fonte
Gerppróprio12/05/262.000não informado±2,2 p.p.43,050,0——-7,50,00fonte
QuaestGenial11/05/262.004presencial±2,2 p.p.42,041,0——+1,20,00fonte
Futura InteligenciaApex08/05/262.000não informado±2,2 p.p.44,446,9——-2,70,00fonte
Meio/IdeiaCanal Meio05/05/261.500telefônico±2,5 p.p.44,745,3——-0,70,00fonte
Real Time Big DataCNN04/05/262.000telefônico±2,2 p.p.43,044,0——-1,10,00fonte
AtlasIntelBloomberg27/04/265.008online±1,4 p.p.47,547,8——-0,30,00fonte
BTG/NexusBTG26/04/262.028telefônico±2,2 p.p.46,045,0——+1,10,00fonte
QuaestGenial13/04/262.004presencial±2,2 p.p.40,042,0——-2,40,00fonte
MDACNT12/04/262.002presencial±2,2 p.p.44,940,2——+5,50,00fonte
Futura InteligenciaApex11/04/262.000não informado±2,2 p.p.42,648,0——-6,00,00fonte
DatafolhaFolha/Globo09/04/262.004presencial±2,2 p.p.45,046,0——-1,10,00fonte
Meio/IdeiaCanal Meio07/04/261.500telefônico±2,5 p.p.45,545,8——-0,30,00fonte
Veritapróprio04/04/2640.500não informado±0,5 p.p.38,442,8——-5,40,00fonte
BTG/NexusBTG29/03/262.000telefônico±2,2 p.p.46,046,0——0,00,00fonte
Paraná Pesquisaspróprio28/03/262.080presencial±2,1 p.p.44,145,26,24,5-1,20,00fonte
Gerppróprio25/03/262.000não informado±2,2 p.p.45,048,0——-3,20,00fonte
AtlasIntelBloomberg23/03/265.028online±1,4 p.p.46,647,6——-1,10,00fonte
Alfa Inteligenciapróprio11/03/261.000não informado±3,1 p.p.41,038,0——+3,80,00fonte
Meio/IdeiaCanal Meio10/03/261.500telefônico±2,5 p.p.47,045,0——+2,20,00fonte
QuaestGenial09/03/262.004presencial±2,2 p.p.41,041,0——0,00,00fonte
Futura InteligenciaApex06/03/262.000não informado±2,2 p.p.40,548,8——-9,30,00fonte
DatafolhaFolha/Globo05/03/262.004presencial±2,2 p.p.46,043,0——+3,40,00fonte
Real Time Big DataCNN02/03/262.000telefônico±2,2 p.p.42,041,0——+1,20,00fonte
Vetorpróprio02/03/269.000não informado±1,0 p.p.40,449,0——-9,60,00fonte
Paraná Pesquisaspróprio25/02/262.080presencial±2,1 p.p.43,844,4——-0,70,00fonte
AtlasIntelBloomberg24/02/264.986online±1,4 p.p.46,246,3——-0,10,00fonte
QuaestGenial09/02/262.004presencial±2,2 p.p.43,038,0——+6,20,00fonte
Futura InteligenciaApex07/02/262.000não informado±2,2 p.p.42,448,2——-6,40,00fonte
Meio/IdeiaCanal Meio02/02/261.500telefônico±2,5 p.p.45,841,1——+5,40,00fonte
Gerppróprio02/02/262.000não informado±2,2 p.p.45,045,0——0,00,00fonte
Paraná Pesquisaspróprio28/01/262.080presencial±2,1 p.p.44,842,2——+3,00,00fonte
AtlasIntelBloomberg20/01/265.418online±1,3 p.p.49,045,0——+4,30,00fonte
Futura InteligenciaApex19/01/262.000não informado±2,2 p.p.41,948,1——-6,90,00fonte
Meio/IdeiaCanal Meio12/01/262.000telefônico±2,2 p.p.46,236,0——+12,40,00fonte
QuaestGenial11/01/262.004presencial±2,2 p.p.45,038,0——+8,40,00fonte
Paraná Pesquisaspróprio22/12/252.038presencial±2,2 p.p.44,141,0——+3,60,00fonte
AtlasIntelBloomberg15/12/258.154online±1,1 p.p.53,041,0——+12,80,00fonte
QuaestGenial14/12/252.004presencial±2,2 p.p.46,036,0——+12,20,00fonte
Gerppróprio10/12/252.000não informado±2,2 p.p.41,042,0——-1,20,00fonte
Futura InteligenciaApex09/12/252.001não informado±2,2 p.p.42,141,6——+0,60,00fonte
Veritapróprio07/12/252.519não informado±2,0 p.p.39,839,2——+0,80,00fonte
DatafolhaFolha/Globo04/12/252.002presencial±2,2 p.p.51,036,0——+17,20,00fonte

Pesquisas estaduais (2º turno Lula × Flávio; a coluna de fontes indica as de fonte única)

UFInstitutoCampo (fim)nMétodoLulaFlávioRegistroFontes
ACVerita15/09/261.030telefônico32,761,3BR-02386/2026fonte · 2ª fonte
ACQuaest12/10/261.098presencial28,060,0BR-05379/2026fonte · 2ª fonte
ALAtlasIntel09/09/261.208online50,640,0AL-00447/2026fonte (fonte única)
ALVerita15/09/261.220telefônico52,840,8BR-04383/2026fonte · 2ª fonte
ALQuaest24/09/26804presencial48,034,0BR-04938/2026fonte · 2ª fonte
AMAtlasIntel03/09/261.185online41,348,0AM-04939/2026fonte (fonte única)
AMVerita15/09/261.220telefônico42,243,1BR-07214/2026fonte · 2ª fonte
AMQuaest23/09/26804presencial45,040,0BR-08101/2026fonte · 2ª fonte
AMPoderData11/10/261.200telefônico49,049,0AM-08245/2026fonte · 2ª fonte
AMQuaest12/10/261.098presencial47,043,0AM-09760/2026fonte · 2ª fonte
APVerita15/09/261.030telefônico43,946,2BR-07861/2026fonte · 2ª fonte
APAtlasIntel02/10/26800online50,743,5AP-01238/2026fonte (fonte única)
BAAtlasIntel02/09/261.804online58,129,0BA-08891/2026fonte · 2ª fonte
BAReal Time Big Data08/09/261.600telefônico61,030,0BR-06566/2026fonte · 2ª fonte
BAVerita15/09/262.020telefônico55,140,5BR-07197/2026fonte · 2ª fonte
BAQuaest23/09/26900presencial62,027,0BA-01850/2026fonte · 2ª fonte
BAReal Time Big Data26/09/261.600telefônico64,028,0BR-02319/2026fonte · 2ª fonte
BAAtlasIntel28/09/262.000online61,331,4BA-02425/2026fonte · 2ª fonte
CEAtlasIntel02/09/261.834online54,630,1BR-07411/2026fonte · 2ª fonte
CEVerita15/09/261.525telefônico54,136,0BR-07551/2026fonte · 2ª fonte
CEReal Time Big Data17/09/261.600telefônico64,027,0BR-03822/2026fonte · 2ª fonte
CEQuaest22/09/26900presencial59,027,0CE-08268/2026fonte · 2ª fonte
CEReal Time Big Data24/09/261.600telefônico63,028,0BR-05316/2026fonte (fonte única)
DFDatafolha21/08/26910presencial39,051,0—fonte · 2ª fonte
DFAtlasIntel01/09/261.193online42,547,4DF-02018/2026fonte (fonte única)
DFDatafolha11/09/26910presencial39,051,0—fonte (fonte única)
DFVerita15/09/261.220telefônico49,745,4BR-01988/2026fonte · 2ª fonte
DFQuaest22/09/261.104presencial38,049,0DF-02596/2026fonte · 2ª fonte
DFDatafolha24/09/26910presencial41,051,0DF-01888/2026fonte · 2ª fonte
DFQuaest28/09/261.104presencial33,048,0DF-02515/2026fonte · 2ª fonte
DFDatafolha30/09/26910presencial39,051,0DF-00905/2026fonte · 2ª fonte
DFAtlasIntel02/10/261.200online39,655,3DF-04297/2026fonte (fonte única)
DFDatafolha08/10/26910presencial43,049,0DF-03365/2026fonte · 2ª fonte
DFQuaest11/10/261.104não informado40,050,0DF-08737/2026fonte (fonte única)
DFQuaest20/10/261.104presencial39,050,0BR-03163/2026fonte (fonte única)
ESVerita17/09/261.220telefônico43,150,9BR-00729/2026fonte · 2ª fonte
ESQuaest13/10/261.098não informado38,051,0BR-01973/2026fonte · 2ª fonte
GOAtlasIntel01/09/261.214online32,954,6GO-05293/2026fonte (fonte única)
GOVerita17/09/261.525telefônico33,453,4BR-06701/2026fonte · 2ª fonte
GOReal Time Big Data21/09/261.600telefônico33,057,0—fonte · 2ª fonte
MAReal Time Big Data09/09/261.600telefônico65,030,0—fonte · 2ª fonte
MAVerita17/09/261.525telefônico53,738,6BR-07504/2026fonte · 2ª fonte
MAQuaest24/09/26900presencial63,026,0—fonte · 2ª fonte
MGDatafolha20/08/261.204presencial46,042,0MG-00446/2026fonte · 2ª fonte
MGAtlasIntel04/09/261.804online48,742,1—fonte (fonte única)
MGDatafolha10/09/261.204presencial46,045,0—fonte · 2ª fonte
MGVerita17/09/262.030telefônico38,445,6BR-03240/2026fonte · 2ª fonte
MGQuaest22/09/261.506presencial40,040,0BR-07664/2026fonte · 2ª fonte
MGDatafolha24/09/261.204presencial44,046,0BR-06824/2026fonte · 2ª fonte
MGQuaest28/09/261.506presencial41,040,0BR-05155/2026fonte · 2ª fonte
MGDatafolha01/10/261.204presencial48,044,0—fonte · 2ª fonte
MGReal Time Big Data02/10/262.000telefônico46,046,0BR-01068/2026fonte · 2ª fonte
MGAtlasIntel02/10/262.189online47,450,2BR-05974/2026fonte (fonte única)
MSAtlasIntel01/09/261.204online36,157,1MS-02047/2026fonte (fonte única)
MSReal Time Big Data09/09/261.600telefônico38,052,0BR-07174/2026fonte (fonte única)
MSVerita17/09/261.220telefônico34,453,5BR-07855/2026fonte · 2ª fonte
MTAtlasIntel09/09/261.209online35,259,1MT-02766/2026fonte (fonte única)
MTVerita17/09/261.220telefônico28,154,9BR-02939/2026fonte · 2ª fonte
MTQuaest24/09/26804presencial30,056,0MT-08098/2026fonte · 2ª fonte
PAVerita17/09/261.525telefônico39,648,8BR-01223/2026fonte · 2ª fonte
PAQuaest25/09/26804presencial44,040,0—fonte · 2ª fonte
PBAtlasIntel02/09/261.207online56,333,4PB-01118/2026fonte · 2ª fonte
PBVerita18/09/261.220telefônico51,640,9BR-08734/2026fonte · 2ª fonte
PBQuaest21/09/26804presencial60,026,0—fonte · 2ª fonte
PBAtlasIntel02/10/261.268online61,433,9BR-07092/2026fonte (fonte única)
PEDatafolha20/08/261.204presencial62,029,0—fonte · 2ª fonte
PEReal Time Big Data02/09/261.600telefônico59,035,0BR-05862/2026fonte · 2ª fonte
PEAtlasIntel16/09/261.793online57,536,2BR-00357/2026fonte · 2ª fonte
PEVerita18/09/262.010telefônico52,940,1BR-07802/2026fonte · 2ª fonte
PEAtlasIntel20/09/261.836online60,532,7PE-02023/2026fonte · 2ª fonte
PEQuaest22/09/261.302presencial58,025,0PE-05671/2026fonte · 2ª fonte
PEDatafolha24/09/261.204presencial64,029,0PE-08125/2026fonte · 2ª fonte
PEReal Time Big Data26/09/261.600telefônico61,035,0BR-02938/2026fonte · 2ª fonte
PEQuaest28/09/261.302presencial61,024,0PE-00324/2026fonte · 2ª fonte
PEDatafolha01/10/261.204presencial65,029,0PE-06822/2026fonte · 2ª fonte
PIVerita18/09/261.220telefônico57,628,7BR-07059/2026fonte · 2ª fonte
PIAtlasIntel28/09/261.218online70,026,0PI-07497/2026fonte · 2ª fonte
PRAtlasIntel09/09/261.799online32,160,7PR-09655/2026fonte (fonte única)
PRReal Time Big Data15/09/261.600telefônico30,061,0BR-08843/2026fonte · 2ª fonte
PRAtlasIntel16/09/261.794online38,857,5PR-02671/2026fonte · 2ª fonte
PRVerita18/09/262.010telefônico30,658,9BR-01839/2026fonte · 2ª fonte
PRReal Time Big Data28/09/261.600telefônico30,061,0BR-03875/2026fonte · 2ª fonte
RJReal Time Big Data01/09/262.000telefônico41,045,0—fonte (fonte única)
RJDatafolha10/09/261.204presencial41,049,0—fonte · 2ª fonte
RJReal Time Big Data12/09/262.000telefônico41,047,0BR-09111/2026fonte · 2ª fonte
RJVerita18/09/262.030telefônico39,353,4BR-02698/2026fonte · 2ª fonte
RJAtlasIntel20/09/261.829online44,744,9RJ-01150/2026fonte (fonte única)
RJQuaest22/09/261.302presencial36,044,0RJ-04982/2026fonte · 2ª fonte
RJDatafolha24/09/261.204presencial42,051,0RJ-02205/2026fonte · 2ª fonte
RJQuaest28/09/261.302presencial34,044,0RJ-04419/2026fonte · 2ª fonte
RJReal Time Big Data29/09/262.000telefônico41,052,0BR-05193/2026fonte · 2ª fonte
RJDatafolha01/10/261.204presencial42,051,0RJ-02070/2026fonte · 2ª fonte
RNVerita18/09/261.220telefônico55,437,4BR-02399/2026fonte · 2ª fonte
RNQuaest24/09/26804presencial56,030,0RN-01492/2026fonte · 2ª fonte
RNQuaest12/10/261.098presencial59,033,0RN-03210/2026fonte · 2ª fonte
ROVerita19/09/261.220telefônico30,563,9BR-07923/2026fonte · 2ª fonte
RRAtlasIntel09/09/261.200online25,666,4RR-07280/2026fonte · 2ª fonte
RRVerita19/09/261.030telefônico23,570,8BR-07242/2026fonte · 2ª fonte
RRQuaest24/09/26804presencial22,067,0—fonte · 2ª fonte
RRAtlasIntel02/10/26800online29,365,3RR-07765/2026fonte (fonte única)
RSAtlasIntel01/09/261.783online42,050,4RS-00652/2026fonte · 2ª fonte
RSReal Time Big Data09/09/261.600telefônico42,053,0BR-00631/2026fonte · 2ª fonte
RSVerita19/09/262.020telefônico37,955,7BR-06983/2026fonte · 2ª fonte
RSAtlasIntel20/09/261.819online44,447,9BR-01816/2026fonte · 2ª fonte
RSReal Time Big Data28/09/261.600telefônico43,053,0BR-08546/2026fonte (fonte única)
SCAtlasIntel09/09/261.211online27,561,5SC-02036/2026fonte · 2ª fonte
SCReal Time Big Data16/09/261.600telefônico30,062,0BR-09582/2026fonte · 2ª fonte
SEVerita19/09/261.220telefônico56,737,6BR-01212/2026fonte · 2ª fonte
SEQuaest23/09/26804presencial56,030,0—fonte · 2ª fonte
SPDatafolha19/08/261.610presencial42,047,0SP-01806/2026fonte · 2ª fonte
SPDatafolha10/09/261.610presencial42,047,0—fonte · 2ª fonte
SPReal Time Big Data12/09/262.000telefônico44,050,0BR-05824/2026fonte (fonte única)
SPReal Time Big Data19/09/262.000telefônico45,051,0BR-09702/2026fonte · 2ª fonte
SPVerita19/09/263.025telefônico40,652,3BR-04470/2026fonte · 2ª fonte
SPAtlasIntel20/09/261.800online42,848,8SP-02676/2026fonte · 2ª fonte
SPQuaest22/09/261.800presencial36,042,0BR-08886/2026fonte · 2ª fonte
SPDatafolha23/09/261.610presencial41,049,0BR-09355/2026fonte · 2ª fonte
SPReal Time Big Data28/09/262.000telefônico43,051,0—fonte · 2ª fonte
SPDatafolha30/09/261.610presencial45,048,0BR-02676/2026fonte · 2ª fonte
TOVerita19/09/261.220telefônico33,661,1BR-09946/2026fonte · 2ª fonte
TOQuaest13/10/261.098não informado42,048,0TO-03829/2026fonte · 2ª fonte
TOParaná Pesquisas17/10/261.500telefônico41,952,9BR-09051/2026fonte (fonte única)

Apêndice B — base completa de pesquisas de 1º turno (desde 1/ago)

Cenário estimulado principal (sem Pablo Marçal, indeferido em 11/set, quando o instituto publicou os dois), em votos totais como divulgados; cada onda confirmada em duas fontes. Ondas em que só Lula e Flávio foram publicados ("—" nas demais colunas) entram só na série dos dois. "Outros" soma os candidatos não listados. Coleta e verificação em 30/set/2026.
InstitutoContratanteCampo aténMétodoLulaFlávioCuryRenanCaiadoZemaOutrosBr/nuloIndec.Registro TSEFontes
QuaestGlobo/O Globo03/103.702presencial403833300103BR-02197/2026fonte · 2ª
DatafolhaFolha de S.Paulo/Globo03/104.006presencial42403340043BR-01708/2026fonte · 2ª
Palverpróprio03/105.000online43471710000BR-00198/2026fonte · 2ª
PoderDataAya02/104.000telefônico42413321242BR-03519/2026fonte · 2ª
CNT/MDACNT02/102.007presencial43,1383,21,530,80,554,7BR-04756/2026fonte · 2ª
Vox Brasilpróprio01/102.100não informado40,441,21,22,23,31,30,23,27BR-00148/2026fonte · 2ª
DatafolhaFolha de S.Paulo/Globo01/102.506presencial42384331252BR-08039/2026fonte · 2ª
Real Time Big DataReal Time Big Data (recursos próprios)30/092.000telefônico43393431133BR-09503/2026fonte · 2ª
DataTrendspróprio30/092.000telefônico4139433—144BR-04414/2026fonte · 2ª
IndexaBroadcast/Estadão29/092.000telefônico39344341047BR-00698/2026fonte · 2ª
Futura InteligenciaApex/100% Cidades29/092.000telefônico39,442,23,83,241,61,22,42,6BR-01122/2026fonte · 2ª
Vox Brasilpróprio28/092.100presencial41,137,82,22,73,81,80,33,56,8BR-00895/2026fonte · 2ª
Meio/IdeiaCanal Meio28/092.000telefônico39,438,46,74,54,43,51,40,71,5BR-08706/2026fonte · 2ª
Gerppróprio28/092.400telefônico40425331123BR-03929/2026fonte · 2ª
AtlasIntelBloomberg (AtlasIntel recursos próprios)28/095.005online45,342,225,21,80,90,50,91,2BR-04391/2026fonte · 2ª
Palverpróprio27/095.000online44441811011BR-02990/2026fonte · 2ª
BTG/NexusBTG Pactual27/092.000telefônico42375451132BR-07557/2026fonte · 2ª
QuaestGlobo/O Globo27/092.004presencial393443410105BR-06520/2026fonte · 2ª
Real Time Big DataReal Time Big Data (recursos próprios)23/092.000telefônico41376621133BR-04202/2026fonte · 2ª
PoderDataPoderData (recursos próprios)/Aya23/093.000telefônico41396321342BR-01739/2026fonte · 2ª
Palverpróprio23/095.000online43432811111BR-09587/2026fonte · 2ª
Futura InteligenciaApex/100% Cidades23/092.000telefônico38,440,45,62,45,70,91,22,13,2BR-05268/2026fonte · 2ª
DatafolhaFolha de S.Paulo/Globo23/092.002presencial40365341152BR-00304/2026fonte · 2ª
Alfa InteligenciaTMC23/092.700presencial40336351165BR-02512/2026fonte · 2ª
AtlasIntelBloomberg (AtlasIntel recursos próprios)22/095.015online45,843,42,14,51,30,90,80,70,5BR-04739/2026fonte · 2ª
American AnalyticsTimes Brasil/CNBC20/092.000não informado38345441068BR-032587/26fonte · 2ª
BTG/NexusBTG Pactual20/092.006telefônico40376351142BR-00485/2026fonte · 2ª
QuaestGlobo/O Globo20/092.004presencial37336341178BR-06004/2026fonte · 2ª
Veritapróprio19/0940.500telefônico4043,65,42,91,80,61,30,73,6—fonte · 2ª
Palverpróprio18/095.000online414221111——1BR-00860/2026fonte · 2ª
PoderDataPoderData (recursos próprios)/Aya16/093.000telefônico37368331642BR-00360/2026fonte · 2ª
Gerppróprio16/092.400telefônico37407231144BR-00535/2026fonte · 2ª
DatafolhaFolha de S.Paulo/Globo16/092.002presencial39366342263BR-04029/2026fonte · 2ª
AtlasIntelBloomberg (AtlasIntel recursos próprios)16/095.018online44,141,73,55,11,71,10,71,60,5BR-06221/2026fonte · 2ª
Futura InteligenciaApex/100% Cidades15/092.000telefônico38,337,76,53,44,512,42,73,4BR-00749/2026fonte · 2ª
DataTrendspróprio14/092.000telefônico39356431048BR-08691/2026fonte · 2ª
QuaestGlobo/O Globo13/092.004presencial363174410710BR-03607/2026fonte · 2ª
CNT/MDACNT13/092.002presencial40,530,462,7312,967,5BR-06902/2026fonte · 2ª
BTG/NexusBTG Pactual13/092.003telefônico42376251142BR-04076/2026fonte · 2ª
IndexaBroadcast/Estadão13/092.000telefônico383463410104BR-03482/2026fonte · 2ª
Futura InteligenciaApex/100% Cidades10/092.000telefônico39,233,66,44,34,40,733,15,2BR-02322/2026fonte · 2ª
DatafolhaFolha de S.Paulo/Globo10/092.002presencial39356342464BR-01833/2026fonte · 2ª
PoderDataPoderData (recursos próprios)/Aya09/093.000telefônico383610321531BR-04914/2026fonte · 2ª
AtlasIntelBloomberg (AtlasIntel recursos próprios)09/095.000online4337,46,66,51,12,12,30,60,4BR-01452/2026fonte · 2ª
Gerppróprio08/092.400telefônico34376451248BR-00251/2026fonte · 2ª
Meio/IdeiaCanal Meio07/091.500telefônico38,437,36,6441,53,12,52,8BR-07935/2026fonte · 2ª
Palverpróprio07/095.000online404041111201BR-05420/2026fonte · 2ª
BTG/NexusBTG Pactual07/092.002telefônico39359441232BR-06790/2026fonte · 2ª
QuaestGlobo/O Globo06/092.004presencial362983321810BR-01720/2026fonte · 2ª
Veritapróprio04/093.804não informado38,438,912,23,71,90,32,61,10,9BR-09426/2026fonte · 2ª
PoderDataPoderData (recursos próprios)/Aya02/093.000telefônico373410321852BR-07561/2026fonte · 2ª
DatafolhaFolha de S.Paulo/Globo02/092.002presencial38338342363BR-03669/2026fonte · 2ª
Futura InteligenciaApex/100% Cidades01/092.000telefônico38,733,69,92,83,51,63,824,1BR-02793/2026fonte · 2ª
QuaestGlobo/O Globo01/092.004presencial3729103111711BR-07065/2026fonte · 2ª
Real Time Big DataReal Time Big Data (recursos próprios)31/082.000telefônico383011742233BR-03490/2026fonte · 2ª
AtlasIntelBloomberg (AtlasIntel recursos próprios)30/085.014online43,433,77,87,63,3130,10,2BR-07972/2026fonte · 2ª
BTG/NexusBTG Pactual30/082.005telefônico393311351133BR-08900/2026fonte · 2ª
Vox Brasilpróprio27/082.100presencial37,134,82,63,352,84,13,56,8BR-05519/2026fonte · 2ª
PoderDataPoderData (recursos próprios)/Aya26/082.400telefônico38354442752BR-04974/2026fonte · 2ª
GerpAESP25/082.400telefônico37381331448BR-03547/2026fonte · 2ª
IndexaBroadcast/Estadão23/082.000telefônico393414510106BR-06366/2026fonte · 2ª
BTG/NexusBTG Pactual23/082.006telefônico41372353153BR-09028/2026fonte · 2ª
Veritapróprio20/083.840telefônico39,339,12,23,83,31,371,82,4BR-04006/2026fonte · 2ª
DatafolhaFolha de S.Paulo/Globo20/082.058presencial39332453464BR-04496/2026fonte · 2ª
BTG/NexusBTG Pactual16/082.003telefônico41361444152BR-03317/2026fonte · 2ª
QuaestGlobo/O Globo13/082.004presencial383124421810BR-06773/2026fonte · 2ª
PoderDataPoderData (recursos próprios)/Aya12/082.400telefônico41353443342BR-06868/2026fonte · 2ª
Gerppróprio10/082.400telefônico38381542147BR-08045/2026fonte · 2ª
CNT/MDACNT09/082.002presencial42,428,71,22,843,33,46,87,2BR-06935/2026fonte · 2ª
BTG/NexusBTG Pactual09/082.001telefônico40351453353BR-08428/2026fonte · 2ª
Palverpróprio07/085.000online444001021110BR-06596/2026fonte · 2ª
Futura InteligenciaApex/100% Cidades07/082.000telefônico38,834,11,23,65,93,626,24,8BR-08109/2026fonte · 2ª
QuaestGenial (Banco Genial S.A.)03/082.004presencial393014422810BR-06591/2026fonte · 2ª
Meio/IdeiaCanal Meio03/081.500telefônico43351,74,75,72,61,324BR-04579/2026fonte · 2ª
BTG/NexusBTG Pactual02/082.002telefônico41371453243BR-02874/2026fonte · 2ª

Depois do 1º turno, voto declarado no 2º turno por voto no 1º (PoderData/Aya 10–12/out, via TMC), % Flávio / Lula: Cury 45/43 · Caiado 44/30 · Renan Santos 38/33 · outro candidato 57/17 (Zema sem linha própria); brancos/nulos: Renan 27%, Caiado 14%, Cury 8%. Datafolha 6–7/out, só o agregado dos quatro: Flávio 46, Lula 32, branco/nulo 15. Antes do 1º turno, 2ª opção declarada (Quaest 3–6/set), % dos eleitores de cada candidato que migram para Flávio / Lula no 2º turno: Zema 59/4 · Renan Santos 58/14 · Marçal 47/19 · Cury 46/24 · Caiado 36/25. O modelo usa a matriz pós-1º turno (PoderData) e confere o agregado com o Datafolha.

Apêndice C — código, dados e reprodução

Tudo neste documento é gerado por dois scripts Python (numpy, pandas, scipy) a partir de um CSV. Para atualizar com novas pesquisas: acrescente linhas ao CSV, rode modelo_eleicao_2026.py (recalcula efeitos de casa, simulações e SVGs; após 4/out, preencha resultado_1turno.csv para ativar o regime pós-1º turno) e depois montar_painel.py / montar_standalone.py. Os arquivos abaixo estão embutidos neste HTML.
⬇ pesquisas_2turno_2026.csv ⬇ pesquisas_1turno_2026.csv ⬇ calibracao_historica.csv ⬇ resultados.json
espaco_estados.py — núcleo: passeio aleatório, efeitos de casa ancorados, variância por instituto, validação (clique para expandir)
# -*- coding: utf-8 -*-
"""
Espaço de estados para agregação de pesquisas (núcleo do modelo v3).

    y_i = x_{t(i)} + h_{p(i)} + ε_i          ε_i ~ N(0, se_i² + σ_{p(i)}²)
    x_t = x_{t-1} + η_t                       η_t ~ N(0, σ_rw²)
    h_p ~ N(0, τ²)   com  Σ_{p ∈ âncora} q_p·h_p = 0   (identificação pelos institutos calibrados)

Estimação por ECM: (1) dado h e σ_p, σ_rw por MAP (prior log-normal) e suavizador RTS para x_t;
(2) dado x_t, atualiza h_p (média posterior ridge) e σ_p² (empírico-Bayes, encolhido para a mediana).
Referências: Jackman (2005), Linzer (2013), Heidemanns–Gelman–Morris (2020).
"""
import math
import numpy as np
from scipy.optimize import minimize_scalar


def _kalman(y, day, R, sig_rw, n_days, x0, keep=False, q_extra=None):
    """Filtro para uma série diária. y, day, R ordenados por dia. Retorna loglik e, se keep, trajetórias.
    q_extra: dict dia → variância adicional do passeio nesse dia (quebra estrutural, ex.: o dia seguinte ao 1º turno)."""
    x, P = x0, 100.0
    ll = 0.0
    xf = np.empty(n_days); Pf = np.empty(n_days); xp = np.empty(n_days); Pp = np.empty(n_days)
    obs_by_day = {}
    for i, d in enumerate(day):
        obs_by_day.setdefault(int(d), []).append(i)
    q = sig_rw ** 2
    q_extra = q_extra or {}
    for d in range(n_days):
        if d > 0:
            P += q + q_extra.get(d, 0.0)
        xp[d], Pp[d] = x, P
        for i in obs_by_day.get(d, []):
            F = P + R[i]
            v = y[i] - x
            ll += -0.5 * (math.log(2 * math.pi * F) + v * v / F)
            K = P / F
            x += K * v
            P *= (1 - K)
        xf[d], Pf[d] = x, P
    if not keep:
        return ll
    # suavizador RTS
    xs = xf.copy(); Ps = Pf.copy()
    for d in range(n_days - 2, -1, -1):
        C = Pf[d] / Pp[d + 1] if Pp[d + 1] > 0 else 0.0
        xs[d] = xf[d] + C * (xs[d + 1] - xp[d + 1])
        Ps[d] = Pf[d] + C * C * (Ps[d + 1] - Pp[d + 1])
    return ll, xs, Ps, xf, Pf


def ajustar(y, day, se2, inst, ancora, q_ancora=None, tau_h=1.5, prior_rw=(math.log(0.25), 0.6),
            k_sigma=4.0, sigma_floor=0.8, n_iter=15, tol=0.01, sig_rw_fixo=None, prior_h=None, tau_prior_h=None,
            n_days=None, q_extra=None, a_h=None):
    """
    y, day, se2: arrays (observação, dia inteiro desde a origem, variância amostral).
    inst: array de rótulos. ancora: conjunto de institutos que identificam o nível.
    q_ancora: pesos dos institutos da âncora (default 1). prior_h: dict instituto → média a priori do efeito de casa
    (usado no regime pós-1º turno); tau_prior_h: desvio desse prior (default tau_h). q_extra: ver _kalman.
    a_h: fator por observação com que o efeito de casa entra (y_i = x + a_i·h_p + ε); default 1. Serve à persistência parcial do efeito
    de casa depois do 1º turno (a = λ < 1 nas pesquisas posteriores a 4/out): a observação identifica a_i·h, não h.
    Retorna dict com x (nível suavizado por dia), P (variância), house, house_var (variância posterior de cada efeito de casa),
    var_ancora (variância da média da âncora, que desloca o nível), sigma_inst, sig_rw, sig_rw_ml, loglik, resid.
    """
    y = np.asarray(y, float); day = np.asarray(day, int); se2 = np.asarray(se2, float); inst = np.asarray(inst)
    a_h = np.ones(len(y)) if a_h is None else np.asarray(a_h, float)
    order = np.argsort(day, kind="stable")
    y, day, se2, inst, a_h = y[order], day[order], se2[order], inst[order], a_h[order]
    n_days = max(int(day.max()) + 1, n_days or 0)   # o estado é propagado até n_days−1 (ex.: hoje) sem observações
    insts = list(dict.fromkeys(inst))
    idx = {p: np.where(inst == p)[0] for p in insts}
    q_ancora = q_ancora or {}
    prior_h = prior_h or {}
    tau_prior_h = tau_prior_h or tau_h
    h = {p: float(prior_h.get(p, 0.0)) for p in insts}
    sig_p = {p: 2.0 for p in insts}
    sig_rw = 0.25 if sig_rw_fixo is None else sig_rw_fixo
    x0 = float(y[0])
    hist_ll = []
    for it in range(n_iter):
        h_arr = a_h * np.array([h[p] for p in inst])
        R = se2 + np.array([sig_p[p] ** 2 for p in inst])
        y_adj = y - h_arr
        if sig_rw_fixo is None:
            def neg_post(z):
                ll = _kalman(y_adj, day, R, math.exp(z), n_days, x0, q_extra=q_extra)
                lp = -0.5 * ((z - prior_rw[0]) / prior_rw[1]) ** 2
                return -(ll + lp)
            opt = minimize_scalar(neg_post, bounds=(math.log(0.02), math.log(2.0)), method="bounded",
                                  options={"xatol": 1e-3})
            sig_rw = math.exp(opt.x)
        ll, xs, Ps, xf, Pf = _kalman(y_adj, day, R, sig_rw, n_days, x0, keep=True, q_extra=q_extra)
        hist_ll.append(ll)
        # (2) efeitos de casa: média posterior sob h_p ~ N(prior, τ²) dado x suavizado
        x_at = xs[day]
        h_new = {}
        for p in insts:
            ii = idx[p]
            w = 1.0 / R[ii]
            tau2 = (tau_prior_h if p in prior_h else tau_h) ** 2
            m0 = prior_h.get(p, 0.0)
            h_new[p] = float((np.sum(w * a_h[ii] * (y[ii] - x_at[ii])) + m0 / tau2) / (np.sum(w * a_h[ii] ** 2) + 1.0 / tau2))
        # identificação: média ponderada da âncora = 0 (o deslocamento vai para o nível)
        anc = [p for p in insts if p in ancora]
        if anc:
            wq = np.array([q_ancora.get(p, 1.0) for p in anc])
            c = float(np.sum(wq * np.array([h_new[p] for p in anc])) / wq.sum())
        else:
            c = float(np.mean(list(h_new.values())))
        h_new = {p: v - c for p, v in h_new.items()}
        xs = xs + c; xf = xf + c; x0 = x0 + c
        delta = max(abs(h_new[p] - h[p]) for p in insts)
        h = h_new
        # (3) variância extra por instituto (EB): resíduo² − variância amostral − variância do estado, encolhido para a mediana
        resid = y - xs[day] - a_h * np.array([h[p] for p in inst])
        excess = {}
        for p in insts:
            ii = idx[p]
            e2 = float(np.mean(resid[ii] ** 2 + Ps[day[ii]] - se2[ii]))
            excess[p] = (len(ii), max(e2, 0.0))
        med = float(np.median([v for _, v in excess.values()]))
        sig_p = {p: math.sqrt(max((n * e2 + k_sigma * med) / (n + k_sigma), sigma_floor ** 2)) for p, (n, e2) in excess.items()}
        if delta < tol and it >= 3:
            break
    # verossimilhança sem prior, para transparência sobre σ_rw
    h_arr = a_h * np.array([h[p] for p in inst]); R = se2 + np.array([sig_p[p] ** 2 for p in inst]); y_adj = y - h_arr
    ml = minimize_scalar(lambda z: -_kalman(y_adj, day, R, math.exp(z), n_days, x0, q_extra=q_extra), bounds=(math.log(0.02), math.log(2.0)),
                         method="bounded", options={"xatol": 1e-3})
    ll, xs, Ps, xf, Pf = _kalman(y_adj, day, R, sig_rw, n_days, x0, keep=True, q_extra=q_extra)
    resid = y - xs[day] - h_arr
    # incerteza dos efeitos de casa (tratados como conhecidos no filtro): variância posterior de cada h_p e da média da âncora
    house_var = {p: float(1.0 / (np.sum(a_h[idx[p]] ** 2 / R[idx[p]]) + 1.0 / ((tau_prior_h if p in prior_h else tau_h) ** 2))) for p in insts}
    anc = [p for p in insts if p in ancora]
    if anc:
        wq = np.array([q_ancora.get(p, 1.0) for p in anc])
        var_ancora = float(np.sum(wq ** 2 * np.array([house_var[p] for p in anc])) / wq.sum() ** 2)
    else:
        var_ancora = float(np.mean(list(house_var.values())) / len(insts))
    return {"x": xs, "P": Ps, "x_filtro": xf, "P_filtro": Pf, "house": h, "house_var": house_var, "var_ancora": var_ancora, "sigma_inst": sig_p, "sig_rw": sig_rw,
            "sig_rw_ml": math.exp(ml.x), "loglik": ll, "resid": resid, "order": order, "R": R, "iter": it + 1,
            "n_days": n_days, "insts": insts}


def validacao_rolling(y, day, se2, inst, ancora, q_ancora, dias, horizonte=7, a_h=None, **kw):
    """Origem móvel: para cada dia d em `dias`, ajusta com observações até d e prevê as de (d, d+horizonte].
    `a_h` é a persistência do efeito de casa por observação (λ nas pesquisas pós-1º turno), a mesma do ajuste validado.
    Retorna lista de (dia, y_obs, y_prev, sd_prev, instituto, instituto_ja_visto)."""
    y = np.asarray(y, float); day = np.asarray(day, int); se2 = np.asarray(se2, float); inst = np.asarray(inst)
    a_h = np.ones(len(y)) if a_h is None else np.asarray(a_h, float)
    out = []
    for d in dias:
        m = day <= d
        if m.sum() < 20:
            continue
        fit = ajustar(y[m], day[m], se2[m], inst[m], ancora, q_ancora, n_iter=8, n_days=d + 1, a_h=a_h[m], **kw)   # estado propagado até o dia d
        x_d, P_d = fit["x"][d], fit["P"][d]
        f = (day > d) & (day <= d + horizonte)
        for i in np.where(f)[0]:
            hp = fit["house"].get(inst[i], 0.0)
            sp = fit["sigma_inst"].get(inst[i], float(np.median(list(fit["sigma_inst"].values()))))
            k = day[i] - d
            sd = math.sqrt(P_d + k * fit["sig_rw"] ** 2 + se2[i] + sp ** 2)
            out.append((int(d), float(y[i]), float(x_d + a_h[i] * hp), sd, str(inst[i]), bool(inst[i] in fit["house"])))
    return out
mcmc_2turno.py — modelo paralelo do 2º turno por MCMC: posterior completo, amostrador e diagnósticos (clique para expandir)
# -*- coding: utf-8 -*-
"""
Modelo do 2º turno estimado por MCMC (Markov chain Monte Carlo): mesmo modelo de espaco_estados.py, com o posterior completo.

    y_i = x_{t(i)} + μ_{r(i)} + β + h_{p(i)} + ε_i        ε_i ~ N(0, se_i² + σ_{p(i)}²)
    x_t = x_{t-1} + η_t        η_t ~ N(0, σ_rw²) até o 1º turno · + SD_QUEBRA² no dia seguinte · N(0, σ_entre²) depois
    h_p ~ N(prior_p, τ²)  com  Σ_{p ∈ âncora} q_p·h_p = 0
    β ~ t₅(0, sd_vies)        erro sistemático comum das pesquisas (μ_r = desconto histórico do regime: antes/depois do 1º turno)
    obs. extras: nível estrutural (voto contado + transferência) = x_{T1+1} + e, sem viés de pesquisa
    log σ_rw ~ N(log 0,25; 0,6) · log σ_entre ~ N(log σ_entre_hist; 0,5)
    log σ_p ~ N(m, 0,5²) truncada em σ_p ≥ 0,8 · m ~ N(log 2; 0,5²)
    previsão: x_{T2} = x_{último dia} + deriva, deriva ~ t₅ com desvio sqrt(Σ σ²_dia) (como em simular())

O modelo atual (espaco_estados.ajustar + simular) fixa σ_rw, σ_p, os efeitos de casa e o nível em estimativas pontuais e sorteia
a deriva e o viés por fora. Aqui tudo é sorteado em conjunto. Amostrador (Gibbs com passos de Metropolis):
 1. σ_rw e σ_entre por Metropolis com (x, β, h) integrados: dado o resto, o modelo é linear-gaussiano e a verossimilhança marginal
    sai da fatoração de Cholesky da precisão conjunta — mistura muito melhor que condicionar na trajetória;
 2. (x, β, h) num bloco só, sorteio exato da normal conjunta (a restrição da âncora entra como observação de variância ~0);
 3. σ_p por Metropolis, dado o resíduo de cada instituto; m por Metropolis; 4. escala da t do viés (ω) por Gama conjugada.
Diagnósticos: R̂ dividido e ESS multicadeia (Vehtari et al. 2021, sem normalização por postos).
"""
import math
import numpy as np
from scipy.linalg import cholesky, solve_triangular
from scipy.stats import norm

DF = 5                          # graus de liberdade da t (deriva e viés), o mesmo DF_ERRO do modelo atual
K_T = (DF - 2) / DF             # escala da t com o mesmo desvio da normal (como _t_unit)
SIG_P_PISO, SIG_P_SD, M_PRIOR = 0.8, 0.5, (math.log(2.0), 0.5)
KAPPA = 1e-6                    # variância da "observação" Σ q_p h_p = 0


def _monta(y, dia, se2, inst, n_dias, mu_obs, ancora, q_ancora, prior_h, tau_h, extras, sd_vies, x0_sd, a_h=None):
    """Pré-calcula a matriz de desenho e as partes fixas da precisão. Vetor z = [x_0 … x_{D−1}, β, h_1 … h_P]."""
    insts = list(dict.fromkeys(inst))
    P, D = len(insts), n_dias
    com_vies = sd_vies > 0
    iB = D if com_vies else None
    i0h = D + (1 if com_vies else 0)
    K = i0h + P
    pidx = np.array([insts.index(p) for p in inst])
    n, ne = len(y), len(extras)
    H = np.zeros((n + ne, K))
    H[np.arange(n), dia] = 1.0
    if com_vies:
        H[:n, iB] = 1.0
    a_h = np.ones(n) if a_h is None else np.asarray(a_h, float)    # persistência do efeito de casa (λ nas pesquisas pós-1º turno)
    H[np.arange(n), i0h + pidx] = a_h
    yy = np.concatenate([np.asarray(y, float) - mu_obs, [e["valor"] - e.get("mu", 0.0) for e in extras]])
    r_extra = np.array([e["sd"] ** 2 for e in extras])
    for j, e in enumerate(extras):
        H[n + j, e["dia"]] = 1.0
        if e.get("com_vies") and com_vies:
            H[n + j, iB] = 1.0
    # prior fixo: efeitos de casa, restrição da âncora, x_0
    Q_fix = np.zeros((K, K)); b_fix = np.zeros(K)
    m_h = np.array([prior_h.get(p, 0.0) for p in insts]); t2 = tau_h ** 2
    Q_fix[i0h + np.arange(P), i0h + np.arange(P)] += 1.0 / t2
    b_fix[i0h:] += m_h / t2
    a = np.zeros(K)
    for j, p in enumerate(insts):
        if p in ancora:
            a[i0h + j] = q_ancora.get(p, 1.0)
    if not a.any():
        a[i0h:] = 1.0
    Q_fix += np.outer(a, a) / KAPPA
    x0_m = float(np.mean(np.sort(np.asarray(y, float)[np.argsort(dia)][:10])))   # média das primeiras ondas, prior difuso
    Q_fix[0, 0] += 1.0 / x0_sd ** 2; b_fix[0] += x0_m / x0_sd ** 2
    return {"H": H, "yy": yy, "n": n, "r_extra": r_extra, "pidx": pidx, "insts": insts, "P": P, "D": D, "K": K, "iB": iB,
            "i0h": i0h, "Q_fix": Q_fix, "b_fix": b_fix, "se2": np.asarray(se2, float), "dia": np.asarray(dia, int), "x0_sd": x0_sd, "a_h": a_h}


def _var_passos(D, dia_t1, sd_quebra, s_rw, s_entre):
    """Variância de η_d para d = 1 … D−1: σ_rw² até o 1º turno, + quebra no dia seguinte, σ_entre² depois."""
    d = np.arange(1, D)
    v = np.where(d <= dia_t1 + 1, s_rw ** 2, s_entre ** 2)
    if sd_quebra > 0 and 1 <= dia_t1 + 1 < D:
        v[dia_t1] += sd_quebra ** 2
    return v


def _posterior(M, v, sig_p, omega, s_b):
    """Precisão e vetor canônico da normal conjunta de z dados os hiperparâmetros; devolve (L, b, log p(y|θ) a menos de constante)."""
    D, n = M["D"], M["n"]
    r = np.concatenate([M["se2"] + sig_p[M["pidx"]] ** 2, M["r_extra"]])
    w = 1.0 / r
    Hw = M["H"] * w[:, None]
    Q = M["Q_fix"] + M["H"].T @ Hw
    b = M["b_fix"] + Hw.T @ M["yy"]
    iv = 1.0 / v
    di = np.arange(D)
    diag = np.zeros(D); diag[1:] += iv; diag[:-1] += iv
    Q[di, di] += diag
    Q[di[1:], di[:-1]] -= iv; Q[di[:-1], di[1:]] -= iv
    if M["iB"] is not None:
        Q[M["iB"], M["iB"]] += omega / s_b ** 2
    L = cholesky(Q, lower=True, check_finite=False)
    u = solve_triangular(L, b, lower=True, check_finite=False)
    # log p(y|θ) = ½log|Q0| − ½log|Q| + ½ bᵀQ⁻¹b − ½ Σ log r − ½ yᵀR⁻¹y (+ constantes); |Q0| só tem a parte de x e de β que varia
    ll = (-0.5 * np.sum(np.log(v)) - np.sum(np.log(np.diag(L))) + 0.5 * u @ u - 0.5 * np.sum(np.log(r)) - 0.5 * np.sum(w * M["yy"] ** 2))
    if M["iB"] is not None:
        ll += 0.5 * math.log(omega)
    return L, u, ll


def _lp_logsig_p(ls, m):
    """Log-prior de log σ_p: normal truncada em σ_p ≥ piso (o termo de truncamento depende de m)."""
    lo = math.log(SIG_P_PISO)
    if np.any(ls < lo):
        return -np.inf
    return float(np.sum(-0.5 * ((ls - m) / SIG_P_SD) ** 2) - len(ls) * norm.logsf((lo - m) / SIG_P_SD))


def _cadeia(M, rng, n_aquec, n_amostras, dia_t1, sd_quebra, prior_rw, prior_entre, sd_vies, n_fut_rw, n_fut_entre, entre_tem_dados, prior_mu_der=(0.0, 0.0)):
    D, P = M["D"], M["P"]
    s_b = sd_vies * math.sqrt(K_T) if sd_vies > 0 else 1.0
    lrw = prior_rw[0] + 0.3 * rng.standard_normal()
    lent = prior_entre[0] + 0.3 * rng.standard_normal()
    ls = np.log(np.maximum(2.0 * np.exp(0.2 * rng.standard_normal(P)), SIG_P_PISO + 0.05))
    m = M_PRIOR[0]; omega = 1.0
    passo = {"rw": 0.25, "entre": 0.3, "m": 0.3}; passo_p = np.full(P, 0.4)
    ac = {"rw": [], "entre": [], "m": []}; ac_p = []
    tot = n_aquec + n_amostras
    out = {k: np.empty(n_amostras) for k in ["x_hoje", "x_ultimo", "x_final", "sig_rw", "sig_entre", "beta", "deriva", "m_sigp", "mu_der"]}
    out["h"] = np.empty((n_amostras, P)); out["sig_p"] = np.empty((n_amostras, P)); out["x"] = np.empty((n_amostras, D))
    lp_rw = lambda z: -0.5 * ((z - prior_rw[0]) / prior_rw[1]) ** 2
    lp_en = lambda z: -0.5 * ((z - prior_entre[0]) / prior_entre[1]) ** 2
    for it in range(tot):
        sp = np.exp(ls)
        L, u, ll = _posterior(M, _var_passos(D, dia_t1, sd_quebra, math.exp(lrw), math.exp(lent)), sp, omega, s_b)
        # 1a. σ_rw (z integrado)
        z_ = lrw + passo["rw"] * rng.standard_normal()
        L2, u2, ll2 = _posterior(M, _var_passos(D, dia_t1, sd_quebra, math.exp(z_), math.exp(lent)), sp, omega, s_b)
        aceita = math.log(rng.random()) < ll2 + lp_rw(z_) - ll - lp_rw(lrw)
        if aceita:
            lrw, L, u, ll = z_, L2, u2, ll2
        ac["rw"].append(aceita)
        # 1b. σ_entre: só tem verossimilhança se houver dias observados depois do 1º turno; senão, sorteio direto do prior
        if entre_tem_dados:
            z_ = lent + passo["entre"] * rng.standard_normal()
            L2, u2, ll2 = _posterior(M, _var_passos(D, dia_t1, sd_quebra, math.exp(lrw), math.exp(z_)), sp, omega, s_b)
            aceita = math.log(rng.random()) < ll2 + lp_en(z_) - ll - lp_en(lent)
            if aceita:
                lent, L, u, ll = z_, L2, u2, ll2
            ac["entre"].append(aceita)
        else:
            lent = prior_entre[0] + prior_entre[1] * rng.standard_normal()
        # 2. (x, β, h) em bloco: z = Q⁻¹b + L⁻ᵀε
        zv = solve_triangular(L.T, u + rng.standard_normal(M["K"]), lower=False, check_finite=False)
        x = zv[:D]; beta = zv[M["iB"]] if M["iB"] is not None else 0.0; h = zv[M["i0h"]:]
        # 3. σ_p por instituto (Metropolis vetorizado, dado o resíduo)
        res = M["yy"][:M["n"]] - x[M["dia"]] - beta - M["a_h"] * h[M["pidx"]]
        def ll_p(lsv):
            r_ = M["se2"] + np.exp(lsv)[M["pidx"]] ** 2
            return np.bincount(M["pidx"], weights=-0.5 * np.log(r_) - 0.5 * res ** 2 / r_, minlength=P)
        lo = math.log(SIG_P_PISO)
        prop = ls + passo_p * rng.standard_normal(P)
        lp_c = -0.5 * ((ls - m) / SIG_P_SD) ** 2; lp_n = np.where(prop >= lo, -0.5 * ((prop - m) / SIG_P_SD) ** 2, -np.inf)
        a_p = np.log(rng.random(P)) < ll_p(prop) + lp_n - ll_p(ls) - lp_c
        ls = np.where(a_p, prop, ls); ac_p.append(a_p)
        # 3b. m (centro hierárquico de log σ_p)
        m_ = m + passo["m"] * rng.standard_normal()
        lm = lambda mm: _lp_logsig_p(ls, mm) - 0.5 * ((mm - M_PRIOR[0]) / M_PRIOR[1]) ** 2
        aceita = math.log(rng.random()) < lm(m_) - lm(m)
        if aceita:
            m = m_
        ac["m"].append(aceita)
        # 4. ω | β (t₅ como mistura de escala de normais)
        if M["iB"] is not None:
            omega = rng.gamma(DF / 2 + 0.5, 1.0 / (DF / 2 + beta ** 2 / (2 * s_b ** 2)))
        # adaptação dos passos no aquecimento (alvo ~0,44 em uma dimensão)
        if it < n_aquec and (it + 1) % 50 == 0:
            for k in passo:
                if ac[k]:
                    passo[k] *= math.exp(np.mean(ac[k][-50:]) - 0.44)
            passo_p *= np.exp(np.mean(ac_p[-50:], axis=0) - 0.44)
        if it >= n_aquec:
            j = it - n_aquec
            # previsão: deriva até 25/out com caudas t₅ e o mesmo desvio da versão normal
            v_fut = n_fut_rw * math.exp(lrw) ** 2 + n_fut_entre * math.exp(lent) ** 2
            om_d = rng.gamma(DF / 2, 2.0 / DF)
            # média da deriva (variante com momentum): sorteada do prior a cada iteração, sem verossimilhança própria (como σ_entre sem dados)
            mu_d = prior_mu_der[0] + prior_mu_der[1] * rng.standard_normal() if prior_mu_der[1] > 0 else prior_mu_der[0]
            der = mu_d + math.sqrt(v_fut * K_T / om_d) * rng.standard_normal()
            out["x_ultimo"][j] = x[-1]; out["x_final"][j] = x[-1] + der; out["deriva"][j] = der; out["mu_der"][j] = mu_d
            out["sig_rw"][j] = math.exp(lrw); out["sig_entre"][j] = math.exp(lent); out["beta"][j] = beta
            out["m_sigp"][j] = math.exp(m); out["h"][j] = h; out["sig_p"][j] = np.exp(ls); out["x"][j] = x
    taxa = {k: round(float(np.mean(v[n_aquec:])), 3) for k, v in ac.items() if len(v) > n_aquec}
    taxa["sig_p_media"] = round(float(np.mean(ac_p[n_aquec:])), 3)
    return out, taxa


def amostrar(y, dia, se2, inst, n_dias, ancora, q_ancora=None, mu_obs=None, sd_vies=0.0, prior_h=None, tau_h=1.5,
             dia_t1=10 ** 9, sd_quebra=0.0, extras=(), prior_rw=(math.log(0.25), 0.6), prior_entre=(math.log(0.25), 0.5),
             n_fut_rw=0, n_fut_entre=0, dia_hoje=None, n_cadeias=4, n_aquec=1500, n_amostras=4000, seed=20261025, x0_sd=10.0, a_h=None,
             prior_mu_der=(0.0, 0.0)):
    """
    y, dia, se2, inst: pesquisas (dia inteiro desde a origem da janela). n_dias: nº de dias do estado (0 … n_dias−1).
    mu_obs: desconto de viés esperado de cada pesquisa (antes/depois do 1º turno). sd_vies: desvio do erro comum β (0 = sem β).
    dia_t1: índice do dia do 1º turno; dia_t1+1 recebe a quebra sd_quebra² e os dias seguintes usam σ_entre.
    extras: observações diretas do estado, dicts {dia, valor, sd, com_vies, mu} (nível estrutural, leitura estadual).
    n_fut_rw / n_fut_entre: dias entre o último dia do estado e 25/out em cada regime de deriva. dia_hoje: dia reportado como "hoje".
    prior_mu_der: (média, desvio) da média da deriva até 25/out; (0, 0) = deriva centrada em zero (modelo principal).
    Retorna dict com as amostras (cadeias × sorteios), taxas de aceitação e diagnósticos.
    """
    q_ancora = q_ancora or {}; prior_h = prior_h or {}
    mu_obs = np.zeros(len(y)) if mu_obs is None else np.asarray(mu_obs, float)
    M = _monta(np.asarray(y, float), np.asarray(dia, int), se2, np.asarray(inst), n_dias, mu_obs, set(ancora), q_ancora, prior_h,
               tau_h, list(extras), sd_vies, x0_sd, a_h)
    entre_tem_dados = n_dias - 1 > dia_t1 + 1
    dia_hoje = n_dias - 1 if dia_hoje is None else dia_hoje
    cad, taxas = [], []
    for c in range(n_cadeias):
        o, t = _cadeia(M, np.random.default_rng([seed, c]), n_aquec, n_amostras, dia_t1, sd_quebra, prior_rw, prior_entre, sd_vies,
                       n_fut_rw, n_fut_entre, entre_tem_dados, prior_mu_der)
        o["x_hoje"] = o["x"][:, dia_hoje]
        cad.append(o); taxas.append(t)
    S = {k: np.stack([o[k] for o in cad]) for k in cad[0]}
    diag = {k: {"rhat": round(rhat(S[k]), 4), "ess": int(ess(S[k]))} for k in ["x_hoje", "x_final", "sig_rw", "sig_entre", "beta", "m_sigp"] + (["mu_der"] if prior_mu_der[1] > 0 else [])}
    for j, p in enumerate(M["insts"]):
        diag[f"h[{p}]"] = {"rhat": round(rhat(S["h"][:, :, j]), 4), "ess": int(ess(S["h"][:, :, j]))}
        diag[f"sig_p[{p}]"] = {"rhat": round(rhat(S["sig_p"][:, :, j]), 4), "ess": int(ess(S["sig_p"][:, :, j]))}
    return {"amostras": S, "insts": M["insts"], "aceitacao": taxas, "diagnostico": diag,
            "rhat_max": max(d["rhat"] for d in diag.values()), "ess_min": min(d["ess"] for d in diag.values()),
            "n_cadeias": n_cadeias, "n_aquec": n_aquec, "n_amostras": n_amostras, "entre_tem_dados": bool(entre_tem_dados)}


# ---------------- diagnósticos ----------------
def _split(x):
    x = np.asarray(x, float)
    h = x.shape[1] // 2
    return np.concatenate([x[:, :h], x[:, h:2 * h]])


def rhat(x):
    """R̂ dividido (Gelman et al., BDA3): cadeias × sorteios."""
    s = _split(x)
    m, n = s.shape
    W = s.var(axis=1, ddof=1).mean()
    B = n * s.mean(axis=1).var(ddof=1)
    if W <= 0:
        return 1.0
    return float(math.sqrt(((n - 1) / n * W + B / n) / W))


def ess(x):
    """Tamanho efetivo de amostra multicadeia (Geyer: soma de pares positivos e monótonos de autocorrelação)."""
    s = _split(x)
    m, n = s.shape
    if s.var() == 0:
        return float(m * n)
    xc = s - s.mean(axis=1, keepdims=True)
    f = np.fft.rfft(xc, n=2 * n, axis=1)
    acov = np.fft.irfft(f * np.conj(f), axis=1)[:, :n] / n
    W = s.var(axis=1, ddof=1).mean()
    var_mais = (n - 1) / n * W + s.mean(axis=1).var(ddof=1)
    rho = 1.0 - (W - acov.mean(axis=0)) / var_mais
    rho[0] = 1.0
    soma, prev = 0.0, np.inf
    for t in range(0, n - 1, 2):
        par = rho[t] + rho[t + 1]
        if par < 0:
            break
        par = min(par, prev); prev = par
        soma += par
    tau = -1.0 + 2.0 * soma
    return float(m * n / max(tau, 1.0 / math.log10(m * n)))


# ---------------- verificação ----------------
if __name__ == "__main__":
    import sys, os, time
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from espaco_estados import _kalman
    # (a) com hiperparâmetros fixos, sem β e sem efeitos de casa, a normal conjunta coincide com o suavizador RTS do filtro de Kalman
    rng = np.random.default_rng(1)
    D = 80; xs_true = np.cumsum(np.r_[2.0, 0.3 * rng.standard_normal(D - 1)])
    dd = np.sort(rng.integers(0, D, 40)); se2 = np.full(40, 4.0); yv = xs_true[dd] + 2 * rng.standard_normal(40)
    M = _monta(yv, dd, se2, np.array(["A"] * 40), D, np.zeros(40), {"A"}, {}, {}, 1.5, [], 0.0, 10.0)
    M["b_fix"][0] = yv[0] / 100.0      # mesmo x_0 do filtro: N(y_0, 100)
    L, u, _ = _posterior(M, np.full(D - 1, 0.3 ** 2), np.array([1.0]), 1.0, 1.0)
    mean = solve_triangular(L.T, u, lower=False)[:D]
    Linv = solve_triangular(L, np.eye(M["K"]), lower=True); var = (Linv ** 2).sum(axis=0)[:D]
    _, xs_k, Ps_k, _, _ = _kalman(yv, dd, se2 + 1.0, 0.3, D, yv[0], keep=True)
    assert np.allclose(mean, xs_k, atol=1e-4), np.abs(mean - xs_k).max()
    assert np.allclose(var, Ps_k, atol=1e-4), np.abs(var - Ps_k).max()
    print(f"[ok] normal conjunta = suavizador RTS (dif. máx. média {np.abs(mean - xs_k).max():.1e}, variância {np.abs(var - Ps_k).max():.1e})")
    # (b) recuperação de parâmetros em dados simulados: casas, σ_rw, σ_p e o nível final
    rng = np.random.default_rng(7)
    D = 120; srw = 0.2; casas = {"A": 0.0, "B": 0.0, "C": 2.5, "D": -2.0, "E": 1.0}; sigp = {"A": 1.0, "B": 1.0, "C": 3.0, "D": 1.5, "E": 1.0}
    xt = np.cumsum(np.r_[-1.0, srw * rng.standard_normal(D - 1)])
    rows = [(d, p) for d in range(0, D, 2) for p in casas if rng.random() < 0.35]
    dd = np.array([d for d, _ in rows]); ii = np.array([p for _, p in rows]); se2 = np.full(len(rows), 2.2 ** 2)
    yv = xt[dd] + np.array([casas[p] for p in ii]) + np.sqrt(se2 + np.array([sigp[p] ** 2 for p in ii])) * rng.standard_normal(len(rows))
    t0 = time.time()
    R = amostrar(yv, dd, se2, ii, D, {"A", "B"}, n_cadeias=4, n_aquec=800, n_amostras=1500, seed=3)
    S = R["amostras"]
    def cobre(a, v, lo=5, hi=95): q_ = np.percentile(a, [lo, hi]); return q_[0] <= v <= q_[1]
    assert cobre(S["sig_rw"], srw), np.percentile(S["sig_rw"], [5, 95])
    assert cobre(S["x_hoje"], xt[-1]), (np.percentile(S["x_hoje"], [5, 95]), xt[-1])
    for j, p in enumerate(R["insts"]):
        assert cobre(S["h"][:, :, j], casas[p] - (casas["A"] + casas["B"]) / 2), (p, np.percentile(S["h"][:, :, j], [5, 95]))
    assert R["rhat_max"] < 1.05, R["rhat_max"]
    print(f"[ok] recuperação: σ_rw {np.median(S['sig_rw']):.3f} (verdadeiro {srw}), nível final {np.median(S['x_hoje']):+.2f} "
          f"(verdadeiro {xt[-1]:+.2f}), R̂ máx {R['rhat_max']:.3f}, ESS mín {R['ess_min']}, {len(rows)} pesquisas, {time.time() - t0:.1f} s")
migracao_markov.py — migração de ausentes, 3ª via e brancos/nulos: cadeia de Markov + Monte Carlo, campanha do 2º turno e autoverificações (clique para expandir)
# -*- coding: utf-8 -*-
"""
Migração de ausentes, terceira via e brancos/nulos entre o 1º e o 2º turno: cadeia de Markov + Monte Carlo (análise paralela).

Pergunta: para onde vão, até 25/out, os 33,5 mi que não votaram em 4/out, os 9,3 mi que votaram na terceira via e os 6,0 mi de
brancos e nulos, e quanto uma campanha de conversa pessoal do PT com esses grupos (guia "Três nomes, três semanas") poderia mudar.

Estrutura (tudo em votos absolutos, por UF + exterior, a partir do resultado oficial em resultado_1turno_uf.csv):
 1. Estado inicial n_u = (Lula, Flávio, Cury, Renan, Caiado, Zema+outros, brancos+nulos, abstenção), Σ = eleitorado_u.
 2. Matriz de um passo M_u (8 origens × 4 destinos: Lula, Flávio, branco/nulo, não vota), uma por sorteio e por UF: cada linha é
    Dirichlet(κ·m) em torno de um centro m sorteado por fora (2ª opção declarada da Quaest; decisão dos "sem 2ª opção" e dos
    brancos/nulos; fluxos brutos de abstenção: r_out sai, r_in entra, com inclinação de perfil τ). destinos_u = n_u · M_u.
 3. Cadeia de Markov da campanha (5 a 24/out, não homogênea, fases do guia): por bolsão (ausentes, terceira via + brancos/nulos,
    eleitor brando de Flávio), estados S0 não alcançado · S1 alcançado · S2 convertido (plano de voto) · S3 confirmado · S4 recusou.
    O alcance vem do nº de voluntários (V_0 inicial, cada um convida 3 com aceitação p_ac, até 3 gerações, teto ψ·votos de Lula);
    a conversão por contato q vem da literatura de mobilização (Gerber & Green; Nickerson & Rogers; Kalla & Broockman). O estoque
    convertido é repartido pelas UFs ∝ voto de Lula e aplicado como fluxo entre destinos, com teto na massa disponível.
 4. Cenários: escada de alcance (sem campanha dirigida / moderada / forte / máxima) × resposta de Flávio (nenhuma / simétrica, rede
    ρ× a do PT e os mesmos priors de eficácia) + referência "metas do guia cumpridas". Números aleatórios comuns entre células.
 5. Saídas: margem Lula − Flávio em válidos, abstenção e brancos/nulos do 2º turno, saldo por frente, trajetória diária, por região
    e por UF, matriz de transição mediana; P(Lula) "só migração" e "com deriva" (a deriva t₅ do modelo principal, para comparar).
Não é previsão: é análise condicional. A manchete continua sendo o modelo principal. Autoverificações: python migracao_markov.py
"""
import json, math, os, time, html as H
import numpy as np
import pandas as pd

ORIG = ["lula", "flavio", "cury", "renan", "caiado", "zo", "bn", "abs"]
DEST = ["lula", "flavio", "bn", "abs"]
TV = ["cury", "renan", "caiado", "zo"]
ROT_ORIG = {"lula": "Votou em Lula", "flavio": "Votou em Flávio", "cury": "Votou em Cury", "renan": "Votou em Renan", "caiado": "Votou em Caiado",
            "zo": "Votou em Zema ou outros", "bn": "Votou branco ou nulo", "abs": "Não votou"}
ROT_DEST = {"lula": "Lula", "flavio": "Flávio", "bn": "Branco/nulo", "abs": "Não vota"}
REGIAO_NOME = {"N": "Norte", "NE": "Nordeste", "CO": "Centro-Oeste", "SE": "Sudeste", "S": "Sul", "EXT": "Exterior"}
KAPPA = {"lula": 400, "flavio": 400, "cury": 100, "renan": 100, "caiado": 100, "zo": 100, "bn": 150, "abs": 150}
INICIO, FIM, T2 = pd.Timestamp("2026-10-05"), pd.Timestamp("2026-10-24"), pd.Timestamp("2026-10-25")
DIAS = pd.date_range(INICIO, FIM)                 # 20 dias de campanha
# fases do guia: (nome, início, fim, multiplicador do hazard de conversão, hazard de confirmação/dia, hazard de encerramento/dia, geração recrutada)
FASES = [("Largada", "2026-10-05", "2026-10-06", 0.0, 0.0, 0.0, 0),
         ("1ª conversa", "2026-10-07", "2026-10-11", 0.8, 0.0, 0.0, 1),
         ("Pausa (Aparecida)", "2026-10-12", "2026-10-12", 0.0, 0.0, 0.0, None),
         ("2ª conversa", "2026-10-13", "2026-10-18", 1.4, 0.0, 0.0, 2),
         ("Fechamento", "2026-10-19", "2026-10-22", 0.8, 0.15, 0.3, 3),
         ("Confirmação", "2026-10-23", "2026-10-24", 0.0, 0.4, 0.3, None)]
BOLSOES = ["ausentes", "terceira_via", "flavio_brando"]
REALIZA = {"ausentes": (0.85, 0.55), "terceira_via": (0.90, 0.55), "flavio_brando": (0.90, 0.55)}   # P(vota como combinado | S3), (| S2)
CENARIOS = [("sem", "Sem campanha dirigida", 0.0, 0.0), ("moderada", "Moderada", 100_000, 0.5),
            ("forte", "Forte", 500_000, 1.0), ("maxima", "Máxima", 2_000_000, 1.5)]
RESPOSTAS = [("nenhuma", "Nenhuma"), ("simetrica", "Simétrica")]
GUIA_METAS = {"terceira_via": 2.4e6, "eleitores_flavio": 1.2e6, "brancos_nulos": 1.0e6, "ausentes": 0.5e6}   # saldo (L − F) por frente
DF_T = 5
BINS = np.arange(-8.0, 4.01, 0.25)          # histograma da margem só pela migração (caudas somadas nas pontas)
# (nome, símbolo, distribuição, média, desvio, fonte)
PRIORS = [
    ("Troca direta entre Lula e Flávio", "ε_x", "Beta", 0.005, 0.003, "prior explícito: troca direta rara entre os dois"),
    ("Eleitor de Lula ou Flávio que vota branco/nulo", "ε_bn", "Beta", 0.01, 0.005, "prior explícito"),
    ("Votou no 1º turno e falta no 2º", "r_out", "Beta", 0.05, 0.015, "cerca de 6 mi; em 2022 a abstenção quase não mudou entre os turnos, com troca de pessoas"),
    ("Variação líquida nacional da abstenção (p.p.)", "δ_nac", "Normal", 0.5, 1.0, "a abstenção subiu no 2º turno em 2006–2018 e caiu em 2022"),
    ("Variação líquida por UF além do padrão de 2022 (p.p.)", "δ_u", "Normal", 0.0, 0.7, "padrão 1º→2º turno de 2022 por UF encolhido 50% e cortado em ±3 (abstencao_2022_uf.csv)"),
    ("Inclinação de perfil de quem entra ou sai (p.p. de margem pró-Lula)", "τ", "Normal, ≥ 0", 10.0, 5.0, "abstenção por escolaridade 2022 (27/17/13%) × voto por escolaridade (Quaest 3/out) ≈ +6; K = 0,25 da seção Abstenção ≈ 20"),
    ("\"Sem 2ª opção\" da 3ª via que escolhem um dos dois", "d_3", "Beta", 0.50, 0.12, "modelo estrutural: pool decidido a 55%"),
    ("Parcela de Lula entre os que escolhem", "s_3", "Beta", 0.50, 0.08, "faixa 35–65% do tornado"),
    ("Brancos e nulos do 1º turno que escolhem um dos dois", "d_bn", "Beta", 0.35, 0.10, "TSE 2022: brancos + nulos caíram de 7,0 mi no 1º turno para 5,7 mi no 2º"),
    ("Brancos e nulos que faltam no 2º turno", "a_bn", "Beta", 0.08, 0.03, "prior explícito"),
    ("Aceitação do convite para montar a própria lista", "p_ac", "Beta", 0.25, 0.10, "organização relacional: adesão de 1 em 4 convidados"),
    ("Teto de voluntários (fração dos votos de Lula)", "ψ", "Beta", 0.05, 0.02, "2,7 mi no centro, ordem de grandeza dos filiados do PT"),
    ("Conversão por contato: ausentes", "q_aus", "Beta", 0.12, 0.05, "Gerber & Green (porta a porta: 1 voto por ~14 contatos); Nickerson & Rogers (plano de voto: +4 p.p.); organização relacional: +5 a +10 p.p."),
    ("Conversão por contato: 3ª via e brancos/nulos", "q_tv", "Beta", 0.05, 0.03, "Kalla & Broockman 2018: persuasão ≈ 0 em eleição geral, não nula entre indecisos; só quem não tem 2ª opção é permeável"),
    ("Conversão por contato: eleitor brando de Flávio", "q_fb", "Beta", 0.02, 0.015, "idem, com o voto já dado ao adversário"),
    ("Eleitores brandos (fração do voto de cada candidato)", "f_br", "Beta", 0.12, 0.04, "guia: 600 mil \"pouco convictos\" é o piso"),
    ("Reversão diária de convertido para alcançado", "δ_rev", "Beta", 0.03, 0.01, "plano de voto que não se cumpre"),
    ("Parcela de Lula entre ausentes trazidos nominalmente", "ν", "Beta", 0.80, 0.07, "trabalho nominal: só a lista do voluntário"),
    ("Chamado aberto: queda dos ausentes nas UFs de Lula (× intensidade)", "ω", "Beta", 0.015, 0.01, "Ipea 2023: passe livre no 2º turno de 2022 sem efeito mensurável; teto baixo"),
    ("Rede de Flávio ÷ rede do PT (resposta simétrica)", "ρ", "Log-normal", 0.6, 0.4, "mesmos priors de eficácia, menos capilaridade"),
    ("Voluntários iniciais (dispersão em torno do cenário)", "V_0", "Log-normal", None, 0.5, "desconhecido: a escada de cenários vai de 0 a 2 mi"),
]


def priors():
    return [{"nome": n, "simbolo": s, "dist": d, "media": m, "sd": sd, "fonte": f} for n, s, d, m, sd, f in PRIORS]


def _p(simbolo):
    for n, s, d, m, sd, f in PRIORS:
        if s == simbolo: return m, sd
    raise KeyError(simbolo)


def _beta(rng, mu, sd, size=None):
    k = mu*(1 - mu)/sd**2 - 1
    assert k > 0, (mu, sd)
    return rng.beta(mu*k, (1 - mu)*k, size)


def _dirichlet(rng, centro, kappa):
    g = rng.gamma(np.maximum(kappa*centro, 1e-6))
    return g/g.sum(axis=-1, keepdims=True)


def _t_unit(rng, n):
    return rng.standard_t(DF_T, n)*math.sqrt((DF_T - 2)/DF_T)


# ------------------------------------------------------------------ dados
def disponivel(DIR):
    return all(os.path.exists(DIR + a) for a in ("resultado_1turno_uf.csv", "referencia_uf.csv", "abstencao_2022_uf.csv"))


def carregar(DIR):
    r = pd.read_csv(DIR + "resultado_1turno_uf.csv")
    br = r[r.uf == "BR"].iloc[0]
    r = r[r.uf != "BR"].reset_index(drop=True)
    ref = pd.read_csv(DIR + "referencia_uf.csv")[["uf", "nome", "regiao"]]
    ab = pd.read_csv(DIR + "abstencao_2022_uf.csv")[["uf", "abst_1t_2022", "abst_2t_2022"]]
    r = r.merge(ref, on="uf", how="left").merge(ab, on="uf", how="left")
    r["regiao"] = r.regiao.fillna("EXT"); r["nome"] = r.nome.fillna("Exterior")
    n = np.column_stack([r.lula, r.flavio, r.cury, r.renan, r.caiado, r.zema + r.outros, r.brancos + r.nulos, r.abstencao]).astype(float)
    return {"tab": r, "n": n, "br": br, "ufs": list(r.uf), "regiao": list(r.regiao), "nome": list(r.nome),
            "eleitorado": n.sum(1), "eleitorado_tse": r.eleitorado.values.astype(float), "comparecimento": r.comparecimento.values.astype(float),   # Σ das caixas (a tabela do TSE difere do eleitorado em poucas centenas em algumas UFs)
            "pad22": np.clip(0.5*(r.abst_2t_2022 - r.abst_1t_2022).fillna(0.0).values, -3, 3),
            "zema": r.zema.values.astype(float), "outros": r.outros.values.astype(float), "ext": np.array([u == "EXT" for u in r.uf])}


def _transf_uf(D, transf):
    """(pF, pL) por origem; Zema e outros fundidos com peso pelos votos de cada UF. Retorna dict origem → (pF (U,), pL (U,))."""
    U = len(D["ufs"]); out = {}
    for o in ("cury", "renan", "caiado"):
        pf, pl = transf[o]; out[o] = (np.full(U, pf), np.full(U, pl))
    z, x = D["zema"], D["outros"]; w = np.where(z + x > 0, z/np.maximum(z + x, 1), 0.5)
    out["zo"] = (w*transf["zema"][0] + (1 - w)*transf["outros"][0], w*transf["zema"][1] + (1 - w)*transf["outros"][1])
    for o, (pf, pl) in out.items():
        assert np.all(pf + pl <= 1.0 + 1e-9), (o, "2ª opção soma mais de 100%")
    return out


def estrutural_check(D, transf, decis=0.55, split=0.5):
    """Reprodução determinística do modelo estrutural: 2ª opção nos centros, pool (sem 2ª opção + brancos/nulos) decidido a 55% e dividido 50/50."""
    n, tr = D["n"], _transf_uf(D, transf)
    L, F = n[:, 0].sum(), n[:, 1].sum(); pool = n[:, 6].sum()
    for j, o in enumerate(TV):
        pf, pl = tr[o]; v = n[:, 2 + j]
        L += (v*pl).sum(); F += (v*pf).sum(); pool += (v*(1 - pf - pl)).sum()
    L += pool*decis*split; F += pool*decis*(1 - split)
    return 100*(L - F)/(L + F)


# ------------------------------------------------------------------ matriz de um passo
def sortear_base(rng, N, U):
    """Parâmetros nacionais por sorteio (comuns a todas as UFs e a todas as células)."""
    B = {}
    for k, s in [("eps_x", "ε_x"), ("eps_bn", "ε_bn"), ("r_out", "r_out"), ("d3", "d_3"), ("s3", "s_3"), ("d_bn", "d_bn"), ("a_bn", "a_bn"),
                 ("p_ac", "p_ac"), ("psi", "ψ"), ("q_aus", "q_aus"), ("q_tv", "q_tv"), ("q_fb", "q_fb"), ("f_br", "f_br"), ("d_rev", "δ_rev"),
                 ("nu", "ν"), ("omega", "ω")]:
        m, sd = _p(s); B[k] = _beta(rng, m, sd, N)
    for k, s in [("q_aus_F", "q_aus"), ("q_tv_F", "q_tv"), ("q_fb_F", "q_fb")]:      # eficácia da resposta de Flávio: mesmos priors, sorteio próprio
        m, sd = _p(s); B[k] = _beta(rng, m, sd, N)
    m, sd = _p("δ_nac"); B["delta_nac"] = rng.normal(m, sd, N)
    m, sd = _p("δ_u"); B["delta_u"] = rng.normal(m, sd, (N, U))
    m, sd = _p("τ"); B["tau"] = np.clip(rng.normal(m, sd, N), 0.0, None)
    m, sd = _p("ρ"); B["rho"] = np.exp(math.log(m) + sd*rng.standard_normal(N))
    B["z_v0"] = rng.standard_normal(N)
    return B


def matriz(D, B, transf, rng, dirichlet=True):
    """Centros m (N, U, 8, 4) e, se dirichlet, linhas Dirichlet(κ·m) em torno deles. Devolve (M, extras)."""
    n = D["n"]; U, N = n.shape[0], len(B["r_out"])
    L, F, BN, ABS = n[:, 0], n[:, 1], n[:, 6], n[:, 7]
    s_u = L/(L + F)
    tr = _transf_uf(D, transf)
    bc = lambda x: np.broadcast_to(np.asarray(x, float).reshape(N, 1), (N, U))
    r = bc(B["r_out"]); th = bc(np.minimum(B["tau"]/100, 0.9)); ex, eb = bc(B["eps_x"]), bc(B["eps_bn"])
    d3, s3, dbn, abn = bc(B["d3"]), bc(B["s3"]), bc(B["d_bn"]), bc(B["a_bn"])
    rL, rF = r*(1 + th), r*(1 - th)                 # quem sai é mais pró-Lula (mesma τ da entrada): sem variação líquida, efeito ≈ 0
    M = np.zeros((N, U, 8, 4))
    M[:, :, 0] = np.stack([1 - ex - eb - rL, ex, eb, rL], -1)
    M[:, :, 1] = np.stack([ex, 1 - ex - eb - rF, eb, rF], -1)
    perm = {}                                        # massas permeáveis (centros) por origem e destino, para tetos e fluxos da campanha
    for j, o in enumerate(TV):
        pf, pl = tr[o]; res = np.clip(1 - pf - pl, 0, 1)[None, :]
        M[:, :, 2 + j] = np.stack([(pl[None, :] + res*d3*s3)*(1 - r), (pf[None, :] + res*d3*(1 - s3))*(1 - r), res*(1 - d3)*(1 - r), r], -1)
        base = n[None, :, 2 + j]*res*(1 - r)
        perm[o] = {"lula": base*d3*s3, "flavio": base*d3*(1 - s3), "bn": base*(1 - d3)}
    M[:, :, 6] = np.stack([dbn*s3*(1 - abn), dbn*(1 - s3)*(1 - abn), (1 - dbn)*(1 - abn), abn], -1)
    base = n[None, :, 6]*(1 - abn)
    perm["bn"] = {"lula": base*dbn*s3, "flavio": base*dbn*(1 - s3), "bn": base*(1 - dbn)}
    # abstenção: meta líquida por UF (padrão 2022 encolhido + nacional + UF) e taxa bruta de entrada derivada
    dA = D["pad22"][None, :] + bc(B["delta_nac"]) + B["delta_u"]
    r_in = np.clip((r*D["comparecimento"][None, :] - dA*D["eleitorado"][None, :]/100)/ABS[None, :], 0.02, 0.6)
    pi = np.clip(s_u[None, :] + bc(B["tau"])/200, 0.0, 1.0)
    M[:, :, 7] = np.stack([r_in*0.95*pi, r_in*0.95*(1 - pi), r_in*0.05, 1 - r_in], -1)
    assert np.allclose(M.sum(-1), 1.0, atol=1e-9)
    if dirichlet:
        for j, o in enumerate(ORIG):
            M[:, :, j] = _dirichlet(rng, M[:, :, j], KAPPA[o])
    return M, {"s_u": s_u, "pi": pi, "perm": perm, "r_in": r_in, "dA": dA}


# ------------------------------------------------------------------ cadeia de Markov da campanha
def _calendario(inicio):
    """Vetores diários: multiplicador de conversão normalizado (Σ = 1 nos dias ativos), hazards de confirmação e encerramento, dias de cada geração."""
    T = len(DIAS); mult = np.zeros(T); conf = np.zeros(T); enc = np.zeros(T); ger = {}
    for nome, a, b, m, c, e, g in FASES:
        idx = [i for i, d in enumerate(DIAS) if pd.Timestamp(a) <= d <= pd.Timestamp(b) and d >= inicio]
        for i in idx: mult[i], conf[i], enc[i] = m, c, e
        if g is not None: ger[g] = idx
    ativos = [i for i, d in enumerate(DIAS) if d >= inicio]
    for g in range(4):                                   # geração sem dias ativos (início tardio): entra no primeiro dia ativo
        if not ger.get(g): ger[g] = ativos[:1]
    w = mult/mult.sum() if mult.sum() > 0 else mult
    return w, conf, enc, ger


def voluntarios(V0, p_ac, teto, ger):
    """Voluntários acumulados por dia (N, T): V0 na largada, cada geração = 3·p_ac × a anterior, até 3 gerações, teto ψ·votos."""
    N, T = len(V0), len(DIAS); arr = np.zeros((N, T)); g = V0.copy()
    for k in range(4):
        if k > 0: g = g*3*p_ac
        for i in ger[k]: arr[:, i] += g/len(ger[k])
    return np.minimum(np.cumsum(arr, axis=1), teto[:, None])


def cadeia(V_t, P, q, d_rev, w, conf, enc):
    """Fracções da cadeia por dia: (T, N, B, 5) com estados S0 não alcançado · S1 alcançado · S2 convertido · S3 confirmado · S4 recusou.
    V_t (N, T) voluntários acumulados = nomes por bolsão; P (N, B) tamanho dos bolsões; q (N, B) conversão por contato."""
    N, T = V_t.shape; Bn = P.shape[1]
    x = np.zeros((N, Bn, 5)); x[:, :, 0] = 1.0
    traj = np.zeros((T, N, Bn, 5)); D_prev = np.zeros((N, Bn)); Pp = np.maximum(P, 1.0)
    for t in range(T):
        Dt = Pp*(1 - np.exp(-V_t[:, t][:, None]/Pp))         # nomes distintos alcançados até t (sorteio com reposição)
        novo = np.clip((Dt - D_prev)/Pp, 0, None); D_prev = Dt
        f12 = x[:, :, 1]*q*w[t]; f21 = x[:, :, 2]*d_rev[:, None]; f23 = x[:, :, 2]*conf[t]; f14 = x[:, :, 1]*enc[t]
        x[:, :, 0] -= novo; x[:, :, 1] += novo - f12 + f21 - f14; x[:, :, 2] += f12 - f21 - f23; x[:, :, 3] += f23; x[:, :, 4] += f14
        traj[t] = x
    return traj


# ------------------------------------------------------------------ simulação
def _q(a, ps):
    return [round(float(v), 3) for v in np.percentile(a, ps)]


def simular(DIR, transf, hoje, sd_deriva, comparacao=None, regiao_uf=None, transf_fonte="", seed=20261025, n_sorteios=20_000, inicio_cadeia=None):
    t0 = time.time()
    rng = np.random.default_rng(seed)
    D = carregar(DIR); n = D["n"]; U = n.shape[0]; N = n_sorteios
    hoje = pd.Timestamp(hoje); inicio = max(INICIO, pd.Timestamp(inicio_cadeia)) if inicio_cadeia is not None else INICIO
    if regiao_uf:
        D["regiao"] = [regiao_uf.get(u, r) for u, r in zip(D["ufs"], D["regiao"])]
    B = sortear_base(rng, N, U)
    M, X = matriz(D, B, transf, rng)
    T_base = n[None, :, :, None]*M                       # (N, U, 8, 4): massa de cada origem em cada destino
    elei = D["eleitorado"]; ext = D["ext"]; dom = ~ext
    L1, F1 = n[:, 0], n[:, 1]
    w_L = np.where(dom, L1, 0.0); w_L = w_L/w_L.sum(); w_F = np.where(dom, F1, 0.0); w_F = w_F/w_F.sum()
    lula_lidera = (X["s_u"] > 0.5) & dom
    s3, dbn = B["s3"], B["d_bn"]
    # bolsões nacionais (sem o exterior)
    P_aus = float(n[dom, 7].sum())
    P_tv = float(sum(sum(v[:, dom].sum(1) for v in X["perm"][o].values()).mean() for o in TV + ["bn"]))   # resíduo "sem 2ª opção" + brancos/nulos (médio)
    _tru = _transf_uf(D, transf)
    sem_2a = float(sum((n[:, 2 + j]*np.clip(1 - _tru[o][0] - _tru[o][1], 0, 1)).sum() for j, o in enumerate(TV)))   # 3ª via sem 2ª opção declarada
    P_fb = B["f_br"]*float(F1[dom].sum()); P_lb = B["f_br"]*float(L1[dom].sum())
    w, conf, enc, ger = _calendario(inicio)
    deriva = sd_deriva*_t_unit(rng, N)                   # a mesma deriva (t₅) em todas as células, para comparar com o modelo principal
    bins = BINS
    PS = [5, 10, 25, 50, 75, 90, 95]

    def frentes_lula(T, V_t, qs, I_cen, saldo):
        """Aplica a campanha do PT sobre T (in place). Devolve trajetória da cadeia e nomes alcançados por bolsão."""
        P = np.column_stack([np.full(N, P_aus), np.full(N, P_tv), P_fb])
        tr = cadeia(V_t, P, np.column_stack(qs), B["d_rev"], w, conf, enc)
        fim = tr[-1]
        conv = np.column_stack([P[:, b]*(REALIZA[BOLSOES[b]][0]*fim[:, b, 3] + REALIZA[BOLSOES[b]][1]*fim[:, b, 2]) for b in range(3)])   # votos realizados
        # ausentes trazidos nominalmente: ABS→L (ν) e ABS→F (1−ν), ∝ voto de Lula, teto na massa que ficaria em casa
        mov = np.minimum(conv[:, 0][:, None]*w_L[None, :], 0.9*T[:, :, 7, 3])
        T[:, :, 7, 3] -= mov; T[:, :, 7, 0] += B["nu"][:, None]*mov; T[:, :, 7, 1] += (1 - B["nu"])[:, None]*mov
        saldo["ausentes"] += ((2*B["nu"] - 1)[:, None]*mov).sum(1)
        # terceira via + brancos/nulos: sai dos destinos Flávio e branco/nulo das linhas permeáveis, ∝ massa permeável, → Lula
        permF = sum(X["perm"][o]["flavio"] for o in TV + ["bn"]); permB = sum(X["perm"][o]["bn"] for o in TV + ["bn"])
        avail = permF + permB
        mov = np.minimum(conv[:, 1][:, None]*w_L[None, :], 0.9*avail)
        for o in TV + ["bn"]:
            j = ORIG.index(o)
            mF = np.minimum(mov*X["perm"][o]["flavio"]/np.maximum(avail, 1), 0.9*T[:, :, j, 1]); mB = np.minimum(mov*X["perm"][o]["bn"]/np.maximum(avail, 1), 0.9*T[:, :, j, 2])
            T[:, :, j, 1] -= mF; T[:, :, j, 2] -= mB; T[:, :, j, 0] += mF + mB
            saldo["terceira_via" if o != "bn" else "brancos_nulos"] += (2*mF + mB).sum(1)
        # eleitor brando de Flávio: F→L, teto f_br da massa que ficaria com Flávio
        mov = np.minimum(conv[:, 2][:, None]*w_L[None, :], B["f_br"][:, None]*T[:, :, 1, 1])
        T[:, :, 1, 1] -= mov; T[:, :, 1, 0] += mov; saldo["eleitores_flavio"] += (2*mov).sum(1)
        # chamado aberto: nas UFs em que Lula liderou, ω·I_cen dos ausentes votam (composição π_u); nas de Flávio, 0,3× (sai pela culatra)
        inten = np.where(lula_lidera, 1.0, 0.3)[None, :]*np.where(dom, 1.0, 0.0)[None, :]*(B["omega"]*I_cen)[:, None]
        e = inten*T[:, :, 7, 3]
        T[:, :, 7, 3] -= e; T[:, :, 7, 0] += e*X["pi"]; T[:, :, 7, 1] += e*(1 - X["pi"])
        saldo["chamado_aberto"] += (e*(2*X["pi"] - 1)).sum(1)
        nomes = np.column_stack([P[:, b]*(1 - np.exp(-V_t[:, -1]/np.maximum(P[:, b], 1))) for b in range(3)])
        return tr, nomes, conv

    def frentes_flavio(T, V_t, qs, I_cen, saldo):
        """Resposta simétrica de Flávio: mesmos bolsões espelhados, ∝ voto de Flávio, com os mesmos priors de eficácia."""
        P = np.column_stack([np.full(N, P_aus), np.full(N, P_tv), P_lb])
        tr = cadeia(V_t, P, np.column_stack(qs), B["d_rev"], w, conf, enc)
        fim = tr[-1]
        conv = np.column_stack([P[:, b]*(REALIZA[BOLSOES[b]][0]*fim[:, b, 3] + REALIZA[BOLSOES[b]][1]*fim[:, b, 2]) for b in range(3)])
        s = np.zeros(N)
        mov = np.minimum(conv[:, 0][:, None]*w_F[None, :], 0.9*T[:, :, 7, 3])
        T[:, :, 7, 3] -= mov; T[:, :, 7, 1] += B["nu"][:, None]*mov; T[:, :, 7, 0] += (1 - B["nu"])[:, None]*mov
        s -= ((2*B["nu"] - 1)[:, None]*mov).sum(1)
        permL = sum(X["perm"][o]["lula"] for o in TV + ["bn"]); permB = sum(X["perm"][o]["bn"] for o in TV + ["bn"]); avail = permL + permB
        mov = np.minimum(conv[:, 1][:, None]*w_F[None, :], 0.9*avail)
        for o in TV + ["bn"]:
            j = ORIG.index(o)
            mL = np.minimum(mov*X["perm"][o]["lula"]/np.maximum(avail, 1), 0.9*T[:, :, j, 0]); mB = np.minimum(mov*X["perm"][o]["bn"]/np.maximum(avail, 1), 0.9*T[:, :, j, 2])
            T[:, :, j, 0] -= mL; T[:, :, j, 2] -= mB; T[:, :, j, 1] += mL + mB; s -= (2*mL + mB).sum(1)
        mov = np.minimum(conv[:, 2][:, None]*w_F[None, :], B["f_br"][:, None]*T[:, :, 0, 0])
        T[:, :, 0, 0] -= mov; T[:, :, 0, 1] += mov; s -= (2*mov).sum(1)
        inten = np.where(lula_lidera, 0.3, 1.0)[None, :]*np.where(dom, 1.0, 0.0)[None, :]*(B["omega"]*I_cen)[:, None]
        e = inten*T[:, :, 7, 3]
        T[:, :, 7, 3] -= e; T[:, :, 7, 0] += e*X["pi"]; T[:, :, 7, 1] += e*(1 - X["pi"])
        s += (e*(2*X["pi"] - 1)).sum(1)
        saldo["resposta_flavio"] += s
        return tr

    def agrega(T, cid, nome, rid, rnome, saldo, tr=None, nomes=None, conv=None, V_t=None, V_F=None):
        dest = T.sum(axis=2)                              # (N, U, 4)
        Lv, Fv, BNv, ABSv = (dest[:, :, k].sum(1) for k in range(4))
        marg = 100*(Lv - Fv)/(Lv + Fv)
        md = marg + deriva
        c = {"id": cid, "nome": nome, "resposta": rid, "resposta_nome": rnome,
             "margem_q": {str(p): round(float(v), 2) for p, v in zip(PS, np.percentile(marg, PS))},
             "p_lula_so_migracao": round(float((marg > 0).mean()), 3), "p_lula_com_deriva": round(float((md > 0).mean()), 3),
             "margem_com_deriva_q10_50_90": _q(md, [10, 50, 90]),
             "abstencao_q10_50_90": _q(100*ABSv/elei.sum(), [10, 50, 90]), "bn_pct_q10_50_90": _q(100*BNv/(Lv + Fv + BNv), [10, 50, 90]),
             "lula_mi_q50": round(float(np.median(Lv))/1e6, 3), "flavio_mi_q50": round(float(np.median(Fv))/1e6, 3),
             "hist_margem": [round(float(v), 4) for v in np.histogram(np.clip(marg, bins[0] + 1e-6, bins[-1] - 1e-6), bins=bins, density=True)[0]]}
        c["saldo_frentes"] = {k: {"q10": round(float(np.percentile(v, 10))), "q50": round(float(np.median(v))), "q90": round(float(np.percentile(v, 90)))} for k, v in saldo.items()}
        tot = sum(saldo.values())
        c["saldo_frentes"]["total"] = {"q10": round(float(np.percentile(tot, 10))), "q50": round(float(np.median(tot))), "q90": round(float(np.percentile(tot, 90)))}
        if nomes is not None:
            for b, k in enumerate(BOLSOES):
                c["saldo_frentes"][{"ausentes": "ausentes", "terceira_via": "terceira_via", "flavio_brando": "eleitores_flavio"}[k]].update(
                    {"contatos_q50": round(float(np.median(nomes[:, b]))), "convertidos_q50": round(float(np.median(conv[:, b])))})
        if V_t is not None:
            c["voluntarios_q10_50_90"] = [round(float(v)) for v in np.percentile(V_t[:, -1], [10, 50, 90])]
            c["nomes_distintos_q50"] = round(float(np.median(nomes.sum(1)))) if nomes is not None else 0
            c["trajetoria"] = {"dias": [str(d.date()) for d in DIAS],
                               "voluntarios": [[round(float(v)) for v in np.percentile(V_t[:, t], [10, 50, 90])] for t in range(len(DIAS))]}
            if V_F is not None: c["trajetoria"]["voluntarios_flavio"] = [[round(float(v)) for v in np.percentile(V_F[:, t], [10, 50, 90])] for t in range(len(DIAS))]
            if tr is not None:
                P = np.column_stack([np.full(N, P_aus), np.full(N, P_tv), P_fb])
                for b, k in enumerate(BOLSOES):
                    est = (tr[:, :, b, 2] + tr[:, :, b, 3])*P[None, :, b]      # (T, N) estoque convertido (plano de voto ou confirmado)
                    c["trajetoria"][k] = [[round(float(v)) for v in np.percentile(est[t], [10, 50, 90])] for t in range(len(DIAS))]
                c["estados_finais_q50"] = {k: [round(float(np.median(tr[-1, :, b, s])), 4) for s in range(5)] for b, k in enumerate(BOLSOES)}
        # por região e por UF
        dL, dF, dAbs = dest[:, :, 0], dest[:, :, 1], dest[:, :, 3]
        sal = (dL - dF) - (L1 - F1)[None, :]
        novos = T[:, :, 7, :3].sum(-1)
        regs = sorted(set(D["regiao"]), key=lambda r_: ["NE", "SE", "S", "N", "CO", "EXT"].index(r_) if r_ in ["NE", "SE", "S", "N", "CO", "EXT"] else 9)
        c["por_regiao"] = []
        for r_ in regs:
            m_ = np.array([x == r_ for x in D["regiao"]])
            c["por_regiao"].append({"regiao": r_, "nome": REGIAO_NOME.get(r_, r_), "ausentes_1t": int(n[m_, 7].sum()), "eleitorado": int(elei[m_].sum()),
                                    "lula_1t": int(L1[m_].sum()), "flavio_1t": int(F1[m_].sum()),
                                    "novos_eleitores_q50": round(float(np.median(novos[:, m_].sum(1)))),
                                    "saldo_q10": round(float(np.percentile(sal[:, m_].sum(1), 10))), "saldo_q50": round(float(np.median(sal[:, m_].sum(1)))),
                                    "saldo_q90": round(float(np.percentile(sal[:, m_].sum(1), 90))),
                                    "abst_1t": round(100*float(n[m_, 7].sum()/elei[m_].sum()), 2), "abst_2t_q50": round(float(np.median(100*dAbs[:, m_].sum(1)/elei[m_].sum())), 2),
                                    "margem_1t": round(100*float((L1[m_].sum() - F1[m_].sum())/(L1[m_].sum() + F1[m_].sum())), 2),
                                    "margem_2t_q50": round(float(np.median(100*(dL[:, m_].sum(1) - dF[:, m_].sum(1))/(dL[:, m_].sum(1) + dF[:, m_].sum(1)))), 2)})
        c["por_uf"] = [{"uf": u, "nome": D["nome"][i], "regiao": D["regiao"][i], "saldo_q50": round(float(np.median(sal[:, i]))),
                        "margem_1t": round(100*float((L1[i] - F1[i])/(L1[i] + F1[i])), 2),
                        "margem_2t_q50": round(float(np.median(100*(dL[:, i] - dF[:, i])/(dL[:, i] + dF[:, i]))), 2),
                        "abst_1t": round(100*float(n[i, 7]/elei[i]), 2), "abst_2t_q50": round(float(np.median(100*dAbs[:, i]/elei[i])), 2)}
                       for i, u in enumerate(D["ufs"])]
        return c, marg, dest

    def matriz_mediana(T):
        Mn = T.sum(axis=1)/n.sum(0)[None, :, None]           # (N, 8, 4) matriz nacional agregada por sorteio
        return {o: {d: [round(100*float(v), 2) for v in np.percentile(Mn[:, i, j], [10, 50, 90])] for j, d in enumerate(DEST)} for i, o in enumerate(ORIG)}

    cenarios, margens, checks = [], {}, {}
    mats = {}
    V_sem = None
    for rid, rnome in RESPOSTAS:
        for cid, cnome, V0_med, I_cen in CENARIOS:
            T = T_base.copy()
            saldo = {k: np.zeros(N) for k in ["ausentes", "terceira_via", "brancos_nulos", "eleitores_flavio", "chamado_aberto", "resposta_flavio"]}
            V0 = V0_med*np.exp(0.5*B["z_v0"])
            V_t = voluntarios(V0, B["p_ac"], B["psi"]*L1[dom].sum(), ger)
            tr, nomes, conv = frentes_lula(T, V_t, [B["q_aus"], B["q_tv"], B["q_fb"]], I_cen, saldo)
            V_F = None
            if rid == "simetrica" and V0_med > 0:
                V_F = voluntarios(B["rho"]*V0, B["p_ac"], B["psi"]*F1[dom].sum(), ger)
                frentes_flavio(T, V_F, [B["q_aus_F"], B["q_tv_F"], B["q_fb_F"]], I_cen, saldo)
            c, marg, dest = agrega(T, cid, cnome, rid, rnome, saldo, tr, nomes, conv, V_t, V_F)
            assert np.allclose(dest.sum(-1), elei[None, :], rtol=1e-9), "massa não conservada"
            cenarios.append(c); margens[(cid, rid)] = marg
            if rid == "nenhuma" and cid in ("sem", "forte"):
                mats[cid] = matriz_mediana(T)
            if cid == "forte" and rid == "nenhuma":
                nomes_forte = nomes; tr_forte = tr
            if cid == "sem" and rid == "nenhuma":
                dest_sem = dest
            del T
    # referência: metas do guia cumpridas (saldo imposto sobre o cenário sem campanha; o que precisaria acontecer)
    T = T_base.copy(); saldo = {k: np.zeros(N) for k in ["ausentes", "terceira_via", "brancos_nulos", "eleitores_flavio", "chamado_aberto", "resposta_flavio"]}
    def _move(T, j_from, d_from, d_to, total, pesos, cap_frac=0.95):
        mov = np.minimum(total*pesos[None, :], cap_frac*T[:, :, j_from, d_from]); T[:, :, j_from, d_from] -= mov; T[:, :, j_from, d_to] += mov; return mov.sum(1)
    mF = sum(_move(T, ORIG.index(o), 1, 0, GUIA_METAS["terceira_via"]/2*X["perm"][o]["flavio"].mean(0).sum()/sum(X["perm"][o_]["flavio"].mean(0).sum() for o_ in TV), w_L) for o in TV)
    saldo["terceira_via"] += 2*mF
    saldo["brancos_nulos"] += _move(T, 6, 2, 0, GUIA_METAS["brancos_nulos"], w_L)
    saldo["eleitores_flavio"] += 2*_move(T, 1, 1, 0, GUIA_METAS["eleitores_flavio"]/2, w_L)
    a_l = _move(T, 7, 3, 0, 1.0e6, w_L); a_f = _move(T, 7, 3, 1, 0.5e6, w_L); saldo["ausentes"] += a_l - a_f
    c_guia, marg_guia, _ = agrega(T, "guia", "Metas do guia cumpridas (referência)", "nenhuma", "Nenhuma", saldo)
    del T
    med_nomes = np.median(nomes_forte, axis=0)      # alcance mediano do cenário forte, por bolsão
    lit = {"ausentes": _p("q_aus")[0], "terceira_via": _p("q_tv")[0], "brancos_nulos": _p("q_tv")[0], "eleitores_flavio": _p("q_fb")[0]}
    nec = {"ausentes": GUIA_METAS["ausentes"]/(2*_p("ν")[0] - 1)/REALIZA["ausentes"][0]/med_nomes[0],
           "terceira_via": GUIA_METAS["terceira_via"]/2/REALIZA["terceira_via"][0]/med_nomes[1],
           "brancos_nulos": GUIA_METAS["brancos_nulos"]/REALIZA["terceira_via"][0]/med_nomes[1],
           "eleitores_flavio": GUIA_METAS["eleitores_flavio"]/2/REALIZA["flavio_brando"][0]/med_nomes[2]}
    guia = {"metas": {k: int(v) for k, v in GUIA_METAS.items()}, "metas_total": int(sum(GUIA_METAS.values())),
            "alcance_forte_q50": {"ausentes": int(med_nomes[0]), "terceira_via": int(med_nomes[1]), "eleitores_flavio": int(med_nomes[2])},
            "taxa_necessaria": {k: {"literatura": round(lit[k], 3), "necessaria": round(float(nec[k]), 3), "razao": round(float(nec[k]/lit[k]), 1)} for k in nec},
            "margem_se_cumprido": c_guia["margem_q"]["50"], "p_lula_se_cumprido": c_guia["p_lula_so_migracao"],
            "p_lula_se_cumprido_com_deriva": c_guia["p_lula_com_deriva"]}
    # ---- verificações
    br = D["br"]
    checks["br_ok"] = bool(all(abs(float(br[c]) - float(D["tab"][c].sum())) < 1 for c in ["eleitorado", "comparecimento", "abstencao", "votos_validos", "brancos", "nulos", "lula", "flavio", "cury", "renan", "caiado", "zema", "outros"]))
    checks["massa_ok"] = bool(np.allclose(dest_sem.sum(-1), elei[None, :], rtol=1e-9))
    checks["linhas_ok"] = bool(np.allclose(M.sum(-1), 1.0, atol=1e-9))
    fr = tr_forte
    checks["cadeia_ok"] = bool(np.all(fr >= -1e-12) and np.allclose(fr.sum(-1), 1.0, atol=1e-9)
                               and np.all((fr[-1, :, :, 2] + fr[-1, :, :, 3])*1.0 <= (1 - fr[-1, :, :, 0]) + 1e-9))
    esp = (comparacao or {}).get("estrutural_base_margem")
    rec = estrutural_check(D, transf)
    checks["estrutural_ok"] = bool(esp is None or abs(rec - esp) <= 0.3)
    ordem = [margens[(c, "nenhuma")] for c, *_ in CENARIOS]
    med = [float(np.median(m)) for m in ordem]; pl = [float((m > 0).mean()) for m in ordem]
    checks["monotonico_ok"] = bool(all(med[i] <= med[i + 1] + 1e-9 for i in range(3)) and all(pl[i] <= pl[i + 1] + 1e-9 for i in range(3))
                                   and all(float(np.median(margens[(c, "simetrica")])) <= float(np.median(margens[(c, "nenhuma")])) + 1e-9 for c, *_ in CENARIOS))
    seg = round(time.time() - t0, 1)
    checks["tempo_ok"] = bool(seg < 60)
    return {"versao": 1, "usado": True, "seed": seed, "n_sorteios": N, "segundos": seg, "hoje": str(hoje.date()),
            "inicio_cadeia": str(inicio.date()), "dias": [str(d.date()) for d in DIAS],
            "fases": [{"nome": a, "inicio": b, "fim": c_, "mult_conversao": m, "confirmacao_dia": cf, "encerramento_dia": e} for a, b, c_, m, cf, e, g in FASES],
            "transf_fonte": transf_fonte, "transf": {k: [round(v[0], 3), round(v[1], 3)] for k, v in transf.items()},
            "estoques_1t": {"lula": int(n[:, 0].sum()), "flavio": int(n[:, 1].sum()), "terceira_via": int(n[:, 2:6].sum()), "sem_2a_opcao": int(round(sem_2a)),
                            "brancos_nulos": int(n[:, 6].sum()), "abstencao": int(n[:, 7].sum()), "eleitorado": int(elei.sum()), "comparecimento": int(D["comparecimento"].sum()),
                            "margem_1t_votos": int(n[:, 0].sum() - n[:, 1].sum()), "margem_1t": round(100*float((n[:, 0].sum() - n[:, 1].sum())/(n[:, 0].sum() + n[:, 1].sum())), 2),
                            "por_regiao": [{"regiao": r_, "nome": REGIAO_NOME.get(r_, r_), "ausentes": int(n[[x == r_ for x in D["regiao"]], 7].sum()),
                                            "terceira_via": int(n[[x == r_ for x in D["regiao"]], 2:6].sum()), "brancos_nulos": int(n[[x == r_ for x in D["regiao"]], 6].sum())}
                                           for r_ in ["NE", "SE", "S", "N", "CO", "EXT"] if r_ in D["regiao"]]},
            "bolsoes": {"ausentes": int(P_aus), "terceira_via_bn": int(P_tv), "flavio_brando_q50": int(np.median(P_fb))},
            "priors": priors(), "cenarios": cenarios + [c_guia], "guia": guia, "matriz_mediana": mats, "hist_bins": [float(b) for b in BINS],
            "comparacao": {**(comparacao or {}), "sd_deriva": round(float(sd_deriva), 2), "margem_sem_campanha_q50": cenarios[0]["margem_q"]["50"],
                           "p_lula_sem_campanha_com_deriva": cenarios[0]["p_lula_com_deriva"],
                           "check_estrutural": {"recalculado": round(float(rec), 2), "esperado": esp, "dif": (None if esp is None else round(float(rec - esp), 2))}},
            "abstencao_bruta": {"r_out_q10_50_90": _q(B["r_out"], [10, 50, 90]), "r_in_q10_50_90": _q(X["r_in"].mean(1), [10, 50, 90]),
                                "saem_mi_q50": round(float(np.median(B["r_out"]*D["comparecimento"].sum()))/1e6, 2),
                                "entram_mi_q50": round(float(np.median((X["r_in"]*n[None, :, 7]).sum(1)))/1e6, 2),
                                "tau_q10_50_90": _q(B["tau"], [10, 50, 90]), "delta_nac_q10_50_90": _q(B["delta_nac"], [10, 50, 90])},
            "checks": checks}


# ------------------------------------------------------------------ HTML
def f(v, d=1, sinal=False):
    v = round(float(v), d) + 0.0
    s = f"{abs(v) if sinal else v:.{d}f}".replace(".", ",")
    if sinal: return ("+" if v > 0 else "−" if v < 0 else "") + s
    return s.replace("-", "−")
def fs(v, d=1): return f(v, d, True)
def pct(p, d=0): return f(100*p, d) + "%"
def mil(n): return f"{int(round(n)):,}".replace(",", ".")
def mi(v, d=1, sinal=False):
    """Votos em milhões (ou mil, abaixo de 1 mi)."""
    v = float(v)
    if abs(v) >= 950_000: return f(v/1e6, d, sinal) + " mi"
    return f(v/1e3, 0, sinal) + " mil"


def _cel(R, cid, rid):
    for c in R["cenarios"]:
        if c["id"] == cid and c["resposta"] == rid: return c
    raise KeyError((cid, rid))


def svg_trajetoria(R, c):
    W, Hh = 900, 300; x0, x1, y0, y1 = 70, 880, 44, 236
    dias = R["dias"]; T = len(dias)
    series = [("ausentes", "Ausentes trazidos às urnas", "var(--accent)"), ("terceira_via", "3ª via e brancos/nulos", "var(--amber)"), ("flavio_brando", "Eleitores brandos de Flávio", "var(--flavio)")]
    vmax = max(max(v[2] for v in c["trajetoria"][k]) for k, _, _ in series)*1.08 or 1
    X = lambda t: x0 + t/(T - 1)*(x1 - x0)
    Y = lambda v: y1 - v/vmax*(y1 - y0)
    s = [f'<svg viewBox="0 0 {W} {Hh}" role="img" aria-label="Trajetória diária do estoque de eleitores convertidos por bolsão, de 5 a 24 de outubro, no cenário forte">']
    for fase in R["fases"]:
        a = dias.index(fase["inicio"]); b = dias.index(fase["fim"])
        op = 0.0 if fase["mult_conversao"] == 0 and fase["confirmacao_dia"] == 0 and fase["nome"] != "Largada" else min(0.05 + 0.08*fase["mult_conversao"], 0.18)
        if fase["nome"].startswith("Pausa"): op = 0.0
        s.append(f'<rect x="{X(a) - (x1 - x0)/(T - 1)/2:.1f}" y="{y0}" width="{(b - a + 1)*(x1 - x0)/(T - 1):.1f}" height="{y1 - y0}" fill="var(--accent)" opacity="{op:.2f}"/>')
        s.append(f'<text class="axis-label" x="{(X(a) + X(b))/2:.1f}" y="{y0 - 8}" text-anchor="middle">{H.escape(fase["nome"])}</text>')
    passo = 10**math.floor(math.log10(vmax)) if vmax > 0 else 1
    if vmax/passo < 3: passo /= 2
    v = 0
    while v <= vmax:
        s.append(f'<line class="grid-line" x1="{x0}" y1="{Y(v):.1f}" x2="{x1}" y2="{Y(v):.1f}"/>')
        s.append(f'<text class="axis-label" x="{x0 - 8}" y="{Y(v) + 4:.1f}" text-anchor="end">{mi(v, 1) if v else "0"}</text>')
        v += passo
    for k, rot, cor in series:
        tr = c["trajetoria"][k]
        band = " ".join(f"{X(t):.1f},{Y(tr[t][2]):.1f}" for t in range(T)) + " " + " ".join(f"{X(t):.1f},{Y(tr[t][0]):.1f}" for t in range(T - 1, -1, -1))
        s.append(f'<polygon points="{band}" fill="{cor}" opacity="0.14"/>')
        s.append(f'<polyline class="series-line" points="{" ".join(f"{X(t):.1f},{Y(tr[t][1]):.1f}" for t in range(T))}" stroke="{cor}"/>')
        s.append(f'<text class="dot-label" x="{x1 + 4}" y="{Y(tr[-1][1]) + 4:.1f}" fill="{cor}" font-size="10">{mi(tr[-1][1])}</text>')
    if R["hoje"] in dias:
        th = dias.index(R["hoje"])
        s.append(f'<line x1="{X(th):.1f}" y1="{y0}" x2="{X(th):.1f}" y2="{y1}" stroke="var(--ink)" stroke-width="1.2" stroke-dasharray="4,3"/>')
        s.append(f'<text class="axis-label" x="{X(th) + 4:.1f}" y="{y1 - 6}" fill="var(--ink)">hoje</text>')
    for t in range(0, T, 2):
        s.append(f'<text class="axis-label" x="{X(t):.1f}" y="{y1 + 16}" text-anchor="middle">{dias[t][8:10]}/{dias[t][5:7]}</text>')
    s.append(f'<text class="axis-label" x="{(x0 + x1)/2:.0f}" y="{y1 + 34}" text-anchor="middle">eleitores com plano de voto ou confirmados, por bolsão (mediana e faixa de 80%); fundo mais escuro = fase de conversa mais intensa</text>')
    lx = x0
    for k, rot, cor in series:
        s.append(f'<rect x="{lx}" y="{Hh - 12}" width="14" height="4" fill="{cor}"/><text class="dot-label" x="{lx + 18}" y="{Hh - 7}" fill="var(--ink-soft)" font-weight="400">{rot}</text>')
        lx += 230
    s.append("</svg>")
    return "\n".join(s)


def svg_margem(R):
    W, Hh = 900, 330; x0, x1, y0, y1 = 60, 880, 106, 276
    bins = np.array(R["hist_bins"])
    cells = [(c, cor) for c, cor in zip([_cel(R, cid, "nenhuma") for cid, *_ in CENARIOS] + [_cel(R, "guia", "nenhuma")], ["var(--ink-3)", "var(--accent-med)", "var(--accent)", "var(--amber)", "var(--lula)"])]
    hmax = max(max(c["hist_margem"]) for c, _ in cells) or 1
    X = lambda v: x0 + (v - bins[0])/(bins[-1] - bins[0])*(x1 - x0)
    Y = lambda d: y1 - d/hmax*(y1 - y0)
    s = [f'<svg viewBox="0 0 {W} {Hh}" role="img" aria-label="Distribuição da margem Lula − Flávio no 2º turno por cenário de campanha, só pela migração">']
    h0 = cells[0][0]["hist_margem"]
    bw = (x1 - x0)/len(h0)
    for i, d in enumerate(h0):
        s.append(f'<rect x="{X(bins[i]) + 0.5:.1f}" y="{Y(d):.1f}" width="{max(bw - 1, 0.5):.1f}" height="{y1 - Y(d):.1f}" fill="var(--ink-3)" opacity="0.28"><title>margem {f(bins[i], 2, True)} a {f(bins[i + 1], 2, True)} p.p. · sem campanha dirigida: {d*(bins[1] - bins[0])*100:.1f}% dos sorteios</title></rect>')
    for c, cor in cells[1:]:
        pts = []
        for i, d in enumerate(c["hist_margem"]):
            pts += [f"{X(bins[i]):.1f},{Y(d):.1f}", f"{X(bins[i + 1]):.1f},{Y(d):.1f}"]
        s.append(f'<polyline points="{" ".join(pts)}" fill="none" stroke="{cor}" stroke-width="2.2" stroke-linejoin="round"{" stroke-dasharray=\"5,3\"" if c["id"] == "guia" else ""}/>')
    s.append(f'<line x1="{X(0):.1f}" y1="{y0 - 6}" x2="{X(0):.1f}" y2="{y1}" stroke="var(--ink)" stroke-width="1.2"/>')
    mp = R["comparacao"].get("mediana_principal")
    if mp is not None:
        s.append(f'<line x1="{X(mp):.1f}" y1="{y0 - 6}" x2="{X(mp):.1f}" y2="{y1}" stroke="var(--flavio)" stroke-width="1.4" stroke-dasharray="4,3"/>')
        s.append(f'<text class="axis-label" x="{X(mp) + 4:.1f}" y="{y0 + 4}" fill="var(--flavio)">mediana do modelo principal {fs(mp)}</text>')
    for v in range(int(bins[0]), int(bins[-1]) + 1):
        s.append(f'<text class="axis-label" x="{X(v):.1f}" y="{y1 + 18}" text-anchor="middle">{"+" if v > 0 else ""}{v}</text>')
    s.append(f'<text class="axis-label" x="{(x0 + x1)/2:.0f}" y="{y1 + 36}" text-anchor="middle">margem Lula − Flávio em 25/out só pela migração, sem a deriva das pesquisas (p.p. de votos válidos; caudas além do eixo somadas nas pontas)</text>')
    for k, (c, cor) in enumerate(cells):
        xx, yy = x0, 14 + k*15
        if k == 0: s.append(f'<rect x="{xx}" y="{yy - 8}" width="14" height="9" fill="var(--ink-3)" opacity="0.28"/>')
        else: s.append(f'<line x1="{xx}" y1="{yy - 4}" x2="{xx + 14}" y2="{yy - 4}" stroke="{cor}" stroke-width="2.2"{" stroke-dasharray=\"5,3\"" if c["id"] == "guia" else ""}/>')
        s.append(f'<text class="dot-label" x="{xx + 20}" y="{yy}" fill="var(--ink-soft)" font-weight="400">{H.escape(c["nome"])}: mediana {fs(c["margem_q"]["50"], 2)} (80% de {fs(c["margem_q"]["10"])} a {fs(c["margem_q"]["90"])}) · Lula {pct(c["p_lula_so_migracao"], 1)} só migração · {pct(c["p_lula_com_deriva"], 1)} com deriva</text>')
    s.append("</svg>")
    return "\n".join(s)


def _q3(d, k="q50", lo="q10", hi="q90", fn=mi):
    return f'{fn(d[k], 2, True)} <span class="muted">({fn(d[lo], 1, True)} a {fn(d[hi], 1, True)})</span>'


def build(R, DIR=None):
    """Seção #migracao a partir de R["migracao"] (resultados.json). Vazio se a análise não rodou."""
    Mg = R.get("migracao") or {}
    if not Mg.get("usado"): return ""
    E, G, C = Mg["estoques_1t"], Mg["guia"], Mg["comparacao"]
    sem, mod, forte, maxi = (_cel(Mg, k, "nenhuma") for k in ("sem", "moderada", "forte", "maxima"))
    forte_s = _cel(Mg, "forte", "simetrica"); guia = _cel(Mg, "guia", "nenhuma")
    sf = forte["saldo_frentes"]
    p_prin = C.get("p_lula_principal"); p_mc = C.get("p_lula_mcmc")
    css = ("<style>"
           "#migracao .mg-tab th,#migracao .mg-tab td{white-space:nowrap}"
           "#migracao .mg-tab td.txt{white-space:normal;min-width:180px}"
           "#migracao .mg-tab tr.ref td{background:var(--surface-2)}"
           "#migracao .mg-tab tr.ref td.txt{font-weight:600}"
           "#migracao .mg-mat td.lula{color:var(--lula);font-weight:600}#migracao .mg-mat td.flavio{color:var(--flavio);font-weight:600}"
           "#migracao .nota{font-size:12.5px;color:var(--ink-soft);margin:8px 0 0;font-family:var(--sans);line-height:1.55}"
           "#migracao h3.sub{font-size:19px;margin:30px 0 6px}"
           "#migracao .estados{display:flex;gap:6px;flex-wrap:wrap;margin:8px 0 0;font:12.5px var(--sans);color:var(--ink-soft)}"
           "#migracao .estados span{border:1px solid var(--line);border-radius:999px;padding:3px 10px;background:var(--surface)}"
           "@media(max-width:680px){#migracao .mg-tab td.txt{min-width:140px}}"
           "</style>")
    def tile(eb, val, cap):
        return f'<div class="tile"><span class="eyebrow">{eb}</span><div class="val" style="font-size:22px;line-height:1.3">{val}</div><div class="cap">{cap}</div></div>'
    sal_tot = sf["total"]
    tiles = ('<div class="tiles">'
             + tile("Saldo da campanha forte", f'{mi(sal_tot["q50"], 2, True)} <small>votos</small>',
                    f'80% entre {mi(sal_tot["q10"], 1, True)} e {mi(sal_tot["q90"], 1, True)}; 500 mil voluntários iniciais, {mi(forte["voluntarios_q10_50_90"][1])} ao fim. O guia pede +{mi(G["metas_total"], 1)}.')
             + tile("Chance de Lula: sem → forte", f'{pct(sem["p_lula_com_deriva"], 1)} → {pct(forte["p_lula_com_deriva"], 1)}',
                    f'com a deriva das pesquisas, como no modelo principal ({pct(p_prin, 1) if p_prin is not None else "—"}; MCMC {pct(p_mc, 1) if p_mc is not None else "—"}). Só pela migração, sem deriva: {pct(sem["p_lula_so_migracao"], 1)} → {pct(forte["p_lula_so_migracao"], 1)}.')
             + tile("Abstenção esperada no 2º turno", f'{f(sem["abstencao_q10_50_90"][1])}%',
                    f'80% entre {f(sem["abstencao_q10_50_90"][0])}% e {f(sem["abstencao_q10_50_90"][2])}%; foi {f(100*E["abstencao"]/E["eleitorado"])}% no 1º turno. Saem {f(Mg["abstencao_bruta"]["saem_mi_q50"])} mi, entram {f(Mg["abstencao_bruta"]["entram_mi_q50"])} mi.')
             + tile("Conversão necessária × literatura", f'{f(G["taxa_necessaria"]["terceira_via"]["razao"], 0)}× <small>na 3ª via</small>',
                    f'para cumprir a meta do guia com o alcance forte, 1 em {f(1/G["taxa_necessaria"]["terceira_via"]["necessaria"], 1) if G["taxa_necessaria"]["terceira_via"]["necessaria"] < 1 else "menos de 1"} contato teria de virar voto; a literatura dá 1 em {f(1/G["taxa_necessaria"]["terceira_via"]["literatura"], 0)}.')
             + '</div>')
    # tabela 1: cenários
    rows1 = []
    for c in Mg["cenarios"]:
        s_ = c["saldo_frentes"]; ref = c["id"] == "guia"
        vol = ("—" if ref else "0" if c["voluntarios_q10_50_90"][1] == 0 else f'{mi(c["voluntarios_q10_50_90"][1])} <span class="muted">({mi(c["voluntarios_q10_50_90"][0])}–{mi(c["voluntarios_q10_50_90"][2])})</span>')
        nomes = "—" if ref else "0" if c["nomes_distintos_q50"] == 0 else mi(c["nomes_distintos_q50"])
        rows1.append(f'<tr{" class=ref" if ref else ""}><td class="txt">{H.escape(c["nome"])}</td><td>{H.escape(c["resposta_nome"])}</td><td class="num">{vol}</td><td class="num">{nomes}</td>'
                     + "".join(f'<td class="num">{mi(s_[k]["q50"], 2, True) if s_[k]["q50"] else "0"}</td>' for k in ["ausentes", "terceira_via", "brancos_nulos", "eleitores_flavio", "chamado_aberto", "resposta_flavio"])
                     + f'<td class="num"><b>{mi(s_["total"]["q50"], 2, True)}</b> <span class="muted">({mi(s_["total"]["q10"], 1, True)} a {mi(s_["total"]["q90"], 1, True)})</span></td>'
                     + f'<td class="pnum {"lead" if c["margem_q"]["50"] > 0 else "trail"}">{fs(c["margem_q"]["50"])} <span class="muted">({fs(c["margem_q"]["10"])} a {fs(c["margem_q"]["90"])})</span></td>'
                     + f'<td class="pnum">{pct(c["p_lula_so_migracao"], 1)}</td><td class="pnum">{pct(c["p_lula_com_deriva"], 1)}</td></tr>')
    # tabela 2: frente a frente com o guia
    ROT_FRENTE = {"terceira_via": ("Terceira via", "dividir meio a meio os votos de Cury, Caiado e Renan"), "eleitores_flavio": ("Votos de Flávio", "trazer 600 mil eleitores pouco convictos"),
                  "brancos_nulos": ("Brancos e nulos", "convencer 1 milhão a votar em Lula"), "ausentes": ("Quem não foi votar", "levar às urnas 1,5 milhão de ausentes em redutos de Lula")}
    rows2 = []
    for k in ["terceira_via", "eleitores_flavio", "brancos_nulos", "ausentes"]:
        tn = G["taxa_necessaria"][k]; alc = G["alcance_forte_q50"]["terceira_via" if k == "brancos_nulos" else k]
        rows2.append(f'<tr><td class="txt"><b>{ROT_FRENTE[k][0]}</b><br><span class="muted">{ROT_FRENTE[k][1]}</span></td><td class="num">{mi(G["metas"][k], 1, True)}</td>'
                     f'<td class="num">{mi(sf[k]["q50"], 2, True)} <span class="muted">({mi(sf[k]["q10"], 2, True)} a {mi(sf[k]["q90"], 2, True)})</span></td>'
                     f'<td class="num">{mi(alc)}</td><td class="num">{pct(tn["literatura"], 0)}</td><td class="num">{pct(tn["necessaria"], 0) if tn["necessaria"] < 1 else "acima de 100%"}</td><td class="num"><b>{f(tn["razao"], 0)}×</b></td></tr>')
    rows2.append(f'<tr class="ref"><td class="txt">Soma</td><td class="num">{mi(G["metas_total"], 1, True)}</td><td class="num">{mi(sal_tot["q50"], 2, True)} <span class="muted">({mi(sal_tot["q10"], 1, True)} a {mi(sal_tot["q90"], 1, True)})</span></td><td class="num">{mi(forte["nomes_distintos_q50"])}</td><td></td><td></td><td></td></tr>')
    # tabela 3: matriz mediana
    mat_s, mat_f = Mg["matriz_mediana"]["sem"], Mg["matriz_mediana"]["forte"]
    rows3 = []
    for o in ORIG:
        rows3.append(f'<tr><td class="txt">{ROT_ORIG[o]}<br><span class="muted">{mi(E[{"lula": "lula", "flavio": "flavio", "bn": "brancos_nulos", "abs": "abstencao"}[o]] if o in ("lula", "flavio", "bn", "abs") else 0) if o in ("lula", "flavio", "bn", "abs") else ""}</span></td>'
                     + "".join(f'<td class="num {d if d in ("lula", "flavio") else ""}">{f(mat_s[o][d][1])}% <span class="muted">({f(mat_s[o][d][0])}–{f(mat_s[o][d][2])})</span></td>' for d in DEST)
                     + "".join(f'<td class="num">{fs(mat_f[o][d][1] - mat_s[o][d][1], 2)}</td>' for d in DEST) + "</tr>")
    # tabela 4: por região (cenário forte, sem resposta)
    rows4 = []
    for r_, r1 in zip(forte["por_regiao"], sem["por_regiao"]):
        rows4.append(f'<tr><td class="txt">{H.escape(r_["nome"])}</td><td class="num">{mi(r_["ausentes_1t"], 2)}</td><td class="num">{f(r_["abst_1t"])}%</td><td class="num">{f(r1["abst_2t_q50"])}%</td>'
                     f'<td class="num">{mi(r1["novos_eleitores_q50"], 2)}</td><td class="num">{mi(r1["saldo_q50"], 2, True)}</td><td class="num">{mi(r_["novos_eleitores_q50"], 2)}</td>'
                     f'<td class="num">{mi(r_["saldo_q50"], 2, True)} <span class="muted">({mi(r_["saldo_q10"], 2, True)} a {mi(r_["saldo_q90"], 2, True)})</span></td>'
                     f'<td class="pnum {"lead" if r_["margem_1t"] > 0 else "trail"}">{fs(r_["margem_1t"])}</td><td class="pnum {"lead" if r_["margem_2t_q50"] > 0 else "trail"}">{fs(r_["margem_2t_q50"])}</td></tr>')
    priors_rows = "".join(f'<tr><td class="txt">{H.escape(p["nome"])}</td><td class="num">{H.escape(p["simbolo"])}</td><td>{H.escape(p["dist"])}</td><td class="num">{"cenário" if p["media"] is None else (f(p["media"], 3) if p["media"] < 0.1 else f(p["media"], 2))}</td><td class="num">{f(p["sd"], 3) if p["sd"] < 0.1 else f(p["sd"], 2)}</td><td class="txt muted">{H.escape(p["fonte"])}</td></tr>' for p in Mg["priors"])
    ab = Mg["abstencao_bruta"]
    ch = Mg["checks"]
    dif_forte = forte["margem_q"]["50"] - sem["margem_q"]["50"]
    dif_max = maxi["margem_q"]["50"] - sem["margem_q"]["50"]
    tldr = (f'Sem campanha dirigida, a migração leva a margem para {fs(sem["margem_q"]["50"])} ponto{"s" if abs(sem["margem_q"]["50"]) >= 2 else ""} (80% entre {fs(sem["margem_q"]["10"])} e {fs(sem["margem_q"]["90"])}). '
            f'Uma campanha forte de conversa pessoal, com 500 mil voluntários iniciais e a eficácia que a literatura mede, rende {mi(sal_tot["q50"], 1, True)} votos de saldo '
            f'e move a margem em {fs(dif_forte)} ponto{"s" if abs(dif_forte) >= 2 else ""}; a máxima, {fs(dif_max)}. A virada exigiria {mi(G["metas_total"], 1, True)}: '
            f'com o alcance forte, a conversão por contato teria de ser {f(G["taxa_necessaria"]["terceira_via"]["razao"], 0)} vezes a da literatura na terceira via. '
            f'A chance de Lula, com a deriva das pesquisas, vai de {pct(sem["p_lula_com_deriva"], 1)} sem campanha a {pct(forte["p_lula_com_deriva"], 1)} com a forte. A manchete não muda: isto é análise condicional, não previsão.')
    hoje_txt = f'{Mg["hoje"][8:10]}/{Mg["hoje"][5:7]}'
    return f"""
  <section id="migracao">
    {css}
    <div class="kicker">Migração</div>
    <h2>Para onde vão os {f(E["abstencao"]/1e6, 0)} milhões que faltaram, os {f(E["terceira_via"]/1e6, 0)} milhões da terceira via e os {f(E["brancos_nulos"]/1e6, 0)} milhões de brancos e nulos</h2>
    <p class="tldr"><b>Em uma frase:</b> {tldr}</p>
    <div class="howto"><b>Como ler.</b> O resultado oficial por UF é o estado inicial: cada eleitor está numa de oito caixas (votou em Lula, em Flávio, em cada um dos candidatos da 3ª via, branco/nulo ou não votou). Uma [[markov|cadeia de Markov]] leva cada caixa aos quatro destinos do 2º turno (Lula, Flávio, branco/nulo, não vota) com uma matriz de transição sorteada: cada linha é uma [[dirichlet|Dirichlet]] em torno da 2ª opção declarada (Quaest, 3–6/set) e dos fluxos de comparecimento. Por cima, uma segunda cadeia, diária, acompanha a campanha de conversa pessoal do guia "Três nomes, três semanas" (5 a 24/out): quem é alcançado, quem faz um plano de voto, quem confirma, quem desiste. <b>"Sem campanha dirigida"</b> não é "sem campanha": a 2ª opção e os fluxos de 2022 já contêm a campanha normal dos dois lados; o que a escada mede é o acréscimo de uma mobilização organizada junto a esses três grupos. {Mg["n_sorteios"]//1000} mil sorteios por célula, os mesmos números aleatórios em todas (comparação pareada).</div>
    {tiles}

    <h3 class="sub">1. A campanha dia a dia: quantos eleitores a conversa alcança e converte</h3>
    <div class="howto"><b>Como ler.</b> Cenário forte, sem resposta de Flávio: {mi(forte["voluntarios_q10_50_90"][1])} voluntários ao fim (80% entre {mi(forte["voluntarios_q10_50_90"][0])} e {mi(forte["voluntarios_q10_50_90"][2])}), cada um com três nomes (um ausente, um da 3ª via ou branco/nulo, um indeciso). A linha é o estoque de eleitores com plano de voto ou confirmados; a faixa, 80% dos sorteios. As fases seguem o calendário do guia: largada (5–6), 1ª conversa (7–11), pausa de Aparecida (12), 2ª conversa (13–18), fechamento (19–22), confirmação (23–24). A cadeia começa em {Mg["inicio_cadeia"][8:10]}/{Mg["inicio_cadeia"][5:7]}; hoje é {hoje_txt}.</div>
    <div class="trend-card">{svg_trajetoria(Mg, forte)}</div>
    <div class="estados"><span>S0 · não alcançado</span><span>S1 · alcançado</span><span>S2 · plano de voto</span><span>S3 · confirmado</span><span>S4 · recusou ou foi deixado</span></div>
    <p class="nota">Ao fim da campanha forte, nos ausentes: {pct(forte["estados_finais_q50"]["ausentes"][1] + forte["estados_finais_q50"]["ausentes"][2] + forte["estados_finais_q50"]["ausentes"][3] + forte["estados_finais_q50"]["ausentes"][4], 1)} do bolsão de {mi(Mg["bolsoes"]["ausentes"])} alcançados, {pct(forte["estados_finais_q50"]["ausentes"][2] + forte["estados_finais_q50"]["ausentes"][3], 2)} com plano de voto ou confirmados. Os contatos são poucos diante do bolsão: o gargalo é o alcance, não a conversa.</p>

    <h3 class="sub">2. A margem do 2º turno em cada cenário</h3>
    <div class="howto"><b>Como ler.</b> Distribuição da margem Lula − Flávio só pela migração (resultado oficial + matriz de transição + campanha), sem a deriva das pesquisas: é o que o voto contado e a 2ª opção declarada permitem dizer. Barras: sem campanha dirigida. Linhas: moderada (100 mil voluntários iniciais), forte (500 mil) e máxima (2 milhões, teto de {pct(0.05)} dos votos de Lula). A linha tracejada é a mediana do modelo principal, que parte das pesquisas e do voto contado e inclui a deriva até 25/out.</div>
    <div class="trend-card">{svg_margem(Mg)}</div>
    <div class="table-card" style="margin-top:14px"><table class="mg-tab" style="min-width:1100px">
      <thead><tr><th>Cenário</th><th>Resposta de Flávio</th><th>Voluntários ao fim</th><th>Nomes alcançados</th><th>Ausentes</th><th>3ª via</th><th>Brancos/nulos</th><th>Eleitores de Flávio</th><th>Chamado aberto</th><th>Resposta de Flávio</th><th>Saldo total <span class="muted">(80%)</span></th><th>Margem <span class="muted">(80%)</span></th><th>Lula só migração</th><th>Lula com deriva</th></tr></thead>
      <tbody>{"".join(rows1)}</tbody></table></div>
    <p class="nota">Saldo em votos (Lula − Flávio), mediana dos sorteios; a soma das frentes pode diferir do total por arredondamento e pelos tetos de massa disponível por UF. "Com deriva" soma à margem da migração a mesma deriva t<sub>5</sub> do modelo principal (σ = {f(C["sd_deriva"])} p.p. até 25/out): é a chance comparável à manchete ({pct(p_prin) if p_prin is not None else "—"} principal, {pct(p_mc) if p_mc is not None else "—"} MCMC), mas sem as pesquisas. A última linha impõe as metas do guia ({mi(G["metas_total"], 1, True)}) sobre o cenário sem campanha: mostra onde a margem iria se elas fossem cumpridas, não quanto é provável cumpri-las.</p>

    <h3 class="sub">3. Frente a frente com o guia: o que precisaria acontecer</h3>
    <div class="howto"><b>Como ler.</b> O guia soma +{mi(G["metas_total"], 1)} em quatro frentes. Para cada uma, o saldo que o modelo espera no cenário forte, o nº de nomes que a campanha forte alcança nesse bolsão e a conversão por contato que seria necessária para cumprir a meta com esse alcance, comparada com a que a literatura de mobilização mede (Gerber & Green; Nickerson & Rogers; Kalla & Broockman).</div>
    <div class="table-card" style="margin-top:14px"><table class="mg-tab" style="min-width:820px">
      <thead><tr><th>Frente</th><th>Meta do guia</th><th>Modelo, campanha forte <span class="muted">(80%)</span></th><th>Nomes alcançados</th><th>Conversão na literatura</th><th>Conversão necessária</th><th>Razão</th></tr></thead>
      <tbody>{"".join(rows2)}</tbody></table></div>
    <p class="nota">Se as quatro metas fossem cumpridas, a margem mediana iria a {fs(G["margem_se_cumprido"])} ponto e a chance de Lula, só pela migração, a {pct(G["p_lula_se_cumprido"], 1)} ({pct(G["p_lula_se_cumprido_com_deriva"], 1)} com deriva). Cada eleitor da 3ª via ou de Flávio que muda vale dois votos no saldo; cada branco/nulo ou ausente trazido, um (os ausentes trazidos nominalmente votam {pct(0.8)} em Lula, por isso rendem 0,6 cada). Brancos e nulos dividem com a 3ª via o mesmo bolsão de nomes.</p>

    <h3 class="sub">4. A matriz de transição: de onde vem e para onde vai cada voto</h3>
    <div class="howto"><b>Como ler.</b> Parcela de cada origem que termina em cada destino no 2º turno (mediana e faixa de 80% dos sorteios), no cenário sem campanha dirigida; à direita, quanto a campanha forte muda cada célula, em pontos. A 2ª opção declarada é o centro das linhas da 3ª via; quem não declarou 2ª opção decide com probabilidade d<sub>3</sub> e se divide s<sub>3</sub>; os brancos e nulos decidem com d<sub>bn</sub>. Quem votou e falta (r<sub>out</sub>) é compensado por quem faltou e vota (r<sub>in</sub>), com a variação líquida da abstenção sorteada em torno do padrão de 2022 e perfil mais pró-Lula nos dois sentidos (τ).</div>
    <div class="table-card" style="margin-top:14px"><table class="mg-tab mg-mat" style="min-width:900px">
      <thead><tr><th rowspan="2">Origem (1º turno)</th><th colspan="4">Destino no 2º turno, sem campanha dirigida</th><th colspan="4">Campanha forte: diferença (p.p.)</th></tr>
      <tr><th>Lula</th><th>Flávio</th><th>Branco/nulo</th><th>Não vota</th><th>Lula</th><th>Flávio</th><th>Branco/nulo</th><th>Não vota</th></tr></thead>
      <tbody>{"".join(rows3)}</tbody></table></div>
    <p class="nota">Fluxos brutos de comparecimento: r<sub>out</sub> mediano {pct(ab["r_out_q10_50_90"][1])} dos que votaram ({f(ab["saem_mi_q50"])} mi), r<sub>in</sub> mediano {pct(ab["r_in_q10_50_90"][1])} dos ausentes ({f(ab["entram_mi_q50"])} mi); variação líquida nacional da abstenção {fs(ab["delta_nac_q10_50_90"][1])} p.p. (80% entre {fs(ab["delta_nac_q10_50_90"][0])} e {fs(ab["delta_nac_q10_50_90"][2])}); inclinação de perfil τ mediana {f(ab["tau_q10_50_90"][1], 0)} p.p. de margem. Com entrada e saída iguais e a mesma inclinação nos dois sentidos, o efeito líquido do comparecimento é próximo de zero; só o desvio em relação a 2022 importa, como na seção Abstenção.</p>

    <h3 class="sub">5. Por região: onde a campanha rende e onde sai pela culatra</h3>
    <div class="howto"><b>Como ler.</b> Ausentes do 1º turno, abstenção esperada no 2º, novos eleitores (quem não votou em 4/out e vota em 25/out) e saldo de votos (variação de Lula − Flávio em relação ao 1º turno) sem campanha e com a campanha forte. O chamado aberto a votar só entra com força nas UFs em que Lula liderou; nas de Flávio, o modelo supõe que um apelo genérico traz 30% do efeito, com a composição local, que favorece Flávio, como o guia avisa.</div>
    <div class="table-card" style="margin-top:14px"><table class="mg-tab" style="min-width:980px">
      <thead><tr><th rowspan="2">Região</th><th rowspan="2">Ausentes 1º turno</th><th rowspan="2">Abstenção 1º turno</th><th rowspan="2">Abstenção 2º turno esperada</th><th colspan="2">Sem campanha dirigida</th><th colspan="2">Campanha forte</th><th rowspan="2">Margem 1º turno</th><th rowspan="2">Margem 2º turno (forte)</th></tr>
      <tr><th>Novos eleitores</th><th>Saldo</th><th>Novos eleitores</th><th>Saldo <span class="muted">(80%)</span></th></tr></thead>
      <tbody>{"".join(rows4)}</tbody></table></div>
    <p class="nota">Saldo em votos e margem em p.p. de válidos, medianas. O exterior entra na massa e na margem, mas fica fora da campanha e da inclinação de perfil.</p>

    <div class="variables" style="margin-top:18px">
      <div class="variable"><h4>Como a cadeia funciona</h4><p>Duas cadeias encaixadas. A primeira é de um passo: cada eleitor do 1º turno, classificado por UF e pelo que fez em 4/out, vai para um dos quatro destinos do 2º turno segundo uma matriz 8 × 4 sorteada; a massa é conservada (a soma dos destinos é o eleitorado de cada UF). A segunda é diária e não homogênea: em cada bolsão (ausentes, 3ª via e brancos/nulos, eleitores brandos de Flávio), um eleitor passa de não alcançado a alcançado quando entra na lista de um voluntário; de alcançado a convertido com probabilidade proporcional à conversão por contato q, distribuída pelas fases do guia (a 2ª conversa pesa 1,4; o fechamento e a 1ª, 0,8; a pausa, 0); de convertido a confirmado no fechamento e na confirmação; e volta a alcançado com δ<sub>rev</sub> por dia. Quem confirma vota como combinado em {pct(0.85)} (ausentes) ou {pct(0.9)} dos casos; quem só tem plano de voto, em {pct(0.55)}. O estoque convertido é repartido pelas UFs em proporção ao voto de Lula e aplicado sobre os destinos da primeira cadeia, com teto na massa disponível em cada caixa.</p></div>
      <div class="variable"><h4>Resposta de Flávio e chamado aberto</h4><p>Na coluna "simétrica", Flávio monta uma rede ρ vezes a do PT (mediana {f(0.6, 1)}) com os mesmos priors de eficácia e bolsões espelhados: ausentes trazidos nominalmente ({pct(0.8)} para ele), 3ª via e brancos/nulos permeáveis, eleitores brandos de Lula. O chamado aberto a votar (rádio, feira, igreja) tira ω × intensidade dos ausentes das UFs em que cada lado liderou, com a composição de quem falta (perfil mais pró-Lula, τ); na UF do adversário, o apelo genérico rende 30% e tem a composição local. O saldo líquido da resposta aparece em coluna própria. Como as duas redes usam os mesmos priors, a diferença entre as colunas mede só a assimetria de tamanho (ρ) e de base (Lula tem mais ausentes nos seus redutos; Flávio tem mais votos a proteger).</p></div>
      <div class="variable"><h4>De onde vêm os parâmetros</h4><p>Os priors estão na tabela abaixo, com a fonte de cada um. Os que mais pesam são o número de voluntários (desconhecido: por isso a escada de cenários) e a conversão por contato, tomada da literatura de experimentos de campo: cerca de um voto a cada 14 contatos porta a porta para comparecimento, efeito quase nulo de persuasão em eleição geral e efeitos maiores quando o contato vem de alguém próximo. A 2ª opção da Quaest é de setembro; quando sair uma onda de 2º turno com a pergunta refeita, ela substitui os centros e a cadeia da campanha passa a começar no dia seguinte ao campo, para não contar duas vezes a conversa já feita.</p></div>
      <div class="variable"><h4>O que isso muda na manchete</h4><p>Nada. A chance de Lula do painel continua vindo do modelo principal ({pct(p_prin) if p_prin is not None else "—"}) e do MCMC ({pct(p_mc) if p_mc is not None else "—"}), que partem das pesquisas e do voto contado. Esta seção responde a outra pergunta: dado o resultado de 4/out, quanto uma campanha de conversa pessoal com os três grupos pode mover, e o que precisaria acontecer para a virada. A versão "com deriva" da chance existe só para pôr as duas contas na mesma régua: sem campanha dirigida ela dá {pct(sem["p_lula_com_deriva"], 1)}, perto do modelo principal, um pouco abaixo porque não carrega a incerteza das pesquisas e dos efeitos de casa; o nível estrutural dos dois é o mesmo (reprodução do modelo de transferência: {fs(C["check_estrutural"]["recalculado"])} contra {fs(C["check_estrutural"]["esperado"]) if C["check_estrutural"]["esperado"] is not None else "—"}).</p></div>
    </div>
    <div class="table-card" style="margin-top:14px"><table class="mg-tab" style="min-width:900px">
      <thead><tr><th>Parâmetro</th><th>Símbolo</th><th>Distribuição</th><th>Média</th><th>Desvio</th><th>Fonte ou justificativa</th></tr></thead>
      <tbody>{priors_rows}</tbody></table></div>
    <p class="nota">Concentrações das linhas Dirichlet: κ = 400 (Lula e Flávio), 100 (3ª via: subamostras da Quaest de 100 a 150 entrevistas, desvio de 4 a 5 p.p.), 150 (brancos/nulos e ausentes). Verificações do script: massa conservada em todos os sorteios ({"ok" if ch["massa_ok"] else "falhou"}); linhas somam 1 ({"ok" if ch["linhas_ok"] else "falhou"}); cadeia em [0, 1] e convertidos ≤ alcançados ({"ok" if ch["cadeia_ok"] else "falhou"}); reprodução do modelo estrutural ({"ok" if ch["estrutural_ok"] else "falhou"}); escada monotônica e resposta simétrica ≤ sem resposta ({"ok" if ch["monotonico_ok"] else "falhou"}); {f(Mg["segundos"], 0)} s.</p>

    <h3 class="sub">Limites desta análise</h3>
    <ul class="nota" style="padding-left:18px;font-size:13px">
      <li><b>Não é previsão.</b> É uma análise condicional: dado o voto contado e uma campanha com estas características, para onde a margem iria. Não usa as pesquisas de 2º turno, que são a base da manchete, e não substitui as rodadas pós-1º turno.</li>
      <li><b>Literatura americana.</b> As taxas de conversão vêm de experimentos de campo nos EUA, com voto facultativo; no Brasil, com voto obrigatório e 2º turno três semanas depois, o efeito de mobilização pode ser outro, e não há experimento comparável publicado.</li>
      <li><b>Número de voluntários desconhecido.</b> Não existe medida da adesão a uma campanha sem coordenação central; a escada de 0 a 2 milhões cobre as ordens de grandeza, e o teto ψ impede que a corrente de convites cresça sem limite.</li>
      <li><b>Transferência declarada.</b> Os centros das linhas da 3ª via vêm da matriz mais recente da base: a PoderData/Aya de 10–12/out, depois do 1º turno (a 2ª opção da Quaest, de setembro, vale só enquanto não há onda posterior). Zema não tem linha própria na PoderData e herda a do grupo "outro candidato". O Datafolha de 6–7/out publicou só o agregado dos quatro (Flávio 46, Lula 32), que dá a Lula 41% dos decididos, contra 45% na PoderData: reescalar a matriz para o agregado do Datafolha baixaria a chance de Lula no modelo principal de 15,9% para 14,3%.</li>
      <li><b>Sem abstenção por município ou perfil.</b> A inclinação τ é um número nacional, calibrado na abstenção por escolaridade de 2022 e no voto por escolaridade das pesquisas; a composição real de quem entra e sai varia por lugar.</li>
      <li><b>Resposta de Flávio suposta simétrica.</b> A rede dele é um múltiplo da do PT com os mesmos priors; uma campanha de outro tipo (mídia, igrejas, lideranças locais) não está modelada.</li>
      <li><b>Exterior fora da campanha.</b> Os {mi(E["eleitorado"] - sum(r["eleitorado"] for r in sem["por_regiao"] if r["regiao"] != "EXT"))} eleitores no exterior entram na massa, mas não nos bolsões nem na inclinação de perfil.</li>
    </ul>
    <p class="nota">Fontes: TSE, resultado oficial do 1º turno por UF (100% das seções); PoderData/Aya 10–12/out (transferência declarada; antes, Quaest 3–6/set); TSE 2022 via CNN Brasil (abstenção por UF nos dois turnos); TSE, perfil do comparecimento 2022 por escolaridade; Gerber & Green, <i>Get Out the Vote</i>; Nickerson & Rogers (2010), <i>Psychological Science</i>; Kalla & Broockman (2018), <i>American Political Science Review</i>; Pereira et al. (Ipea, 2023) sobre o passe livre de 2022. Código: migracao_markov.py (cálculo, autoverificações e esta seção), chamado por modelo_eleicao_2026.py (bloco 5b).</p>
  </section>
"""


def metodologia(R):
    """Passo 8 da metodologia (div.variable), no formato dos passos 1 a 7 de montar_painel.py."""
    Mg = R.get("migracao") or {}
    if not Mg.get("usado"): return ""
    sem, forte = _cel(Mg, "sem", "nenhuma"), _cel(Mg, "forte", "nenhuma")
    return f"""
      <div class="variable">
        <h4>8 · Migração por cadeia de Markov</h4>
        <div class="formula">destinos<sub>u</sub> = n<sub>u</sub> · M<sub>u</sub> · M<sub>u</sub>[o, ·] ~ Dirichlet(κ<sub>o</sub> · m<sub>o</sub>) · m<sub>3ª via</sub> = 2ª opção + resíduo decidido (d<sub>3</sub>, s<sub>3</sub>) · m<sub>ausente</sub> = (r<sub>in</sub>π<sub>u</sub>, r<sub>in</sub>(1−π<sub>u</sub>), ·, 1−r<sub>in</sub>)<br>campanha: x<sub>t+1</sub> = x<sub>t</sub> · P<sub>p,t</sub> sobre S0…S4 · alcance D<sub>p</sub> = P<sub>p</sub>(1 − e<sup>−N/P<sub>p</sub></sup>) · conversão q<sub>p</sub> por contato, fases do guia · margem = 100(ΣL − ΣF)/(ΣL + ΣF)</div>
        <p>Análise paralela (<code>migracao_markov.py</code>), condicional e sem efeito na manchete: a partir do resultado oficial por UF, uma [[markov|cadeia de Markov]] leva cada eleitor do 1º turno (Lula, Flávio, cada candidato da 3ª via, branco/nulo, ausente) a um dos quatro destinos do 2º turno por uma matriz sorteada linha a linha ([[dirichlet|Dirichlet]] em torno da 2ª opção declarada da Quaest, dos fluxos brutos de comparecimento e de uma inclinação de perfil). Uma segunda cadeia, diária e não homogênea, segue a campanha de conversa pessoal de 5 a 24/out (alcance por voluntários e recrutamento em cadeia; conversão por contato tomada dos experimentos de Gerber &amp; Green, Nickerson &amp; Rogers e Kalla &amp; Broockman; reversão; confirmação) e aplica o estoque convertido como fluxo entre destinos, com teto na massa disponível. {Mg["n_sorteios"]//1000} mil sorteios por célula, em 4 cenários de alcance × 2 respostas de Flávio, mais a referência "metas do guia cumpridas". Sem campanha dirigida a margem mediana é {fs(sem["margem_q"]["50"])}; com a forte, {fs(forte["margem_q"]["50"])}. A chance "com deriva" soma a mesma deriva t<sub>5</sub> do passo 4, só para comparar com o modelo principal.</p>
      </div>"""


# ------------------------------------------------------------------ verificação
if __name__ == "__main__":
    import sys
    DIR = os.path.dirname(os.path.abspath(__file__)) + "/"
    tr = pd.read_csv(DIR + "transferencia_2turno.csv")
    transf = {k: (float(g.pct_flavio.mean())/100, float(g.pct_lula.mean())/100) for k, g in tr.groupby("origem")}
    comp = {}
    if os.path.exists(DIR + "resultados.json"):
        Rj = json.load(open(DIR + "resultados.json", encoding="utf-8"))
        comp = {"estrutural_base_margem": Rj.get("estrutural_base_margem"), "p_lula_principal": Rj.get("p_lula"), "p_lula_mcmc": (Rj.get("mcmc") or {}).get("p_lula"),
                "mediana_principal": (Rj.get("quantis_margem_final") or {}).get("50"), "sd_deriva_principal": Rj.get("sd_deriva")}
    n_s = int(sys.argv[1]) if len(sys.argv) > 1 else 20_000
    t0 = time.time()
    A = simular(DIR, transf, "2026-10-06", comp.get("sd_deriva_principal") or 4.24, comp, seed=7, n_sorteios=n_s)
    A2 = simular(DIR, transf, "2026-10-06", comp.get("sd_deriva_principal") or 4.24, comp, seed=7, n_sorteios=n_s)
    ch = A["checks"]
    print("[checks] " + " · ".join(f"{k}={'ok' if v else 'FALHOU'}" for k, v in ch.items()))
    print(f"[estrutural] recalculado {A['comparacao']['check_estrutural']['recalculado']:+.2f} esperado {A['comparacao']['check_estrutural']['esperado']}")
    assert A["cenarios"][0]["margem_q"] == A2["cenarios"][0]["margem_q"] and A["cenarios"][2]["saldo_frentes"] == A2["cenarios"][2]["saldo_frentes"], "semente fixa deveria reproduzir"
    print("[ok] semente fixa reproduz")
    for c in A["cenarios"]:
        s = c["saldo_frentes"]
        print(f"  {c['nome']:<40} {c['resposta_nome']:<9} vol {c.get('voluntarios_q10_50_90', ['—'] * 3)[1]:>9} | saldo {s['total']['q50']/1e6:+.2f} mi "
              f"(aus {s['ausentes']['q50']/1e6:+.2f}, 3v {s['terceira_via']['q50']/1e6:+.2f}, bn {s['brancos_nulos']['q50']/1e6:+.2f}, fl {s['eleitores_flavio']['q50']/1e6:+.2f}, "
              f"ch {s['chamado_aberto']['q50']/1e6:+.2f}, resp {s['resposta_flavio']['q50']/1e6:+.2f}) | margem {c['margem_q']['50']:+.2f} [{c['margem_q']['10']:+.2f}, {c['margem_q']['90']:+.2f}] "
              f"| P(Lula) {c['p_lula_so_migracao']:.3f} / com deriva {c['p_lula_com_deriva']:.3f} | abst {c['abstencao_q10_50_90'][1]:.1f}%")
    print("[guia]", json.dumps(A["guia"]["taxa_necessaria"], ensure_ascii=False), "margem se cumprido", A["guia"]["margem_se_cumprido"])
    print(f"[tempo] {A['segundos']} s por chamada ({time.time() - t0:.1f} s as duas)")
    assert all(ch.values()), ch
modelo_eleicao_2026.py — calibração, 1º e 2º turnos, Monte Carlo, tornado, saídas (clique para expandir)
# -*- coding: utf-8 -*-
"""
Agregador de pesquisas + modelo probabilístico — Eleição presidencial 2026 (Brasil). Versão 3 (auditoria de 30/set/2026).

Componentes
 1. Dados: pesquisas de 2º turno (Lula × Flávio) e de 1º turno (todos os candidatos), calibração histórica 2006–2022
 2. Calibração: qualidade por instituto (MAE histórico), viés sistemático das pesquisas por ciclo (âncora), movimento entre turnos
 3. Tendência + efeitos de casa: espaço de estados único (espaco_estados.py) — passeio aleatório, efeitos de casa ridge
    identificados pela âncora (institutos com histórico verificado), variância extra por instituto estimada nos dados
 3c. Pesquisas estaduais → leitura nacional independente (swing sobre 2022)
 3d. 1º turno: um estado por candidato (votos válidos), viés histórico do 1º turno, simulação → P(decide no 1º turno), P(Flávio no 2º)
 4. Monte Carlo do 2º turno: nível + deriva em duas fases (até 4/out; entre turnos, calibrada em 2014/18/22) − viés + choques regionais
 4b. Ensemble com pesos por regime (antes/depois do 1º turno) e tabela "o que mudou" (v2 → v3)
 5. Tornado das variáveis decisivas · 6. O que o 1º turno dirá sobre o 2º · 6b. Diagnóstico por instituto · 7. Saídas
 8. Regime pós-1º turno: ativado por resultado_1turno.csv (erro por instituto → priors do 2º turno)
 5b. Migração (migracao_markov.py): ausentes, 3ª via e brancos/nulos por cadeia de Markov + Monte Carlo, análise paralela e condicional

Reprodutível: python3 modelo_eleicao_2026.py  (HOJE pode ser fixado por variável de ambiente ELEICAO_HOJE=YYYY-MM-DD)
"""
import json, math, os, sys
import numpy as np
import pandas as pd
from scipy.stats import norm, beta as beta_dist
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from espaco_estados import ajustar, validacao_rolling

rng = np.random.default_rng(20261025)
HOJE = pd.Timestamp(os.environ.get("ELEICAO_HOJE") or pd.Timestamp.today().normalize())
T1   = pd.Timestamp("2026-10-04")
T2   = pd.Timestamp("2026-10-25")
DATA = os.path.dirname(os.path.abspath(__file__)) + "/"                 # bases (CSV) e previsão congelada: no repositório
OUT  = os.environ.get("ELEICAO_OUT", "/tmp/outputs/modelo/")            # saídas intermediárias (JSON, SVGs) para montar o painel
os.makedirs(OUT, exist_ok=True)
# Variantes do toggle do herói (19/out): com ELEICAO_SO_POS1T a base de 2º turno fica só com as ondas de campo depois de 4/out.
#   ELEICAO_SO_POS1T=1      → sem o voto contado: o nível NÃO é combinado ao resultado do 1º turno + transferência (W_ESTRUT = 0);
#                             grava resultados_pos1t.json.
#   ELEICAO_SO_POS1T=estrut → com o voto contado: mesma combinação do modelo principal; grava resultados_pos1t_estrut.json.
# As duas rodadas terminam logo depois do ensemble (sem MCMC, abstenção, tornado, migração, congelados, CSVs ou SVGs).
SO_POS1T = bool(os.environ.get("ELEICAO_SO_POS1T"))
POS1T_COM_ESTRUT = os.environ.get("ELEICAO_SO_POS1T") == "estrut"
# Variante "com momentum" (21/out): com ELEICAO_MOMENTUM=1 a deriva até 25/out ganha uma média, vinda do agregado das diferenças entre
# ondas consecutivas do mesmo instituto (mudanca_2turno.resumo, em p.p./dia), encolhida por ENCOLHE_MOMENTUM; o desvio dessa média é o
# erro-padrão do agregado (piso SD_MOMENTUM_PISO por dia), encolhido na mesma proporção, para não inflar a variância da deriva. O MCMC recebe a mesma prior. Grava resultados_momentum.json e termina
# depois do MCMC. No modelo principal (flag ausente) a deriva continua com média zero: nenhuma saída muda.
MOMENTUM = bool(os.environ.get("ELEICAO_MOMENTUM"))
ENCOLHE_MOMENTUM, SD_MOMENTUM_PISO = 0.5, 0.1
T0   = pd.Timestamp("2025-12-01")           # origem do índice diário
INICIO_JANELA = pd.Timestamp("2026-06-01")  # janela de campanha usada para o nível de hoje e para σ_rw
def dia(ts): return int((pd.Timestamp(ts) - T0).days)
t_now = dia(HOJE)

# ----------------------------------------------------------------------------
# 1. DADOS — 2º turno
# ----------------------------------------------------------------------------
df = pd.read_csv(DATA + "pesquisas_2turno_2026.csv", parse_dates=["campo_fim"])
df = df[df.campo_fim <= HOJE].copy()
df["margem_total"] = df.lula - df.flavio
df["lula_valid"]   = 100 * df.lula / (df.lula + df.flavio)
df["margem_valid"] = 100 * (df.lula - df.flavio) / (df.lula + df.flavio)
df["idade_dias"]   = (HOJE - df.campo_fim).dt.days
df["t"]            = (df.campo_fim - T0).dt.days.astype(float)
df["dia"]          = df.t.astype(int)

def _se_amostral(l_, f_, n_):
    """Erro padrão da margem em votos válidos m = 100(L−F)/(L+F) sob amostragem aleatória simples (multinomial)."""
    pl, pf = l_/100.0, f_/100.0
    return 100*math.sqrt((pl + pf - (pl - pf)**2/(pl + pf))/n_)/(pl + pf)   # = 100·sqrt(4s(1−s)/(n·V)), s = L/(L+F), V = L+F
N_MAX_EFETIVO = 5000   # teto do tamanho de amostra efetivo: acima disso o erro não amostral domina (e uma onda não deve pesar 20× outra)
# Auditoria de 16/out: (a) a coluna opcional n_efetivo da base traz o n efetivo declarado pelo instituto (Palver: Kish, efeito dos pesos 2,8
# a 4,3 — o n bruto de 5.000 subestimava o erro em até 2×); (b) AtlasIntel (recrutamento digital aleatório, não probabilístico) declara
# ±1,0 p.p. com n≈5.000, mas a amostragem aleatória simples já dá ±1,4 e o efeito de desenho não é reportado: teto de 2.500.
N_EFETIVO_INST = {"AtlasIntel": 2500}
df["n_ef"] = (df["n_efetivo"] if "n_efetivo" in df.columns else df["n"]).fillna(df["n"]).astype(float)
df["n_ef"] = [min(r.n_ef, N_EFETIVO_INST.get(r.instituto, N_MAX_EFETIVO), N_MAX_EFETIVO) for r in df.itertuples()]
df["se_margem_teorico"] = [_se_amostral(r.lula, r.flavio, r.n_ef) for r in df.itertuples()]
df["se2"] = df.se_margem_teorico**2

# ----------------------------------------------------------------------------
# 2. CALIBRAÇÃO HISTÓRICA (calibracao_historica.csv: erro na margem esq−dir em válidos; + = superestimou o PT)
# ----------------------------------------------------------------------------
cal = pd.read_csv(DATA + "calibracao_historica.csv")
hist = cal.rename(columns={"ciclo": "eleicao", "erro_margem": "erro_pp"})[["instituto", "eleicao", "turno", "erro_pp", "referencia"]]
mae_inst = hist.groupby("instituto").erro_pp.apply(lambda s: s.abs().mean())
bias_inst = hist.groupby("instituto").erro_pp.mean()
n_inst = hist.groupby("instituto").size()
bias_el = hist.groupby(["eleicao", "turno"]).erro_pp.mean()

# Qualidade do instituto: erro absoluto médio EM RELAÇÃO À MÉDIA DO CICLO/TURNO (auditoria de 3/out). O MAE bruto mistura o erro
# próprio do instituto com o erro comum do ciclo (em 2014 e 2022 todos erraram o 1º turno em ~9 pontos), que já é tratado pelo
# desconto de viés; quem só tem histórico em ciclos "bons" parecia melhor. Só entram ciclos/turnos com 3 ou mais institutos.
_grp = hist.groupby(["eleicao", "turno"]).erro_pp
hist["erro_rel"] = hist.erro_pp - _grp.transform("mean")
_rel = hist[_grp.transform("size") >= 3]
mae_rel = _rel.groupby("instituto").erro_rel.apply(lambda s: s.abs().mean())
n_rel = _rel.groupby("instituto").size()
MAE_REF, Q_DEFAULT, K_SHRINK = round(float(mae_rel.median()), 2), 0.8, 2.0
def peso_qualidade(inst):
    """Qualidade = MAE relativo mediano / MAE relativo do instituto, encolhida para 0,8 conforme poucas eleições sustentam o MAE."""
    if inst not in mae_rel.index:
        return Q_DEFAULT
    q_ = float(np.clip(MAE_REF / max(mae_rel[inst], 0.5), 0.6, 1.5))
    k_ = float(n_rel[inst])
    return float((k_ * q_ + K_SHRINK * Q_DEFAULT) / (k_ + K_SHRINK))

# Viés sistemático do 2º turno: média por ciclo dos institutos de referência (referencia=1), depois média entre ciclos
# com recência suave (meia-vida de dois ciclos = 8 anos). σ_b = desvio entre ciclos, com piso 2,0 pela mistura de métodos de 2026.
_h2 = hist[(hist.turno == 2) & (hist.referencia == 1)]
bias_2t_ciclos = _h2.groupby("eleicao").erro_pp.mean().to_dict()
MEIA_VIDA_CICLOS = 8.0
pesos_ciclo = {c_: 0.5 ** ((2022 - c_) / MEIA_VIDA_CICLOS) for c_ in bias_2t_ciclos}
BIAS_MEAN = round(sum(bias_2t_ciclos[c_]*pesos_ciclo[c_] for c_ in bias_2t_ciclos) / sum(pesos_ciclo.values()), 2)
# Desvio preditivo para um ciclo novo: desvio entre ciclos × sqrt(1 + 1/n) (a média também é estimada), com piso 2,0.
# As caudas são t de Student (DF_ERRO graus de liberdade, reescalada para o mesmo desvio): com 3 a 5 ciclos, a normal é otimista.
DF_ERRO = 5
def _t_unit(r_, n_): return r_.standard_t(DF_ERRO, n_) * math.sqrt((DF_ERRO - 2) / DF_ERRO)
def _sd_pred(v_): v_ = list(v_); return float(np.std(v_, ddof=1) * math.sqrt(1 + 1/len(v_)))
BIAS_SD = round(max(2.0, _sd_pred(bias_2t_ciclos.values())), 2)
BIAS_MEAN_IGUAL = round(float(np.mean(list(bias_2t_ciclos.values()))), 2)
# 1º turno: os ciclos verificáveis (2010, 2014, 2018, 2022) subestimaram o principal adversário do PT. Prior = 60% da média com
# recência: é uma HIPÓTESE (os institutos ajustam método entre ciclos — em 2026 vários ponderam pelo voto declarado de 2022 — e o
# sinal do erro pode mudar), por isso o desvio é o preditivo entre ciclos (≈ 3,8), não mais 3,0 fixo.
_h1 = hist[(hist.turno == 1) & (hist.referencia == 1)]
bias_1t_ciclos = _h1.groupby("eleicao").erro_pp.mean().to_dict()
_p1 = {c_: 0.5 ** ((2022 - c_) / MEIA_VIDA_CICLOS) for c_ in bias_1t_ciclos}
BIAS_1T_HIST = round(sum(bias_1t_ciclos[c_]*_p1[c_] for c_ in bias_1t_ciclos) / sum(_p1.values()), 2)
ENCOLHIMENTO_1T = 0.6
BIAS_1T_MEAN, BIAS_1T_SD = round(ENCOLHIMENTO_1T * BIAS_1T_HIST, 2), round(max(3.0, _sd_pred(bias_1t_ciclos.values())), 2)
# Viés por método — só diagnóstico (poucas observações de 2º turno para telefônico e online: não se aplica)
METODO_HIST = {"Datafolha": "presencial", "Quaest": "presencial", "Ipec": "presencial", "Ibope": "presencial", "MDA": "presencial",
               "Vox Populi": "presencial", "Parana Pesquisas": "presencial", "AtlasIntel": "online", "PoderData": "telefonico",
               "Ipespe": "telefonico", "Verita": "nao_informado", "Futura Inteligencia": "nao_informado"}
_h2m = hist[hist.turno == 2].assign(metodo=lambda d: d.instituto.map(METODO_HIST))
BIAS_METODO = {m_: round(float(g.erro_pp.mean()), 2) for m_, g in _h2m.groupby("metodo")}
BIAS_N_METODO = _h2m.groupby("metodo").size().to_dict()
# Movimento entre turnos (pesquisas de 2º turno do mesmo instituto: primeira após o 1º turno → última antes do 2º)
mov = pd.read_csv(DATA + "movimento_entre_turnos.csv")
_se_pol = 2.2   # erro amostral típico da margem (n≈2.000) — cada delta observado carrega 2 erros de pesquisa
_var_obs = float((mov.delta_margem**2).mean()); _dias_med = float(mov.dias.mean())
SIG_ENTRE_DIA_BRUTO = math.sqrt(max(_var_obs - 2*_se_pol**2, 1.0) / _dias_med)

# Âncora: institutos com histórico presidencial verificado (referencia=1 no 2º turno) presentes na base de 2026
ANCORA = sorted(set(_h2.instituto) & set(df.instituto))
Q_ANCORA = {p_: peso_qualidade(p_) for p_ in ANCORA}
if SO_POS1T:
    # Variante pós-1º turno: o filtro entra DEPOIS da âncora e dos pesos de qualidade, que ficam os do modelo principal (ajustar ignora
    # âncoras sem observação), de modo que a seção 3d (1º turno) sai idêntica; só a série de 2º turno muda. Sem ondas pré-4/out,
    # _inst_pre1t fica vazio e a_casa = 1 em todas: cada onda identifica o efeito de casa inteiro, encolhido ao prior do erro do 1º turno.
    assert HOJE >= T1, "variante pós-1º turno só faz sentido depois de 4/out"
    df = df[df.campo_fim > T1].copy()
    assert len(df) >= 5, "variante pós-1º turno: menos de 5 pesquisas depois de 4/out"
    assert set(ANCORA) & set(df.instituto), "variante pós-1º turno: nenhuma pesquisa da âncora depois de 4/out"

# ----------------------------------------------------------------------------
# 3. TENDÊNCIA + EFEITOS DE CASA — espaço de estados único (Jackman 2005; Linzer 2013; Economist 2020)
# ----------------------------------------------------------------------------
# Regime pós-1º turno (seção 8): priors de efeito de casa vindos do erro observado no 1º turno
res1t_path = DATA + "resultado_1turno.csv"
RESULTADO_1T = None
if os.path.exists(res1t_path):
    _r1 = pd.read_csv(res1t_path)
    _r1["votos_validos_pct"] = pd.to_numeric(_r1.votos_validos_pct, errors="coerce")
    if len(_r1) and _r1.votos_validos_pct.notna().any():
        RESULTADO_1T = dict(zip(_r1.candidato.str.lower(), _r1.votos_validos_pct))
POS_1T = RESULTADO_1T is not None and HOJE >= T1
if HOJE > T1 and not POS_1T:
    print("[AVISO] já passou o 1º turno e resultado_1turno.csv está vazio: o modelo segue no regime pré-1º turno.")
prior_h_2t, tau_prior_h = {}, None
# Persistência do efeito de casa depois do 1º turno (auditoria de 16/out, estimada nas 14 pesquisas pós-4/out que havia então). A inclinação
# da margem observada sobre o efeito de casa estimado antes do 1º turno era +0,58 ± 0,68 (+0,13 ± 1,0 sem Gerp e Palver; persistência
# total = 1, nula = 0), e a dispersão entre institutos AUMENTAVA depois da correção (DP 0,8 → 2,1 na semana de 10–14/out, sem a Gerp):
# vários institutos passaram a pesar pelo voto declarado no 1º turno (Palver v3, Ipespe). λ = 0,5 entra como compromisso entre a
# persistência total do modelo antigo e o zero que os dados também comportam; a observação pós-1º turno identifica λ·h, não h.
# λ só faz sentido para quem tem efeito de casa estimado ANTES do 1º turno: uma série nova (Palver v3, MDA v2, só com ondas pós-4/out)
# não tem efeito anterior para "persistir", e nela a observação identifica h inteiro (a_casa = 1).
LAMBDA_CASA_POS1T = 0.5
LAMBDA_CASA = LAMBDA_CASA_POS1T if (RESULTADO_1T is not None and HOJE >= T1) else 1.0
_inst_pre1t = set(df.loc[df.campo_fim <= T1, "instituto"])
df["a_casa"] = np.where((df.campo_fim > T1) & df.instituto.isin(_inst_pre1t), LAMBDA_CASA, 1.0)
FLAVIO_TOP2_REAL = None
ERRO_COMUM_1T = None      # erro médio da âncora na margem Lula − Flávio do 1º turno (+ = superestimou Lula)
if POS_1T:
    # Seção 8: o erro de cada instituto na margem Lula − Flávio do 1º turno (última onda até 4/out, em válidos), medido em relação
    # à média da âncora, vira prior do efeito de casa no 2º turno, com metade do peso (o erro de um turno só prevê em parte o do outro).
    _real = RESULTADO_1T.get("lula", np.nan) - RESULTADO_1T.get("flavio", np.nan)
    _ord = sorted(RESULTADO_1T, key=lambda k_: -(RESULTADO_1T[k_] if pd.notna(RESULTADO_1T[k_]) else -1))
    FLAVIO_TOP2_REAL = "flavio" in _ord[:2]
    _p1e = pd.read_csv(DATA + "pesquisas_1turno_2026.csv", parse_dates=["campo_fim"])
    _p1e = _p1e[_p1e.campo_fim <= T1].sort_values("campo_fim").groupby("instituto").tail(1)
    _cand_cols = ["lula", "flavio", "cury", "renan", "caiado", "zema", "outros"]
    _val = _p1e[_cand_cols].sum(axis=1)
    _err = dict(zip(_p1e.instituto, 100*(_p1e.lula - _p1e.flavio)/_val - _real))
    _anc = [v for k_, v in _err.items() if k_ in set(_h2.instituto)]
    _c0 = float(np.mean(_anc)) if _anc else float(np.mean(list(_err.values())))
    if np.isfinite(_real):
        ERRO_COMUM_1T = _c0
        prior_h_2t = {k_: 0.5*(v - _c0) for k_, v in _err.items() if np.isfinite(v)}
        tau_prior_h = 1.5
        # As séries novas do 2º turno ("Palver v3", "MDA v2", "Futura Inteligencia v2") não recebem este prior (a chave é o nome do 1º turno):
        # entram com prior 0 e τ = 1,5, de propósito, porque mudaram a ponderação e o erro do 1º turno foi medido com a régua antiga.

# Quebra estrutural: as simulações de 2º turno feitas antes do 1º turno e as pesquisas feitas depois não são a mesma série (em
# 2014, 2018 e 2022 o salto foi de 9 a 14 pontos). No regime pós-1º turno o passeio recebe variância extra de 6² no dia seguinte
# a 4/out, de modo que o nível passa a seguir as pesquisas novas em vez de levar semanas para se afastar das antigas.
SD_QUEBRA_1T = 6.0
Q_EXTRA = {dia(T1) + 1: SD_QUEBRA_1T**2} if POS_1T else {}
fit_full = ajustar(df.margem_valid.values, df.dia.values, df.se2.values, df.instituto.values, set(ANCORA), Q_ANCORA, n_days=t_now + 1, q_extra=Q_EXTRA,
                   a_h=df.a_casa.values)
jan = df[df.campo_fim >= INICIO_JANELA].copy()
d0_jan = int(jan.dia.min())
Q_EXTRA_WIN = {k_ - d0_jan: v for k_, v in Q_EXTRA.items()}
def _ajusta_janela(sub_):
    return ajustar(sub_.margem_valid.values, (sub_.dia - d0_jan).values, sub_.se2.values, sub_.instituto.values, set(ANCORA), Q_ANCORA,
                   n_days=t_now - d0_jan + 1, prior_h=prior_h_2t, tau_prior_h=tau_prior_h, q_extra=Q_EXTRA_WIN, a_h=sub_.a_casa.values)
fit_win = _ajusta_janela(jan)
def x_win(d_):   # nível suavizado da janela no dia absoluto d_
    k_ = int(np.clip(d_ - d0_jan, 0, fit_win["n_days"] - 1)); return float(fit_win["x"][k_]), float(fit_win["P"][k_])
level_nac, P_nac = x_win(t_now)
se_nac = math.sqrt(P_nac + fit_win["var_ancora"])   # o filtro trata os efeitos de casa como conhecidos; soma a incerteza da média da âncora
house = dict(fit_full["house"]); house.update(fit_win["house"])          # janela prevalece; institutos só antigos ficam com o ajuste completo
sigma_inst = dict(fit_full["sigma_inst"]); sigma_inst.update(fit_win["sigma_inst"])
SIG_RW_KF, SIG_RW_ML = fit_win["sig_rw"], fit_win["sig_rw_ml"]
insts = list(df.instituto.unique())
# casa (instituto sem o sufixo " vN"): "Palver v3" e "MDA v2" são séries novas só para o efeito de casa; nos contadores onda a onda
# e na "última pesquisa por instituto" continuam a Palver e a CNT/MDA. "Futura Inteligencia v2" (21/out): a partir da onda de 19–20/out a Futura
# pondera escolaridade pela PNAD Contínua em vez do cadastro do TSE (mudança declarada no relatório), série nova pelo mesmo critério
df["inst_base"] = df.instituto.str.replace(r" v\d$", "", regex=True)
df["house"] = df.instituto.map(house)
df["house_aplicado"] = df.house * df.a_casa          # o que de fato desconta a margem de cada pesquisa (λ·h depois do 1º turno)
df["sigma_inst"] = df.instituto.map(sigma_inst)
df["w_precisao"] = 1.0 / (df.se2 + df.sigma_inst**2)
df["w_recencia"] = np.exp(-np.log(2) * df.idade_dias / 21.0)      # só para exibição (a recência entra pelo passeio aleatório)
df["w_qualidade"] = df.instituto.map(peso_qualidade)
df["w_tamanho"] = np.clip(np.sqrt(df.n / 2000.0), 0.7, 1.5)
df["w"] = df.w_recencia * df.w_precisao / df.w_precisao.mean()
print(f"[Espaço de estados] janela desde {INICIO_JANELA.date()}: {len(jan)} pesquisas | σ_rw MAP={SIG_RW_KF:.3f} (ML {SIG_RW_ML:.3f}) p.p./dia | "
      f"nível hoje={level_nac:+.2f} ± {se_nac:.2f} | âncora={ANCORA} | iterações {fit_win['iter']}")
level_kf, se_kf = level_nac, se_nac
level_pspline, se_pspline = float(fit_full["x"][t_now]), math.sqrt(float(fit_full["P"][t_now]))   # ajuste completo (dez–set), para comparação

# média ponderada simples (última pesquisa por casa, ajustada pelo efeito de casa da série a que ela pertence) — para comparação
last = df.sort_values("campo_fim").groupby("inst_base").tail(1).copy()
last["margem_adj"] = last.margem_valid - last.house_aplicado
last["w_last"] = last.w
media_simples_adj = float((last.margem_adj * last.w_last).sum() / last.w_last.sum())
media_simples_bruta = float((last.margem_valid * last.w_last).sum() / last.w_last.sum())

# validação de origem móvel (últimos 60 dias, passo 3, horizonte 7 dias) — cobertura e viés das previsões do próprio modelo
# Mesmo modelo do ajuste da janela (prior do efeito de casa vindo do erro do 1º turno e λ nas ondas pós-1º turno): antes a validação
# rodava sem os dois e media um modelo diferente do que o painel usa.
_rv = [] if SO_POS1T else validacao_rolling(jan.margem_valid.values, (jan.dia - d0_jan).values, jan.se2.values, jan.instituto.values, set(ANCORA), Q_ANCORA,
                        dias=range(max(t_now - d0_jan - 60, 25), t_now - d0_jan - 6, 3), q_extra=Q_EXTRA_WIN,
                        prior_h=prior_h_2t, tau_prior_h=tau_prior_h, a_h=jan.a_casa.values)
_z = np.array([(o_ - p_)/s_ for _, o_, p_, s_, _, _ in _rv]) if _rv else np.array([0.0])
# viés separado: institutos que o ajuste já conhecia × institutos novos (entram com efeito de casa 0). Se o viés está só nos novos,
# é composição da base; se está também nos conhecidos, é tendência que o passeio aleatório (sem inclinação) não antecipa.
_ev = [o_ - p_ for _, o_, p_, _, _, v_ in _rv if v_]; _en = [o_ - p_ for _, o_, p_, _, _, v_ in _rv if not v_]
VALIDACAO = {"n_previsoes": len(_rv), "vies_medio": round(float(np.mean([o_ - p_ for _, o_, p_, _, _, _ in _rv])), 2) if _rv else None,
             "vies_inst_vistos": round(float(np.mean(_ev)), 2) if _ev else None, "n_inst_vistos": len(_ev),
             "vies_inst_novos": round(float(np.mean(_en)), 2) if _en else None, "n_inst_novos": len(_en),
             "cobertura_80": round(float(np.mean(np.abs(_z) < 1.2816)), 3), "cobertura_95": round(float(np.mean(np.abs(_z) < 1.96)), 3),
             "erro_abs_medio": round(float(np.mean([abs(o_ - p_) for _, o_, p_, _, _, _ in _rv])), 2) if _rv else None,
             "sd_z": round(float(_z.std()), 2), "horizonte_dias": 7}
print(f"[Validação] {VALIDACAO}")

# curva para o gráfico (ajuste completo dez→hoje)
tt_grid = np.arange(int(df.dia.min()), t_now + 1)
f_grid = fit_full["x"][tt_grid]
se_grid = np.sqrt(fit_full["P"][tt_grid])

# ----------------------------------------------------------------------------
# 3c. PESQUISAS ESTADUAIS → estimativa nacional independente (swing sobre o 2º turno de 2022 por UF)
# ----------------------------------------------------------------------------
MARGEM_2022 = 1.80
SIG_EXTRA_UF, SIG_SIST_UF, RHO_EST_NAC, COBERTURA_MIN, HALF_LIFE = 3.0, 1.5, 0.3, 0.30, 21.0
ref_uf = pd.read_csv(DATA + "referencia_uf.csv") if os.path.exists(DATA + "referencia_uf.csv") else None
est = pd.read_csv(DATA + "pesquisas_estaduais_2026.csv", parse_dates=["campo_fim"]) if os.path.exists(DATA + "pesquisas_estaduais_2026.csv") else None
estadual = {"usado": False, "motivo": "base estadual ausente"}
REGIAO_UF, PESO_UF = {}, {}
if ref_uf is not None:
    ref_uf["peso"] = ref_uf.eleitorado / ref_uf.eleitorado.sum()
    ref_uf["peso_v"] = ref_uf.votos_validos_2022 / ref_uf.votos_validos_2022.sum()   # a margem nacional é média ponderada por votos válidos
    ref_uf["m22"] = ref_uf.lula_2022 - ref_uf.bolsonaro_2022
    REGIAO_UF = dict(zip(ref_uf.uf, ref_uf.regiao)); PESO_UF = dict(zip(ref_uf.uf, ref_uf.peso))
if ref_uf is not None and est is not None:
    e2 = est[(est.turno == 2) & ((HOJE - est.campo_fim).dt.days <= 45) & (est.campo_fim <= HOJE)].copy()
    if POS_1T:
        e2 = e2[e2.campo_fim > T1]      # a quebra de 4/out vale também para as estaduais: só entram as feitas depois do 1º turno
    e2 = e2.merge(ref_uf[["uf", "m22", "peso", "peso_v", "regiao"]], on="uf", how="inner")
    if len(e2):
        e2["margem_bruta"] = 100*(e2.lula - e2.flavio)/(e2.lula + e2.flavio)
        e2["t"] = (e2.campo_fim - T0).dt.days.astype(float)
        e2["ajuste_casa"] = -e2.instituto.map(house).fillna(0.0)
        e2["ajuste_tend"] = [level_nac - x_win(int(tt))[0] for tt in e2.t]
        e2["margem_valid"] = e2.margem_bruta + e2.ajuste_casa + e2.ajuste_tend
        e2["swing"] = e2.margem_valid - e2.m22
        e2["w"] = (np.exp(-np.log(2)*(HOJE - e2.campo_fim).dt.days/HALF_LIFE) * np.clip(np.sqrt(e2.n/1000), 0.6, 1.5)
                   * e2.instituto.map(lambda i_: peso_qualidade(i_)))
        e2["var_i"] = [_se_amostral(r.lula, r.flavio, r.n)**2 + SIG_EXTRA_UF**2 for r in e2.itertuples()]
        por_uf = []
        for uf_, g in e2.groupby("uf"):
            wv = g.w/g.var_i
            s_raw = float((g.swing*wv).sum()/wv.sum())
            v_raw = float(1/(g.w/g.var_i).sum()*g.w.sum()/len(g)) if len(g) > 1 else float(g.var_i.iloc[0])
            por_uf.append({"uf": uf_, "n_pesq": int(len(g)), "swing_bruto": s_raw, "var": v_raw, "peso": float(g.peso.iloc[0]), "peso_v": float(g.peso_v.iloc[0]),
                           "m22": float(g.m22.iloc[0]), "margem_2026": float((g.margem_valid*g.w).sum()/g.w.sum()),
                           "institutos": ", ".join(sorted(g.instituto.unique())), "ultimo_campo": str(g.campo_fim.max().date())})
        pu = pd.DataFrame(por_uf)
        wi = 1/pu["var"]; mu_fe = float((pu.swing_bruto*wi).sum()/wi.sum())
        Qh = float((wi*(pu.swing_bruto - mu_fe)**2).sum()); c_ = float(wi.sum() - (wi**2).sum()/wi.sum())
        tau2 = max(0.0, (Qh - (len(pu) - 1))/c_) if len(pu) > 1 and c_ > 0 else 0.0
        wr = 1/(pu["var"] + tau2); mu_s = float((pu.swing_bruto*wr).sum()/wr.sum()); var_mu = float(1/wr.sum())
        pu["regiao"] = pu.uf.map(REGIAO_UF)
        mu_reg = {}
        for r_, g_ in pu.groupby("regiao"):
            wg = 1/(g_["var"] + tau2); m_r = float((g_.swing_bruto*wg).sum()/wg.sum()); v_r = float(1/wg.sum())
            k_r = (tau2/2)/((tau2/2) + v_r) if tau2 > 0 else 0.0
            mu_reg[r_] = mu_s + k_r*(m_r - mu_s)
        pu["mu_regiao"] = pu.regiao.map(mu_reg)
        pu["swing"] = pu.mu_regiao + (tau2/2)/((tau2/2) + pu["var"])*(pu.swing_bruto - pu.mu_regiao)
        pu["var_post"] = 1/(1/max(tau2/2, 1e-6) + 1/pu["var"])
        cobertura = float(pu.peso.sum()); cobertura_v = float(pu.peso_v.sum())
        _sem = ref_uf[~ref_uf.uf.isin(pu.uf)]
        swing_sem = float(sum(r_.peso_v*mu_reg.get(r_.regiao, mu_s) for r_ in _sem.itertuples()))
        swing_nac = float((pu.swing*pu.peso_v).sum() + swing_sem)
        var_est = float((pu.peso_v**2*pu.var_post).sum() + (1 - cobertura_v)**2*(var_mu + tau2/2) + SIG_SIST_UF**2)
        nivel_est, se_est = MARGEM_2022 + swing_nac, math.sqrt(var_est)
        estadual = {"usado": cobertura >= COBERTURA_MIN, "motivo": "" if cobertura >= COBERTURA_MIN else f"cobertura {cobertura:.0%} < {COBERTURA_MIN:.0%}",
                    "nivel": nivel_est, "se": se_est, "mu_swing": mu_s, "tau": math.sqrt(tau2), "cobertura": cobertura,
                    "n_pesq": int(len(e2)), "n_uf": int(len(pu)), "por_uf": pu.sort_values("peso", ascending=False),
                    "mu_regiao": {k_: round(v, 2) for k_, v in mu_reg.items()},
                    "ajuste_medio_tend": float((e2.ajuste_tend).mean()), "ajuste_medio_casa": float(e2.ajuste_casa.mean())}
        print(f"[Estadual] {len(e2)} pesquisas em {len(pu)} UFs (cobertura {cobertura:.0%}) | swing médio {mu_s:+.2f} τ={math.sqrt(tau2):.2f} | nacional {nivel_est:+.2f} ± {se_est:.2f}")

level_final, se_final = level_nac, se_nac
w_est_comb = 0.0
if estadual["usado"]:
    s1, s2 = se_nac, estadual["se"]
    den = s1**2 + s2**2 - 2*RHO_EST_NAC*s1*s2
    w_est_comb = float(np.clip((s1**2 - RHO_EST_NAC*s1*s2)/den, 0, 1))
    level_final = (1 - w_est_comb)*level_nac + w_est_comb*estadual["nivel"]
    se_final = math.sqrt(s1**2*s2**2*(1 - RHO_EST_NAC**2)/den)
    estadual["peso_na_combinacao"] = w_est_comb

# ----------------------------------------------------------------------------
# 3d. 1º TURNO — um estado por candidato (votos válidos), efeitos de casa por instituto × candidato, viés histórico, simulação
# ----------------------------------------------------------------------------
CANDS = ["lula", "flavio", "cury", "renan", "caiado", "zema", "outros"]
CAND_LABEL = {"lula": "Lula", "flavio": "Flávio Bolsonaro", "cury": "Augusto Cury", "renan": "Renan Santos", "caiado": "Ronaldo Caiado",
              "zema": "Romeu Zema", "outros": "Outros"}
p1 = pd.read_csv(DATA + "pesquisas_1turno_2026.csv", parse_dates=["campo_fim"])
p1 = p1[(p1.campo_fim <= HOJE)].sort_values("campo_fim").reset_index(drop=True)
# Ondas em que só Lula e Flávio foram publicados: os demais candidatos são imputados (última onda completa do mesmo instituto ou,
# na falta, média recente entre institutos) apenas para calcular os válidos; o candidato imputado NÃO entra no seu próprio ajuste.
_terc = ["cury", "renan", "caiado", "zema", "outros"]
_obs = {c_: p1[c_].notna().values.copy() for c_ in CANDS}           # máscara antes de qualquer imputação (inclusive de "outros")
p1["imputada"] = p1[["cury", "renan", "caiado", "zema"]].isna().any(axis=1)
for i_, r_ in p1.iterrows():
    for c_ in _terc:
        if pd.isna(r_[c_]):
            prev = p1[(p1.instituto == r_.instituto) & (p1.index < i_) & p1[c_].notna()]
            ref = p1[(p1.index < i_) & p1[c_].notna()].tail(8)
            p1.loc[i_, c_] = float(prev[c_].iloc[-1]) if len(prev) else (float(ref[c_].mean()) if len(ref) else 0.0)
p1["validos"] = p1[CANDS].sum(axis=1)
p1["n_valid"] = np.minimum(p1.n, N_MAX_EFETIVO) * p1.validos / 100.0   # teto de n: amostras gigantes (Veritá 40.500) não dominam
for c_ in CANDS: p1[c_ + "_v"] = 100 * p1[c_] / p1.validos
p1["dia"] = (p1.campo_fim - T0).dt.days.astype(int)
p1["margem_v"] = p1.lula_v - p1.flavio_v
d0_1t = int(p1.dia.min())
JANELA_MENORES = 28   # dias: o nível dos candidatos menores usa só as ondas recentes (depois da quebra de Cury no início de setembro)
def _fit_cand(c_, sig_rw_fixo=None, desde=None):
    m_ = _obs[c_] & ((p1.campo_fim >= desde).values if desde is not None else True)
    sub_ = p1[m_]
    pv = sub_[c_ + "_v"].values / 100.0
    se2c = np.maximum(pv * (1 - pv) / sub_.n_valid.values, 1e-6) * 1e4
    d0_ = int(sub_.dia.min())
    fit_ = ajustar(sub_[c_ + "_v"].values, (sub_.dia - d0_).values, se2c, sub_.instituto.values, set(ANCORA), Q_ANCORA,
                   n_days=t_now - d0_ + 1, tau_h=1.2, sigma_floor=0.6, sig_rw_fixo=sig_rw_fixo)
    fit_["d0"] = d0_
    return fit_
fit1 = {c_: _fit_cand(c_) for c_ in CANDS}
# Candidatos menores: σ_rw comum (mediana das estimativas individuais). Com σ próprio, uma quebra estrutural (Cury: 1–2% → 10% → 3%)
# leva o passeio de um candidato a ~1 p.p./dia e o nível passa a copiar a última pesquisa, o que fazia o 3º lugar oscilar a cada onda.
MENORES = ["cury", "renan", "caiado", "zema", "outros"]
SIG_RW_MENORES_LIVRE = {c_: round(float(fit1[c_]["sig_rw"]), 3) for c_ in MENORES}
SIG_RW_MENORES = float(np.median(list(SIG_RW_MENORES_LIVRE.values())))
for c_ in MENORES:
    fit1[c_] = _fit_cand(c_, SIG_RW_MENORES, desde=min(HOJE, T1) - pd.Timedelta(days=JANELA_MENORES))   # depois de 4/out não há pesquisa de 1º turno: a janela fica ancorada em T1
share_now = {c_: float(fit1[c_]["x"][t_now - fit1[c_]["d0"]]) for c_ in CANDS}
se_share = {c_: math.sqrt(float(fit1[c_]["P"][t_now - fit1[c_]["d0"]])) for c_ in CANDS}
# Lula e Flávio (auditoria de 3/out): em vez de duas séries independentes, o espaço de estados é ajustado na MARGEM Lula − Flávio
# (como no 2º turno: o efeito de casa e o viés histórico são da margem) e na SOMA dos dois (quanto sobra para os demais).
# Lula = (soma + margem)/2, Flávio = (soma − margem)/2. Duas séries independentes ignoravam que os dois disputam o mesmo voto.
_pl, _pf = p1.lula_v/100.0, p1.flavio_v/100.0
p1["soma_v"] = p1.lula_v + p1.flavio_v
def _fit_par(col_, se2_, tau_, piso_, sub_=None):
    sub_ = p1 if sub_ is None else sub_
    f_ = ajustar(sub_[col_].values, (sub_.dia - d0_1t).values, se2_[sub_.index.values], sub_.instituto.values, set(ANCORA), Q_ANCORA,
                 n_days=t_now - d0_1t + 1, tau_h=tau_, sigma_floor=piso_)
    return f_
_se2_m1 = ((_pl + _pf - (_pl - _pf)**2) / p1.n_valid * 1e4).values
_se2_s1 = (np.maximum((_pl + _pf)*(1 - _pl - _pf), 1e-4) / p1.n_valid * 1e4).values
fit_m1 = _fit_par("margem_v", _se2_m1, 1.5, 0.8)
fit_s1 = _fit_par("soma_v", _se2_s1, 1.2, 0.6)
_m1, _s1 = float(fit_m1["x"][-1]), float(fit_s1["x"][-1])
SE_M1 = math.sqrt(float(fit_m1["P"][-1]) + fit_m1["var_ancora"]); SE_S1 = math.sqrt(float(fit_s1["P"][-1]) + fit_s1["var_ancora"])
share_now["lula"], share_now["flavio"] = (_s1 + _m1)/2, (_s1 - _m1)/2
se_share["lula"] = se_share["flavio"] = math.sqrt(SE_M1**2 + SE_S1**2)/2
_norm = sum(share_now.values())
share_now = {c_: 100 * v / _norm for c_, v in share_now.items()}        # renormaliza (compositional)
house_1t = {c_: fit1[c_]["house"] for c_ in CANDS}
sig_rw_1t = float(np.median([fit1[c_]["sig_rw"] for c_ in CANDS]))
# última pesquisa por instituto (totais e válidos) — para a tabela do painel
last1 = p1.sort_values("campo_fim").groupby("instituto").tail(1).sort_values("campo_fim", ascending=False)
# agregado em totais (para o modelo de transferência): brancos/indecisos = média ponderada por recência das últimas ondas
_w1 = np.exp(-np.log(2) * (HOJE - last1.campo_fim).dt.days / 14.0)
agg1 = {c_: float((last1[c_] * _w1).sum() / _w1[last1[c_].notna()].sum()) for c_ in CANDS + ["branco_nulo", "indecisos"]}   # NaN fora do numerador e do denominador
_tot = sum(agg1[c_] for c_ in CANDS)
_resto = 100 - agg1["branco_nulo"] - agg1["indecisos"]
agg1.update({c_: share_now[c_] * _resto / 100 for c_ in CANDS})     # totais coerentes com o nível de hoje em válidos
agg1["L"], agg1["F"], agg1["branco"], agg1["indec"] = agg1["lula"], agg1["flavio"], agg1["branco_nulo"], agg1["indecisos"]
if POS_1T:
    # Depois do 1º turno o modelo de transferência parte do RESULTADO OFICIAL (votos válidos), não mais do agregado de pesquisas.
    _rv1 = {c_: float(RESULTADO_1T.get(c_, np.nan)) for c_ in CANDS}
    _conh = sum(v for v in _rv1.values() if np.isfinite(v))
    _rv1 = {c_: (v if np.isfinite(v) else (max(100 - _conh, 0.0) if c_ == "outros" else 0.0)) for c_, v in _rv1.items()}
    agg1.update({c_: _rv1[c_] * _resto / 100 for c_ in CANDS})
    agg1["L"], agg1["F"] = agg1["lula"], agg1["flavio"]
for k_, v_ in [("Cury", "cury"), ("Renan", "renan"), ("Caiado", "caiado"), ("Zema", "zema")]:
    agg1[k_] = agg1[v_]
L_valid_now = share_now["lula"]; gap_valid = 50.0 - L_valid_now
# matriz de transferência declarada (2ª opção, Quaest 3–6/set/2026): % dos eleitores de cada candidato que migram p/ (Flávio, Lula)
# Lida de transferencia_2turno.csv (instituto, campo_fim, origem, pct_flavio, pct_lula). Havendo ondas com campo depois do 1º turno,
# só elas entram (média por origem); antes disso vale a 2ª opção declarada.
_tr = pd.read_csv(DATA + "transferencia_2turno.csv", parse_dates=["campo_fim"])
_tr = _tr[_tr.campo_fim <= HOJE]
if (_tr.campo_fim > T1).any(): _tr = _tr[_tr.campo_fim > T1]
TRANSF_FONTE = "; ".join(sorted(set(f"{r_.instituto} {r_.campo_fim:%d/%m}" for r_ in _tr.itertuples())))
transf = {k_: (float(g_.pct_flavio.mean())/100, float(g_.pct_lula.mean())/100) for k_, g_ in _tr.groupby("origem")}
assert set(transf) == {"cury", "renan", "caiado", "zema", "outros"}, "transferencia_2turno.csv precisa cobrir as cinco origens"
share_lula_3via = {k_: transf[k_][1]/(transf[k_][0]+transf[k_][1]) for k_ in transf}
T3 = {k_: share_now[k_] for k_ in transf}; T3_total = sum(T3.values())

# Simulação do 1º turno (200 mil): nível + incerteza + deriva até 4/out + colapso tardio da 3ª via (voto útil) + viés histórico,
# renormalizado a 100 (compositional). O viés entra na margem Lula−Flávio: −b/2 em Lula, +b/2 em Flávio.
M1 = 200_000
r1 = np.random.default_rng(41)
dias_1t = max((T1 - HOJE).days, 0)
sim1 = {}
_m_sim = r1.normal(share_now["lula"] - share_now["flavio"], math.sqrt(SE_M1**2 + dias_1t * fit_m1["sig_rw"]**2), M1)
_s_sim = r1.normal(share_now["lula"] + share_now["flavio"], math.sqrt(SE_S1**2 + dias_1t * fit_s1["sig_rw"]**2), M1)
sim1["lula"], sim1["flavio"] = (_s_sim + _m_sim)/2, (_s_sim - _m_sim)/2
for c_ in CANDS:
    _sig_dia = fit1[c_]["sig_rw"]
    if c_ in ("lula", "flavio"):
        continue
    else:
        # menores: incerteza extra de reta final proporcional ao tamanho (1,5 p.p. para quem tem 5%), sorteada em escala
        # log-normal com a mesma média e o mesmo desvio, para não gerar parcelas negativas nem exigir corte e renormalização
        _sd = math.sqrt(se_share[c_]**2 + dias_1t * _sig_dia**2 + (1.5*math.sqrt(max(share_now[c_], 0.1)/5.0))**2)
        _mu = max(share_now[c_], 0.05); _s = math.sqrt(math.log(1 + (_sd/_mu)**2))
        sim1[c_] = _mu * np.exp(r1.normal(-_s**2/2, _s, M1))
colapso = beta_dist.rvs(2, 8, size=M1, random_state=r1)          # fração da 3ª via que migra na reta final (média 0,20)
mig_l = np.zeros(M1); mig_f = np.zeros(M1)
for k_ in transf:
    m_ = sim1[k_] * colapso
    sim1[k_] = sim1[k_] - m_
    mig_l += m_ * share_lula_3via[k_]; mig_f += m_ * (1 - share_lula_3via[k_])
# O viés histórico do 1º turno compara as pesquisas finais com o resultado e por isso JÁ inclui a migração tardia da 3ª via
# (2014: Marina → Aécio; 2022: Ciro/Tebet → Bolsonaro). Para não contar duas vezes, o colapso redistribui votos com efeito
# médio nulo na margem Lula − Flávio: mantém a dispersão (quanto e para quem migra varia), mas o deslocamento médio fica no viés.
_desloc_colapso = float((mig_l - mig_f).mean())
mig_l -= _desloc_colapso/2; mig_f += _desloc_colapso/2
b1 = BIAS_1T_MEAN + BIAS_1T_SD*_t_unit(r1, M1) if not POS_1T else np.zeros(M1)
sim1["lula"] = sim1["lula"] + mig_l - b1/2
sim1["flavio"] = sim1["flavio"] + mig_f + b1/2
for c_ in ("lula", "flavio"): sim1[c_] = np.clip(sim1[c_], 0.1, None)
_tot1 = sum(sim1[c_] for c_ in CANDS)
for c_ in CANDS: sim1[c_] = 100 * sim1[c_] / _tot1
lula_valid_mc = sim1["lula"]
margem_1t_mc = sim1["lula"] - sim1["flavio"]
_mat = np.column_stack([sim1[c_] for c_ in CANDS])
_rank = np.argsort(-_mat, axis=1)
p_1t = float((lula_valid_mc > 50).mean())
p_flavio_1t = float((sim1["flavio"] > 50).mean())
P_FLAVIO_TOP2_VOTOS = float(((_rank[:, 0] == 1) | (_rank[:, 1] == 1)).mean())
p_lula_lidera_1t = float((margem_1t_mc > 0).mean())
# 3º lugar entre candidatos (o agregado "outros" soma vários nomes e não concorre a posição)
_cn = [c_ for c_ in CANDS if c_ != "outros"]
_rank_c = np.argsort(-np.column_stack([sim1[c_] for c_ in _cn]), axis=1)
p_terceiro = {c_: float((_rank_c[:, 2] == i_).mean()) for i_, c_ in enumerate(_cn) if c_ not in ("lula", "flavio")}
q_1t = np.percentile(lula_valid_mc, [5, 25, 50, 75, 95])
q_margem_1t = np.percentile(margem_1t_mc, [5, 10, 25, 50, 75, 90, 95])
q_share = {c_: [round(float(v), 1) for v in np.percentile(sim1[c_], [10, 50, 90])] for c_ in CANDS}
# sensibilidade do 1º turno ao viés (0 / prior / histórico cheio)
_r_sens = np.random.default_rng(43)
def _p1t_sens(bm):
    # troca o viés sorteado por outro com média alternativa e a mesma incerteza (a linha "prior" reproduz a manchete)
    mm = margem_1t_mc + b1 - (bm + BIAS_1T_SD*_t_unit(_r_sens, M1) if bm else 0.0)
    return {"p_lula_lidera": round(float((mm > 0).mean()), 3), "margem_mediana": round(float(np.median(mm)), 1)}
sens_1t = {"sem viés": _p1t_sens(0.0), f"prior ({BIAS_1T_MEAN:+.1f})": _p1t_sens(BIAS_1T_MEAN), f"histórico cheio ({BIAS_1T_HIST:+.1f})": _p1t_sens(BIAS_1T_HIST)}
print(f"[1º turno] {len(p1)} pesquisas | válidos hoje: " + ", ".join(f"{CAND_LABEL[c_]} {share_now[c_]:.1f}" for c_ in CANDS)
      + f" | viés prior {BIAS_1T_MEAN:+.1f}±{BIAS_1T_SD} | P(Lula>50%)={p_1t:.3f} P(Flávio top-2)={P_FLAVIO_TOP2_VOTOS:.3f} P(Lula lidera)={p_lula_lidera_1t:.3f}")

def estrutural_2t(L=agg1["L"], F=agg1["F"], terceiros=None, indec=agg1["indec"]+agg1["branco"],
                  mult_lula=1.0, mult_flavio=1.0, split_indec=0.5, decis_indec=0.55, abst_dif=0.0):
    terceiros = terceiros or {k_: agg1[k_] for k_ in transf}
    l, f = L, F
    for k_, share in terceiros.items():
        pf, pl = transf[k_]
        l += share * min(pl*mult_lula, 1-pf*mult_flavio)
        f += share * pf*mult_flavio
    pool = indec * decis_indec
    l += pool*split_indec; f += pool*(1-split_indec)
    l -= abst_dif
    return 100*(l-f)/(l+f), l, f
# Nível estrutural pós-1º turno: resultado oficial + transferência. Nos primeiros dias depois de 4/out é a melhor informação que
# existe (voto contado, sem erro de pesquisa); é combinado ao nível das pesquisas por precisão e perde peso sozinho conforme as
# pesquisas pós-1º turno se acumulam. O desvio cobre a incerteza da transferência declarada e da mudança de comparecimento.
# É uma foto de 4/out: como medida do nível de HOJE, à incerteza da transferência soma-se a deriva do eleitorado desde o 1º turno
# (SIG_ENTRE_DIA por dia). Sem essa parcela o peso do voto contado ficava preso em ~50% independentemente de quantas pesquisas
# novas houvesse (auditoria de 18/out), ao contrário do que o parágrafo acima promete e do que o MCMC faz (observação no dia 5/out).
SD_ESTRUT = 2.5
SD_ESTRUT_HOJE = SD_ESTRUT
NIVEL_ESTRUT = float(estrutural_2t()[0]) if POS_1T else None
W_ESTRUT = 0.0
level_pesq, se_pesq = level_final, se_final
# Fração do nível de hoje que vem de pesquisas de 2º turno feitas DEPOIS do 1º turno (revisão de 4/out, com o resultado na mão).
# Depois da quebra de SD_QUEBRA_1T no passeio, o filtro pesa as ondas novas por precisão contra o nível antigo com variância
# P + 6²; essa é a mesma conta, feita fora do filtro, para saber qual desconto de viés vale para o nível.
F_POS1T = 0.0
if POS_1T:
    _wpos = float(df.loc[df.campo_fim > T1, "w_precisao"].sum())
    F_POS1T = _wpos / (_wpos + 1.0/(se_pesq**2 + SD_QUEBRA_1T**2)) if _wpos > 0 else 0.0
N_POS1T = int((df.campo_fim > T1).sum()) if POS_1T else 0

RISCO_CANDIDATURA = 0.0 if HOJE >= T2 else 0.01   # com as urnas abertas não há mais substituição de candidato
P_FLAVIO_2T_MERC = 0.88     # referência histórica (v24): mercado "Flávio em 2º lugar"
P_FLAVIO_2T = ((1.0 if FLAVIO_TOP2_REAL else 0.0) if POS_1T else P_FLAVIO_TOP2_VOTOS) * (1 - RISCO_CANDIDATURA)

# ----------------------------------------------------------------------------
# 4. MONTE CARLO — 2º TURNO
# ----------------------------------------------------------------------------
N = 400_000
dias = max((T2 - HOJE).days, 0)
dias_ate_t1, dias_t1_t2 = max((T1 - HOJE).days, 0), max((T2 - max(T1, HOJE)).days, 0)
# Deriva em duas fases: até 4/out, σ_rw estimado; entre turnos, σ calibrado no movimento observado em 2014/18/22 (descontado o
# erro de pesquisa) e encolhido 50% para σ_rw, porque são só três ciclos.
SIG_ENTRE_DIA = math.sqrt(0.5*SIG_ENTRE_DIA_BRUTO**2 + 0.5*SIG_RW_KF**2)
SD_DIARIO = SIG_RW_KF
MULT_ENTRE_TURNOS = round(SIG_ENTRE_DIA / SIG_RW_KF, 2)
sd_deriva = math.sqrt(dias_ate_t1*SIG_RW_KF**2 + dias_t1_t2*SIG_ENTRE_DIA**2)
sd_deriva_antigo = 0.55*math.sqrt(max(dias, 0))
# Média da deriva (só na variante com momentum): metade do agregado das diferenças intra-instituto, em p.p./dia, vezes os dias que faltam.
MU_DERIVA, SD_MU_DERIVA, MOM = 0.0, 0.0, None
if MOMENTUM and POS_1T:
    import mudanca_2turno
    MOM = mudanca_2turno.resumo(DATA, hoje=HOJE)
    if MOM:
        MU_DERIVA = ENCOLHE_MOMENTUM * MOM["m_dia"] * dias_t1_t2
        SD_MU_DERIVA = ENCOLHE_MOMENTUM * max(MOM["se_dia"], SD_MOMENTUM_PISO) * dias_t1_t2   # o encolhimento vale para o erro também
BIAS_APLICADO = BIAS_MEAN
BIAS_SD_FINAL = BIAS_SD
# Antes do 1º turno, as pesquisas de 2º turno ainda não passaram pelo resultado do 1º turno. O desconto de BIAS_MEAN vem da rodada
# final (véspera do 2º turno) e subestima o erro das rodadas feitas antes do 1º turno: sem ajuste, o 2º turno partia das pesquisas
# de hoje com desconto de ~1 ponto, enquanto o 1º turno, com as mesmas pesquisas, recebia ~3,5, e as duas previsões não
# conversavam (a tabela "1º turno → 2º turno" partia de outra margem). Regra até 4/out: o 2º turno recebe o mesmo desconto do
# 1º turno (BIAS_1T_MEAN ± BIAS_1T_SD), porque um erro na margem do 1º turno se transmite quase um para um ao ponto de partida do
# 2º (inclinação da tabela 1º → 2º turno ≈ 1). O histórico das rodadas de 2º turno publicadas antes do 1º turno
# (vies_2t_pre_1turno.csv: 2014, 2018, 2022) tem erros ainda maiores (+3 a +16); ele fica como referência, não como regra,
# porque 2022 sozinho domina a média e porque em 2026 a distância entre 1º e 2º turno nas pesquisas já é pequena.
BIAS_PRE1T_HIST, BIAS_PRE1T_CICLOS = None, {}
_pre_path = DATA + "vies_2t_pre_1turno.csv"
if os.path.exists(_pre_path):
    _pre = pd.read_csv(_pre_path)
    BIAS_PRE1T_CICLOS = _pre.groupby("ciclo").erro_margem.mean().to_dict()
    _pp = {c_: 0.5 ** ((2022 - c_) / MEIA_VIDA_CICLOS) for c_ in BIAS_PRE1T_CICLOS}
    BIAS_PRE1T_HIST = round(sum(BIAS_PRE1T_CICLOS[c_]*_pp[c_] for c_ in BIAS_PRE1T_CICLOS) / sum(_pp.values()), 2)
BIAS_PRE1T_MEAN, BIAS_PRE1T_SD = BIAS_1T_MEAN, round(max(BIAS_SD, BIAS_1T_SD), 2)
# Auditoria de 3/out: a troca de regime dependia só da data (em 4/out o desconto caía de +3,5 para +0,95 sem resultado nem
# pesquisa nova). Agora depende do resultado: enquanto resultado_1turno.csv estiver vazio vale o desconto pré-1º turno. Com o
# resultado, o desconto das pesquisas de 2º turno é o histórico das rodadas finais mais uma fração do erro COMUM observado no 1º
# turno (nos três ciclos com dados, o erro do 2º turno acompanhou o do 1º com inclinação ≈ 0,25), e só incide sobre a parcela
# do nível que vem de pesquisas (a parcela estrutural vem de voto contado).
# Revisão de 22/out (v56): RHO_1T_2T passou de 0,25 para 0. O erro de 2026 no 1º turno teve a assinatura de voto útil: a média
# final acertou Lula (45,8 contra 45,16) e errou Flávio (43,1 contra 47,03) pelo que os demais perderam (11,0 contra 7,8). Esse
# mecanismo não existe num 2º turno com dois nomes; o erro próprio de 2º turno segue no desconto das rodadas finais (BIAS_MEAN).
# Com 0,25, o 2º turno herdava ~0,9 ponto (1,19 × a parcela de pesquisas) e a chance de Lula era 12,8% (ensemble 15,0%) em 22/out.
RHO_1T_2T, BIAS_POS1T_SD = 0.0, 2.5
# Revisão de 4/out (com o resultado): a regra acima vale para pesquisas feitas DEPOIS do 1º turno. As ondas de 2º turno feitas
# antes dele são as mesmas que, no 1º turno, erraram a margem em ERRO_COMUM_1T; antes do resultado elas recebiam o desconto
# suposto do 1º turno (BIAS_PRE1T_MEAN), e agora recebem o erro observado. Sem isso, na manhã de 5/out, sem nenhuma pesquisa nova,
# o desconto caía de +3,4 para +2,0 e a chance de Lula subia justamente no dia em que o resultado mostrou o erro contra ele.
# O desconto do nível é a média dos dois, pesada pela fração do nível que vem de ondas pós-1º turno (F_POS1T).
# A combinação com o nível estrutural (voto contado) usa o erro TOTAL do nível das pesquisas (amostral + sistemático): antes
# usava só o amostral (± 0,7 contra ± 2,5) e o resultado oficial pesava 8%.
BIAS_PRE1T_OBS = BIAS_POS_REGRA = None
if not POS_1T:
    BIAS_APLICADO, BIAS_SD = BIAS_PRE1T_MEAN, BIAS_PRE1T_SD
else:
    BIAS_PRE1T_OBS = float(ERRO_COMUM_1T if ERRO_COMUM_1T is not None else BIAS_PRE1T_MEAN)
    BIAS_POS_REGRA = BIAS_MEAN + RHO_1T_2T*(ERRO_COMUM_1T or 0.0)
    _b_pesq = F_POS1T*BIAS_POS_REGRA + (1 - F_POS1T)*BIAS_PRE1T_OBS
    _var_pesq = se_pesq**2 + BIAS_POS1T_SD**2
    _dias_desde_t1 = max((HOJE - T1).days, 0)
    SD_ESTRUT_HOJE = math.sqrt(SD_ESTRUT**2 + _dias_desde_t1 * SIG_ENTRE_DIA**2)     # foto de 4/out + deriva até hoje
    W_ESTRUT = float(_var_pesq / (_var_pesq + SD_ESTRUT_HOJE**2))
    level_final = (1 - W_ESTRUT)*level_pesq + W_ESTRUT*NIVEL_ESTRUT
    se_final = math.sqrt((1 - W_ESTRUT)**2*se_pesq**2 + W_ESTRUT**2*SD_ESTRUT_HOJE**2)
    BIAS_APLICADO = round((1 - W_ESTRUT)*_b_pesq, 2)
    BIAS_SD = round((1 - W_ESTRUT)*BIAS_POS1T_SD, 2)
    if SO_POS1T and not POS1T_COM_ESTRUT:
        # Variante: nível = só as pesquisas pós-1º turno, sem parcela de voto contado; todo o desconto é a regra pós-1º turno.
        F_POS1T, W_ESTRUT = 1.0, 0.0
        level_final, se_final = level_pesq, se_pesq
        BIAS_APLICADO, BIAS_SD = round(BIAS_POS_REGRA, 2), round(BIAS_POS1T_SD, 2)
SIG_REGIAO = 2.5
if REGIAO_UF:
    PESO_REGIAO = {r_: sum(PESO_UF[u_] for u_ in PESO_UF if REGIAO_UF[u_] == r_) for r_ in sorted(set(REGIAO_UF.values()))}
else:
    PESO_REGIAO = {"N": 0.085, "NE": 0.265, "CO": 0.075, "SE": 0.43, "S": 0.145}
# Choques regionais: retirados da simulação na auditoria de 3/out (ver simular); as constantes ficam só para o histórico do painel.
SD_RUIDO = math.sqrt(sum((SIG_REGIAO*w_)**2 for w_ in PESO_REGIAO.values()))

def simular(level, bias_mean, n=N, seed=1, se_level=None, sdd=None, pflav=None, bias_sd=None):
    r = np.random.default_rng(seed)
    # Deriva e erro sistemático com caudas t de Student (mesmo desvio da versão normal). O sorteio de choques por macrorregião foi
    # retirado: somava normais sem alterar nenhuma saída nacional (o erro total já era BIAS_SD).
    est    = r.normal(level, se_final if se_level is None else se_level, n)
    deriva = (sd_deriva if sdd is None else sdd) * _t_unit(r, n)
    if SD_MU_DERIVA > 0:      # variante com momentum: a deriva tem média (incerta); fora dela nada é sorteado e o fluxo aleatório não muda
        deriva = deriva + r.normal(MU_DERIVA, SD_MU_DERIVA, n)
    _bsd   = BIAS_SD if bias_sd is None else bias_sd
    vies   = bias_mean + _bsd * _t_unit(r, n)
    adv    = r.random(n) < (P_FLAVIO_2T if pflav is None else pflav)
    est_outro = r.normal(7.0, 4.0, n)
    m = np.where(adv, est, est_outro) + deriva - vies
    return m, adv

margem_final, adv_flavio = simular(level_final, BIAS_APLICADO)
p_lula   = float((margem_final > 0).mean())
p_lula_vs_flavio = float((margem_final[adv_flavio] > 0).mean())
p_empate = float((np.abs(margem_final) < 1.5).mean())
q = np.percentile(margem_final, [5, 10, 25, 50, 75, 90, 95])
var_parts = {"Agregado hoje (amostral+casas)": se_final**2, "Deriva até 25/out": sd_deriva**2,
             "Erro sistemático das pesquisas": BIAS_SD**2}
tot_var = sum(var_parts.values())
var_share = {k_: v/tot_var for k_, v in var_parts.items()}

sens = {}
for lab_l, lv in [("nowcast combinado", level_final), ("média ponderada últimas", media_simples_adj)]:
    for lab_b, bm in [("viés recalibrado", BIAS_APLICADO), ("sem correção de viés", 0.0)]:
        m_, _ = simular(lv, bm, n=200_000, seed=7)
        sens[f"{lab_l} | {lab_b}"] = round(float((m_ > 0).mean()), 3)
sens_bias = {f"{bm:+.1f}": round(float((simular(level_final, bm, n=200_000, seed=7)[0] > 0).mean()), 3) for bm in sorted({0.0, BIAS_MEAN, BIAS_APLICADO, 2.1})}

# "O que mudou no modelo (v3)": parte do resultado do modelo anterior (v2, valores do último resultados.json publicado) e liga um
# fator por vez. O nível "v2" é recomposto com a identificação antiga (média de todos os institutos, peso qualidade × nº de ondas).
_wq_all = {i_: peso_qualidade(i_) * int((jan.instituto == i_).sum()) for i_ in jan.instituto.unique()}
_shift_v2 = sum(fit_win["house"][i_]*_wq_all[i_] for i_ in _wq_all) / sum(_wq_all.values())   # média dos efeitos de casa sob a âncora (v2 forçava = 0)
level_v2_like = level_nac + _shift_v2
def _p_cfg(level, se_l, bias, bsd, sdd, pflav):
    m_, _ = simular(level, bias, n=200_000, seed=11, se_level=se_l, sdd=sdd, pflav=pflav, bias_sd=bsd)
    return round(float((m_ > 0).mean()), 3)
efeitos_fatores = {"Modelo anterior (v2): âncora em todos os institutos, viés +2,1, deriva 4,3": _p_cfg(level_v2_like, se_nac, 2.1, 2.0, 4.3, P_FLAVIO_2T_MERC)}
efeitos_fatores["+ âncora nos institutos calibrados (identificação dos efeitos de casa)"] = _p_cfg(level_nac, se_nac, 2.1, 2.0, 4.3, P_FLAVIO_2T_MERC)
efeitos_fatores[f"+ viés recalibrado ({BIAS_MEAN:+.1f} ± {BIAS_SD_FINAL})"] = _p_cfg(level_nac, se_nac, BIAS_MEAN, BIAS_SD_FINAL, 4.3, P_FLAVIO_2T_MERC)
efeitos_fatores[f"+ deriva em duas fases calibrada ({sd_deriva:.1f})"] = _p_cfg(level_nac, se_nac, BIAS_MEAN, BIAS_SD_FINAL, sd_deriva, P_FLAVIO_2T_MERC)
efeitos_fatores[f"+ P(Flávio no 2º turno) do modelo do 1º turno ({P_FLAVIO_2T:.2f})"] = _p_cfg(level_nac, se_nac, BIAS_MEAN, BIAS_SD_FINAL, sd_deriva, P_FLAVIO_2T)
if estadual["usado"]:
    efeitos_fatores["+ pesquisas estaduais"] = _p_cfg(level_final, se_final, BIAS_MEAN, BIAS_SD_FINAL, sd_deriva, P_FLAVIO_2T)
if BIAS_APLICADO != BIAS_MEAN:
    efeitos_fatores[f"+ viés das pesquisas de 2º turno anteriores ao 1º turno ({BIAS_APLICADO:+.1f} ± {BIAS_SD})"] = _p_cfg(level_final, se_final, BIAS_APLICADO, BIAS_SD, sd_deriva, P_FLAVIO_2T)

# ENSEMBLE — 75% modelo / 25% mercados / 0% fundamentos quando há preço de mercado válido; senão 100% modelo (a versão até 3/out usava
# 65/25/10 antes do 1º turno)
MERC = json.load(open(DATA + "mercados.json", encoding="utf-8"))   # leitura do mercado (valor, data, volume): fonte única para modelo e painel
P_MERC, SD_ALT = float(MERC["p_lula"]), 4.5
# Fundamentos: ótimo+bom do governo no Datafolha durante a campanha, antes do 1º turno (série homogênea; a versão anterior
# misturava a aprovação pessoal de Lula na CNT/Sensus de 2008, 77,7, com outras medidas). Margem = 2º turno do incumbente.
FUND_HIST = [(2006, 47.0, 21.66), (2014, 39.0, 3.28), (2022, 31.0, -1.80)]
FUND_MEDIDA = "Datafolha, durante a campanha"
APROVACAO_ATUAL = {"Datafolha 22–23/set": 34}   # mesma medida da série histórica (ótimo + bom)
aprovacao_media = float(np.mean(list(APROVACAO_ATUAL.values())))
_fx = np.array([a_ for _, a_, _ in FUND_HIST]); _fy = np.array([m_ for _, _, m_ in FUND_HIST])
FUND_B, FUND_A = np.polyfit(_fx, _fy, 1)
FUND_EMPATE = -FUND_A/FUND_B
mu_fund = float(FUND_A + FUND_B*aprovacao_media)
P_FUND = float(norm.cdf(mu_fund/SD_ALT))
mu_merc = SD_ALT * norm.ppf(P_MERC)
# Pesos (auditoria de 3/out). Fundamentos: reta com três eleições e desvio preditivo real acima de 8 pontos — a componente dizia
# ~50% e não informava; fica calculada para exibição, com peso 0. Mercado: só entra se o preço for recente (até 5 dias) e, no
# regime pós-1º turno, posterior ao resultado; um preço velho com 25% do peso contaminaria o resultado sem aviso. Um preço lido
# na própria noite de 4/out, depois da apuração, vale se mercados.json trouxer "pos_resultado": true.
_md = pd.Timestamp(MERC["data_iso"]) if MERC.get("data_iso") else None
MERC_VALIDO = bool(_md is not None and (HOJE - _md).days <= 5 and not (POS_1T and _md < T1)
                   and not (POS_1T and _md == T1 and not MERC.get("pos_resultado")))
W_ENS = {"modelo": 0.75, "mercados": 0.25, "fundamentos": 0.0} if MERC_VALIDO else {"modelo": 1.0, "mercados": 0.0, "fundamentos": 0.0}
r2 = np.random.default_rng(99)
comp = r2.random(N)
ens = np.where(comp < W_ENS["modelo"], margem_final,
               np.where(comp < W_ENS["modelo"] + W_ENS["mercados"], r2.normal(mu_merc, SD_ALT, N), r2.normal(mu_fund, SD_ALT, N)))
p_ens = float((ens > 0).mean())
cen = {"Lula por mais de 4 p.p.": float((ens > 4).mean()), "Lula por 0 a 4 p.p.": float(((ens > 0) & (ens <= 4)).mean()),
       "Flávio por 0 a 4 p.p.": float(((ens <= 0) & (ens > -4)).mean()), "Flávio por mais de 4 p.p.": float((ens <= -4).mean())}
p_ens_empate = float((np.abs(ens) < 1.5).mean())
q_ens = np.percentile(ens, [10, 50, 90])
def _saida_variante(variante):
    """Saída das variantes do herói: só o que o herói e a metodologia precisam."""
    return {
      "data_base": str(HOJE.date()), "atualizado_em": pd.Timestamp.now(tz="America/Sao_Paulo").strftime("%Y-%m-%dT%H:%M"), "variante": variante,
      "regime_pos_1t": POS_1T, "n_pesquisas": int(len(df)), "n_institutos": int(df.inst_base.nunique()), "n_series_casa": len(insts),
      "primeira_pesquisa": str(df.campo_fim.min().date()), "ultima_pesquisa": str(df.campo_fim.max().date()),
      "ancora_presente": sorted(set(ANCORA) & set(df.instituto)), "n_ondas_ancora": int(df.instituto.isin(ANCORA).sum()),
      "nivel_ajustado_hoje_margem_valid": round(level_final, 2), "se_total": round(se_final, 2),
      "nivel_pesquisas": round(level_pesq, 2), "se_pesquisas": round(se_pesq, 2), "var_ancora": round(fit_win["var_ancora"], 3),
      "nivel_estrutural": round(NIVEL_ESTRUT, 2), "sd_estrutural": round(SD_ESTRUT_HOJE, 2), "peso_estrutural": round(W_ESTRUT, 3), "f_pos1t": round(F_POS1T, 3), "lambda_casa_pos1t": None,
      "bias_aplicado": round(BIAS_APLICADO, 2), "bias_sd_aplicado": round(BIAS_SD, 2), "bias_pos_regra": round(BIAS_POS_REGRA, 2),
      "sigma_passeio_estimado": round(SIG_RW_KF, 3), "sigma_passeio_ml": round(SIG_RW_ML, 3), "sd_deriva": round(sd_deriva, 2), "sig_entre_turnos_dia": round(SIG_ENTRE_DIA, 3),
      "house_effects": {i: round(house[i], 2) for i in insts}, "sigma_inst": {i: round(sigma_inst[i], 2) for i in insts},
      "media_ponderada_ultimas_ajustada": round(media_simples_adj, 2),
      "p_lula": round(p_lula, 3), "p_lula_vs_flavio": round(p_lula_vs_flavio, 3), "p_empate_1p5": round(p_empate, 3), "sensibilidade": sens,
      "quantis_margem_final": {str(p): round(float(v), 1) for p, v in zip([5, 10, 25, 50, 75, 90, 95], q)},
      "p_ensemble": round(p_ens, 3), "p_ensemble_empate": round(p_ens_empate, 3), "pesos_ensemble": W_ENS, "mercado_valido": MERC_VALIDO, "p_mercados": P_MERC,
      "cenarios_ensemble": {k_: round(v, 3) for k_, v in cen.items()}, "q_ensemble_10_50_90": [round(float(v), 1) for v in q_ens],
      "mu_deriva": round(MU_DERIVA, 2), "sd_mu_deriva": round(SD_MU_DERIVA, 2),
    }
if SO_POS1T:
    # termina aqui para não tocar nenhum arquivo do modelo principal.
    res_pos = _saida_variante("pos1t_estrut" if POS1T_COM_ESTRUT else "so_pos1t")
    json.dump(res_pos, open(OUT + ("resultados_pos1t_estrut.json" if POS1T_COM_ESTRUT else "resultados_pos1t.json"), "w", encoding="utf-8"), ensure_ascii=False, indent=2)
    print(f"[Pós-1º turno{' + voto contado' if POS1T_COM_ESTRUT else ''}] {len(df)} pesquisas de {res_pos['n_institutos']} institutos desde {df.campo_fim.min():%d/%m} | nível {level_final:+.2f} ± {se_final:.2f} "
          f"(estrutural {NIVEL_ESTRUT:+.2f}, peso {W_ESTRUT:.0%}) | viés {BIAS_APLICADO:+.2f} ± {BIAS_SD} | σ_rw {SIG_RW_KF:.3f} | P(Lula)={p_lula:.3f} ensemble {p_ens:.3f}")
    sys.exit(0)

def p_dado_lead(lead):
    sd = math.sqrt(BIAS_SD**2 + 0.8**2)
    return 1 - norm.cdf(0, loc=lead - BIAS_APLICADO, scale=sd)
leads = np.arange(-2, 9, 1)
tabela_lead = {int(l): round(p_dado_lead(l), 3) for l in leads}
lead_breakeven = BIAS_APLICADO
lead_70 = BIAS_APLICADO + norm.ppf(0.70) * math.sqrt(BIAS_SD**2 + 0.64)
lead_90 = BIAS_APLICADO + norm.ppf(0.90) * math.sqrt(BIAS_SD**2 + 0.64)

# ----------------------------------------------------------------------------
# 4c. MODELO PARALELO POR MCMC (mcmc_2turno.py) — mesmas pesquisas da janela, mesmos priors e as mesmas peças do Monte Carlo acima
#     (efeitos de casa ancorados, prior do erro de cada instituto no 1º turno, quebra de 4/out, nível estrutural, desconto de viés por
#     regime com caudas t, deriva em duas fases), mas com o posterior completo: σ_rw, σ_entre, σ_p, efeitos de casa, viés e trajetória
#     sorteados em conjunto, em vez de fixados em estimativas pontuais. Não altera nenhuma saída do modelo principal (manchete).
# ----------------------------------------------------------------------------
import time as _time
import mcmc_2turno
_t0_mc = _time.time()
_ult = max(t_now, dia(T1) + 1) if POS_1T else t_now          # último dia do estado: inclui o dia seguinte ao 1º turno (voto contado)
_mu_obs = (np.where(jan.campo_fim > T1, BIAS_POS_REGRA, BIAS_PRE1T_OBS) if POS_1T else np.full(len(jan), BIAS_PRE1T_MEAN))
_extras = []
if POS_1T:
    _extras.append({"dia": dia(T1) + 1 - d0_jan, "valor": NIVEL_ESTRUT, "sd": SD_ESTRUT, "com_vies": False})
if estadual["usado"]:
    _extras.append({"dia": t_now - d0_jan, "valor": estadual["nivel"], "sd": estadual["se"], "com_vies": True,
                    "mu": BIAS_POS_REGRA if POS_1T else BIAS_PRE1T_MEAN})
_fut = np.arange(_ult + 1, dia(T2) + 1)
MC = mcmc_2turno.amostrar(jan.margem_valid.values, (jan.dia - d0_jan).values, jan.se2.values, jan.instituto.values, _ult - d0_jan + 1,
                          set(ANCORA), Q_ANCORA, mu_obs=_mu_obs, sd_vies=(BIAS_POS1T_SD if POS_1T else BIAS_PRE1T_SD),
                          prior_h=prior_h_2t, tau_h=tau_prior_h or 1.5, dia_t1=dia(T1) - d0_jan,
                          sd_quebra=(SD_QUEBRA_1T if POS_1T else 0.0), extras=_extras,
                          prior_entre=(math.log(SIG_ENTRE_DIA), 0.5), n_fut_rw=int((_fut <= dia(T1)).sum()),
                          n_fut_entre=int((_fut > dia(T1)).sum()), dia_hoje=t_now - d0_jan, a_h=jan.a_casa.values,
                          prior_mu_der=(MU_DERIVA, SD_MU_DERIVA))
_S = MC["amostras"]
margem_mcmc = _S["x_final"].ravel()
_rm = np.random.default_rng(5)
_adv_m = _rm.random(len(margem_mcmc)) < P_FLAVIO_2T
margem_mcmc = np.where(_adv_m, margem_mcmc, _rm.normal(7.0, 4.0, len(margem_mcmc)))
p_lula_mcmc = float((margem_mcmc > 0).mean())
q_mcmc = np.percentile(margem_mcmc, [5, 10, 25, 50, 75, 90, 95])
_comp_m = _rm.random(len(margem_mcmc))
ens_mcmc = np.where(_comp_m < W_ENS["modelo"], margem_mcmc,
                    np.where(_comp_m < W_ENS["modelo"] + W_ENS["mercados"], _rm.normal(mu_merc, SD_ALT, len(margem_mcmc)),
                             _rm.normal(mu_fund, SD_ALT, len(margem_mcmc))))
def _qs(a_, ps=(10, 50, 90)): return [round(float(v), 2) for v in np.percentile(a_, ps)]
_xh = _S["x_hoje"].ravel()
# variância da margem final do MCMC decomposta como na seção 4 (nível de hoje, deriva até 25/out; o viés já está dentro do nível)
_var_der = float(_S["deriva"].var()); _var_niv = float(_xh.var())
MCMC_CONVERGIU = MC["rhat_max"] < 1.05
mcmc_res = {
    "p_lula": round(p_lula_mcmc, 3), "p_lula_vs_flavio": round(float((margem_mcmc[_adv_m] > 0).mean()), 3),
    "p_empate_1p5": round(float((np.abs(margem_mcmc) < 1.5).mean()), 3),
    "p_ensemble": round(float((ens_mcmc > 0).mean()), 3), "q_ensemble_10_50_90": _qs(ens_mcmc),
    "quantis_margem_final": {str(p): round(float(v), 1) for p, v in zip([5, 10, 25, 50, 75, 90, 95], q_mcmc)},
    "nivel_hoje": round(float(_xh.mean()), 2), "se_nivel_hoje": round(float(_xh.std()), 2),
    "nivel_hoje_q": _qs(_xh), "sd_deriva": round(float(_S["deriva"].std()), 2),
    "var_share": {"Nível de hoje (pesquisas, casas, viés, voto contado)": round(_var_niv/(_var_niv + _var_der), 3),
                  "Deriva até 25/out": round(_var_der/(_var_niv + _var_der), 3)},
    "sig_rw": _qs(_S["sig_rw"]), "sig_entre": _qs(_S["sig_entre"]), "beta": _qs(_S["beta"]), "m_sigma_inst": _qs(_S["m_sigp"]),
    "house_effects": {p_: _qs(_S["h"][:, :, j_]) for j_, p_ in enumerate(MC["insts"])},
    "sigma_inst": {p_: _qs(_S["sig_p"][:, :, j_]) for j_, p_ in enumerate(MC["insts"])},
    "trajetoria": {"inicio": str((T0 + pd.Timedelta(days=d0_jan)).date()),
                   "q10_50_90": [[round(float(v), 2) for v in col] for col in np.percentile(_S["x"].reshape(-1, _S["x"].shape[-1]), [10, 50, 90], axis=0).T]},
    "desconto_obs": {"antes_1t": round(float(_mu_obs[(jan.campo_fim <= T1).values].mean()), 2) if (jan.campo_fim <= T1).any() else None,
                     "depois_1t": (round(float(BIAS_POS_REGRA), 2) if POS_1T else None), "sd_vies": BIAS_POS1T_SD if POS_1T else BIAS_PRE1T_SD},
    "obs_extras": [{k_: (round(v, 2) if isinstance(v, float) else v) for k_, v in e_.items()} for e_ in _extras],
    "rhat_max": round(MC["rhat_max"], 3), "ess_min": MC["ess_min"], "convergiu": MCMC_CONVERGIU,
    "diagnostico": {k_: MC["diagnostico"][k_] for k_ in ["x_hoje", "x_final", "sig_rw", "sig_entre", "beta", "m_sigp"]},
    "aceitacao": MC["aceitacao"], "n_cadeias": MC["n_cadeias"], "n_aquec": MC["n_aquec"], "n_amostras": MC["n_amostras"],
    "sig_entre_tem_dados": MC["entre_tem_dados"], "segundos": round(_time.time() - _t0_mc, 1),
    # comparação com o modelo principal: o nível dele é o das pesquisas (antes do desconto); o "verdadeiro" é nível − viés aplicado
    "comparacao": {"p_lula_principal": round(p_lula, 3), "p_ensemble_principal": round(p_ens, 3),
                   "nivel_verdadeiro_principal": round(level_final - BIAS_APLICADO, 2),
                   "se_nivel_verdadeiro_principal": round(math.sqrt(se_final**2 + BIAS_SD**2), 2),
                   "mediana_principal": round(float(q[3]), 1), "sd_deriva_principal": round(sd_deriva, 2),
                   "sig_rw_principal": round(SIG_RW_KF, 3), "sig_entre_principal": round(SIG_ENTRE_DIA, 3)},
}
print(f"[MCMC] {MC['n_cadeias']}×{MC['n_amostras']} sorteios | hoje {mcmc_res['nivel_hoje']:+.2f} ± {mcmc_res['se_nivel_hoje']:.2f} | "
      f"σ_rw {mcmc_res['sig_rw'][1]:.3f} | P(Lula)={p_lula_mcmc:.3f} (principal {p_lula:.3f}) | ensemble {mcmc_res['p_ensemble']:.3f} "
      f"(principal {p_ens:.3f}) | R̂ máx {MC['rhat_max']:.3f} ESS mín {MC['ess_min']} | {mcmc_res['segundos']} s")
if not MCMC_CONVERGIU:
    print(f"[AVISO] MCMC sem convergência (R̂ máx {MC['rhat_max']:.3f} > 1,05): resultado do modelo paralelo não confiável.")
if MOMENTUM:
    # Variante com momentum: sai depois do MCMC (que recebeu a mesma prior de deriva), sem tocar nenhum arquivo do modelo principal.
    res_mom = _saida_variante("momentum")
    res_mom["momentum"] = ({k_: (round(v_, 4) if isinstance(v_, float) else v_) for k_, v_ in MOM.items() if k_ != "lento"} if MOM else None)
    res_mom["encolhimento_momentum"] = ENCOLHE_MOMENTUM; res_mom["sd_momentum_piso_dia"] = SD_MOMENTUM_PISO
    res_mom["mcmc"] = {k_: mcmc_res[k_] for k_ in ["p_lula", "p_ensemble", "quantis_margem_final", "q_ensemble_10_50_90", "nivel_hoje", "se_nivel_hoje",
                                                   "sd_deriva", "rhat_max", "ess_min", "convergiu", "segundos"]}
    res_mom["mcmc"]["mu_der"] = _qs(_S["mu_der"]) if "mu_der" in _S else None
    json.dump(res_mom, open(OUT + "resultados_momentum.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2)
    print(f"[Momentum] deriva com média {MU_DERIVA:+.2f} ± {SD_MU_DERIVA:.2f} (agregado {MOM['taxa']:+.2f} ± {MOM['se_taxa']:.2f} p.p./semana, {MOM['n_dif']} diferenças de {MOM['n_inst']} séries, encolhido {ENCOLHE_MOMENTUM:.0%})"
          if MOM else "[Momentum] sem diferenças suficientes: deriva com média zero")
    print(f"[Momentum] nível {level_final:+.2f} ± {se_final:.2f} | P(Lula)={p_lula:.3f} ensemble {p_ens:.3f} | MCMC {p_lula_mcmc:.3f} ensemble {mcmc_res['p_ensemble']:.3f}")
    sys.exit(0)

# ----------------------------------------------------------------------------
# 4b. ABSTENÇÃO — quanto a margem nacional muda se o comparecimento de 2026 se afastar do padrão de 2022.
#     Dois canais: (i) geográfico, entre UFs: cada UF pesa no total pelo seu comparecimento, então faltar mais onde um
#     candidato lidera reduz o peso dessa UF; (ii) composicional, dentro de cada UF: quem falta é mais pobre e menos
#     escolarizado (abstenção de 52% entre analfabetos × 12% no superior, 1T 2022), grupos em que Lula lidera; calibrado em
#     K = 0,25 p.p. de margem por p.p. de abstenção (Laureno: +4 p.p. de abstenção ≈ −1 ponto na vantagem de Lula), faixa 0–0,4.
#     O padrão de 2022 já está embutido no desconto histórico (as finais são comparadas com resultados que incluem a abstenção
#     daquele ano), então os cenários medem só o DESVIO em relação a 2022, sem dupla contagem.
# ----------------------------------------------------------------------------
abst = {"usado": False}
_abst_path = DATA + "abstencao_2022_uf.csv"
if ref_uf is not None and os.path.exists(_abst_path):
    K_COMP, K_COMP_LO, K_COMP_HI = 0.25, 0.0, 0.40
    au = ref_uf.merge(pd.read_csv(_abst_path), on="uf", how="inner").copy()
    _sw = {}
    if estadual.get("por_uf") is not None:
        _sw = dict(zip(estadual["por_uf"].uf, estadual["por_uf"].swing))
    _mu_reg = estadual.get("mu_regiao", {}); _mu_s = estadual.get("mu_swing", 0.0)
    au["swing"] = [_sw.get(u_, _mu_reg.get(r_, _mu_s)) for u_, r_ in zip(au.uf, au.regiao)]
    au["a0"] = au.abst_2t_2022/100
    def _nac(a_vec, marg):
        V_ = au.eleitorado*(1 - a_vec)
        return float((V_*marg).sum()/V_.sum())
    _m_raw = au.m22 + au.swing
    _c = level_final - _nac(au.a0, _m_raw)            # alinha o nível por UF ao placar nacional do modelo
    au["m26"] = _m_raw + _c
    base_nac = _nac(au.a0, au.m26)
    def cenario(delta_reg=None, delta_uf=None, k=K_COMP):
        d_ = np.zeros(len(au))
        if delta_reg: d_ = np.array([delta_reg.get(r_, 0.0) for r_ in au.regiao])/100
        if delta_uf is not None: d_ = np.asarray(delta_uf)/100
        geo = _nac(au.a0 + d_, au.m26) - base_nac
        V_ = au.eleitorado*(1 - au.a0)
        comp = -k*100*float((V_*d_).sum()/V_.sum())  # média ponderada do aumento de abstenção × K
        return geo, comp
    _todas = {r_: 1.0 for r_ in au.regiao.unique()}
    cen_defs = [
        ("Abstenção igual à de 2022 (20,6%)", {}, None),
        ("Sobe para cerca de 22% em todo o país (projeção de analistas)", {r_: 1.4 for r_ in _todas}, None),
        ("Sobe para cerca de 25% em todo o país (cenário de desinteresse)", {r_: 4.4 for r_ in _todas}, None),
        ("Nordeste falta mais: +3 p.p. só no NE", {"NE": 3.0}, None),
        ("Sul e Centro-Oeste faltam mais: +3 p.p. só em S e CO", {"S": 3.0, "CO": 3.0}, None),
        ("Mobilização no Nordeste: −2 p.p. só no NE", {"NE": -2.0}, None),
        ("Repete a variação 1º→2º turno de 2022, UF por UF", None, (au.abst_2t_2022 - au.abst_1t_2022).values),
    ]
    _cen_out = []
    for nome_, dreg_, duf_ in cen_defs:
        geo_, comp_ = cenario(dreg_ or None, duf_)
        _, comp_lo = cenario(dreg_ or None, duf_, K_COMP_LO)
        _, comp_hi = cenario(dreg_ or None, duf_, K_COMP_HI)
        dm_ = geo_ + comp_
        m_, _ = simular(level_final + dm_, BIAS_APLICADO, n=200_000, seed=7)
        _cen_out.append({"cenario": nome_, "geo": round(geo_, 2), "comp": round(comp_, 2), "total": round(dm_, 2),
                         "total_faixa": [round(geo_ + comp_lo, 2), round(geo_ + comp_hi, 2)], "p_lula": round(float((m_ > 0).mean()), 3)})
    _p_base = _cen_out[0]["p_lula"]
    for c_ in _cen_out: c_["dp_lula"] = round(c_["p_lula"] - _p_base, 3)
    _geo_ne1, _comp_ne1 = cenario({"NE": 1.0}); _geo_u1, _comp_u1 = cenario({r_: 1.0 for r_ in _todas})
    reg_tab = []
    for r_, g_ in au.groupby("regiao"):
        V_ = g_.eleitorado*(1 - g_.a0)
        reg_tab.append({"regiao": r_, "eleitorado_pct": round(100*float(g_.eleitorado.sum()/au.eleitorado.sum()), 1),
                        "abst_1t_2022": round(float((g_.abst_1t_2022*g_.eleitorado).sum()/g_.eleitorado.sum()), 1),
                        "abst_2t_2022": round(float((g_.abst_2t_2022*g_.eleitorado).sum()/g_.eleitorado.sum()), 1),
                        "m22": round(float((V_*g_.m22).sum()/V_.sum()), 1), "m26": round(float((V_*g_.m26).sum()/V_.sum()), 1)})
    abst = {"usado": True, "k_comp": K_COMP, "k_faixa": [K_COMP_LO, K_COMP_HI], "base_nac": round(base_nac, 2),
            "check_m22": round(_nac(au.a0, au.m22), 2), "abst_base_pond": round(float((au.abst_2t_2022*au.eleitorado).sum()/au.eleitorado.sum()), 2),
            "cenarios": _cen_out, "por_regiao": sorted(reg_tab, key=lambda d_: -d_["eleitorado_pct"]),
            "por_pp_ne": round(_geo_ne1 + _comp_ne1, 2), "por_pp_nacional": round(_geo_u1 + _comp_u1, 2)}
    print(f"[Abstenção] base {base_nac:+.2f} (check 2022 {abst['check_m22']:+.2f}) | +1 p.p. NE: {abst['por_pp_ne']:+.2f} | +1 p.p. nacional: {abst['por_pp_nacional']:+.2f}")

# ----------------------------------------------------------------------------
# 5. MODELO ESTRUTURAL DE TRANSFERÊNCIA (1º → 2º turno) E TORNADO
# ----------------------------------------------------------------------------
base_m, base_l, base_f = estrutural_2t()
tornado = []
def add(nome, lo_kwargs, hi_kwargs, obs):
    lo = estrutural_2t(**lo_kwargs)[0] - base_m
    hi = estrutural_2t(**hi_kwargs)[0] - base_m
    tornado.append({"variavel": nome, "baixo": lo, "alto": hi, "obs": obs})
add("Aprovação do governo (−3 / +3 p.p. → base de Lula)", dict(L=agg1["L"]-2.4), dict(L=agg1["L"]+2.4),
    "elasticidade ≈0,8 voto/p.p. de aprovação (Lavareda: aprovação ≈ voto no 2º turno)")
add("Transferência da 3ª via para Lula (−1/3 / +50% vs. Quaest)", dict(mult_lula=0.67), dict(mult_lula=1.5),
    "hoje ~2:1 para Flávio; se Cury/Caiado dividissem 50/50, +3 p.p. de margem")
add("Transferência da 3ª via para Flávio (−20% / +15%)", dict(mult_flavio=1.15), dict(mult_flavio=0.80),
    "consolidação antipetista vs. dispersão em branco/nulo")
add("Indecisos/brancos que decidem: divisão (35% / 65% Lula)", dict(split_indec=0.35), dict(split_indec=0.65),
    "estoque de 10–17%; Atlas: independentes Lula 38 × Flávio 26")
if abst["usado"]:
    _tots = [c_["total"] for c_ in abst["cenarios"]]
    tornado.append({"variavel": "Abstenção em relação a 2022 (cenários da seção Abstenção)", "baixo": min(_tots), "alto": max(_tots),
                    "obs": "geográfico + perfil de quem falta (K = 0,25 p.p. por p.p.); de 25% de abstenção a mobilização no NE"})
else:
    add("Abstenção diferencial (Lula −1,5 p.p. / 0)", dict(abst_dif=1.5), dict(abst_dif=0.0),
        "abstenção 21%→>22%; eleitor de menor renda/NE falta mais (Ibmec)")
add("Base de Flávio (evangélicos/segurança: +2 / −2 p.p.)", dict(F=agg1["F"]+2), dict(F=agg1["F"]-2),
    "evangélicos ≈30% do eleitorado; Flávio 62×29 (Datafolha)")
tornado.append({"variavel": f"Erro sistemático das pesquisas ({-(BIAS_APLICADO+BIAS_SD):.0f} / {-(BIAS_APLICADO-BIAS_SD):+.0f} p.p.)",
                "baixo": -(BIAS_APLICADO + BIAS_SD), "alto": -(BIAS_APLICADO - BIAS_SD),
                "obs": "medição, não causa: pesquisas finais superestimaram o PT em 3 dos 5 últimos 2º turnos"})
tornado_df = pd.DataFrame(tornado)
tornado_df["amplitude"] = (tornado_df.alto - tornado_df.baixo).abs()
tornado_df = tornado_df.sort_values("amplitude", ascending=False)

# ----------------------------------------------------------------------------
# 6. O QUE O 1º TURNO DIRÁ SOBRE O 2º — margem inicial do 2º turno implicada por cada resultado do 1º (transferência declarada)
# ----------------------------------------------------------------------------
def margem_2t_dado_1t(margem_1t):
    """Desloca Lula e Flávio simetricamente até a margem observada e aplica a transferência da 3ª via + indecisos."""
    d_ = (margem_1t - (share_now["lula"] - share_now["flavio"])) / 2
    L_, F_ = agg1["L"] + d_*_resto/100, agg1["F"] - d_*_resto/100
    return estrutural_2t(L=L_, F=F_)[0]
# A tabela parte do resultado oficial do 1º turno (não de pesquisa), então não leva erro de medição: a incerteza é a deriva entre
# os turnos e a da própria transferência declarada (2ª opção), da ordem da amplitude das variáveis de transferência no tornado.
SD_TRANSF = 2.0
_sd_t12 = math.sqrt(max(dias_t1_t2, 0)*SIG_ENTRE_DIA**2 + SD_TRANSF**2)
tabela_1t_2t = [{"margem_1t": m_, "margem_2t_inicial": round(margem_2t_dado_1t(m_), 1),
                 "p_lula_2t": round(float(1 - norm.cdf(0, loc=margem_2t_dado_1t(m_), scale=_sd_t12)), 3)}
                for m_ in [-6, -4, -2, 0, 2, 4, 6, 8, 10]]
hist_1t = [("2006", "Lula", 56.0, 48.6, "Datafolha 12/set: 50% totais (≈56% válidos)"),
           ("2010", "Dilma", 55.0, 46.9, "Datafolha 21–22/set: 51% totais (≈55% válidos)"),
           ("2014", "Dilma", 46.0, 41.6, "Datafolha 29–30/set: 40% totais (≈46% válidos)"),
           ("2018", "Bolsonaro", 33.0, 46.0, "Datafolha 26–28/set: 28% totais (≈33% válidos) — onda antipetista tardia"),
           ("2022", "Lula", 50.0, 48.4, "Datafolha 1/out: 50% válidos")]

# ----------------------------------------------------------------------------
# 6a. INFLUÊNCIA — quanto cada pesquisa dos últimos 7 dias desloca o nível de hoje (reajuste sem ela). Em séries que terminam hoje, a
#     última pesquisa pesa muito; a tabela mostra de onde vem cada movimento do placar.
# ----------------------------------------------------------------------------
_inf = {}
_corte = HOJE - pd.Timedelta(days=7)
for i_ in jan.index[jan.campo_fim > _corte]:
    f_ = _ajusta_janela(jan.drop(i_))
    _inf[(jan.instituto[i_], str(jan.campo_fim[i_].date()))] = {"d_nivel_2t": round(level_nac - float(f_["x"][-1]), 2)}
if not POS_1T:
    for i_ in p1.index[p1.campo_fim > _corte]:
        f_ = _fit_par("margem_v", _se2_m1, 1.5, 0.8, p1.drop(i_))
        _inf.setdefault((p1.instituto[i_], str(p1.campo_fim[i_].date())), {})["d_margem_1t"] = round(_m1 - float(f_["x"][-1]), 2)
influencia = sorted([{"instituto": k_[0], "campo_fim": k_[1], **v} for k_, v in _inf.items()],
                    key=lambda d_: -max(abs(d_.get("d_nivel_2t", 0)), abs(d_.get("d_margem_1t", 0))))

# ----------------------------------------------------------------------------
# 6b. DIAGNÓSTICO DE VIÉS E DISPERSÃO POR INSTITUTO (resíduos do modelo único)
# ----------------------------------------------------------------------------
def moe95(n_): return 1.96 * math.sqrt(0.25 / n_) * 100
df["x_tend"] = [float(fit_full["x"][d_]) for d_ in df.dia]
df["resid_spline"] = df.margem_valid - df.x_tend                       # desvio da tendência comum (casa + ruído)
df["resid_full"]   = df.resid_spline - df.house_aplicado                # após remover o efeito de casa (o aplicado: λ·h depois do 1º turno)
df["moe_declarada"] = df.n.map(moe95)
df["moe_margem_95"] = 1.96 * df.se_margem_teorico
diag = []
for i_ in insts:
    sub = df[df.instituto == i_]
    n_i = len(sub)
    # variância posterior do efeito de casa vinda do próprio ajuste (conta a_casa = λ nas ondas pós-1º turno, que identificam só λ·h);
    # a soma simples das precisões superestimava a informação dessas ondas
    _hv = (fit_win.get("house_var") or {}).get(i_, (fit_full.get("house_var") or {}).get(i_))
    se_h = math.sqrt(_hv) if _hv else math.sqrt(1.0 / (sub.w_precisao.sum() + 1/1.5**2))
    rms_tend = float(np.sqrt((sub.resid_spline ** 2).mean()))
    rms_full = float(np.sqrt((sub.resid_full ** 2).mean()))
    se_teo = float(sub.se_margem_teorico.mean())
    diag.append({"instituto": i_, "n_ondas": int(n_i), "n_medio": int(sub.n.mean()),
                 "metodo": sub.sort_values("campo_fim").metodo.iloc[-1], "contratante": sub.sort_values("campo_fim").contratante.iloc[-1],
                 "house": round(house[i_], 2), "house_se": round(se_h, 2), "house_z": round(house[i_] / se_h, 1),
                 "sigma_extra": round(sigma_inst[i_], 2), "ancora": i_ in ANCORA,
                 "rms_vs_tendencia": round(rms_tend, 2), "rms_idiossincratico": round(rms_full, 2),
                 "moe_declarada": round(float(sub.moe_declarada.mean()), 2), "moe_margem_95": round(float(sub.moe_margem_95.mean()), 2),
                 "se_margem_teorico": round(se_teo, 2), "razao_erro_teorico": round(rms_tend / se_teo, 2),
                 "mae_hist": round(float(mae_inst[i_]), 2) if i_ in mae_inst.index else None,
                 "n_obs_hist": int(n_inst[i_]) if i_ in n_inst.index else 0, "peso_qualidade": round(peso_qualidade(i_), 2),
                 "ultima_onda": sub.campo_fim.max().strftime("%d/%m"), "diagnostico_confiavel": bool(n_i >= 3)})
diag = sorted(diag, key=lambda d: -d["n_ondas"])
rms_tend_global = float(np.sqrt((df.resid_spline ** 2).mean()))
se_teo_global = float(df.se_margem_teorico.mean())
moe_decl_global = float(df.moe_declarada.mean()); moe_margem_global = float(df.moe_margem_95.mean())
razao_global = rms_tend_global / se_teo_global
sigma2 = float((df.resid_full**2).mean())
recentes = df[df.idade_dias <= 21]
# Teste de manada (auditoria de 3/out): compara a dispersão dos RESÍDUOS (margem − tendência − efeito de casa) com o erro amostral.
# A versão anterior usava as margens brutas, que carregam as diferenças permanentes entre institutos: razão acima de 1 era certa
# e não dizia nada. Razão bem abaixo de 1 nos resíduos indicaria pesquisas mais parecidas entre si do que o acaso permite.
sd_bruto_recente = float(recentes.margem_valid.std(ddof=1))
sd_obs_recente = float(np.sqrt((recentes.resid_full ** 2).mean())); sd_esp_recente = float(np.sqrt((recentes.se_margem_teorico ** 2).mean()))
herding_ratio = sd_obs_recente / sd_esp_recente

# ----------------------------------------------------------------------------
# 5b. MIGRAÇÃO — ausentes, terceira via e brancos/nulos por cadeia de Markov + Monte Carlo (migracao_markov.py). Análise paralela e
#     condicional: parte do resultado oficial por UF e da 2ª opção declarada, sorteia a matriz de transição (Dirichlet) e os fluxos
#     brutos de comparecimento, simula dia a dia a campanha de conversa pessoal do 2º turno (guia "Três nomes, três semanas") e a
#     resposta de Flávio, e compara com as metas do guia. Não altera nenhuma saída do modelo principal (manchete).
# ----------------------------------------------------------------------------
import migracao_markov
migracao_res = {"usado": False, "motivo": "só com o resultado oficial do 1º turno por UF"}
if POS_1T and migracao_markov.disponivel(DATA):
    _ini_cad = str((_tr.campo_fim.max() + pd.Timedelta(days=1)).date()) if (_tr.campo_fim > T1).any() else None   # 2ª opção pós-1º turno: a cadeia começa depois do campo
    migracao_res = migracao_markov.simular(DATA, transf, HOJE, sd_deriva, regiao_uf=REGIAO_UF, transf_fonte=TRANSF_FONTE, inicio_cadeia=_ini_cad,
                                           comparacao={"estrutural_base_margem": round(base_m, 2), "p_lula_principal": round(p_lula, 3), "p_lula_mcmc": round(p_lula_mcmc, 3),
                                                       "mediana_principal": round(float(q[3]), 1), "sd_deriva_principal": round(sd_deriva, 2)})
    _mg = {c_["id"]: c_ for c_ in migracao_res["cenarios"] if c_["resposta"] == "nenhuma"}
    print(f"[Migração] {migracao_res['n_sorteios']} sorteios × {len(migracao_res['cenarios'])} células | sem campanha {_mg['sem']['margem_q']['50']:+.2f} "
          f"(P(Lula) com deriva {_mg['sem']['p_lula_com_deriva']:.3f}; principal {p_lula:.3f}) | forte {_mg['forte']['margem_q']['50']:+.2f} "
          f"(saldo {_mg['forte']['saldo_frentes']['total']['q50']/1e6:+.2f} mi; P {_mg['forte']['p_lula_com_deriva']:.3f}) | metas do guia: margem "
          f"{migracao_res['guia']['margem_se_cumprido']:+.2f}, conversão necessária {migracao_res['guia']['taxa_necessaria']['terceira_via']['razao']:.0f}× a literatura "
          f"| checks {'ok' if all(migracao_res['checks'].values()) else 'FALHOU'} | {migracao_res['segundos']} s")
    if not all(migracao_res["checks"].values()):
        print(f"[AVISO] migração: verificação falhou: {migracao_res['checks']}")

# ----------------------------------------------------------------------------
# 7. SAÍDAS — JSON + CSVs + SVGs
# ----------------------------------------------------------------------------
res = {
  "data_base": str(HOJE.date()), "atualizado_em": pd.Timestamp.now(tz="America/Sao_Paulo").strftime("%Y-%m-%dT%H:%M"), "versao_modelo": 3,
  "influencia": influencia, "df_erro": DF_ERRO, "mae_rel": {i: round(float(mae_rel[i]), 2) for i in mae_rel.index}, "mae_ref": MAE_REF,
  "erro_comum_1t": (None if ERRO_COMUM_1T is None else round(ERRO_COMUM_1T, 2)), "rho_1t_2t": RHO_1T_2T, "sd_quebra_1t": SD_QUEBRA_1T,
  "nivel_pesquisas": round(level_pesq, 2), "se_pesquisas": round(se_pesq, 2), "nivel_estrutural": (None if NIVEL_ESTRUT is None else round(NIVEL_ESTRUT, 2)),
  "sd_estrutural": round(SD_ESTRUT_HOJE, 2), "sd_estrutural_base": SD_ESTRUT, "peso_estrutural": round(W_ESTRUT, 3), "f_pos1t": round(F_POS1T, 3), "n_pos1t": N_POS1T, "bias_pre1t_obs": None if BIAS_PRE1T_OBS is None else round(BIAS_PRE1T_OBS, 2), "bias_pos_regra": None if BIAS_POS_REGRA is None else round(BIAS_POS_REGRA, 2), "mercado_valido": MERC_VALIDO, "transf_fonte": TRANSF_FONTE,
  "transf": {k_: [round(v[0], 2), round(v[1], 2)] for k_, v in transf.items()},
  "t1_margem_nivel": round(share_now["lula"] - share_now["flavio"], 2), "t1_margem_se": round(SE_M1, 2), "t1_sig_rw_margem": round(float(fit_m1["sig_rw"]), 3),
  "t1_house_margem": {i_: round(v, 2) for i_, v in fit_m1["house"].items()}, "var_ancora": round(fit_win["var_ancora"], 3), "regime_pos_1t": POS_1T, "n_pesquisas": int(len(df)), "n_institutos": int(df.inst_base.nunique()), "n_series_casa": len(insts),
  "ancora": ANCORA, "q_ancora": {k_: round(v, 2) for k_, v in Q_ANCORA.items()}, "inicio_janela": str(INICIO_JANELA.date()), "n_pesquisas_janela": int(len(jan)),
  "diagnostico_institutos": diag, "rms_vs_tendencia_global": round(rms_tend_global, 2),
  "moe_declarada_media": round(moe_decl_global, 2), "moe_margem_95_media": round(moe_margem_global, 2),
  "se_margem_teorico_medio": round(se_teo_global, 2), "razao_erro_teorico_global": round(razao_global, 2),
  "herding": {"sd_observado": round(sd_obs_recente, 2), "sd_esperado_amostral": round(sd_esp_recente, 2), "razao": round(herding_ratio, 2), "n_ondas_21d": int(len(recentes)), "sd_bruto": round(sd_bruto_recente, 2)},
  "validacao_rolling": VALIDACAO,
  "nivel_ajustado_hoje_margem_valid": round(level_final, 2), "se_total": round(se_final, 2),
  "nivel_pspline": round(level_pspline, 2), "se_pspline": round(se_pspline, 2), "nivel_kalman": round(level_kf, 2), "se_kalman": round(se_kf, 2),
  "nivel_nacional_combinado": round(level_nac, 2), "se_nacional_combinado": round(se_nac, 2), "nivel_v2_like": round(level_v2_like, 2), "shift_ancora": round(_shift_v2, 2),
  "sigma_passeio_estimado": round(SIG_RW_KF, 3), "sigma_passeio_ml": round(SIG_RW_ML, 3), "sigma_extra_kalman": round(float(np.median(list(sigma_inst.values()))), 2), "sd_diario_usado": round(SD_DIARIO, 3),
  "sig_entre_turnos_dia": round(SIG_ENTRE_DIA, 3), "sig_entre_turnos_bruto": round(SIG_ENTRE_DIA_BRUTO, 3), "mult_entre_turnos": MULT_ENTRE_TURNOS, "sd_deriva_antigo": round(sd_deriva_antigo, 2),
  "movimento_entre_turnos": mov.to_dict(orient="records"),
  "bias_metodo": BIAS_METODO, "bias_n_metodo": {k_: int(v) for k_, v in BIAS_N_METODO.items()}, "bias_aplicado": round(BIAS_APLICADO, 2), "bias_sd_aplicado": round(BIAS_SD, 2), "bias_pre1t_mean": BIAS_PRE1T_MEAN, "bias_pre1t_sd": BIAS_PRE1T_SD, "bias_pre1t_ciclos": {str(k_): round(v, 2) for k_, v in BIAS_PRE1T_CICLOS.items()}, "bias_pre1t_hist": BIAS_PRE1T_HIST, "bias_final_mean": BIAS_MEAN,
  "bias_2t_ciclos": {str(k_): round(v, 2) for k_, v in bias_2t_ciclos.items()}, "pesos_ciclo": {str(k_): round(v, 2) for k_, v in pesos_ciclo.items()},
  "bias_prior_mean_igual": BIAS_MEAN_IGUAL, "sens_bias": sens_bias,
  "bias_1t_ciclos": {str(k_): round(v, 2) for k_, v in bias_1t_ciclos.items()}, "bias_1t_hist": BIAS_1T_HIST, "bias_1t_mean": BIAS_1T_MEAN, "bias_1t_sd": BIAS_1T_SD, "encolhimento_1t": ENCOLHIMENTO_1T,
  "efeitos_fatores": efeitos_fatores, "abstencao": abst, "sd_ruido_regional": round(SD_RUIDO, 2), "sig_regiao": SIG_REGIAO, "peso_regiao": {k_: round(v, 3) for k_, v in PESO_REGIAO.items()},
  "estadual": ({"usado": estadual["usado"], "motivo": estadual.get("motivo", ""), "nivel": round(estadual["nivel"], 2), "se": round(estadual["se"], 2),
                "mu_swing": round(estadual["mu_swing"], 2), "mu_regiao": estadual["mu_regiao"],
                "ajuste_medio_tend": round(estadual["ajuste_medio_tend"], 2), "ajuste_medio_casa": round(estadual["ajuste_medio_casa"], 2), "tau": round(estadual["tau"], 2), "cobertura": round(estadual["cobertura"], 3),
                "n_pesq": estadual["n_pesq"], "n_uf": estadual["n_uf"], "peso_na_combinacao": round(estadual.get("peso_na_combinacao", 0.0), 3),
                "por_uf": estadual["por_uf"][["uf","n_pesq","institutos","ultimo_campo","m22","margem_2026","swing_bruto","swing","peso"]].round(3).to_dict(orient="records")}
               if "nivel" in estadual else {"usado": False, "motivo": estadual["motivo"]}),
  "p_flavio_2t_usado": round(P_FLAVIO_2T, 3), "sig_rw_menores": round(SIG_RW_MENORES, 3), "sig_rw_menores_livre": SIG_RW_MENORES_LIVRE, "janela_menores_dias": JANELA_MENORES, "desloc_colapso_removido": round(_desloc_colapso, 2), "n_max_efetivo": N_MAX_EFETIVO, "n_efetivo_inst": N_EFETIVO_INST, "lambda_casa_pos1t": LAMBDA_CASA, "p_flavio_top2_votos": round(P_FLAVIO_TOP2_VOTOS, 4), "risco_candidatura": RISCO_CANDIDATURA, "p_flavio_2t_mercado": P_FLAVIO_2T_MERC,
  "sigma_resid": round(math.sqrt(sigma2), 2),
  "media_ponderada_ultimas_bruta": round(media_simples_bruta, 2), "media_ponderada_ultimas_ajustada": round(media_simples_adj, 2),
  "house_effects": {i: round(house[i], 2) for i in insts}, "sigma_inst": {i: round(sigma_inst[i], 2) for i in insts},
  "mae_hist": {i: round(float(mae_inst[i]), 2) for i in mae_inst.index}, "bias_hist": {i: round(float(bias_inst[i]), 2) for i in bias_inst.index},
  "bias_prior_mean": BIAS_MEAN, "bias_prior_sd": BIAS_SD_FINAL, "bias_prior_sd_final": BIAS_SD_FINAL, "sd_deriva": round(sd_deriva, 2),
  "p_lula": round(p_lula, 3), "p_lula_vs_flavio": round(p_lula_vs_flavio, 3), "p_empate_1p5": round(p_empate, 3),
  "sensibilidade": sens, "p_ensemble": round(p_ens, 3), "p_ensemble_empate": round(p_ens_empate, 3), "pesos_ensemble": W_ENS,
  "cenarios_ensemble": {k_: round(v, 3) for k_, v in cen.items()}, "q_ensemble_10_50_90": [round(float(v), 1) for v in q_ens],
  "p_mercados": P_MERC, "mercado": MERC, "p_fundamentos": round(P_FUND, 3), "aprovacao_media_corrente": round(aprovacao_media, 1),
  "fund_inclinacao": round(float(FUND_B), 2), "fund_empate": round(float(FUND_EMPATE), 1), "fund_margem_esperada": round(mu_fund, 1),
  "fund_hist": [{"ciclo": c_, "aprovacao": a_, "margem": m_} for c_, a_, m_ in FUND_HIST], "fund_medida": FUND_MEDIDA, "aprovacao_atual": APROVACAO_ATUAL,
  "quantis_margem_final": {str(p): round(float(v), 1) for p, v in zip([5,10,25,50,75,90,95], q)},
  "var_share": {k_: round(v, 3) for k_, v in var_share.items()},
  "p_dado_lead": tabela_lead, "lead_breakeven": round(lead_breakeven, 1), "lead_70": round(lead_70, 1), "lead_90": round(lead_90, 1),
  "estrutural_base_margem": round(base_m, 2), "tornado": tornado_df.round(2).to_dict(orient="records"),
  # 1º turno
  "t1_n_pesquisas": int(len(p1)), "t1_n_institutos": int(p1.instituto.nunique()), "t1_sig_rw": round(sig_rw_1t, 3),
  "t1_share_valid": {c_: round(v, 1) for c_, v in share_now.items()}, "t1_se_share": {c_: round(v, 2) for c_, v in se_share.items()},
  "t1_q_share": q_share, "t1_house": {c_: {i_: round(v, 2) for i_, v in house_1t[c_].items()} for c_ in CANDS},
  "t1_agregado": {k_: round(v, 1) for k_, v in agg1.items() if k_ in ["L","F","Cury","Renan","Caiado","Zema","outros","branco","indec"]},
  "t1_lula_valid_now": round(L_valid_now, 1), "t1_gap": round(gap_valid, 1),
  "t1_por_instituto": [{"instituto": r.instituto, "campo_fim": str(r.campo_fim.date()), "n": int(r.n), **{c_: round(float(getattr(r, c_)), 1) for c_ in CANDS},
                        "branco_nulo": (None if pd.isna(r.branco_nulo) else round(float(r.branco_nulo), 1)), "indecisos": (None if pd.isna(r.indecisos) else round(float(r.indecisos), 1)),
                        "L_valid": round(float(r.lula_v), 1), "F_valid": round(float(r.flavio_v), 1), "margem_v": round(float(r.margem_v), 1), "registro_tse": (r.registro_tse if isinstance(r.registro_tse, str) else "")}
                       for r in last1.itertuples()],
  "p_1turno": round(p_1t, 4), "p_flavio_1turno": round(p_flavio_1t, 4), "p_lula_lidera_1t": round(p_lula_lidera_1t, 3), "q_1turno": [round(float(v), 1) for v in q_1t],
  "q_margem_1t": {str(p): round(float(v), 1) for p, v in zip([5,10,25,50,75,90,95], q_margem_1t)}, "p_terceiro": {c_: round(v, 3) for c_, v in p_terceiro.items()},
  "sens_1t": sens_1t, "tabela_1t_2t": tabela_1t_2t, "hist_1t": hist_1t,
  "share_lula_3via": {k_: round(v, 2) for k_, v in share_lula_3via.items()}, "T3_total": round(T3_total, 1),
  "resultado_1t": RESULTADO_1T,
}

# termômetro de tendência (últimas duas semanas): nível suavizado da janela dia a dia, com a leitura estadual na mesma proporção de hoje.
# É o nível SÓ das pesquisas (sem o voto contado), com o erro do filtro mais o da âncora; o painel o compara com o nível das pesquisas de
# hoje (nivel_pesquisas), não com a manchete mesclada (21/out: antes o erro da série era o do nível mesclado).
_serie = []
for _k in range(14, -1, -1):
    _lv, _Pk = x_win(t_now - _k)
    _lv = (1 - w_est_comb)*_lv + w_est_comb*(estadual["nivel"] if estadual["usado"] else 0.0)
    _serie.append({"data": str((HOJE - pd.Timedelta(days=_k)).date()), "nivel": round(_lv, 2), "se": round(math.sqrt(_Pk + fit_win["var_ancora"]), 2)})
_d7 = _serie[-1]["nivel"] - _serie[-8]["nivel"]; _d14 = _serie[-1]["nivel"] - _serie[0]["nivel"]
_LIM = 0.5
_quem = lambda v: "Lula" if v > 0 else "Flávio"
_dir = (f"a favor de {_quem(_d7)}" if abs(_d7) >= _LIM else (f"a favor de {_quem(_d14)}, desacelerando" if abs(_d14) >= 2*_LIM else "estável"))
_mov = []
_dfs = df.sort_values("campo_fim")
for _r in _dfs[_dfs.campo_fim > HOJE - pd.Timedelta(days=14)].itertuples():
    # onda anterior da mesma série; depois do 1º turno só pares pós-4/out, para não contar a quebra de 4/out como movimento
    _prev = _dfs[(_dfs.instituto == _r.instituto) & (_dfs.campo_fim < _r.campo_fim) & ((_dfs.campo_fim > T1) if POS_1T else True)]
    if len(_prev) == 0: continue
    _p = _prev.iloc[-1]
    _mov.append({"instituto": _r.instituto, "campo_fim": str(_r.campo_fim.date()), "anterior": str(_p.campo_fim.date()),
                 "lula": float(_r.lula), "flavio": float(_r.flavio), "lula_ant": float(_p.lula), "flavio_ant": float(_p.flavio),
                 "delta_margem": round(float(_r.margem_valid - _p.margem_valid), 1)})
res["nivel_ajustado_14d_atras"] = _serie[0]["nivel"]
# mesma linha desde o 1º turno, para o gráfico evolutivo das pesquisas pós-4/out (evolucao_pesquisas.py)
_serie_pos1t = []
for _k in range(max((HOJE - T1).days, 0), -1, -1):
    _lv, _Pk = x_win(t_now - _k)
    _lv = (1 - w_est_comb)*_lv + w_est_comb*(estadual["nivel"] if estadual["usado"] else 0.0)
    _serie_pos1t.append({"data": str((HOJE - pd.Timedelta(days=_k)).date()), "nivel": round(_lv, 2), "se": round(math.sqrt(_Pk + fit_win["var_ancora"]), 2)})
res["serie_pos1t"] = _serie_pos1t
res["termometro"] = {"serie": _serie, "delta_7d": round(_d7, 2), "delta_14d": round(_d14, 2), "limiar": _LIM, "direcao": _dir, "movimentos": _mov,
                     "n_lula": sum(m["delta_margem"] >= 0.5 for m in _mov), "n_flavio": sum(m["delta_margem"] <= -0.5 for m in _mov),
                     "n_estavel": sum(abs(m["delta_margem"]) < 0.5 for m in _mov)}

# previsões do 1º turno congeladas (última rodada antes de 4/out fica gravada no repositório; a partir de 4/out nunca se reescreve,
# nem se o arquivo faltar — recriá-lo depois da eleição produziria uma "previsão" feita com o resultado conhecido)
frozen_path = DATA + "previsoes_1turno_congeladas.json"
if HOJE < T1 and not os.environ.get("ELEICAO_NAO_CONGELAR"):
    json.dump({"data_base": str(HOJE.date()), "share_valid": res["t1_share_valid"], "q_share": q_share, "p_1turno": res["p_1turno"],
               "p_flavio_top2": res["p_flavio_top2_votos"], "p_lula_lidera": res["p_lula_lidera_1t"], "q_margem_1t": res["q_margem_1t"],
               "p_terceiro": res["p_terceiro"], "bias_1t_mean": BIAS_1T_MEAN, "ultimas_por_instituto": res["t1_por_instituto"],
               "nivel_2t": res["nivel_ajustado_hoje_margem_valid"], "p_lula_2t": res["p_lula"], "p_ensemble_2t": res["p_ensemble"]},
              open(frozen_path, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
if os.path.exists(frozen_path):
    res["previsoes_congeladas"] = json.load(open(frozen_path, encoding="utf-8"))
res["mcmc"] = mcmc_res
res["migracao"] = migracao_res

# previsões do 2º turno congeladas, dos dois modelos em paralelo (mesma regra do 1º turno: reescritas a cada rodada até a véspera de
# 25/out e nunca depois), para comparar com o resultado oficial
frozen2_path = DATA + "previsoes_2turno_congeladas.json"
if HOJE < T2 and not os.environ.get("ELEICAO_NAO_CONGELAR"):
    json.dump({"data_base": str(HOJE.date()),
               "principal": {"p_lula": res["p_lula"], "p_ensemble": res["p_ensemble"], "quantis_margem_final": res["quantis_margem_final"],
                             "q_ensemble_10_50_90": res["q_ensemble_10_50_90"], "nivel_pesquisas": res["nivel_pesquisas"], "nivel_ajustado": res["nivel_ajustado_hoje_margem_valid"],
                             "nivel_verdadeiro": mcmc_res["comparacao"]["nivel_verdadeiro_principal"]},
               "mcmc": {k_: mcmc_res[k_] for k_ in ["p_lula", "p_ensemble", "quantis_margem_final", "q_ensemble_10_50_90", "nivel_hoje",
                                                     "se_nivel_hoje", "rhat_max", "ess_min", "convergiu"]}},
              open(frozen2_path, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
if os.path.exists(frozen2_path):
    res["previsoes_2turno_congeladas"] = json.load(open(frozen2_path, encoding="utf-8"))

json.dump(res, open(OUT + "resultados.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2)
def _csv(d_, path, nd=3):
    d_ = d_.copy()
    for c_ in d_.select_dtypes(include="number").columns: d_[c_] = d_[c_].round(nd)
    d_.to_csv(path, index=False)
_csv(df, OUT + "pesquisas_2turno_2026_ponderadas.csv")
last["sigma_inst"] = last.instituto.map(sigma_inst)
_csv(last[["instituto","campo_fim","n","n_ef","lula","flavio","margem_valid","margem_adj","house_aplicado","w_recencia","w_tamanho","w_qualidade","w","sigma_inst","w_precisao"]], OUT + "ultimas_por_instituto.csv")
_csv(p1, OUT + "pesquisas_1turno_2026_processadas.csv", 2)

# ---------------- SVG helpers ----------------
def fmt(v, d=1): return f"{v:.{d}f}".replace(".", ",")
LABEL = {"Parana Pesquisas": "Paraná Pesquisas", "Futura Inteligencia": "Futura Inteligência", "MDA": "CNT/MDA", "Palver v3": "Palver (calibração v3)", "MDA v2": "CNT/MDA (ponderada pelo 1º turno)", "Futura Inteligencia v2": "Futura Inteligência (escolaridade pela PNAD)",
         "Verita": "Veritá", "Alfa Inteligencia": "Alfa Inteligência", "Vetor": "Vetor/Arrow",
         "Instituto Franca": "Instituto França"}

def svg_trend():
    W, H = 900, 300
    x0, x1, y0, y1 = 60, 880, 20, 250
    tmin, tmax = int(df.dia.min()) - 1, (T2 - T0).days
    ymin, ymax = -12, 20
    def X(t): return x0 + (t - tmin)/(tmax - tmin)*(x1-x0)
    def Y(v): return y1 - (v - ymin)/(ymax - ymin)*(y1-y0)
    s = [f'<svg viewBox="0 0 {W} {H}" role="img" aria-label="Margem Lula menos Flávio em votos válidos, pesquisas e tendência ajustada por efeitos de casa">']
    for v in range(-12, 21, 4):
        s.append(f'<line class="grid-line" x1="{x0}" y1="{Y(v):.1f}" x2="{x1}" y2="{Y(v):.1f}"/>')
        s.append(f'<text class="axis-label" x="{x0-8}" y="{Y(v)+4:.1f}" text-anchor="end">{"+" if v>0 else ""}{v}</text>')
    s.append(f'<line x1="{x0}" y1="{Y(0):.1f}" x2="{x1}" y2="{Y(0):.1f}" stroke="var(--ink-soft)" stroke-width="1.2"/>')
    MESES = {1:"jan",2:"fev",3:"mar",4:"abr",5:"mai",6:"jun",7:"jul",8:"ago",9:"set",10:"out",11:"nov",12:"dez"}
    for m in pd.date_range("2025-12-01", "2026-11-01", freq="MS"):
        tt = (m - T0).days
        if tmin-15 <= tt <= tmax:
            s.append(f'<text class="axis-label" x="{X(tt):.1f}" y="{y1+22}" text-anchor="middle">{MESES[m.month]}</text>')
    up = " ".join(f"{X(t):.1f},{Y(f+1.96*e):.1f}" for t, f, e in zip(tt_grid, f_grid, se_grid))
    lo = " ".join(f"{X(t):.1f},{Y(f-1.96*e):.1f}" for t, f, e in zip(tt_grid[::-1], f_grid[::-1], se_grid[::-1]))
    s.append(f'<polygon points="{up} {lo}" fill="var(--accent)" opacity="0.13"/>')
    s.append('<polyline class="series-line" stroke="var(--accent)" stroke-width="2.4" points="' + " ".join(f"{X(t):.1f},{Y(f):.1f}" for t, f in zip(tt_grid, f_grid)) + '"/>')
    for _, r in df.iterrows():
        s.append(f'<circle cx="{X(r.t):.1f}" cy="{Y(r.margem_valid):.1f}" r="4" fill="var(--other)" stroke="var(--surface)" stroke-width="1.2" opacity="0.85" class="iso-dot" tabindex="0"><title>{LABEL.get(r.instituto, r.instituto)} · {r.campo_fim.strftime("%d/%m/%y")} · Lula {fmt(r.lula)} × Flávio {fmt(r.flavio)} → margem válida {fmt(r.margem_valid)} · efeito de casa {fmt(r.house, 1)}</title></circle>')
    marcos = [(t_now, "hoje", "end"), ((T1 - T0).days, "1º turno", "middle"), (tmax, "2º turno", "end")]
    xs = [X(tt) for tt, _, _ in marcos]
    rows = [0]
    for i in range(1, len(xs)):
        rows.append(1 if abs(xs[i] - xs[i-1]) < 58 and rows[i-1] == 0 else 0)
    for (tt, lab, anchor), xr, row in zip(marcos, xs, rows):
        s.append(f'<line x1="{xr:.1f}" y1="{y0}" x2="{xr:.1f}" y2="{y1}" stroke="var(--line)" stroke-dasharray="3,4"/>')
        dx = -4 if anchor == "end" else 0
        ry = (y0 - 6) - (14 if row else 0)
        s.append(f'<text class="axis-label" x="{xr+dx:.1f}" y="{ry}" text-anchor="{anchor}">{lab}</text>')
    tq = tmax; med = q[3]; p10, p90 = q[1], q[5]
    cor_proj = "var(--lula)" if med > 0 else "var(--flavio)"
    s.append(f'<line x1="{X(tq):.1f}" y1="{Y(p10):.1f}" x2="{X(tq):.1f}" y2="{Y(p90):.1f}" stroke="{cor_proj}" stroke-width="6" opacity="0.35"/>')
    s.append(f'<circle cx="{X(tq):.1f}" cy="{Y(med):.1f}" r="5" fill="{cor_proj}"/>')
    s.append(f'<text class="dot-label" x="{x1-4:.1f}" y="{y0+16}" text-anchor="end" fill="{cor_proj}">projeção 25/out · mediana {("+" if med>0 else "")}{fmt(med)}</text>')
    s.append(f'<text class="dot-label" x="{x1-4:.1f}" y="{y0+30}" text-anchor="end" fill="{cor_proj}">intervalo 80%: {fmt(p10)} a {("+" if p90>0 else "")}{fmt(p90)}</text>')
    s.append(f'<circle cx="{X(t_now):.1f}" cy="{Y(level_final):.1f}" r="5.5" fill="var(--accent)" stroke="var(--surface)" stroke-width="1.5"><title>Nível hoje: {fmt(level_final)} ± {fmt(1.96*se_final)}</title></circle>')
    s.append(f'<text class="dot-label" x="{X(t_now)-8:.1f}" y="{Y(level_final)+26:.1f}" text-anchor="end" fill="var(--accent)">hoje {("+" if level_final>0 else "")}{fmt(level_final)} ±{fmt(1.96*se_final)}</text>')
    s.append("</svg>")
    return "\n".join(s)

def svg_hist():
    W, H = 900, 260
    x0, x1, y0, y1 = 60, 880, 44, 220
    bins = np.arange(-14, 18.5, 1.0)
    h, edges = np.histogram(margem_final, bins=bins, density=True)
    hmax = h.max()
    def X(v): return x0 + (v - bins[0])/(bins[-1]-bins[0])*(x1-x0)
    def Y(d): return y1 - d/hmax*(y1-y0)
    s = [f'<svg viewBox="0 0 {W} {H}" role="img" aria-label="Distribuição simulada da margem final Lula menos Flávio em 25 de outubro">']
    for i, d in enumerate(h):
        c = "var(--lula)" if edges[i] >= 0 else "var(--flavio)"
        quem = "Lula" if edges[i] >= 0 else "Flávio"
        s.append(f'<rect x="{X(edges[i])+1:.1f}" y="{Y(d):.1f}" width="{(x1-x0)/len(h)-2:.1f}" height="{y1-Y(d):.1f}" fill="{c}" opacity="{0.85 if abs(edges[i]+0.5)>1.5 else 0.45}" tabindex="0"><title>margem {fmt(edges[i])} a {fmt(edges[i]+1)} p.p. · {quem} vence nessa faixa · densidade {d/hmax*100:.0f}% do pico</title></rect>')
    s.append(f'<line x1="{X(0):.1f}" y1="{y0}" x2="{X(0):.1f}" y2="{y1}" stroke="var(--ink)" stroke-width="1.2"/>')
    for v in range(-12, 17, 4):
        s.append(f'<text class="axis-label" x="{X(v):.1f}" y="{y1+18}" text-anchor="middle">{"+" if v>0 else ""}{v}</text>')
    s.append(f'<text class="axis-label" x="{(x0+x1)/2:.0f}" y="{y1+34}" text-anchor="middle">margem Lula − Flávio em 25/out (p.p. de votos válidos)</text>')
    # rótulos em cor de texto; a cor da série fica no quadrado ao lado (Ardósia: texto de gráfico nunca na cor da série)
    s.append(f'<rect x="{X(6.5):.1f}" y="{y0+5}" width="9" height="9" rx="1" fill="var(--lula)"/><text class="dot-label" x="{X(6.5)+14:.1f}" y="{y0+14}" fill="var(--ink)">Lula vence · {p_lula*100:.0f}%</text>')
    s.append(f'<rect x="{X(-6.5)-9:.1f}" y="{y0+5}" width="9" height="9" rx="1" fill="var(--flavio)"/><text class="dot-label" x="{X(-6.5)-14:.1f}" y="{y0+14}" text-anchor="end" fill="var(--ink)">Flávio vence · {(1-p_lula)*100:.0f}%</text>')
    s.append(f'<text class="dot-label" x="{X(0):.1f}" y="{y0-16}" text-anchor="middle" fill="var(--ink-soft)">faixa clara = |margem| &lt; 1,5 p.p. (resultado contestável): {p_empate*100:.0f}%</text>')
    s.append("</svg>")
    return "\n".join(s)

def svg_mcmc():
    """Os dois modelos lado a lado: barras = modelo principal (Monte Carlo com parâmetros fixos), linha = MCMC (posterior completo)."""
    W, H = 900, 270
    x0, x1, y0, y1 = 60, 880, 56, 220
    bins = np.arange(-20, 20.5, 1.0)
    h_a, edges = np.histogram(np.clip(margem_final, -19.99, 19.99), bins=bins, density=True)
    h_m, _ = np.histogram(np.clip(margem_mcmc, -19.99, 19.99), bins=bins, density=True)
    hmax = max(h_a.max(), h_m.max())
    def X(v): return x0 + (v - bins[0])/(bins[-1]-bins[0])*(x1-x0)
    def Y(d): return y1 - d/hmax*(y1-y0)
    s = [f'<svg viewBox="0 0 {W} {H}" role="img" aria-label="Distribuição da margem final em 25 de outubro: modelo principal em barras, modelo MCMC em linha">']
    bw = (x1-x0)/len(h_a)
    for i, d in enumerate(h_a):
        s.append(f'<rect x="{X(edges[i])+1:.1f}" y="{Y(d):.1f}" width="{bw-2:.1f}" height="{y1-Y(d):.1f}" fill="var(--ink-3)" opacity="0.32" tabindex="0">'
                 f'<title>margem {fmt(edges[i])} a {fmt(edges[i]+1)} p.p. · principal {d*100:.1f}% · MCMC {h_m[i]*100:.1f}% das simulações</title></rect>')
    pts = []
    for i, d in enumerate(h_m):
        pts += [f"{X(edges[i]):.1f},{Y(d):.1f}", f"{X(edges[i+1]):.1f},{Y(d):.1f}"]
    s.append(f'<polyline points="{" ".join(pts)}" fill="none" stroke="var(--accent)" stroke-width="2.4" stroke-linejoin="round"/>')
    s.append(f'<line x1="{X(0):.1f}" y1="{y0-6}" x2="{X(0):.1f}" y2="{y1}" stroke="var(--ink)" stroke-width="1.2"/>')
    for med, cor, dy in [(q[3], "var(--ink-3)", 0), (q_mcmc[3], "var(--accent)", 0)]:
        s.append(f'<line x1="{X(med):.1f}" y1="{y0-6}" x2="{X(med):.1f}" y2="{y1}" stroke="{cor}" stroke-width="1.4" stroke-dasharray="4,3"/>')
    for v in range(-20, 21, 4):
        s.append(f'<text class="axis-label" x="{X(v):.1f}" y="{y1+18}" text-anchor="middle">{"+" if v>0 else ""}{v}</text>')
    s.append(f'<text class="axis-label" x="{(x0+x1)/2:.0f}" y="{y1+36}" text-anchor="middle">margem Lula − Flávio em 25/out (p.p. de votos válidos; caudas além de ±20 somadas nas pontas)</text>')
    s.append(f'<rect x="{x0}" y="{y0-44}" width="14" height="10" fill="var(--ink-3)" opacity="0.32"/>')
    s.append(f'<text class="dot-label" x="{x0+20}" y="{y0-35}" fill="var(--ink-soft)">principal (Monte Carlo): Lula {p_lula*100:.0f}% · mediana {fmt(q[3])} · 80% de {fmt(q[1])} a {fmt(q[5])}</text>')
    s.append(f'<line x1="{x0}" y1="{y0-19}" x2="{x0+14}" y2="{y0-19}" stroke="var(--accent)" stroke-width="2.4"/>')
    s.append(f'<text class="dot-label" x="{x0+20}" y="{y0-15}" fill="var(--accent)">MCMC (posterior completo): Lula {p_lula_mcmc*100:.0f}% · mediana {fmt(q_mcmc[3])} · 80% de {fmt(q_mcmc[1])} a {fmt(q_mcmc[5])}</text>')
    s.append("</svg>")
    return "\n".join(s)

def svg_tornado():
    W = 900; rowh = 34; H = 40 + rowh*len(tornado_df) + 30
    x0, x1 = 400, 880; vmax = max(tornado_df.alto.abs().max(), tornado_df.baixo.abs().max()) + 0.5
    def X(v): return (x0+x1)/2 + v/vmax*(x1-x0)/2
    s = [f'<svg viewBox="0 0 {W} {H}" role="img" aria-label="Tornado: efeito de cada variável sobre a margem de Lula no 2º turno">']
    for v in [-4, -2, 0, 2, 4]:
        if abs(v) <= vmax:
            s.append(f'<line class="grid-line" x1="{X(v):.1f}" y1="30" x2="{X(v):.1f}" y2="{H-30}"/>')
            s.append(f'<text class="axis-label" x="{X(v):.1f}" y="{H-12}" text-anchor="middle">{"+" if v>0 else ""}{v} p.p.</text>')
    s.append(f'<line x1="{X(0):.1f}" y1="30" x2="{X(0):.1f}" y2="{H-30}" stroke="var(--ink)" stroke-width="1.2"/>')
    for i, (_, r) in enumerate(tornado_df.iterrows()):
        y = 40 + i*rowh
        lo, hi = sorted([r.baixo, r.alto])
        s.append(f'<text x="{x0-16}" y="{y+13}" text-anchor="end" font-size="12.5" fill="var(--ink)">{r.variavel}</text>')
        tt_lab = f'<title>{r.variavel}: {fmt(lo)} a +{fmt(hi)} p.p. na margem de Lula · {r.obs}</title>'
        if lo < 0: s.append(f'<rect x="{X(lo):.1f}" y="{y}" width="{X(min(hi,0))-X(lo):.1f}" height="20" fill="var(--flavio)" opacity="0.8" tabindex="0">{tt_lab}</rect>')
        if hi > 0: s.append(f'<rect x="{X(max(lo,0)):.1f}" y="{y}" width="{X(hi)-X(max(lo,0)):.1f}" height="20" fill="var(--lula)" opacity="0.8" tabindex="0">{tt_lab}</rect>')
        s.append(f'<text class="dot-label" x="{X(lo)-6:.1f}" y="{y+14}" text-anchor="end" fill="var(--ink-soft)">{fmt(lo)}</text>')
        s.append(f'<text class="dot-label" x="{X(hi)+6:.1f}" y="{y+14}" fill="var(--ink-soft)">+{fmt(hi)}</text>')
    s.append("</svg>")
    return "\n".join(s)

def svg_house():
    dd = sorted([d for d in diag if d["diagnostico_confiavel"]], key=lambda d: d["house"])
    W = 900; rowh = 26; H = 54 + rowh * len(dd) + 26
    x0, x1 = 240, 870
    vmax = max(abs(d["house"]) + 1.96 * d["house_se"] for d in dd) + 0.4
    def X(v): return (x0 + x1) / 2 + v / vmax * (x1 - x0) / 2
    s_ = [f'<svg viewBox="0 0 {W} {H}" role="img" aria-label="Efeito de casa de cada instituto na margem Lula menos Flávio, com intervalo de 95%">']
    s_.append(f'<text class="axis-label" x="{X(-vmax*0.55):.0f}" y="20" text-anchor="middle">mede mais Flávio que a âncora</text>')
    s_.append(f'<text class="axis-label" x="{X(vmax*0.55):.0f}" y="20" text-anchor="middle">mede mais Lula que a âncora</text>')
    for v in range(-6, 7, 2):
        if abs(v) <= vmax:
            s_.append(f'<line class="grid-line" x1="{X(v):.1f}" y1="30" x2="{X(v):.1f}" y2="{H-26}"/>')
            s_.append(f'<text class="axis-label" x="{X(v):.1f}" y="{H-8}" text-anchor="middle">{"+" if v>0 else ""}{v}</text>')
    s_.append(f'<line x1="{X(0):.1f}" y1="30" x2="{X(0):.1f}" y2="{H-26}" stroke="var(--ink)" stroke-width="1.2"/>')
    for i_, d in enumerate(dd):
        yy = 44 + i_ * rowh
        lo, hi = d["house"] - 1.96 * d["house_se"], d["house"] + 1.96 * d["house_se"]
        cor = "var(--lula)" if d["house"] > 0 else "var(--flavio)"
        signif = abs(d["house_z"]) >= 1.96
        nome = LABEL.get(d["instituto"], d["instituto"]) + (" ◆" if d["ancora"] else "")
        s_.append(f'<text x="{x0-14}" y="{yy+4}" text-anchor="end" font-size="12" font-weight="{"600" if d["ancora"] else "400"}" fill="var(--ink)">{nome}</text>')
        s_.append(f'<line x1="{X(lo):.1f}" y1="{yy}" x2="{X(hi):.1f}" y2="{yy}" stroke="{cor}" stroke-width="2" opacity="0.45"/>')
        s_.append(f'<circle cx="{X(d["house"]):.1f}" cy="{yy}" r="{5 if signif else 3.5}" fill="{cor}" opacity="{1 if signif else 0.55}" tabindex="0">'
                  f'<title>{nome}: efeito de casa {fmt(d["house"])} p.p. (IC 95%: {fmt(lo)} a {fmt(hi)}) · {d["n_ondas"]} ondas · variância extra estimada σ={fmt(d["sigma_extra"])} · '
                  f'erro típico {fmt(d["rms_vs_tendencia"])} p.p. vs. {fmt(d["se_margem_teorico"])} esperado pela amostragem</title></circle>')
        s_.append(f'<text class="dot-label" x="{X(hi)+7:.1f}" y="{yy+4}" fill="var(--ink-3)">{fmt(d["house"], 1)}{"*" if signif else ""}</text>')
    s_.append("</svg>")
    return "\n".join(s_)

def svg_1turno():
    """Barras horizontais: share em válidos por candidato, mediana e intervalo 80% da simulação; marca a linha de 50%."""
    cands = sorted(CANDS, key=lambda c_: -share_now[c_])
    W = 900; rowh = 34; H = 40 + rowh*len(cands) + 34
    x0, x1 = 180, 860; vmax = 60
    def X(v): return x0 + v/vmax*(x1-x0)
    s = [f'<svg viewBox="0 0 {W} {H}" role="img" aria-label="Votos válidos esperados por candidato no 1º turno, com intervalo de 80%">']
    for v in range(0, 61, 10):
        s.append(f'<line class="grid-line" x1="{X(v):.1f}" y1="30" x2="{X(v):.1f}" y2="{H-30}"/>')
        s.append(f'<text class="axis-label" x="{X(v):.1f}" y="{H-12}" text-anchor="middle">{v}%</text>')
    s.append(f'<line x1="{X(50):.1f}" y1="30" x2="{X(50):.1f}" y2="{H-30}" stroke="var(--ink)" stroke-width="1.2" stroke-dasharray="4,3"/>')
    s.append(f'<text class="axis-label" x="{X(50)+4:.1f}" y="26">50% + 1 decide no 1º turno</text>')
    for i, c_ in enumerate(cands):
        y = 40 + i*rowh
        lo, med, hi = q_share[c_]
        cor = "var(--lula)" if c_ == "lula" else ("var(--flavio)" if c_ == "flavio" else "var(--other)")
        s.append(f'<text x="{x0-12}" y="{y+14}" text-anchor="end" font-size="12.5" fill="var(--ink)">{CAND_LABEL[c_]}</text>')
        s.append(f'<rect x="{X(0):.1f}" y="{y+2}" width="{X(med)-X(0):.1f}" height="18" fill="{cor}" opacity="0.75" tabindex="0"><title>{CAND_LABEL[c_]}: mediana {fmt(med)}% dos válidos · 80% entre {fmt(lo)} e {fmt(hi)} · nível nas pesquisas hoje {fmt(share_now[c_])}%</title></rect>')
        s.append(f'<line x1="{X(lo):.1f}" y1="{y+11}" x2="{X(hi):.1f}" y2="{y+11}" stroke="var(--ink)" stroke-width="2"/>')
        s.append(f'<line x1="{X(lo):.1f}" y1="{y+5}" x2="{X(lo):.1f}" y2="{y+17}" stroke="var(--ink)" stroke-width="1.5"/><line x1="{X(hi):.1f}" y1="{y+5}" x2="{X(hi):.1f}" y2="{y+17}" stroke="var(--ink)" stroke-width="1.5"/>')
        s.append(f'<text class="dot-label" x="{X(hi)+8:.1f}" y="{y+15}" fill="var(--ink)">{fmt(med)}% <tspan fill="var(--ink-3)">({fmt(lo)}–{fmt(hi)})</tspan></text>')
    s.append("</svg>")
    return "\n".join(s)

open(OUT + "svg_trend.html", "w", encoding="utf-8").write(svg_trend())
open(OUT + "svg_hist.html", "w", encoding="utf-8").write(svg_hist())
open(OUT + "svg_mcmc.html", "w", encoding="utf-8").write(svg_mcmc())
open(OUT + "svg_house.html", "w", encoding="utf-8").write(svg_house())
open(OUT + "svg_tornado.html", "w", encoding="utf-8").write(svg_tornado())
open(OUT + "svg_1turno.html", "w", encoding="utf-8").write(svg_1turno())

print(json.dumps({k_: res[k_] for k_ in ["nivel_ajustado_hoje_margem_valid","se_total","nivel_v2_like","shift_ancora","house_effects","sigma_inst","bias_prior_mean","bias_prior_sd",
                                          "bias_2t_ciclos","sd_deriva","sig_entre_turnos_dia","p_lula","p_lula_vs_flavio","p_empate_1p5","sensibilidade","sens_bias","p_ensemble",
                                          "cenarios_ensemble","efeitos_fatores","var_share","t1_share_valid","p_1turno","p_flavio_top2_votos","p_lula_lidera_1t","q_margem_1t","p_terceiro","sens_1t","validacao_rolling"]},
                 ensure_ascii=False, indent=1))
print(tornado_df.round(2).to_string())
print("MAE hist:", mae_inst.round(2).to_dict())
print("bias por eleição/turno:", bias_el.round(2).to_dict())

Hipóteses explícitas do modelo

  • Margem em votos válidos: Lula ÷ (Lula + Flávio) — brancos, nulos e indecisos fora do denominador, como no resultado oficial.
  • Espaço de estados único: y = x_t + h_p + ε, com x_t passeio aleatório (σ_rw = 0,31 p.p./dia, MAP com prior log-normal centrado em 0,25; ML 0,32), h_p ~ N(0, 1,5²) com média ponderada zero na âncora (AtlasIntel, Datafolha, Ipespe, CNT/MDA, Paraná Pesquisas, PoderData, Quaest, pesos = qualidade histórica, medida pelo erro do instituto em relação à média de cada ciclo), Var ε = erro amostral² + σ_p², σ_p empírico-Bayes (encolhimento k=4 para a mediana, piso 0,8). Estimação ECM. Janela de campanha desde 1/jun para o nível de hoje; série completa só para o gráfico.
  • Erro sistemático: rodadas finais de 2º turno, média por ciclo dos institutos de referência (2006–2022) com recência → +0,9 ± 2,0. Até sair o resultado do 1º turno vale o desconto do 1º turno (+3,4 ± 3,8); aplicado hoje: +0,8 ± 2,0. Com o resultado: só o desconto final (σ = 2,5), sobre a parcela do nível que vem de pesquisas (até 22/out somava 0,25 × o erro comum da âncora no 1º turno; retirado na v56 porque esse erro foi voto útil); o erro de cada instituto em relação à âncora vira prior do seu efeito de casa; o passeio recebe uma quebra de 6 p.p. em 5/out; e o resultado oficial + transferência (± 5,0) entra no nível, combinado por precisão. Caudas t de Student (5 g.l.) no erro e na deriva.
  • Deriva: σ_rw até 4/out; entre turnos 0,95 p.p./dia (movimento das pesquisas de 2º turno de Datafolha e Ibope/Ipec em 2014/18/22, descontado o erro amostral e encolhido 50% para σ_rw) → 0,0 p.p. até 25/out. Choques regionais retirados em 3/out (não alteravam saídas nacionais). P(adversário = Flávio) = 100% (modelo do 1º turno × 0% de risco de impugnação); senão margem ~ N(+7, 4). Leitura estadual incluída quando cobre ≥30% do eleitorado.
  • Ensemble: 75% modelo, 25% mercados (P=0,20 → N(μ, 4,5), Polymarket 25/out, 8h), 0% fundamentos (P=0,53, só referência). O mercado só entra com preço de até 5 dias e, depois do 1º turno, posterior a 4/out.
  • 1º turno: estados para a margem Lula − Flávio, a soma dos dois e cada candidato menor, em votos válidos, mesma âncora; colapso tardio da 3ª via ~ Beta(2,8) (média 20%) repartido pela 2ª opção declarada; viés ~ t5(+3,4, 3,8) na margem Lula − Flávio (60% da média com recência dos erros de 2010/14/18/22: +5,7); ±1,5 p.p. de incerteza extra nos demais candidatos; renormalização a 100%. Previsão congelada em 03/10.

Limitações conhecidas

  • Datas de campo de algumas ondas (PoderData, RTBD, Quaest jan–mai, Atlas jan/abr) aproximadas ao mês — afeta a posição no gráfico, pouco os pesos.
  • O tornado é semi-estrutural: intervalos escolhidos a partir das pesquisas, não estimados por regressão; mede sensibilidade, não efeito causal identificado.
  • O desconto de erro sistemático assume que a média dos cinco últimos ciclos informa 2026, embora a direção tenha variado (três ciclos pró-PT, dois contra); é a hipótese que mais muda o resultado, e a tabela de sensibilidade mostra o resultado com desconto zero e com o desconto antigo. No 1º turno, o desconto (60% da média histórica) é ainda mais incerto: só três ciclos, todos com o principal candidato de direita subestimado.
  • A validação de origem móvel mostra viés médio de -0,3 p.p. nas previsões a 7 dias durante a queda de agosto–setembro: o passeio aleatório não extrapola tendências, por desenho.
  • Mercados e fundamentos não são independentes do modelo (todos leem as mesmas pesquisas); o ensemble é uma ponderação de julgamento, não uma combinação ótima estimada.
  • As pesquisas estaduais entram como segunda leitura do placar nacional (peso 0%), e a abstenção, por cenários de comparecimento (seção Abstenção); nenhuma das duas mede quem de fato vai votar.

Referências

Fontes consultadas entre 8 de setembro e 1º de outubro de 2026 (links ativos na data), com atualizações de pesquisa até 1º/out. A base completa de pesquisas (Apêndice A) traz o link-fonte de cada onda. Pesquisas com registro TSE citado no texto.
  1. Datafolha 8–10/set (TSE BR-01833/2026, divulgada 11/set às 18h45): 1º turno (Lula 39 × Flávio 35): Metrópoles, BPMoney; 2º turno (Lula 46 × Flávio 44): Exame; horário/detalhes técnicos: NSC Total, CartaCapital
  2. Datafolha 1–3/set: Gazeta do Povo; CNN Brasil (2º turno); rejeição: Terra; aprovação: Jovem Pan
  3. Datafolha 18–20/ago: CNN Brasil; Brasil de Fato; rejeição: Tribuna de Jundiaí; segmentos: RIC; aprovação: Metrópoles
  4. Datafolha jul, jun, abr, mar, dez/25: Gazeta (jul); Gazeta (jun); CNN (abr); Reuters/Yahoo (mar); Gazeta (dez/25)
  5. Genial/Quaest 3–6/set: CNN Brasil; transferência: O Antagonista; rejeição: DGABC; análise Nunes: Sem Pauta; 1º turno: Transmissão Política
  6. Genial/Quaest 30/ago–1/set: Gazeta do Povo; aprovação: Poder360; Lulinha/Master: InfoMoney
  7. Genial/Quaest ago, jul, jun, mar, fev, jan: Al Jazeera (13/ago); CNN (3/ago); Poder360 (rejeição ago); Gazeta (jun); CNN (mar); Painel Político (série); Poder360 (aprovação jul); Correio Braziliense (ago)
  8. AtlasIntel/Bloomberg: Poder360 (25–30/ago); Exame; Gazeta (1º turno ago); Metrópoles (jul); CNN (jun); CNN (mai); JOTA (mar); Reuters/AOL (fev); aprovação: Jornal de Brasília, Poder360 (jul)
  9. PoderData/Aya: Poder360 (7 ondas, 3/set); Gazeta (1º turno); aprovação: Tribuna de Jundiaí; idade: Poder360
  10. Real Time Big Data: CNN Brasil (série); Gazeta (1º turno); rejeição/aprovação: Poder360
  11. Paraná Pesquisas: CNN Brasil (mar); Brasil de Fato
  12. BTG/Nexus: Poder360 (24/ago); Nexus/FSB (31/ago). Gerp: Poder360. Ipsos-Ipec: Ipsos (jun), CartaCapital. Futura Inteligência (2ª onda): Poder360 (4–10/set). Meio/Ideia: Poder360 (2º turno, 4–7/set)
  13. Calibração 2022: Poder360 (véspera 2º turno); CNN Brasil (1º turno); Congresso em Foco; CNN (Atlas); CNN (Paraná); Congresso em Foco (deriva 2022)
  14. Calibração 2018: Brasil de Fato (1º turno); O Povo (2º turno); Seu Dinheiro (set/18); ND Mais. Ciclos anteriores: ISTOÉ Dinheiro; Gazeta (2010); Agência Brasil (2014)
  15. Economia: Agência Gov (IPCA jul); Poder360 (IPCA-15); Agência Gov (PNAD); CNN (Copom); InfoMoney (Focus); Agência Brasil (PIB 2T); Estadão/DGABC (ICC); Bloomberg Línea; CNN (Datafolha economia)
  16. Mercados e análises: Polymarket; Kalshi; Prediction Hunt; Eurasia 60% (Correio); Eurasia ago (Guia do Investidor); XP Research; Morgan Stanley (Bloomberg Línea); Brasil em Mapas; OddsShopper
  17. Analistas: Lavareda (CNN); Roman/Atlas (Exame); Sanches/Atlas (CartaCapital); Nicolau/Lavareda (CartaCapital); Americas Quarterly; Gazeta (abstenção); Quaest (estadual)
  18. Eventos: Agência Brasil (domiciliar); Agência Brasil (visitas); Jornal Grande Bahia (tarifas); InfoMoney (INSS); Gazeta (convenções); InfoMoney (TV); Poder360 (Kassab/Caiado); Poder360 (Marçal); Gazeta (debates); BBC/O Povo (candidatos)
  19. Conformidade eleitoral: Res. TSE nº 23.747/2026 (registro e divulgação de pesquisas, altera a Res. nº 23.600/2019); Res. TSE nº 23.755/2026 (uso de IA na propaganda eleitoral); TSE (registro obrigatório de pesquisas); TRE-AL (vedação a enquetes informais, 16/ago)
  20. Pesquisas qualitativas e escândalos: MDP/LEMEP-UERJ, Relatório #121 e Relatório #125 (grupos focais contínuos no WhatsApp); percepção de Lula: CartaCapital/Quaest. Banco Master: Wikipédia; ND Mais/PoderData (21–24/jun); CNN Brasil (visão de conjunto). INSS: Wikipédia; Congresso em Foco; Revista Oeste/Quaest (29/mai–1/jun/2025). Dark Horse/Azarão: Agência Pública; Agenda do Poder/Meio-Ideia (28/mai); Brasil 247/Meio-Ideia (4–7/set); Lulinha: DN.pt
  21. Base de pesquisas de 2º turno (reconstrução de 15/set): Wikipédia — Pesquisas de opinião para a eleição presidencial no Brasil em 2026 (link primário de cada onda no Apêndice A); CNT/MDA ago: Gazeta do Povo; CNT/MDA set: CartaCapital; Indexa set: Gazeta do Povo; Datafolha rejeição 8–10/set: Brasil 247
  22. Desfecho da sessão do STF de 15/set (questão de ordem, placar e pedido de vista de Dino): Congresso em Foco. Repercussão na campanha: Revista Oeste (Lula defende Moraes); O Tempo; Band (crise vira munição eleitoral). Pesquisa DataTrends (12–14/set, TSE BR-08691/2026): Revista Oeste
  23. Relatório da PF Moraes–Vorcaro e sessão do STF de 15/set: CNN Brasil; InfoMoney; O Tempo; Correio Braziliense. Inquérito Dark Horse: Rio Times. Lulinha: Poder360. Debate de 14/set cancelado: Revista Fórum
  24. Crise do STF e quebra de sigilo do Master (8–10/set/2026): Times Brasil; Portal 6; Brasil de Fato; N1N; impacto nos mercados: Prediction Hunt, Polymarket
  25. Pesquisas de 2º turno incorporadas em 14/set: BTG/Nexus 11–13/set (TSE BR-04076/2026): Poder360; Palver 4–9/set: JOTA; Veritá 16–20/ago: Itatiaia; Veritá 1–4/set (TSE BR-04006/2026): Revista Oeste