Spaces:
Runtime error
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). | |
| ```bash | |
| # 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): | |
| ```env | |
| VITE_API_KEY=mesma-chave-do-servidor | |
| ``` | |
| --- | |
| ## Saúde e meta | |
| ### `GET /health` | |
| Status do datalake e contadores. | |
| ```bash | |
| 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. | |
| ```bash | |
| 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 no `body_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. | |
| ```json | |
| { | |
| "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:** | |
| ```json | |
| { | |
| "home_team": "Brasil", | |
| "away_team": "Marrocos", | |
| "phase": "group" | |
| } | |
| ``` | |
| **Body com FEPT automático (Sofascore):** | |
| ```json | |
| { | |
| "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: | |
| ```bash | |
| ingest-sofascore --event-id 11774480 --stats-only --json | |
| ingest-sofascore --event-id 11774480 --with-stats | |
| ``` | |
| ### Previsão de escanteios (Poisson) | |
| `POST /worldcup/corners/predict` | |
| ```json | |
| { | |
| "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): | |
| ```json | |
| { | |
| "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`:** | |
| ```json | |
| { | |
| "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). | |
| ```json | |
| { "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`. | |
| ```json | |
| { | |
| "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 | |
| ```bash | |
| # 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 | |
| ``` | |