O World Cup Analytics é um produto analítico que construí para acompanhar a Copa do Mundo 2026 com dados de partidas, seleções e jogadores: placares, xG, finalizações, rankings, perfis individuais, mapas de chute e análises de confrontos.
Este artigo é sobre a construção do projeto: de onde vieram os dados, quais decisões de arquitetura fizeram diferença e quais problemas apareceram no caminho. Mais do que uma interface com estatísticas, a ideia foi construir um produto de dados completo, da ingestão até a visualização final.
A ideia
Eu queria construir um projeto real de analytics engineering: algo com dados chegando continuamente, necessidade de modelagem, atualização automática, decisões de performance e visualizações úteis para o usuário final.
A Copa do Mundo em andamento era o cenário perfeito para isso. Os dados chegam aos poucos, cada rodada cria novas perguntas, e o produto precisa se adaptar ao estado atual da competição: fase de grupos, melhores terceiros, mata-mata, perfis de jogador, rankings e análise de partidas.
Também era um bom contexto para testar algo que eu considero essencial em projetos de dados: transformar informação bruta em leitura. Mesmo tendo números brutos, o produto precisa ajudar a responder perguntas como:
- quem está jogando melhor?
- quais seleções estão criando mais?
- quais jogadores estão acima do esperado?
- como uma partida foi decidida?
- que tipo de dado realmente ajuda a explicar futebol?
De onde vieram os dados
O primeiro desafio foi encontrar uma fonte estruturada e confiável de futebol. Eu precisava de informações como escalações, estatísticas por jogador, gols, eventos da partida, xG, coordenadas de finalizações e dados por seleção.
Alguns sites tinham parte dessas informações disponíveis, mas rate limits e instabilidade tornavam o processo pouco confiável para um produto em atualização constante. A alternativa foi buscar uma API especializada.
Depois de avaliar opções, escolhi a TheStatsAPI. Ela cobre a Copa 2026 partida a partida, com dados de escalação, estatísticas de jogadores, mapa de finalizações com coordenadas no campo e timeline de eventos.
Também considerei a StatsBomb, uma fonte gratuita e muito conhecida na área de dados de futebol. O acervo público é rico, mas o pacote gratuito não cobre competições em andamento. Por isso, não era uma opção para este momento. Quando esses dados estiverem disponíveis, pretendo considerar essa fonte em uma revisão futura do projeto.
O que precisei curar manualmente
Mesmo com uma API principal, nem tudo que eu precisava existia pronto. Duas partes importantes do site exigiram curadoria manual.
A primeira foi a cor dos uniformes de cada partida. A FIFA define o uniforme de cada seleção jogo a jogo para evitar conflitos de cor em campo. Em alguns casos, isso força até duas equipes a usarem combinações alternativas. Esses dados aparecem em PDFs oficiais da FIFA, com informações de uniformes de linha e goleiros por partida.
Como essa informação não vinha estruturada na API, organizei manualmente as cores por fase e transformei em um formato que o site consegue consumir. A ideia foi permitir que cada partida refletisse melhor a identidade visual real daquele confronto, em vez de usar sempre a cor padrão de cada seleção.
A segunda parte foi o número da camisa dos jogadores. A API trazia, em muitos casos, o número usado no clube, não o número usado pela seleção na Copa. Em mais de 900 dos 1.248 jogadores da edição, o número exibido estava incorreto. Em algumas escalações da primeira versão, jogadores da mesma seleção apareciam com números repetidos.
Para corrigir isso, usei listas públicas de convocados como referência e conferi jogador por jogador. Foi um processo pouco glamouroso, mas necessário. Esse tipo de detalhe parece pequeno até aparecer errado na tela.
A lição aqui é simples: raramente existe uma fonte única e perfeita. Em projetos reais, o mais comum é ter uma fonte principal, algumas fontes auxiliares e uma camada de curadoria para ajustar aquilo que ninguém organizou exatamente do jeito que o produto precisa.
A arquitetura: dado bruto guardado, dado pronto servido
A primeira versão do projeto lia a API e calculava tudo na hora em que alguém acessava uma página: percentis, rankings, comparações, resumos de seleção, estatísticas de jogadores e dados agregados da competição.
Funcionava no começo. Mas, com a Copa em andamento e dezenas de partidas já disputadas, cada carregamento da Home passava a varrer centenas de arquivos e refazer cálculos do zero. Algumas páginas começaram a levar segundos para carregar.
A decisão de arquitetura que resolveu esse problema foi separar o projeto em duas camadas principais.
Camada bruta
A camada bruta salva cada resposta da API exatamente como ela veio, sem transformação. Cada partida possui seus próprios arquivos de escalação, estatísticas, eventos e finalizações.
Esse processo é idempotente: se uma partida já foi buscada, ela não precisa ser buscada de novo. Isso importa porque a API tem limite de requisições por minuto. Reprocessar a Copa inteira do zero seria lento e desperdiçaria chamadas; retomar uma busca interrompida precisa ser rápido.
A camada bruta nunca é sobrescrita. Ela funciona como a fonte de verdade do projeto. Se alguma métrica estiver errada, eu posso corrigir o cálculo e reconstruir tudo a partir dos dados originais.
Camada pronta
A camada pronta lê a camada bruta, calcula o que as telas precisam e grava o resultado em banco já no formato de consumo da aplicação.
Isso inclui:
- rankings de jogadores;
- rankings de seleções;
- dados de partidas;
- radares;
- comparações;
- perfis individuais;
- resumos da competição;
- dados agregados por fase;
- estatísticas de finalização;
- métricas derivadas.
Com isso, quando alguém acessa o site, o servidor não precisa recalcular a competição inteira. Ele apenas busca dados já processados.
Essa mudança derrubou o tempo de carregamento de páginas de alguns segundos para dezenas de milissegundos.
Memória também é arquitetura
Uma das limitações mais concretas apareceu na hora de recalcular a camada pronta. O projeto roda em uma máquina pequena, e o processo de reconstrução chegou a travar por falta de memória. O problema não estava em uma fórmula específica, mas no volume de dados sendo inserido de uma vez. Em alguns momentos, o banco saltava de pouca memória usada para mais de 1 GB durante uma única etapa de carga.
A solução foi simples, mas importante: quebrar as inserções em lotes menores e liberar memória assim que cada parte terminava. O pico de uso caiu drasticamente. Foi um lembrete prático de que arquitetura também é decidir quanto dado entra em memória, quando limpar objetos, quando dividir processamento e como evitar que uma tarefa pequena derrube a máquina inteira.
Mudar cálculo não deveria exigir migração arriscada
Outra decisão importante foi separar bem cálculo de estrutura.
Como a camada pronta é reconstruída a partir da camada bruta, corrigir uma fórmula ou ajustar uma métrica quase sempre significa rodar o processo de novo. Não precisa editar dados manualmente no banco, nem fazer migração arriscada para corrigir resultado derivado. Mudanças estruturais de banco ainda existem, claro, mas são mais raras. Quando acontecem, ficam documentadas e versionadas separadamente.
Esse modelo deixou o projeto mais fácil de corrigir, especialmente porque, durante a construção, várias métricas precisaram ser revistas.
Como o site se atualiza sozinho
Durante a competição, um processo automático roda a cada 15 minutos. Ele confere o calendário, identifica partidas encerradas que ainda não tiveram os detalhes salvos, busca os dados faltantes respeitando o limite de requisições da API e reconstrói a camada pronta.
Também existe uma trava simples para impedir que duas execuções rodem ao mesmo tempo. Se uma atualização ainda estiver acontecendo, a próxima espera ou não executa. Na prática, quando um jogo termina, o site tende a refletir o resultado, os destaques da partida e o impacto nas tabelas em até meia hora, sem intervenção manual.
Problemas reais nos dados
Mesmo usando uma fonte profissional, os dados apresentaram problemas. A maioria deles não aparece apenas olhando o código. Eles só ficam claros quando você conhece o domínio e compara o que a tela mostra com o que realmente aconteceu no jogo.
Prorrogação sendo mostrada como pênaltis
O jogo entre Argentina e Cabo Verde, vencido por 3–2 pela Argentina sem disputa de pênaltis, chegou a aparecer no site como “1–1, Argentina venceu nos pênaltis por 3–2”. A fonte separa o placar em partes diferentes, como tempo normal, prorrogação e pênaltis. Uma leitura apressada de um campo com nome enganoso misturou essas informações. A correção exigiu tratar prorrogação e disputa de pênaltis como estados diferentes, não apenas como extensões genéricas do placar.
Gol contra creditado ao time errado
A timeline da fonte creditava todo gol ao time do autor do evento, inclusive gols contra. Isso fazia um gol contra aparecer como se tivesse sido marcado a favor do time errado. A informação correta estava escondida em outro endpoint: o mapa de finalizações. Foi necessário cruzar timeline e shot map para identificar quando um gol era contra e atribuir o placar ao lado certo. Pelo menos eu escapei de mostrar quem fez gol contra na lista de artilheiros…
Números de camisa de clube, não de seleção
Como mencionei antes, mais de 900 jogadores vieram com números de camisa incorretos. A API mostrava, em muitos casos, o número do clube, não o número usado pela seleção na Copa. Isso gerava escalações visualmente erradas e até jogadores com camisa repetida dentro do mesmo time. A solução foi uma camada de correção baseada nas listas públicas de convocados.
Mapa de chutes espelhado
O mapa de finalizações apareceu espelhado verticalmente em relação à transmissão real da partida. Só ficou óbvio depois de comparar a visualização com o vídeo do jogo. A coordenada em si parecia plausível, mas o desenho não batia com a realidade. A correção exigiu ajustar a interpretação das coordenadas antes de renderizar o campo.
xG inflado em disputas de pênaltis
A fonte atribuía um valor fixo e alto de xG para cada cobrança em disputa de pênaltis. Isso fazia um jogo 0–0 decidido nos pênaltis aparecer com um total de xG absurdo, como se tivesse sido uma partida cheia de grandes chances. Foi necessário separar pênaltis da disputa final das finalizações de jogo. Cobranças de disputa podem ser analisadas em um módulo próprio, mas não devem inflar as estatísticas normais de xG da partida.
Stack e infraestrutura
O projeto foi pensado para ser simples de operar, mas com separação clara entre dados, API e interface.
A estrutura atual envolve:
- uma aplicação web pública para navegação e visualização;
- uma API somente leitura;
- um banco de dados em rede fechada;
- processos automáticos de atualização;
- camada bruta de dados preservada;
- camada pronta recalculável;
- backup diário do banco;
- proxy cuidando de HTTPS.
A infraestrutura roda em uma máquina pequena na nuvem. O banco não possui porta exposta para a internet. A área pública é apenas de leitura. Não existe tela administrativa exposta.
O objetivo era montar uma base suficiente para hospedar um produto real com segurança adequada ao escopo, atualização automática e possibilidade de reconstrução dos dados, sem criar complexidade desnecessária.
Como usei IA durante o desenvolvimento
Usei assistentes de IA como ferramenta de desenvolvimento durante a construção. Eles ajudaram a acelerar partes do processo: gerar componentes, sugerir estruturas, revisar textos, propor correções e transformar ideias em código inicial. No entanto, isso não substituiu o trabalho principal do projeto. Meu papel foi definir o produto, quebrar os problemas, revisar implementações, validar os dados, ajustar a arquitetura e corrigir tudo que não batia com a realidade do futebol.
Esse ponto ficou claro várias vezes. A IA podia gerar uma tela tecnicamente funcional, mas não sabia, sozinha, que um gol contra estava indo para o lado errado, que um mapa de chutes estava espelhado ou que um goleiro derrotado não deveria ser automaticamente tratado como melhor jogador da partida por causa de uma métrica mal ponderada. Ferramentas ajudam muito. Mas, em um projeto de dados, o valor continua vindo de saber o que perguntar, o que desconfiar e como validar o resultado.
O que ficou de aprendizado
A principal lição do projeto foi simples: nenhuma fonte de dados é 100% confiável de primeira.
Mesmo usando uma API profissional, os erros mais importantes só apareceram quando comparei a tela com o que realmente aconteceu nas partidas. Engenharia de dados também é entender o domínio o suficiente para desconfiar do dado, validar a transformação e entregar uma leitura que faça sentido. Não é apenas mover dado de um lado para o outro. Visão crítica é essencial.
Outra lição importante foi que performance precisa entrar cedo na arquitetura. Calcular tudo em tempo real parecia aceitável quando havia poucas partidas. Mas, com a competição avançando, ficou claro que o produto precisava servir dados já preparados.
Por fim, o projeto reforçou algo que considero central em analytics: números não falam sozinhos. A interface precisa transformar estatística em leitura.
O produto está no ar em worldcup.jvmello.dev.
Dúvidas, ideias ou vontade de trocar uma ideia sobre dados de futebol? [email protected].