O contexto
A GT League é uma liga de e-soccer da Bet365 com partidas de 12 minutos. O volume é alto e o ciclo é curto: começa, acaba, começa outra. Quem opera nessa liga procura padrão estatístico — sobretudo em torno do primeiro gol, que é o evento que reprecifica o resto da partida.
O trabalho manual é insustentável por definição. São várias partidas simultâneas, cada uma durando pouco mais de dez minutos, e a janela de decisão útil dura segundos. Ninguém acompanha isso em planilha.
O problema
Três exigências que brigam entre si.
Latência: um alerta que chega depois da janela de decisão não vale nada. Não adianta a análise estar certa se chega tarde.
Vazão em rajada: o volume não é constante. Em horário de pico existem muitas partidas simultâneas, e o sistema precisa aguentar o pico sem enfileirar processamento até o alerta perder validade.
Histórico confiável: o ranking de padrões só significa alguma coisa se cada partida foi registrada corretamente. Um evento perdido ou contado duas vezes contamina a estatística que fundamenta a decisão — e o erro é silencioso, porque o número continua aparecendo.
Decisões técnicas
Workers assíncronos em vez de processar o feed na requisição. O consumo do feed roda fora do ciclo web. Se estivesse dentro, uma partida lenta de processar atrasaria a resposta de quem só quer abrir o dashboard, e o pico de jogos derrubaria a interface junto. Separado, o painel continua respondendo mesmo com o processamento sob carga.
Redis para estado quente do jogo, PostgreSQL para o histórico. Estado de partida ao vivo muda a cada segundo e vive doze minutos. Escrever isso em tabela é gerar escrita cara para dado que vai morrer. Redis segura o estado corrente e serve de deduplicação — o feed reemite evento, e sem uma chave de idempotência o mesmo gol entra duas vezes na estatística. O que precisa durar (partida encerrada, resultado, padrão registrado) vai para o PostgreSQL 17, que é onde a consulta analítica do ranking roda.
Inertia.js em vez de API REST com SPA separada. A opção tradicional seria Laravel expondo REST e um React consumindo — o que significa versionar contrato, escrever tipo em dois lugares e manter uma camada de fetch e cache no cliente. Inertia entrega a experiência de SPA sem essa camada: o controller Laravel devolve as props direto para o componente React, e não existe endpoint intermediário para manter em sincronia. Em troca, o frontend fica acoplado a este backend — não serve para alimentar um app de terceiro. Como não existe consumidor externo previsto, é troca boa.
TypeScript no frontend. Dado estatístico com muitos campos numéricos é onde erro silencioso mora. Confundir taxa com contagem não estoura exceção; só produz um número errado com cara de certo.
Telegram como canal de alerta, não notificação própria. Push próprio exigiria app publicado nas lojas, permissão do usuário e infraestrutura de entrega. O público-alvo já vive em grupo de Telegram, e o Bot API entrega em segundos com um fração do esforço. É a decisão de usar o canal onde o usuário já está em vez de construir mais um lugar que ele precisa abrir.
Ranking por expectativa matemática, não por taxa de acerto. Ordenar padrão por percentual de acerto premia padrão que acerta muito e ganha pouco. O ranqueamento considera retorno esperado, que é o número que de fato importa para quem opera.
O que ficou pronto
Sistema em operação: monitoramento ao vivo das partidas, registro de primeiro gol, alertas parametrizados em canais do Telegram, dashboard com filtros por mercado (Over 1.5, Over 2.5, Ambas Marcam), análise por jogador e confronto, ranking de padrões por retorno e gestão de banca. PostgreSQL 17 e Redis em Docker.
O que eu faria diferente hoje
Guardaria o feed bruto de cada partida, além do dado já processado. Hoje, quando quero testar um padrão novo, só tenho o que decidi extrair na época. Com o bruto arquivado, dá para reprocessar o histórico inteiro sob uma regra nova sem esperar meses de jogos novos.
Interface do Sistema

