Spaces:
Runtime error
Referência da API
Base URL: http://localhost:8000
Documentação interativa: /docs
Versão: 0.2.0
Todas as respostas são JSON. CORS habilitado (*).
Autenticação (API key)
Quando API_KEY está definida no ambiente do servidor, todas as rotas exigem chave, exceto:
GET /health/live(liveness Fly)/docs,/redoc,/openapi.json
Envie a chave de uma destas formas:
| Forma | Exemplo |
|---|---|
| Header | X-API-Key: sua-chave |
| Bearer | Authorization: Bearer sua-chave |
Várias chaves válidas: API_KEY=chave-antiga,chave-nova (rotação).
Sem API_KEY no servidor, a API permanece aberta (desenvolvimento local).
# Fly.io
fly secrets set API_KEY="$(openssl rand -hex 32)" -a api-noticia
curl -H "X-API-Key: $API_KEY" https://api-noticia.fly.dev/news/all
Frontend (build/deploy):
VITE_API_KEY=mesma-chave-do-servidor
Saúde e meta
GET /health
Status do datalake e contadores.
curl -s http://localhost:8000/health
GET /data/pulse
Pulso do datalake (mesmo uso no navegador): contadores, latest_silver_at, última coleta em _meta/collections.jsonl e links das ações (POST /news/sync, GET /news/all).
Em cada requisição (exceto /health/live e docs), a API inclui headers de pulso na resposta:
| Header | Conteúdo |
|---|---|
X-Data-Pulse-At |
ISO8601 do snapshot |
X-Articles-Silver |
Artigos no silver |
X-Fixtures |
Fixtures (Brasileirão) |
X-WC-Models-Ready |
true / false |
X-Collections-Last-Run |
Última linha em _meta/collections.jsonl |
X-Latest-Silver-At |
mtime do parquet silver mais recente |
O frontend lê esses headers em todo apiFetch e atualiza o badge de status.
curl -sI http://localhost:8000/news/all | grep -i x-data-pulse
curl -s http://localhost:8000/data/pulse
GET /
Lista de endpoints disponíveis.
Notícias
POST /news/sync
Dispara coleta RSS → bronze → silver (até ~800 artigos: RSS_MAX_ENTRIES_PER_SOURCE=160 × 5 fontes).
Query opcional:
full_rebuild=true— reprocessa todo o bronze no silver.fetch_body=true— baixa o HTML de cada matéria (corpo completo nobody_preview; sync bem mais lento).
Por padrão, body_preview e summary usam o maior texto no silver sem limite de caracteres. O feed RSS costuma trazer só um trecho; para texto integral use fetch_body=true e depois full_rebuild=true.
GET /news/feed
Feed agregado de artigos silver (filtros via query params — ver Swagger).
GET /news/all
Retorna todas as notícias do silver em uma única resposta (mesmo formato do /news/feed).
| Query | Descrição | Padrão |
|---|---|---|
days |
Janela em dias | omitido = histórico completo no lake |
offset |
Deslocamento se passar de 5000 itens | 0 |
source, q, team, home_team, away_team, teams |
Mesmos filtros do feed/cards | — |
Teto interno: 5000 artigos por requisição (proteção de memória).
GET /news/cards
Notícias formatadas para cards no frontend (NewsArticleCard).
| Query | Descrição | Padrão |
|---|---|---|
limit |
Máx. cards | 12 (máx. 48) |
offset |
Paginação | 0 |
days |
Janela em dias | 14 |
team |
Um time/seleção | — |
home_team / away_team |
Filtro por confronto | — |
teams |
Lista Brasil,Marrocos |
— |
source, q |
Fonte e busca textual | — |
Resposta: { total, limit, offset, teams, cards[] } — cada item tem title, body_preview, sentiment_label, teams_mentioned, etc.
Brasileirão (notícias + heurística)
POST /context
Contexto de notícias para um jogo.
{
"home_team": "Flamengo",
"away_team": "Palmeiras",
"round_number": 1,
"competition": "Brasileirão",
"season": 2024
}
Resposta: context_text, contagens de notícias, sentimento, posição, forma.
POST /predict
Igual /context + palpite baseline (1/X/2), confiança e motivo.
GET /round/predict
Palpites da rodada em data/rounds/current.json.
Copa do Mundo — previsão
POST /worldcup/predict
Palpite avulso com ensemble completo.
Body mínimo:
{
"home_team": "Brasil",
"away_team": "Marrocos",
"phase": "group"
}
Body com FEPT automático (Sofascore):
{
"home_team": "Brasil",
"away_team": "Argentina",
"phase": "group",
"sofascore_event_id": 11774480
}
Quando sofascore_event_id é enviado e kxl_match.fept está ausente, a API busca escalação + notas no Sofascore (requer curl_cffi no servidor). Metadados em model_breakdown.kxl_fept.
Estatísticas Sofascore (escanteios, chutes, posse)
GET /worldcup/sofascore/{event_id}/statistics?refresh=false
Retorna métricas achatadas (home_corners, away_corners, home_possession_pct, home_xg, cartões via incidentes, etc.). Com refresh=false, lê cache em data/lake/sofascore/match_stats.parquet se existir.
CLI equivalente:
ingest-sofascore --event-id 11774480 --stats-only --json
ingest-sofascore --event-id 11774480 --with-stats
Previsão de escanteios (Poisson)
POST /worldcup/corners/predict
{
"home_team": "Brasil",
"away_team": "Marrocos",
"phase": "group"
}
Resposta inclui expected_corners (ex. "5.8x4.2"), expected_total_corners, probabilidades over_9.5 / under_9.5 e fatores do modelo (lambda_home, lambda_away). Treina com histórico em data/lake/sofascore/match_stats.parquet; com poucos jogos, faz blend com λ de gols (proxy).
CLI: predict-corners --home Brasil --away Marrocos --json
Body com entrada dinâmica KXL (opcional):
{
"home_team": "Brasil",
"away_team": "Marrocos",
"phase": "group",
"kxl_match": {
"fecl": { "previsao_chuva_pct": 55, "estado_gramado": "Molhado" },
"feju": { "perfil": "punitivista", "indice_cartao_falta": 0.28 },
"fede": {
"desfalques_visitante": [
{ "jogador": "Ziyech", "impacto_nota_elenco": -0.8 }
]
},
"fept": {
"esquema_mandante": "4-3-3",
"mandante_titulares_notas": {
"goleiro": { "nome": "Alisson", "nota_sofascore": 7.1 },
"atacantes": [{ "nome": "Vini Jr", "nota_sofascore": 7.9 }]
}
},
"feem": { "contexto_peso_caos": 1.25, "jogo_decisivo": true }
}
}
Resposta principal:
| Campo | Descrição |
|---|---|
prediction |
1, X ou 2 |
confidence |
Probabilidade do palpite (0–1) |
prob_home, prob_draw, prob_away |
Probabilidades finais |
poisson_score |
Placar mais provável (Dixon-Coles) |
expected_goals |
Ex.: "1.4x1.0" |
context |
Texto IA (stats + DNA KXL + colisão) |
model_breakdown |
Ver abaixo |
model_breakdown:
{
"dixon_coles": { "1": 0.69, "X": 0.21, "2": 0.10 },
"logistic": { "1": 0.56, "X": 0.22, "2": 0.22 },
"dixon_coles_rho": 0.08,
"poisson_factors": {
"lambda_home": 1.42,
"lambda_away": 0.98,
"home_attack": 1.15,
"away_attack": 0.92,
"rho": 0.08
},
"ensemble_weights": { "dixon_coles": 0.30, "logistic": 0.70 },
"kxl_baseline": { "1": 0.45, "X": 0.28, "2": 0.27, "blend_weight": 0.25 },
"kxl_collision": { },
"kxl_dynamic": { "blocks_used": ["fecl", "fept"], "engine": "wc_kxl_collision" }
}
GET /worldcup/round
Palpites para todos os jogos em data/rounds/wc_2026.json.
GET /worldcup/teams
Lista de seleções (fixtures + rodada).
Copa — validação histórica
GET /worldcup/editions
Edições disponíveis (1986–2022 no lake típico).
GET /worldcup/editions/{season}/matches
Jogos reais de uma edição (placar, fase, resultado). Mata-mata: group_name: null.
POST /worldcup/validate
Backtest com recorte temporal (só dados anteriores ao jogo).
{ "season": 2022, "home_team": "Brasil", "away_team": "Sérvia" }
ou { "season": 2022, "match_id": "..." }.
Resposta: correct: true/false, actual_result, cutoff_note.
Value bets (odds + EV)
POST /worldcup/value/live
Requer ODDS_API_KEY no .env.
{
"min_edge": 0.03,
"schedule_file": "data/rounds/wc_2026.json"
}
Cruza probabilidades do modelo com odds The Odds API. Retorna edges[] com EV, fair odd, Kelly ¼.
Códigos de erro comuns
| HTTP | Causa |
|---|---|
| 400 | Body inválido, chave odds ausente |
| 404 | Rodada/arquivo/ jogo não encontrado |
| 502 | Falha na Odds API externa |
Exemplos curl
# Palpite WC
curl -s -X POST http://localhost:8000/worldcup/predict \
-H "Content-Type: application/json" \
-d '{"home_team":"Brasil","away_team":"Marrocos"}'
# Validar Copa 2022
curl -s -X POST http://localhost:8000/worldcup/validate \
-H "Content-Type: application/json" \
-d '{"season":2022,"home_team":"Brasil","away_team":"Sérvia"}'
# Via proxy frontend
curl -s http://localhost:5173/api/health