Blog /
Dos arquivos eleitorais brutos a um modelo dimensional: construindo um pipeline de dados para as eleições de 2026 no Brasil
Em 2024, a cobertura eleitoral em que eu trabalhava dependia de um sistema terceirizado bem caro, que caiu justamente na noite da apuração e quase transformou a cobertura em prejuízo total. Em 2026 a decisão foi outra: em vez de comprar uma solução pronta, eu e outro desenvolvedor construímos o pipeline internamente em cerca de três meses, com seis serviços pequenos e um único agendamento diário.
O primeiro post desta série contou a história: três encontros com os dados eleitorais do Brasil, e na terceira vez coube a mim liderar a construção. Este post é sobre essa construção: o que o TSE (a autoridade eleitoral do Brasil, que conduz toda eleição e publica os dados por trás dela) realmente publica, como esses dados chegam, e como viraram um modelo estruturado que uma API consegue servir.
O que o TSE realmente envia
Em julho de 2026, o TSE fez uma audiência técnica com redações e instituições que pretendem redistribuir os resultados oficiais. Os números apresentados ali dão uma noção do tamanho do sistema com que todo mundo nessa área precisa se integrar.
Quando as urnas fecham às 17h, horário de Brasília, no dia da eleição, um cartão de memória sai de cada urna eletrônica do país e começa uma jornada: pontos de coleta, validação, o data center do TSE, totalização dos votos, publicação. O que chega à rede pública de distribuição cobre todos os níveis de agregação — o país, 27 estados, 5.569 municípios, 6.883 zonas eleitorais, e cada uma das cerca de 464 mil seções eleitorais. Quatro arquivos por seção vêm direto das urnas, sem nenhuma alteração: só aí são cerca de 1,86 milhão de arquivos. Some fotos de candidatos, assinaturas digitais e arquivos de configuração, e o conjunto completo se aproxima de 2,5 milhões de arquivos por ciclo eleitoral — e, a partir deste ciclo, o TSE mantém dois ciclos online ao mesmo tempo.
Consumir esse volume tem regras. Não há como listar o que existe: cada sistema que consome os dados precisa montar o caminho de cada arquivo a partir de uma convenção de nomes. Cada IP tem direito a no máximo 100 requisições por segundo; passe disso e você fica bloqueado por dez minutos. Insista em pedir arquivos que ainda não existem e o bloqueio vem antes. O TSE roda ensaios gerais antes do dia da eleição — totalizações simuladas indo de 0 a 100% — justamente para que o sistema de cada redação possa ser testado antes da noite que importa.
É nesse contexto que este pipeline opera. A parte que ele consome é a que existe muito antes da noite da eleição: os dados de candidaturas — quem está concorrendo, a que cargo, onde, e com quem.
Os arquivos de candidaturas, de perto
O TSE publica os dados de candidaturas num catálogo aberto. Cada recurso é um ZIP contendo um CSV, e um endpoint de metadados informa quando um recurso foi modificado pela última vez. Uma vez por dia, uma tarefa agendada aciona o coletor para conferir essa data: se ela mudou, há dados novos para buscar; se não, nada mais roda. O agendamento decide quando olhar; os dados decidem se alguma coisa acontece.
Os arquivos cobrem os dados principais de registro (uma linha por candidatura), um arquivo complementar, declarações de bens, perfis em redes sociais, e o número de vagas em disputa por cargo e localidade. Comparado ao meu primeiro contato em 2020, quando a documentação se contradizia entre um PDF e um vídeo e nenhum dos dois batia com os arquivos reais, é uma melhora de verdade — o catálogo é consultável e os arquivos são internamente consistentes.
Ainda assim, eles têm suas manias. Uma amostra do que o código precisa tratar:
- Tudo é delimitado por ponto e vírgula, entre aspas duplas, e codificado em latin-1, um padrão de caracteres antigo, e não em UTF-8, que é o padrão atual. Em 2026.
- Os servidores do TSE derrubam a conexão (
socket hang up) se você pedir arquivos rápido demais. É a versão informal dos limites de requisição que a rede de resultados aplica formalmente. O coletor espera 500 ms entre chamadas e, quando uma falha, tenta de novo esperando cada vez mais (backoff exponencial).
E essas são só as duas que valem a pena contar. Atrás delas vem uma lista longa de outras menores: nenhuma difícil, nenhuma interessante, todas obrigatórias. É assim que esse trabalho é de verdade: não um problema difícil, mas uma pilha de problemas triviais, cada um descoberto na prática, e todos resolvidos antes de a primeira linha útil chegar ao banco de dados.
O pipeline, em resumo
Seis serviços, todos containers numa nuvem gerenciada. Em vez de um orquestrador, a ferramenta central que normalmente comanda cada etapa de um pipeline de dados, eles se comunicam por filas de mensagens: um serviço termina seu trabalho, deixa um aviso na fila, e o próximo começa quando o aviso chega.
catálogo de dados abertos do TSE
→ coletor (Node.js, job serverless one-shot, disparado diariamente)
→ object storage (ZIP → CSV) + mensagem na fila
→ processador (Python, orientado a eventos)
→ tabela de staging → modelo dimensional (Postgres)
→ serviço de migrations (dono do schema, roda como job one-shot)
→ API REST (Python, lê o modelo)
→ gateway (proxy fino em Node.js na frente da API)
→ gerador de artigos (consome a API, rascunha conteúdo editorial)O coletor busca os ZIPs e deixa uma mensagem na fila. O processador é acionado pela mensagem, lê os CSVs e carrega o Postgres. O schema, que é a definição das tabelas do banco, fica num repositório separado, que aplica as alterações de estrutura (migrations) como uma tarefa que roda uma vez e termina. Nenhum serviço altera o schema como efeito colateral; um repositório existe só para cuidar dele.
Não há orquestrador em nenhum dos seis repositórios, e existe exatamente um agendamento: o disparo diário que aciona o coletor. Tudo depois dele é orientado a eventos, ou seja, cada serviço reage a uma mensagem, não a um relógio. Para um pipeline cujo gatilho real é “o TSE publicou dados novos”, essa divisão se mostrou a mais fiel à realidade: uma checagem agendada e barata na entrada, e nenhum serviço adiante rodando só porque chegou a hora marcada.
A tabela de staging
O arquivo principal de candidaturas chega primeiro a uma tabela de staging, uma área de preparação que guarda o dado como ele veio: 49 colunas, quase todas texto bruto, de propósito sem chave primária. É uma cópia fiel do CSV, estruturada só o suficiente para ser consultada.
O processamento roda em duas etapas. Primeiro, esvaziar a tabela de staging e carregar nela, em massa, o arquivo de cada estado. Depois, fazer o upsert (inserir ou atualizar num único comando) das tabelas do modelo final a partir do staging, de uma vez só, em SQL. Bruto primeiro, transformação depois: se uma transformação estiver errada, o dado bruto continua ali para ser reprocessado, e a investigação acontece em SQL, não no rastro de erro de um leitor de arquivos.
A parte desse desenho que mais valeu a pena não é a tabela de staging em si, e sim a guarda de schema construída em cima dela: uma verificação que impede o pipeline de escrever no banco quando a estrutura das tabelas não é a que o código espera. A lógica de upsert monta cada lista de colunas a partir dos metadados da tabela, sem nada fixo no código, e valida o schema duas vezes: uma quando o código é carregado, e outra em tempo de execução, contra o information_schema do banco real — antes de qualquer limpeza ou escrita.
Essa guarda foi testada por uma mudança real no meio do projeto. A chave primária da tabela de unidades eleitorais precisou crescer de duas colunas para três, e outra tabela perdeu sua chave substituta na mesma migration. A alteração que adaptou o processador mexeu em três arquivos. E o que importa é o que teria acontecido sem ela: não dados corrompidos em silêncio, mas um erro explícito de schema fora de sincronia que para o pipeline, um crash loop que qualquer um consegue detectar.
É isso que “a tabela de staging isola o pipeline” significa na prática. Não quer dizer que o pipeline não sente as mudanças. Quer dizer que o estrago fica contido e que a falha aparece na hora, em vez de ficar escondida.
Um modelo desenhado a partir de três eleições
Esta é a parte que eu adoraria fazer soar mais científica: o modelo dimensional, a forma como os dados ficam organizados em tabelas para serem consultados, não foi descoberto por um algoritmo. Foi desenhado à mão, num diagrama, antes de a maior parte do código existir.
O que tornou isso possível foi a engenharia reversa acumulada de dois ciclos anteriores. As semanas que passei em 2020 conferindo documentação contraditória contra os arquivos reais — entendendo cada entidade pelo que os dados mostravam, não pelo que a especificação dizia — foram onde o mapa desses dados se formou: quais colunas descrevem um candidato, quais descrevem um partido, quais descrevem a eleição em si, e quais servem, sem avisar, para ligar uma linha a outra.
O melhor exemplo dessa última categoria: o vice. No arquivo plano do TSE não existe uma coluna “vice de”. Um presidente e um vice-presidente são só duas linhas, cada uma com seu próprio ID de candidato. O vínculo só existe se você souber onde procurar: duas linhas compartilhando o mesmo número de urna, eleição e localidade — mas com códigos de cargo diferentes. No código, esse conhecimento é um pequeno mapa:
RUNNING_MATE_MAP = {
PRESIDENT: VICE_PRESIDENT,
GOVERNOR: VICE_GOVERNOR,
MAYOR: VICE_MAYOR,
}Esse mapeamento fica na camada que fala com o banco de dados, com um comentário explicando o porquê: quais cargos têm vice é uma regra de negócio dos dados, não um detalhe da API. Nenhum documento do TSE afirma isso. É o tipo de conhecimento que você só tem depois de já ter errado com esses dados.
O modelo que saiu do diagrama:
- Dimensões, as tabelas que descrevem cada entidade: eleição, partido, cargo, candidato e unidade eleitoral. Todas são identificadas por uma chave composta que inclui o ID da eleição, porque nesses dados quase nada é estável entre eleições. Números de partido são reaproveitados, cargos variam por ciclo. A eleição não é um atributo; é parte de toda chave.
- Tabelas de detalhe para bens, redes sociais e dados complementares, ligadas ao candidato.
- Uma única tabela-ponte, a de vagas em disputa, que liga duas dimensões: cargo e unidade eleitoral.
- E um desacoplamento intencional: uma tabela de anotações editoriais sobre candidatos que não tem chave estrangeira para a dimensão de candidato, ou seja, o banco não exige que o candidato anotado exista naquele momento. A migration documenta o raciocínio: o pipeline de ingestão é assíncrono e os dados de origem podem estar inconsistentes, então o dado editorial precisa sobreviver mesmo quando a linha do candidato está sendo reescrita. Uma relação fraca, escolhida conscientemente.
O primeiro cliente
O principal cliente da API hoje não é uma tela — é outro serviço: o gerador de artigos, que consome o modelo e transforma dados eleitorais em rascunhos de conteúdo editorial, filtrados por cargo, estado ou município. Os dois serviços cresceram em paralelo, e a API evoluiu conforme as necessidades desse cliente. Dá para ver essa relação em como eles se encaixam hoje:
- Para cargos com poucos candidatos, o gerador faz uma chamada extra por candidato para buscar o nome do município e o vice — campos que o endpoint de listagem não expõe. Para cargos de alto volume, como vereador, ele pula esses campos em vez de fazer milhares de chamadas.
- Sua política de novas tentativas tem uma exceção intencional: uma resposta de “não encontrado” nunca é tentada de novo. O TSE aplica a mesma lição em escala — na rede de resultados, clientes que insistem em pedir arquivos que não existem são bloqueados antes mesmo de bater no limite de requisições.
- Os totais nacionais de vagas são somados do lado do cliente; a API os serve por estado.
Cada um desses pontos de encaixe está documentado no repositório do próprio cliente, ao lado da solução de contorno correspondente. Esse é o acordo de trabalho entre os dois serviços: a API cobre o caso comum, o cliente cobre seus próprios casos particulares, e cada lacuna é um commit que dá para consultar, em vez de uma reunião que ninguém registrou.
O acompanhamento desse acordo em produção segue a mesma filosofia do resto da stack: os dashboards ficam no monitoramento gerenciado da plataforma de nuvem, fora dos repositórios, observando os serviços de fora. O código em si só expõe endpoints de saúde e status — incluindo estatísticas ao vivo do pool de conexões com o banco, que se mostraram necessárias durante os testes de carga.
O dia da apuração: 135 matérias que se atualizam sozinhas
No dia da apuração, o modelo descrito acima foi usado para o que ele foi feito: publicar a cobertura dos 26 estados + DF e mantê-la atualizada sem intervenção manual.
Foram 135 matérias automáticas. Para cada estado e para o Distrito Federal, uma matéria por cargo (governador, senador, deputado federal e deputado estadual, que no DF é o deputado distrital) e uma matéria geral, com todos os cargos, atualizada conforme os resultados iam se fechando. Cinco matérias por unidade, 26 estados + DF.
Cada matéria passava por um fluxo de versões. Às 8h configurei o sistema para publicar a primeira versão das 135, já com as informações dos candidatos em disputa, entre outros dados. Naquele momento nenhum voto tinha sido contado: a matéria existia para ser atualizada depois.
A apuração começou às 17h, e o nosso sistema recebeu os primeiros eventos de resultado às 18h30, quando um candidato foi matematicamente eleito, isto é, quando os votos que faltavam já não podiam mudar o resultado. A partir daí funcionou assim: quando um resultado se define, a matéria daquele estado e daquele cargo é atualizada, e a matéria geral daquele estado também. Ninguém dispara isso manualmente; cada atualização é a reação a um evento, o mesmo princípio do resto do pipeline.
Em paralelo, tínhamos projetado uma tela especial de apuração, desenvolvida junto com a equipe de frontend, e ela aguentou bem o tráfego. Isso não foi sorte. Nos testes de carga com o JMeter, preparamos a infraestrutura para um cenário extremo, de 1K a 3K requisições por segundo, que era o pico de tráfego esperado pela equipe de jornalismo.
O que vem a seguir
O modelo descrito aqui é a fundação, mas uma fundação se justifica pelo que ela sustenta. O próximo post traz os resultados: o que essa cobertura automática entregou na prática. E traz também as críticas que levantei ao projeto depois de refletir sobre ele.