# RoboBET Antigo — dados e API para IAs > pt-PT primeiro; resumo em inglês no fim. Site: https://robobet-antigo-viewer.pages.dev > Visualizador humano: `/` · Esta página: `/ai.md` (igual a `/llms.txt`) · Manifesto de dados: `/data/index.json` (exige chave de acesso — ver secção 4) ## 1. O que é Dados (só leitura) da app **RoboBET antiga** (`br.com.robobet` 2.1.3), API `https://app.alefut.com/api`. Há três tipos de estratégias: - **Bots da app** (“Bots”, ecrã principal da app; 2 bots: “Monstro 2⛳1⛳” e “📉 [6.5.2026]”): bots do utilizador com **odds reais** por entrada — aqui mostra-se o **ROI tal como a app o calcula** (rótulo “ROI (da app)”). - **Bots legado** (“funnels” do utilizador, 18): configuração completa disponível em `/data/` (com chave) (condições de entrada ao vivo, minutos, mercado, cenários). - **Lucy**: funis/sinais geridos pela plataforma (ids conhecidos 71, 7403, 7405, 7406; outros podem aparecer, ex. 26672 “010 LUCY | AMBAS ou OVER 1.5”). A configuração interna da Lucy **não é exposta**; o perfil (minuto de entrada, stats típicos) é inferido das entradas. Regras fixas destes dados: - O bot **Lutero** está **excluído** de tudo (e `/bot/market`, que lista bots Lutero, nunca é usado). - **Bots legado e Lucy: nunca ROI.** As entradas são linhas asiáticas/“limite” sem odd fixa por entrada; só se publica **taxa de acerto (hit rate)** com amostra. Exceção: **bots da app**, que têm odd por entrada → “ROI (da app)” = soma das unidades (stake 1) × 100, exatamente como a app mostra (ex.: últimos 30 dias). - Sem credenciais, tokens, emails nem ids de utilizador nos dados publicados. ## 2. Definições - **Entrada**: um sinal do bot num jogo. Resultado `green` (acerto) ou `red` (falha). - **Hit rate** = `100 × green ÷ n`, com `n = green + red`. Apresente sempre `n`; amostras pequenas (n < 20–30) são pouco fiáveis. - **⚠ Void nos bots legado (`void_conta_como_green: true`)**: os bots legado **não reconhecem Void** — uma aposta devolvida (push) é registada como `green`. O hit rate dos legados está, por isso, **inflacionado**, sobretudo nas entradas de linha asiática. Trate-o como limite superior; não o compare diretamente com o acerto G/(G+R) dos bots da app. Campo explícito em `/data/bots_legado.json` (raiz e `hit` de cada bot), em `/data/index.json` (`avisos`) e em `desempenho.void_conta_como_green` do esquema normalizado. Nota: o Pedro vai passar os legados de linha asiática para linha **limite** (só Green ou Red); quando isso acontecer o efeito desaparece para as entradas novas. - **Janela anual**: `/lucy/reports` devolve uma linha por entrada do período “Dados do Ano” (sem paginação, sem datas). - **Lifetime Lucy**: só existe o campo `history` em cada entrada recente (`percent_green`, `total_green`, `total_matches`). Varia de jogo para jogo (aparenta ser o histórico de longo prazo por liga/contexto), **não** é um total único do funil. Para bots legado vem `-1` (indisponível). - **Minuto de entrada**: `events.timer` da entrada. - **Horas**: `created` = `"HH:MM - DD/MM/AAAA"` em **hora de Brasília (UTC−3)**. ## 3. Configuração dos bots legado (campos) `/funnel/show` devolve cada bot com ~200 campos. Rótulos pt-PT completos em `/data/campos.json`. Principais: | Campo | Significado | |---|---| | `strategy_name` | nome do bot | | `time_interval_ini` / `time_interval_max` | janela de minutos para entrar | | `timer` | período: 1 = 1.ª parte (HT), 2 = 2.ª parte (FT) (inferido) | | `market` | mercado: 1 = cantos, 0 = golos (inferido) | | `home_X` / `away_X` | condição X por equipa. São **espelhadas** (iguais) quase sempre; em `bots_legado.json` cada uma aparece uma vez com `diferente_casa_fora`. **Semântica (inferida, não confirmada pela app): uma condição espelhada vale para UMA das equipas — normalmente a que pressiona — e não para as duas ao mesmo tempo** (ex.: o funil 71 pede RT ≥ 60, e a posse não pode ser ≥ 60% nas duas). Campo para IAs: `condicoes_por_equipa_semantica: "uma_das_equipas (inferido: RT/posse não pode ser ≥ limiar nas duas)"` | | `*_superiority` | superioridade (%) mínima | | `*_appm`, `*_appm5`, `*_appm10` | ataques perigosos por minuto (total / últimos 5 / 10 min) mínimos | | `*_pi` | indicador de pressão (gráfico de pressão) mínimo | | `*_cg`, `*_cg5`, `*_cg10` | chances de golo mínimas | | `*_total_on_target_shots*` | remates à baliza mínimos | | `*_total_corners` | cantos mínimos | | `*_rt` | posse de bola (%) mínima | | `*_card_red` | filtro de cartão vermelho (1/0) | | `overall_*` | mesmas métricas para o jogo inteiro (soma das equipas); `overall_avg_*` = médias pré-jogo | | `super_favorite_losing`, `favorite_losing`, `favorite_losing_draw`, `team_losing`, `tied_game`, `goalless_draw`, … | cenários de placar permitidos (1 = sim) | | `pre_corner_after_N`, `over_*`, `both_score*`, `cantos_A_B` | filtros estatísticos pré-jogo (%) | | `param_leagues` / `leagues` | **1 = a lista `leagues` são ligas EXCLUÍDAS** (confirmado pelo Pedro); 0 = todas as ligas (a lista, se existir, é ignorada). Nos dados publicados: `filtro_ligas_modo` (`exclusao` \| `todas`) e `ligas_excluidas: [{id, nome}]` (nome `id (nome desconhecido)` se não mapeado) | | `event_reset`, `timer`, `leaderbooards`, `overall_goal_difference`, `*_active` | rótulos da app com **significado por confirmar** (ver glossário abaixo; marcados com * nos rótulos) | `null` = condição não usada. Em `bots_legado.json`, `config` traz os campos brutos (sem `user_id`; `leagues` substituído por `n_ligas`). ## 3b. Bots da app (`/bot/show`) - Config: `start`/`end` (janela de minutos), `market` (4 = escanteios asiáticos, linha limite +0.5/+1 sobre o total atual — inferido), `line_odd` (odd alvo da linha; `line_option` 3 = linha com odd mais próxima — inferido), `stake`, `scenarios[]` (`target`/`condition`/`value`: códigos numéricos; por analogia com o bot legado de origem 2:1–2:3 = APPM, 2:4–2:6 = CG, 5:10 = cantos ≥ — confiança média), `conditions[]` (filtros adicionais, não decodificados), `bw_type`/`bw_list` (tipo 1 = lista de **exclusão** de ligas — inferido porque quase nenhuma entrada cai nessas ligas). - Entradas (`/bot/showByBotEntries?bot_id=ID`, máx. 1000 mais recentes): `date_entry` (hora de Brasília), `league_name`, `team_home`, `team_away`, `minute`, `scoreboard`, `corners`, `handicap` (linha), `line_odd` (odd), `fav`/`zebra`, `status` (1 = gain, 2 = push, 5 = loss), `roi` (unidades: odd−1, 0 ou −1). - `/bot/entriesDetails?bot_id=ID`: `roi` (série acumulada) e `league[]` (`qt`, `roi` por liga). - Números do ecrã da app = janela “últimos 30 dias”: TIPS = nº de entradas, ODD MÉDIA = média de `line_odd`, ROI = soma de `roi` × 100, gain/push/loss = contagens de `status`. - **Não usar `/bot/market`** (lista bots de terceiros, incluindo Lutero). - **Monstro 2 (id 94114) — ROI real.** É o único bot com odd mínima configurada, por isso o ROI é real e pode ser usado por liga. Estados: `status` 1 = **Green** (lucro = odd − 1), 5 = **Red** (−1), 2 = **Void** (0); stake 1. **Acerto = G/(G+R)** (Void excluído); **ROI = lucro/(G+R+V)**; odd média sobre G+R+V. Fonte das tips: `/bot/showByBotEntries?bot_id=94114` (só as **1000 mais recentes**, sem paginação — testados page/offset/limit/datas). A recolha diária acumula-as em `/data/monstro2_entradas.jsonl` (sem duplicados; estados atualizados). Comparação: `/bot/entriesDetails` dá o total por liga da app em todo o histórico (`qt`, `roi` em unidades); a app mostra o ROI como soma das unidades × 100 (ex.: 2.85 u → “285%”), enquanto `roi_pct` aqui é lucro/tips. Resultado em `/data/monstro2_ligas.json`; ligas com menos de 10 tips têm `amostra_pequena: true`. ## 3c. Esquema normalizado (qualquer bot) — `robobet-antigo/bot@1` `/data/bots/.json` (e `todos_bots.json`): `{id, nome, tipo: bot_app|bot_legado|lucy, mercado, periodo, janela_minutos{de,ate}, descricao_natural, condicoes[{campo, rotulo, valor, texto, unidade, aplica_a, diferente_casa_fora, confianca, base}], cenarios[], espelhado_casa_fora, filtro_ligas, desempenho, acerto_por_liga[{liga, n, greens, reds, acerto_pct, push?, roi_app_unidades?}], entradas_recentes[], configuracao_reconstruida? (lucy)}`. O mesmo JSON é gerado pelo botão “Copiar JSON” do visualizador. **Lucy — configuração reconstruída:** regras inferidas das estatísticas no momento da entrada (minuto, placar, limiares = percentil 10 observado de ataques perigosos, remates, cantos, posse…), cada uma com `confianca` (alta/média/baixa/muito baixa) e `base` (mín/p10/mediana/p90/máx). É uma inferência, não a configuração real. A API não tem nenhum endpoint de leitura da config exata da Lucy: a app só mostra um código `BOOT_` para colar no bot de Telegram @Alefut_bot, que **cria uma cópia** do funil na conta do utilizador (aí passa a aparecer em `/funnel/show` com a config completa). Não usar esse fluxo sem autorização explícita do Pedro. **Configurações oficiais já copiadas pelo Pedro** ficam guardadas de forma permanente (mesmo depois de o bot ser apagado da conta) em `/data/lucy_boot.json` e aparecem nos funis Lucy como `configuracao_oficial` (fonte «copiada via BOOT_», confiança 100%); nesses funis, `condicoes` = config. oficial e `configuracao_reconstruida` fica só como verificação cruzada. Atualmente: **os 5 funis (71, 7403, 7405, 7406, 26672) têm config. oficial**. Funis **não espelhados** entre casa e fora: 7406 (condições extra **só na casa**: `home_cg`=2, `home_win_ini`=60, `home_active`) e 26672 (`appm5` casa 0.8 vs fora 1 → **fora mais exigente**) — campo `nao_espelhado[].lado`. Nos restantes campos (e nos funis espelhados) as condições por equipa valem para **uma das equipas** (a que pressiona; inferido). O 7406 tem 25 ligas excluídas. Comparação lado a lado: `/data/lucy_configs_oficiais.xlsx` e `comparacao` em `/data/lucy_config_reconstruida.json`. Os bots legado 98924–98928 eram cópias temporárias criadas pelos códigos BOOT_ e **não** contam como bots legado do Pedro (excluídos de `bots_legado.json` e das estatísticas); a config vem sempre de `lucy_boot.json`. **Glossário — significado por confirmar** (rótulo da app → hipótese): - `home_rt`/`away_rt` «RT» → posse de bola da equipa em % (confirmado pelo Pedro). - `home_pi`/`away_pi` «PI» → indicador de pressão: a informação do gráfico de pressão da app (confirmado pelo Pedro). - `*_superiority` «Superioridade» → índice de superioridade fornecido pela RoboBET: pressão ofensiva de uma equipa sobre a outra (%), equivalente ao da nova versão; fórmula só conhecida pelo criador (confirmado pelo Pedro). - `event_reset` (0/1/2) → reinício/repetição do sinal no mesmo jogo. - `timer` → 1 = HT, 2 = FT; 3 = provavelmente jogo todo (26672 entra 18–70′). - `leaderbooards` (sic; [1, 2] no 26672) → posições/classificação. - `overall_goal_difference` (1) → provável diferença máxima de golos. - `*_active` → equipa cujas condições são avaliadas (7406: só a casa). ## 3d. Acerto do bot na liga de cada entrada (bot×liga) - As entradas com estatísticas (`/funnel/details`, `/funnel/detailsLucy`) **não trazem a liga**. A liga é resolvida por `match_id` (jogos do dia `GET /matches/showMatchesList2`, feed público de jogos ao vivo `wss://app.robobet.app/wss/`, tips do Monstro 2, `GET /matches/showDetail?id=` → `league_id`, só jogos recentes) ou, em falta, resolvida por **um único resolvedor** (igual nos dados publicados — `jogos_ligas.py`/`build_data.py` — e no visualizador — `resolverLiga`), por esta ordem: 1) fontes por jogo em conflito + pesquisa web conclusiva → `web`; 2) `jogo` (fonte por jogo, datada: exato); 3) `web` — pesquisa na web « vs » só para casos ambíguos (sem fonte por jogo e apenas inferência pelas equipas, ou fontes em conflito), porque uma equipa joga várias competições (ex.: MLS vs Leagues Cup); conta as ligas candidatas (ligas das equipas + anual do bot) nos resultados que citam as duas equipas, decide só com vantagem clara (≥ 1 menção do nome completo, pontuação ≥ 2 e ≥ 2× a 2.ª; empate no topo desempata pela inferida); cache por match_id em `historico/jogos_ligas.json['web']` (resultados brutos em `historico/ligas_web_resultados.json`), máx. 8 pesquisas por recolha; 4) `equipas` (inferido, ~); 5) `equipa` (inferido, menos fiável, ~). A inferência pelas equipas **nunca** se sobrepõe a uma fonte por jogo; desempate determinístico (maior contagem, depois nome). No ecrã: `~` = inferida, `🔎` = pesquisa web. - **% atual** por bot×liga = anual da API (`/lucy/reports`, recolhido em `anual_recolhido_em`) + entradas datadas e resolvidas criadas depois (histórico acumulado `entradas_historico.jsonl` + novas; sem duplicar pela chave `funnel_id|match_id|created`). Recalculada em cada recolha diária/publicação; no modo ao vivo o visualizador junta no browser as entradas ao vivo ainda não contadas. - **% antes desta entrada** (mostrada em cada linha do separador “Entradas ao vivo”, com n) = % atual − entradas datadas da mesma liga criadas a partir dessa entrada (incluindo a própria, se resolvida). A API anual não tem datas, por isso é uma reconstrução: exata se o histórico acumulado tiver todas as entradas posteriores. Sem data → mostra-se a % atual (`modo: "atual"`). - Cada entrada publicada traz `_liga`, `_liga_id`, `_liga_metodo` e `_acerto_liga {modo: antes|atual|sem_liga, green, n, pct, amostra_pequena (n<10), atual{green,n,pct}, base_anual{green,n}, novas_depois_do_anual, entradas_posteriores_descontadas, anual_recolhido_em}`. - **Bots legado**: `_acerto_liga.void_conta_como_green: true`, `roi_disponivel: false`, `ressalvas: ["pode incluir Void contado como Green", "sem ROI: não há odd mínima de entrada"]`. - Tabela completa: `/data/acerto_bot_liga.json|csv` (`por_bot_liga: [{tipo, bot_id, bot, liga_id, liga, green, red, n, acerto_pct, amostra_pequena, base_anual_n, novas_depois_do_anual, void_conta_como_green?, roi_disponivel?, ressalvas?}]`, `anual_recolhido_em`, `atualizado_em`). Mapa usado: `/data/jogos_ligas.json` (`precedencia`, `jogos: {match_id: [league_id, liga]}`, `web: {match_id: [league_id, liga]}` (só pesquisas conclusivas), `conflitos: {match_id: [[fonte, liga]…]}`, `equipas: {id_emblema: [[liga, league_id, vezes]]}` ordenado por vezes desc. e nome). ## 3e. Separador Filtros (jogos ao vivo, gráficos e filtros do Pedro) - **Jogos ao vivo + gráficos**: a app recebe os jogos ao vivo por um feed WebSocket público `wss://app.robobet.app/wss/` (sem login; cada mensagem = `{result:[jogo…]}`, a cada poucos segundos). Cada jogo: `id, lid, lg (liga), mt (início, epoch), ss (placar), timer{tm minuto}, odd, stats{goals, attacks, dangerous_attacks, appm, possession_rt, on_target, off_target, corner, xg, cartões: [casa, fora]}, pressure{last10/last5/last3/last1 (ATPM: app, appm, kicks, kicks_target, corners, goals, exg, exc, temperature por equipa), pressureIndex{home[],away[]} (por minuto; fora negativo), pi2{home[],away[]} (gráfico de pressão por minuto), appm[{time, homePressure, awayPressure}], events[{minute, team 0 casa|1 fora, event, qty, count}]}, az{h,a,r} (análise pré-jogo), prognosis`. O visualizador liga-se diretamente ao feed (ao vivo, em qualquer modo); o instantâneo publicado é `/data/filtros_ao_vivo.json` (uma mensagem capturada na recolha). - **Jogos do dia**: `GET /matches/showMatchesList2` (lista do dia com odds e análise pré-jogo `az`; `mt` em hora de Brasília). Publicado resumido em `/data/jogos_do_dia.json`. - **Filtros do Pedro** (“os meus filtros”): `GET /matches/showFilterV2` → 10 filtros. Brutos (sem user_id) em `/data/filtros.json`; normalizados em `/data/filtros_normalizados.json` (esquema `robobet-antigo/filtro@1`) e `/data/filtros.xlsx`. - Campos: `start/end` (janela de minutos), `prelive`, `conditions[{type, target, modality, condition, consider, analysis, value}]` (pré-jogo), `scenarios[{target, condition, consider, value}]` (ao vivo), `bw_list/bw_type` (ligas), `leaderboards`. - Legenda **inferida** dos nomes dados pelo Pedro (não confirmada pela app): `target` 0 casa · 1 fora · 2 jogo (casa e fora) · 5 por confirmar; `modality` 0 FT · 1 HT; `type` 1 = % de jogos com Over (linha em `condition`: 0 = 0.5, 1 = 1.5, 2 = 2.5) · 4 = % vitórias · 5 = % ambas marcam · 0 = média de golos (baixa confiança) · 16 = odd pré-jogo (baixa) · 11/13/19 por confirmar; `consider` 0 = ≥ · 1 = ≤. Cenários: `target 2` usa os códigos dos bots da app (1 APPM, 2 APPM 5 min, 3 APPM 10 min, 4 CG, 5 CG 10 min, 6 CG 5 min); `target 3` = cenário de placar (código); restantes por confirmar. - A API **não devolve estatísticas/acerto** dos filtros (`estatisticas: null`). O backtest da app (`/auth/backtest`, `/lucy/backTestV2`) não foi usado. - Endpoints `/matches/createFilterV2` e `/matches/deleteFilterV2` existem mas são de escrita: nunca usados. ## 3f. Sinais nos jogos ao vivo (Filtros → Jogos ao vivo) — apoio à decisão, não garantia Calculados no browser a partir do feed público; mapas, parâmetros e fórmulas em `/data/sinais_ao_vivo.json` (com a avaliação do instantâneo `filtros_ao_vivo.json`); ícones editáveis em `/filtros_icones.json` (público, sem chave: `{"filtros":{id:{nome,icone,sentido,definido_por}}}`). - **Ícones dos filtros do Pedro**: 9726 Casa Ganha75 = `🏠V1ft`, 10100 %ht80% = `0,8HT`, 9727 Golo HT75 = `7️⃣5️⃣%1️⃣T` (definidos pelo Pedro); 4648 HT2GOLOS 75 = `⚽⚽1️⃣T75%`, 4649 Filtro simples = `🏠≤1.75🔥`, 4811 golo HT 80% = `⚽1️⃣T80%`, 5095 pre jogo Over HT = `📋O0.5HT`, 5406 Gol --> = `⚽➡️🔥`, 8411 Pré AmbasMarcam75 = `🤝BTTS75%`, 10919 gol HT = `⚽1️⃣T🔥0.6` (criados pelo assistente). Cumprido = janela de minuto + todas as condições avaliáveis (pré-jogo via `az`, cenários ao vivo via stats/pressure); quase (esbatido) = falha exatamente 1; `?` = há códigos por confirmar (não avaliados). - **Métricas pré-jogo que o feed tem** (`az.h`/`az.a`/`az.r`): médias de golos (ga, gaht, gaft), médias de cantos (ca, caht, caft), % ambas marcam (bs, bsht), % Over 0.5–4.5 FT/HT (go05…go45, …h), % vitórias (wap, waht), % canto/golo após minuto (caft75/82/88, caht35/38, goal_after_35/80), `r.probability`, odds 1X2 pré-jogo (`odd`). **Não tem** pressão/APPM pré-jogo, xG pré-jogo nem remates esperados → não há “mais pressão do que o previsto”; a pressão entra só como sinal ao vivo (APPM 10′ ≥ 1.0). - **Discrepância**: esperado(m) = HT·m/45 (m ≤ 45) ou HT + (FT − HT)·(m−45)/45; desvio % = 100·(atual − esperado)/esperado, para golos e cantos do jogo e de cada equipa. Só extremos: minuto ≥ 10, |desvio| ≥ 50%, diferença absoluta ≥ 1 golo / 2 cantos (1 golo / 1.5 cantos por equipa), esperado mínimo 0.5 golos / 1.5 cantos; máx. 3 por jogo. Favorito = odd pré-jogo mais baixa ≤ 2.0; “Favorito a perder (com pressão alta)” a partir dos 20′. - **Similitude** com bots legado e funis Lucy (config. oficial): % = 100·Σcrédito/aplicáveis, crédito = 1 se cumpre, senão min(1, valor/limiar). Condições por equipa no lado ativo com melhor total (espelhadas = UMA equipa). Janela (fora da janela → “entra aos X′”) e ligas excluídas assinaladas; campos sem equivalente no feed não contam. Junta-se o acerto do bot nessa liga (legado: pode incluir Void como Green). - **Confluência**: nº de sinais no mesmo sentido (golo/cantos/resultado) — filtros cumpridos, pressão alta ao vivo (fundida com “menos golos do que o previsto”), favorito a perder com pressão, mais cantos do que o previsto, bots/Lucy ≥ 80% (≥ 3 condições, na janela, liga não excluída). Resumo quando ≥ 2; lista ordenada por confluência (botão para ordenar por minuto). ## 3g. Gestão de alertas visuais (Entradas ao vivo e Filtros) — só apresentação Nada deixa de ser registado: todas as entradas continuam guardadas e contam para as estatísticas; a gestão só decide como cada alerta aparece. Valores por omissão em `/alertas_config.json` (público); ajustes feitos no ecrã (⚙ Gestão de alertas) ficam no `localStorage` do browser. Resultado por entrada em `/data/gestao_alertas.json`, `entradas_historico.jsonl` (`_acerto_liga.gestao_alerta`) e `.csv` (`gestao_tier`, `gestao_delta_pts`, `gestao_mult`, `media_bot_pct`); nos Filtros em `sinais_ao_vivo.json` (`bots_mais_parecidos[].gestao_alerta`). - **delta** = acerto do bot na liga **antes** da entrada (§3d) − média geral do bot **antes** da entrada (anual + entradas datadas posteriores, menos as datadas a partir desta; "atual" se não houver data), em pontos percentuais. - **Níveis**: `forte` (🔥, pisca depressa, brilho verde) ≥ +25·mult; `destaque` (▲, pisca suave) ≥ +15·mult; `normal`; `esbatido` (▼) ≤ −15·mult; `oculto` ≤ −25·mult (escondido; com "mostrar ocultos" aparece esbatido com o motivo); `sem_dados` (sem liga ou sem %). - **Amostra (n da liga, antes da entrada)**: n < 5 → sem gestão (normal, ⚠); 5–9 → ×1.6 (±24/±40) e só se o intervalo de Wilson 95% excluir a média; 10–19 → ×1.3 (±19.5/±32.5); 20–39 → ×1.0 (±15/±25); ≥ 40 → ×0.8 (±12/±20). Se o intervalo de Wilson contém a média: forte→destaque e oculto→esbatido. Ex.: média 70%, liga 40% com n=20 → oculto (−30); liga 60% → normal (−10). - **Por bot**: Automático (omissão) | Manual (+X pisca, 🔥 a partir de X·5/3; −Y oculta, esbatido a partir de Y·0.6; n mínimo próprio; ignora o automático) | Desligado (mostra sempre). Globais: n mínimo, mostrar ocultos, piscar on/off (com `prefers-reduced-motion` o brilho fica estático), repor. - Nos Filtros os bots com alerta `oculto` saem do resumo ≈ e não contam para a confluência (continuam na lista detalhada com o motivo). Bots legado: o acerto pode incluir Void contado como Green; sem ROI. ## 3h. Notificações do browser (só novas entradas de Entradas ao vivo) Web Notifications API no próprio visualizador (sem servidor): botão «🔔 Ativar notificações» (permissão pedida só no clique); deduplicação por `funnel_id|match_id|created` em `localStorage` (`rb_notif_vistos_v1`; na primeira carga marca tudo como visto); texto = bot · minuto · equipas · liga: % (n) + marca do nível; `oculto` não notifica, `forte`/`destaque` usam `requireInteraction`; opções «só destacados» e «som» (`rb_notif_v1`). Nada de Filtros/sinais/confluência. Só com o separador aberto e só quando o visualizador recarrega as entradas (modo API: ao entrar/«Atualizar»; modo instantâneo: dados publicados, que são recolhidos de 2 em 2 h e publicados a cada 100 novas entradas). ## 3i. Atualização automática no visualizador Com sessão da API no browser: `GET /funnel/details?funnel_id=` de cada bot legado (13) + `GET /funnel/detailsLucy` (+ até 4 `GET /matches/showDetail` para entradas novas sem liga), em sequência, 1,5 s entre pedidos, de 2 em 2 min (configurável 1/2/5 min ou desligado; `localStorage` `rb_autoref_v1`); erros → espera exponencial (máx. 10 min); 401/expiração → fluxo de login existente («Manter sessão»: palavra-passe só na memória JS do separador (nunca em storage/cookies/logs), `POST /auth/login` de 50 em 50 min e em qualquer 401 (1 vez); indicador «sessão renovada às hh:mm · próxima às hh:mm»; se a renovação falhar pára e avisa mantendo os dados; ao recarregar a palavra-passe perde-se e volta o formulário com nota). Novas entradas fundem-se em Entradas ao vivo (chave `funnel_id|match_id|created`), com liga, gestão de alertas e notificações. Sem sessão: relê `/data/index.json` a cada 5 min e recarrega se `gerado_em` mudou. Nada disto corre no servidor. **Sessão e recarregar (F5).** O token JWT da API (só o token, nunca a palavra-passe) fica em `sessionStorage` (`rbA_token`) e a chave dos dados em `sessionStorage` (`rbA_datakey`). Ambos se apagam quando o separador fecha ou quando se carrega em «Sair». Ao recarregar, o token é validado pelo `exp`: se ainda for válido, aparece logo o último instantâneo (quando existe a chave) ou um cartão «a restaurar sessão», e a seguir os dados ao vivo; a atualização automática retoma sozinha. Se o token já tiver expirado, é apagado e volta o login com uma mensagem. O token da RoboBET dura 60 min (`expires_in: 60`, `refresh_token: null`) e a API não tem rota de renovação, por isso não há sessões de 12 h: ao fim de 60 min é preciso entrar de novo. A exceção é ter marcado «Manter sessão» (palavra-passe só em memória do separador, renovação a cada 50 min; perde-se ao recarregar e o formulário volta com uma nota). O botão «Entrar na API» passa do instantâneo para o modo ao vivo. ## 3j. Stats (separador Filtros → botão «📊 Stats», à direita de «Jogos ao vivo») Estatísticas por nº de sinais ativos, confluência, assimetria (pré-jogo vs ao vivo), minuto, liga e filtro, sempre com n, intervalo de Wilson 95%, **base** (taxa sem sinais / de todas as entradas) e **lift** em pontos percentuais (✓ = Wilson não contém a base e n≥5; marca 🔥▲▼ pela mesma regra da gestão de alertas, amostra pequena n<10 / n<5 sem leitura). ROI **só** onde há odd real (Monstro 2: Green = odd−1, Red = −1, Void = 0); legados: Void conta como Green, sem ROI. - Recolha (sem pedidos extra à API): no fim de cada `snapshot.py` (2 em 2 h) `stats_log.py` avalia os jogos do feed público já capturado (`stats_eval.mjs`, mesmo núcleo JS dos sinais) e acrescenta 1 registo compacto por jogo em `historico/stats_log/AAAA-MM-DD.jsonl` (dedupe match_id + faixa de 15′; .gz após 3 dias). Resultados finais (golos FT/HT, cantos FT) vêm de `/matches/showMatchesList2` (já pedido) → `historico/stats_resultados.json`. Resultados medidos: golo depois do sinal, canto depois, over 2.5 golos, over 9.5 cantos. - Publicado (atrás da chave): `/data/stats_resumo.json` (cartões + estado da recolha), `/data/stats_entradas.json` (entradas dos bots por nº de bots no mesmo jogo/minuto/estado/tipo/bot/liga + ROI Monstro 2), `/data/stats_sinais.json` (registo ponto-a-ponto liquidado). O browser só os pede ao abrir Stats; tabelas com 50 linhas + «Mostrar mais». - O histórico ponto-a-ponto **não existe retroativamente** (o feed não guarda passado nem a API devolve resultados por filtro): começa a 2026-10-09. Leitura útil a partir de ~200 registos liquidados. - Descarregar o projeto: `/data/projeto_robobet_antigo.zip` (mesma chave; sem segredos; gerado por `build_zip.py` a cada publicação com verificação de segredos). Plano: `PLANO_STATS.md`. - Desempenho: agregados pré-calculados em Python (`stats_agg.py`); separador carrega a pedido; `Cache-Control: private, no-cache` + ETag em `/data/*` (304 sem corpo); `/vendor/*` com cache de 7 dias; redesenho do ecrã «Entradas ao vivo» saltado quando os dados não mudaram (hash). ## 3k. Probabilidade de green 🎯 (modelo empírico-Bayes, `modelo_prob.py`) - **Alvo**: bots/entradas = green da entrada no fim do período do bot (HT se o nome tem HT, senão FT; legados: Void conta como Green → taxas inflacionadas). Filtros = golo (sentido golo) ou canto (cantos) depois do sinal até ao fim do período (HT se minuto ≤45 e o filtro termina ≤45′, senão FT). Sem chip quando o período está quase a acabar (>43′ HT, >88′ FT). - **Modelo** (sem caixa-preta): `base[período][faixa de minuto]` → `p_bot=(g+10·base)/(n+10)` → `p_bot×liga=(g_l+15·p_bot)/(n_l+15)` → log-odds + ajustes logísticos (ridge) aprendidos: 2 bots / 3+ bots no jogo, marcador empatado. Filtros: prior de Poisson com médias pré-jogo (golos/cantos em falta) encolhido para a frequência observada do registo Stats: `(g+20·p0)/(n+20)`. - **Confiança**: «prelim.» (modelo <200 linhas, ou n da liga <10, ou filtro sem observações), «razoável», «sólida» (n da liga ≥40, Brier fora da amostra melhor que a média e erro de calibração ≤5 pp). - **Treino**: a cada `build_data.py` (falha → mantém o modelo anterior). Saídas: `pages/public/modelo_prob.json` (público, só coeficientes globais, ~3 KB, cache 5 min), `/data/stats_modelo.json` (calibração, Brier modelo vs média, histórico), `historico/modelo_prob_hist.jsonl` (1 linha/dia). Avaliação: treino = 70 % mais antigo, teste = 30 % mais recente; as taxas bot/liga de cada linha são as de ANTES da entrada. - **Campos para outras IAs**: `prob_green` em `/data/gestao_alertas.json` (por entrada), em `/data/sinais_ao_vivo.json` (`filtros_cumpridos[]`, `bots_mais_parecidos[]`, `prob_green_melhor`). O cálculo no browser (`probBot`, `probFiltro`, no núcleo JS) é idêntico ao do Python. - **Golo vs canto**: por jogo, `prob_golo` e `prob_canto` (melhor sinal de cada mercado: filtros sentido golo/cantos + bots de golos/cantos) e `ideia_mais_provavel` ({ideia: golo|canto|equilibrado|só golo|só canto, margem_pts}; |diferença|<5 pts = equilibrado; períodos HT/FT podem diferir) em `/data/sinais_ao_vivo.json`. Over/ambas ainda não distinguidos. - UI: chip em Jogos ao vivo (resumo, bots, filtros) e Entradas ao vivo (pendentes); Stats → «🎯 Modelo %». - `stats_log` guarda `p0g_HT/p0g_FT/p0c` (prior) para treinar filtros quando houver ≥200 registos liquidados. ## 3l. ROI por cenário («se fosse um bot») e Bots de dados - **Odds disponíveis**: Monstro 2 e bots da app (/bot/showByBotEntries) → `line_odd` REAL + Green/Red/Void. O feed público (wss) só tem 1X2 (guardado em `stats_log.odd`); não há odds de linhas de golos/cantos/asiáticas em feed, showDetail ou showFilterV2 → filtros, bots legado e Lucy NÃO têm ROI real. - **ROI real** (odd real): lucro = odd−1 / −1 / 0 (stake 1), **1 aposta por jogo** por cenário (apostas no mesmo jogo não são independentes), IC 95% = média ± 1.96·dp/√n, queda máxima e maior sequência de reds. Leitura só com n ≥ 30 jogos distintos. - **Sem odd real**: odd de equilíbrio = 1/taxa (intervalo de Wilson) e «ROI hipotético» a odds ASSUMIDAS (cantos 1.75 / golos 1.80, mínimos da Lucy), sempre rotulado e visualmente separado; legados têm Void=Green → inflacionado. - **Ficheiros /data**: `stats_entradas.json` (`roi_hip` por linha; `roi_monstro2`, `roi_apps`, `roi_bots_de_dados`), `stats_sinais.json` (`roi_hip` por cenário; vazio até haver sinais liquidados), `stats_cenarios_bot.json` (ranking exploratório n≥30 e limite inferior>0; avisos de comparações múltiplas), `stats_roi_real.json` (linhas compactas para o ROI interativo), `app_entradas_odd_real.jsonl` (Monstro 2 + bots de dados, com `tipo_bot`). - **Bots de dados** (`historico/apps/bot_.jsonl`, recolhidos por `snapshot.py` via `app_hist.py`: todos os bots ativos de /bot/show exceto Monstro 2 e Lutero, novos entram sozinhos; sem user_id; nunca /bot/market): tipo `bot_de_dados` por omissão (promover a estratégia: `historico/apps/estrategias.json` = [ids]). Só fornecem odds+resultados imparciais; ficam FORA de rankings de bots, gestão de alertas, notificações/Telegram, similitude e «melhores cenários como bot». Entradas muito cedo (≤15′ 1.ª parte / 46–60′) podem ter odds irreais → filtro «Minuto ≥» na vista ROI. - UI: Stats → «ROI (odd real)» (agrupar por bot/liga/minuto/linha/mercado/odd, fonte, min≥), «💰 Cenários como bot», «🧪 Bots de dados»; colunas «Odd equil.» / «ROI hipot.*» (fundo tracejado) nas tabelas. ## 3m. Captura do feed público a cada 15 min (`feed_loop.sh` / `feed_capture.py`) - Loop separado do `recolha_loop.sh` (que não foi alterado), sem IA e SEM login: abre `wss://app.robobet.app/wss/` (público), avalia os jogos ao vivo com `stats_eval.mjs` (dados locais) e grava registos compactos em `historico/stats_log/AAAA-MM-DD.feed.jsonl` (o snapshot de 2 h continua em `AAAA-MM-DD.jsonl`; dedupe match_id + faixa de 15′ lê os dois; `flock` em `.lock`; gzip após 3 dias). - Config `historico/feed_loop.json` (`ativo`, `intervalo_min`=15, `hora_ini`=8, `hora_fim`=24 PT). PID `historico/feed_loop.pid`, log `historico/feed_loop.log`. Backoff em falhas (até 1 h); falhas silenciosas. - A liquidação (resultados finais/HT/cantos) fica no snapshot de 2 h (showMatchesList2 é autenticado). Não escreve `raw/wss_live.json`. ## 3n. Botão pausa/retoma da captura (Sobre) e observação «odd mínima 1,4» - **/api/feed** (Pages Function `functions/api/feed.js`, KV gratuito `FEED_KV`, mesma chave DATA_KEY; 401 sem chave; sem CORS): GET → `{cfg:{ativo,intervalo_min 15|30|60,atualizado_em}, captura}`; POST `{ativo?,intervalo_min?}` grava; POST `{captura:{t,jogos,novos}}` (a box) regista a última captura. `feed_capture.py` faz 1 POST por ciclo (lê a config e, após captura, regista-a); falha → usa `historico/feed_loop.json`. Pausado: o loop só relê a flag de 5 em 5 min. - **Odd mínima 1,4** nos legados: `historico/minquote_hist.jsonl` (snapshot.py regista quando `/funnel/show.minimum_quote` muda; a 2026-10-07 09:50 PT estava vazia; deteção 2026-10-09 12:54 PT; instante exato desconhecido → janela «incerta»). `/data/stats_minquote.json` (antes/depois por bot/mercado/minuto/liga, referência de odds reais por minuto, preliminar até ≥3 dias); linha de base fixa em `historico/minquote_baseline.json`. Legados não expõem odd/linha. ## 4. Dados estáticos publicados (atualizados pela rotina diária) **Acesso protegido por chave.** Tudo em `/data/*` exige uma chave de acesso só de leitura, fornecida pelo Pedro a quem precisa (não está nesta página nem no site). Peça-a ao utilizador ou leia-a de uma variável de ambiente (ex. `ROBOBET_DATA_KEY`); nunca a escreva em ficheiros públicos, logs ou respostas. - Preferido: header `X-Data-Key: ` - Alternativas: `Authorization: Bearer ` ou query `?key=` (evite a query: fica em logs/histórico) - Sem chave ou chave errada → `401` JSON `{"erro": "...", "error": "missing or invalid access key"}`. CORS aberto (o header `X-Data-Key` é permitido). Ficheiros (só leitura, UTF-8): | URL | Conteúdo | |---|---| | `/data/index.json` | manifesto: `gerado_em`, contagens, schema, sha256 | | `/data/bots_legado.json` (+ `.csv`, `bots_legado_condicoes.csv`) | bots legado: config completa, condições legíveis, hit anual, hit por liga, gráfico recente, entradas recentes com stats | | `/data/lucy.json` (+ `lucy.csv`) | funis Lucy: hit anual, hit recente, intervalo do campo history, minuto de entrada inferido, por liga, entradas recentes | | `/data/hit_por_liga.json` (+ `.csv`) | hit por liga × bot (formato longo) e totais por liga | | `/data/entradas_historico.jsonl` (+ `.csv`) | histórico acumulado de entradas com estatísticas no momento da entrada | | `/data/campos.json` | rótulos pt-PT dos campos de configuração | | `/data/bots_app.json` (+ `bots_app.csv`, `bots_app_entradas.csv`) | bots da app: config, entradas com odds/resultado (Monstro 2: histórico acumulado), ROI por liga da app | | `/data/monstro2_ligas.json` (+ `.csv`) | Monstro 2 — ROI real: total e por liga (tips, green, red, void, acerto_pct, lucro_u, roi_pct, odd_media, amostra_pequena) + comparação com a app (app_tips, app_lucro_u, app_roi_pct) | | `/data/monstro2_entradas.jsonl` (+ `.csv`) | Monstro 2 — todas as tips acumuladas (data BRT, liga, equipas, minuto, linha, odd, estado green/void/red, lucro) | | `/data/bots/.json` · `/data/bots/.xlsx` | um bot (qualquer tipo) no esquema normalizado · Excel (Resumo, Condições, Acerto por liga, Entradas recentes, + config. reconstruída na Lucy, + “ROI por liga” no Monstro 2) | | `/data/todos_bots.json` · `/data/todos_bots.xlsx` | todos os bots normalizados · Excel com Índice + 1 folha por bot + folha “ROI por liga — Monstro 2” | | `/data/lucy_config_reconstruida.json` | por funil Lucy: config. oficial (se copiada via BOOT) + regras reconstruídas com confiança | | `/data/lucy_configs_oficiais.xlsx` | Excel das configs OFICIAIS da Lucy: Comparação (5 funis lado a lado), Descrições, Ligas excluídas, Não espelhado, Glossário, 1 folha por funil | | `/data/lucy_boot.json` | configurações OFICIAIS da Lucy copiadas via BOOT_ (confiança 100%), guardadas de forma permanente | Exemplo: ```bash curl -s -H "X-Data-Key: $ROBOBET_DATA_KEY" https://robobet-antigo-viewer.pages.dev/data/index.json curl -s -H "X-Data-Key: $ROBOBET_DATA_KEY" https://robobet-antigo-viewer.pages.dev/data/bots_legado.json | jq '.bots[] | {nome, hit, minuto_ini, minuto_max}' ``` ```python import os, requests B="https://robobet-antigo-viewer.pages.dev/data" K={"X-Data-Key": os.environ["ROBOBET_DATA_KEY"]} # chave fornecida pelo utilizador bots=requests.get(B+"/bots_legado.json",headers=K).json()["bots"] for b in sorted(bots,key=lambda b:-b["hit"]["n"]): print(b["nome"], b["hit"]["pct"], "n=", b["hit"]["n"]) liga=[r for r in requests.get(B+"/hit_por_liga.json",headers=K).json()["por_liga_total"] if r["n"]>=40] ``` ```js const r=await fetch("https://robobet-antigo-viewer.pages.dev/data/lucy.json",{headers:{"X-Data-Key":process.env.ROBOBET_DATA_KEY}}); if(r.status===401) throw new Error("chave de acesso em falta/inválida"); const {funnels}=await r.json(); ``` ## 5. API ao vivo (precisa das credenciais do próprio utilizador) Nunca embuta credenciais. O utilizador fornece email/palavra-passe da sua conta RoboBET; peça-as em tempo de execução ou leia de variáveis de ambiente (ex. `ROBOBET_ANTIGO_EMAIL`, `ROBOBET_ANTIGO_PASSWORD`). - Login: `POST https://app.alefut.com/api/auth/login` JSON `{"email","password"}` → `result.access_token` (JWT, válido **60 min**, sem refresh; renovar = novo login). - Pedidos: header `Authorization: Bearer `. CORS aberto (`Access-Control-Allow-Origin: *`). - Respostas: `{"result": ..., "error": null|"CODIGO"}`. - **Só leitura**: use apenas os GET abaixo. Não chame endpoints de criar/alterar/apagar. Não use `/bot/market` (bots de terceiros/Lutero) e exclua qualquer bot “Lutero”. Mantenha ~1 pedido/s. | GET | Devolve | |---|---| | `/funnel/show` | lista de bots legado com configuração completa | | `/lucy/reports?funnel_id=ID` | entradas da janela anual: `[{league_id, league_name, status}]`, status 1 = green, 2 = red. Funciona para bots legado e funis Lucy | | `/funnel/details?funnel_id=ID` | entradas recentes (~5–30) com `events` (stats no momento), `home`, `away`, `created`, `green`, `red`, `history` | | `/funnel/detailsLucy` | entradas Lucy recentes (~40) com `events` e `history` | | `/funnel/graph?funnel_id=ID` | green/red por dia (últimos ~3 dias) | | `/funnel/showLeagues` | catálogo de ligas (~2400) | | `/lucy/show` | estado da subscrição Lucy do utilizador | | `/bot/show` | bots da app do utilizador (config) — excluir qualquer bot “Lutero” | | `/bot/showByBotEntries?bot_id=ID` | até 1000 entradas do bot com odd, linha, resultado (`status` 1/2/5) e `roi` | | `/bot/entriesDetails?bot_id=ID` | ROI acumulado e ROI/qt por liga (como a app) | Nas entradas de `/funnel/details*`, use `green`/`red` (o `status` vem sempre 1). Campos de `events`: `timer` (minuto), `h_/a_scoreboard` (golos), `h_/a_attacks`, `d_h_/d_a_attacks` (ataques perigosos), `h_/a_possession`, `h_/a_on_target` (remates à baliza), `h_/a_kick_out` (remates fora), `h_/a_corners`, `h_/a_red_cards`, `h_/a_position`, `out*` (resolução, quando existe). ```bash TOKEN=$(curl -s -X POST https://app.alefut.com/api/auth/login -H 'Content-Type: application/json' \ -d "{\"email\":\"$ROBOBET_ANTIGO_EMAIL\",\"password\":\"$ROBOBET_ANTIGO_PASSWORD\"}" | jq -r .result.access_token) curl -s https://app.alefut.com/api/funnel/show -H "Authorization: Bearer $TOKEN" | jq '.result[] | {id, strategy_name}' curl -s "https://app.alefut.com/api/lucy/reports?funnel_id=7403" -H "Authorization: Bearer $TOKEN" | jq '[.result[] | select(.status==1)] | length' ``` ```python import os, requests A="https://app.alefut.com/api" tok=requests.post(A+"/auth/login",json={"email":os.environ["ROBOBET_ANTIGO_EMAIL"],"password":os.environ["ROBOBET_ANTIGO_PASSWORD"]}).json()["result"]["access_token"] H={"Authorization":"Bearer "+tok} bots=[b for b in requests.get(A+"/funnel/show",headers=H).json()["result"] if "lutero" not in b["strategy_name"].lower()] for b in bots: rows=requests.get(A+"/lucy/reports",params={"funnel_id":b["id"]},headers=H).json()["result"] or [] g=sum(r["status"]==1 for r in rows); n=sum(r["status"] in (1,2) for r in rows) print(b["strategy_name"], f"{100*g/n:.1f}%" if n else "-", "n=",n) ``` ```js const A="https://app.alefut.com/api"; const {result}=await (await fetch(A+"/auth/login",{method:"POST",headers:{"Content-Type":"application/json"}, body:JSON.stringify({email:process.env.ROBOBET_ANTIGO_EMAIL,password:process.env.ROBOBET_ANTIGO_PASSWORD})})).json(); const H={Authorization:"Bearer "+result.access_token}; const lucy=(await (await fetch(A+"/funnel/detailsLucy",{headers:H})).json()).result; console.log(lucy.map(e=>[e.strategy,e.events.timer,e.green?"G":"R"])); ``` ## 6. Limitações - Hit legado só na janela anual; sem lifetime para bots legado. - Estatísticas ao vivo só nas entradas recentes; o histórico com stats cresce com a recolha diária (`entradas_historico.jsonl`). - `timer`/`market` e alguns rótulos de campos são inferidos do comportamento da app. --- ## English summary Read-only data from the old RoboBET app (API `https://app.alefut.com/api`). 2 “app bots” (`/bot/show`, real odds per tip → ROI shown exactly as the app computes it: sum of units ×100, labelled “ROI (da app)”; the app home screen uses the last 30 days), 18 user “legacy bots” (full configs published; `home_*`/`away_*` conditions are mirrored, differences flagged) and Lucy platform funnels (internal config not exposed; entry minute inferred from `events.timer`). The “Lutero” bot is excluded everywhere. For legacy bots and Lucy **never compute ROI** (Asian/limit lines without fixed odds): only hit rate = green/(green+red) with sample size n. Static snapshots (refreshed daily) are **protected by a read-only access key** that the user (Pedro) gives to the AIs that need it; it is not published anywhere. Send it as header `X-Data-Key: ` (preferred), or `Authorization: Bearer `, or `?key=`; without it `/data/*` returns 401 JSON. Never echo or store the key publicly. Files: `/data/index.json` (manifest + schema), `/data/bots_legado.json`, `/data/lucy.json`, `/data/hit_por_liga.json`, `/data/entradas_historico.jsonl`, CSV equivalents, `/data/campos.json` (field labels), `/data/bots_app.json`, `/data/bots/.json|.xlsx` (normalized schema `robobet-antigo/bot@1` for any bot, incl. `descricao_natural`), `/data/todos_bots.json|.xlsx`, `/data/lucy_config_reconstruida.json` (inferred Lucy rules with confidence levels — an inference, not the real config). The exact Lucy config is not readable via the API; the app only offers a `BOOT_` code for the Telegram bot @Alefut_bot, which creates a copy in the user's account (never do that without Pedro's approval). Official Lucy configs already copied by Pedro are stored permanently in `/data/lucy_boot.json` and exposed as `configuracao_oficial` (source «copiada via BOOT_», 100% confidence) — all 5 funnels (71, 7403, 7405, 7406, 26672). Not mirrored home/away: 7406 and 26672 (`nao_espelhado`). With `param_leagues = 1` the `leagues` list are **excluded** leagues → `filtro_ligas_modo: "exclusao"`, `ligas_excluidas: [{id, nome}]` (7406 excludes 25). Fields RT, PI, superiority, event_reset, timer, leaderbooards, overall_goal_difference, active keep the app's labels; meaning to be confirmed. Legacy bots 98924–98928 were temporary BOOT copies and are excluded from legacy stats. Live API: user supplies their own credentials (never embed them) → `POST /auth/login {email,password}` → JWT `result.access_token` (60 min) → `Authorization: Bearer`. CORS is open. Use only the GET endpoints listed in section 5; never call create/update/delete endpoints or `/bot/market`; exclude any “Lutero” bot. `created` timestamps are Brasília time (UTC−3). Bot "Monstro 2" (app bot id 94114) is the only one with a minimum odd configured, so its ROI is real: Green = odd−1, Red = −1, Void = 0 (stake 1); hit = G/(G+R); ROI = profit/(G+R+V). See `/data/monstro2_ligas.json` (per league, `amostra_pequena` when < 10 tips, compared with the app's own per-league totals) and `/data/monstro2_entradas.jsonl` (accumulated tips; the API only returns the latest 1000). **Legacy bots do not recognise Void: a refunded bet counts as Green (`void_conta_como_green: true`), so their hit rates are inflated, especially for Asian lines; Pedro plans to switch them to limit lines (Green/Red only).** Mirrored per-team conditions (`home_*`/`away_*` with equal values) apply to **one of the teams — usually the one pressing — not both at once** (inferred, not confirmed by the app: e.g. RT = possession ≥ 60% cannot hold for both). Field: `condicoes_por_equipa_semantica`. Non-mirrored funnels: 7406 (extra conditions on the home side only), 26672 (appm5 stricter for away).