openapi: 3.1.0
info:
  title: RealLeader API
  version: "1.0.0"
  description: |
    API comercial de dados de futebol do ecossistema RealLeader — classificação
    ponderada (RealLeader/PRL), peso real, probabilidades, cenários e mais.

    Autenticação por **API key** (`Authorization: Bearer <key>` ou header `X-API-Key`).
    Toda resposta segue o envelope `{ data, meta }`; erros usam `{ data: null, errors: [...] }`.
    Rate limit e quota por plano (headers `X-RateLimit-*`).
  contact:
    name: RealLeader
    url: https://realleader.com

servers:
  - url: https://api.realleader.com
    description: Produção
  - url: http://realleader-api.test
    description: Desenvolvimento (Laragon)

security:
  - bearerAuth: []
  - apiKeyHeader: []

tags:
  - name: Descoberta
  - name: Classificação
  - name: Jogos
  - name: Métricas RealLeader
  - name: Projeções

paths:
  /v1/campeonatos:
    get:
      tags: [Descoberta]
      summary: Campeonatos ativos
      description: Lista os campeonatos ativos com a rodada atual — o ponto de partida para descobrir `cod_campeonato`.
      operationId: listarCampeonatos
      responses:
        "200":
          description: Lista de campeonatos ativos.
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/X-RateLimit-Limit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/X-RateLimit-Remaining" }
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Campeonato" }
                  meta:
                    type: object
                    properties:
                      total: { type: integer }
              example:
                data:
                  - { cod_campeonato: 71, tipo: "BRA", nome: "Brasileirão 2026", sigla: "Série A", temporada: "2026", rodada_atual: 18, total_rodadas: 38, status: "AT", dat_fim: "2026-12-02" }
                meta: { total: 16 }
        "401": { $ref: "#/components/responses/Erro401" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/classificacao:
    get:
      tags: [Classificação]
      summary: Classificação (normal + RealLeader)
      description: Tabela do campeonato com posição oficial e RealLeader lado a lado, pontos, aproveitamento e zona.
      operationId: getClassificacao
      parameters:
        - $ref: "#/components/parameters/codCampeonato"
        - $ref: "#/components/parameters/rodada"
      responses:
        "200":
          description: Linhas da classificação (uma por clube).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/ClassificacaoLinha" }
                  meta: { $ref: "#/components/schemas/MetaRodada" }
              example:
                data:
                  - { cod_clube: 121, nome: "Palmeiras", sigla: "PAL", escudo: "https://realleader.com/assets/img/times/BRA/p/palmeiras.svg", posicao: 1, posicao_real: 1, pontos: 41, jogos: 18, vitorias: 12, empates: 5, derrotas: 1, gols_pro: 30, gols_contra: 13, saldo_gols: 17, aproveitamento: 75.93, zona: "sobe" }
                meta: { cod_campeonato: 71, rodada: 18, total: 20 }
        "401": { $ref: "#/components/responses/Erro401" }
        "422": { $ref: "#/components/responses/Erro422" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/jogos:
    get:
      tags: [Jogos]
      summary: Jogos de uma rodada
      description: "Jogos da rodada com placar, status e dados RealLeader (posição e peso/fator) de cada lado. Jogos não realizados retornam `gols: null`."
      operationId: getJogos
      parameters:
        - $ref: "#/components/parameters/codCampeonato"
        - $ref: "#/components/parameters/rodada"
        - name: status
          in: query
          required: false
          schema: { type: string, enum: [agendado, adiado] }
          description: "Filtra por situação (agendado = AGN, adiado = ADI). Omitido = todos da rodada."
      responses:
        "200":
          description: Jogos da rodada.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/Jogo" }
                  meta: { $ref: "#/components/schemas/MetaRodada" }
              example:
                data:
                  - cod_jogo: 1492280
                    rodada: 18
                    data: "2026-05-30 16:00:00"
                    status: "FIN"
                    mandante: { cod_clube: 134, nome: "Athletico-PR", sigla: "CAP", escudo: "https://realleader.com/assets/img/times/BRA/p/athletico_pr.svg", gols: 1, posicao: 1, fator: 1.3 }
                    visitante: { cod_clube: 7848, nome: "Mirassol", sigla: "MIR", escudo: "https://realleader.com/assets/img/times/BRA/p/mirassol.svg", gols: 0, posicao: 15, fator: -1 }
                meta: { cod_campeonato: 71, rodada: 18, status: null, total: 10 }
        "401": { $ref: "#/components/responses/Erro401" }
        "422": { $ref: "#/components/responses/Erro422" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/jogos/{cod_jogo}:
    get:
      tags: [Jogos]
      summary: Um jogo específico
      description: "Um jogo pelo código — mesmos campos de `/v1/jogos` (placar, status, posição e peso/fator de cada lado)."
      operationId: getJogo
      parameters:
        - name: cod_jogo
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: O jogo.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/Jogo" }
              example:
                data:
                  cod_jogo: 1492291
                  rodada: 19
                  data: "2026-07-16 19:30:00"
                  status: "AGN"
                  mandante: { cod_clube: 120, nome: "Botafogo", sigla: "BOT", escudo: "https://realleader.com/assets/img/times/BRA/p/botafogo.svg", gols: null, posicao: 6, fator: -0.3 }
                  visitante: { cod_clube: 121, nome: "Palmeiras", sigla: "PAL", escudo: "https://realleader.com/assets/img/times/BRA/p/palmeiras.svg", gols: null, posicao: 1, fator: 1.2 }
        "401": { $ref: "#/components/responses/Erro401" }
        "404": { $ref: "#/components/responses/Erro404" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/peso-real:
    get:
      tags: [Métricas RealLeader]
      summary: Peso RealLeader por mando
      description: Peso do adversário médio, mérito, aproveitamento e tier (TOP·MED·MIN) por clube num mando. Ordenado por peso.
      operationId: getPesoReal
      parameters:
        - $ref: "#/components/parameters/codCampeonato"
        - name: mando
          in: query
          required: false
          schema: { type: string, enum: [casa, fora], default: casa }
      responses:
        "200":
          description: Peso por clube.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PesoRealLinha" }
                  meta:
                    type: object
                    properties:
                      cod_campeonato: { type: integer }
                      mando: { type: string }
                      total: { type: integer }
              example:
                data:
                  - { cod_clube: 134, nome: "Athletico-PR", sigla: "CAP", escudo: "https://realleader.com/assets/img/times/BRA/p/athletico_pr.svg", mando: "casa", peso_rl: 1.3, peso_adv: 0.9, merito: 0.4, aproveitamento: 76.7, jogos: 10, tier: "top" }
                meta: { cod_campeonato: 71, mando: "casa", total: 20 }
        "401": { $ref: "#/components/responses/Erro401" }
        "422": { $ref: "#/components/responses/Erro422" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/pontuacao-real:
    get:
      tags: [Métricas RealLeader]
      summary: Pontuação RealLeader (PRL) da rodada
      description: Quanto cada clube rendeu de PRL na rodada (`pontos` + `peso` do adversário). Ordenado por PRL desc.
      operationId: getPontuacaoReal
      parameters:
        - $ref: "#/components/parameters/codCampeonato"
        - $ref: "#/components/parameters/rodada"
      responses:
        "200":
          description: PRL por clube.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/PontuacaoRealLinha" }
                  meta: { $ref: "#/components/schemas/MetaRodada" }
              example:
                data:
                  - { cod_clube: 1062, nome: "Atlético-MG", sigla: "CAM", escudo: "https://realleader.com/assets/img/times/BRA/p/atletico_mg.svg", mando: "fora", adversario: "Vasco", sigla_adversario: "VAS", escudo_adversario: "https://realleader.com/assets/img/times/BRA/p/vasco.svg", gols_pro: 1, gols_contra: 0, resultado: "V", pontos: 3, peso: 0.8, prl: 3.81, status_jogo: "FIN" }
                meta: { cod_campeonato: 71, rodada: 18, total: 20 }
        "401": { $ref: "#/components/responses/Erro401" }
        "422": { $ref: "#/components/responses/Erro422" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/probabilidades:
    get:
      tags: [Projeções]
      summary: Probabilidade de posição final
      description: Probabilidade (%) de cada clube terminar em cada posição, para um horizonte. `prob_por_posicao` mapeia `posição → %`.
      operationId: getProbabilidades
      parameters:
        - $ref: "#/components/parameters/codCampeonato"
        - name: horizonte
          in: query
          required: false
          schema: { type: string, enum: [final, proxima], default: final }
      responses:
        "200":
          description: Distribuição de probabilidade por clube.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/ProbabilidadePosicao" }
                  meta:
                    type: object
                    properties:
                      cod_campeonato: { type: integer }
                      horizonte: { type: string }
                      total: { type: integer }
              example:
                data:
                  - { cod_clube: 121, nome: "Palmeiras", sigla: "PAL", escudo: "https://realleader.com/assets/img/times/BRA/p/palmeiras.svg", prob_por_posicao: { "1": 49.77, "2": 35.83, "3": 8.4, "4": 3.63 } }
                meta: { cod_campeonato: 71, horizonte: "final", total: 20 }
        "401": { $ref: "#/components/responses/Erro401" }
        "422": { $ref: "#/components/responses/Erro422" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/cenarios:
    get:
      tags: [Projeções]
      summary: Classificação projetada
      description: A classificação como ficaria ao fim do horizonte escolhido.
      operationId: getCenarios
      parameters:
        - $ref: "#/components/parameters/codCampeonato"
        - name: horizonte
          in: query
          required: false
          schema: { type: string, enum: [atual, proxima], default: atual }
      responses:
        "200":
          description: Projeção da classificação.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: "#/components/schemas/CenarioLinha" }
                  meta:
                    type: object
                    properties:
                      cod_campeonato: { type: integer }
                      horizonte: { type: string }
                      total: { type: integer }
              example:
                data:
                  - { posicao: 1, cod_clube: 121, nome: "Palmeiras", sigla: "PAL", escudo: "https://realleader.com/assets/img/times/BRA/p/palmeiras.svg", pontos: 41, jogos: 18, vitorias: 12, saldo_gols: 17, gols_pro: 30 }
                meta: { cod_campeonato: 71, horizonte: "atual", total: 20 }
        "401": { $ref: "#/components/responses/Erro401" }
        "422": { $ref: "#/components/responses/Erro422" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/times/{slug}:
    get:
      tags: [Classificação]
      summary: Ficha de um clube
      description: Resumo do clube num campeonato — posição oficial/RealLeader, pontos reais e a campanha casa/fora (pontos, posição e peso por mando).
      operationId: getTime
      parameters:
        - name: slug
          in: path
          required: true
          description: "Nome do clube em minúsculas com `_` (ex.: `palmeiras`, `sao_paulo`, `atletico_mg`)."
          schema: { type: string }
          example: palmeiras
        - $ref: "#/components/parameters/codCampeonato"
      responses:
        "200":
          description: Ficha do clube.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: "#/components/schemas/TimeResumo" }
                  meta:
                    type: object
                    properties:
                      cod_campeonato: { type: integer }
                      slug: { type: string }
              example:
                data:
                  cod_clube: 121
                  nome: "Palmeiras"
                  sigla: "PAL"
                  slug: "palmeiras"
                  escudo: "https://realleader.com/assets/img/times/BRA/p/palmeiras.svg"
                  posicao: 1
                  posicao_real: 1
                  pontos: 41
                  pontos_reais: 39.9
                  jogos: 18
                  vitorias: 12
                  empates: 5
                  derrotas: 1
                  gols_pro: 30
                  gols_contra: 13
                  saldo_gols: 17
                  casa: { pontos: 23, pontos_reais: 16.5, posicao: 2, posicao_real: 4, peso: 0.8 }
                  fora: { pontos: 18, pontos_reais: 23.4, posicao: 1, posicao_real: 1, peso: 0.1 }
                meta: { cod_campeonato: 71, slug: "palmeiras" }
        "401": { $ref: "#/components/responses/Erro401" }
        "404": { $ref: "#/components/responses/Erro404" }
        "422": { $ref: "#/components/responses/Erro422" }
        "429": { $ref: "#/components/responses/Erro429" }

  /v1/recortes:
    get:
      tags: [Classificação]
      summary: Classificação por área
      description: "A tabela recortada em três faixas (superior/central/inferior) conforme a config do campeonato (ex.: BRA = 6 topo / 7 fundo). Cada item tem a estrutura de `/v1/classificacao`."
      operationId: getRecortes
      parameters:
        - $ref: "#/components/parameters/codCampeonato"
        - name: tipo
          in: query
          required: false
          schema: { type: string, enum: [normal, real], default: normal }
      responses:
        "200":
          description: Classificação em 3 faixas.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      superior: { type: array, items: { $ref: "#/components/schemas/ClassificacaoLinha" } }
                      central: { type: array, items: { $ref: "#/components/schemas/ClassificacaoLinha" } }
                      inferior: { type: array, items: { $ref: "#/components/schemas/ClassificacaoLinha" } }
                  meta:
                    type: object
                    properties:
                      cod_campeonato: { type: integer }
                      tipo: { type: string }
                      total: { type: integer }
        "401": { $ref: "#/components/responses/Erro401" }
        "422": { $ref: "#/components/responses/Erro422" }
        "429": { $ref: "#/components/responses/Erro429" }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "API key: `Authorization: Bearer <key>`"
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key

  parameters:
    codCampeonato:
      name: cod_campeonato
      in: query
      required: true
      description: Código do campeonato (obtido em `/v1/campeonatos`).
      schema: { type: integer, minimum: 1 }
      example: 71
    rodada:
      name: rodada
      in: query
      required: true
      description: Rodada (snapshot). Use `rodada_atual` do campeonato.
      schema: { type: integer, minimum: 0 }
      example: 18

  headers:
    X-RateLimit-Limit:
      description: Limite de requisições por minuto do plano.
      schema: { type: integer }
    X-RateLimit-Remaining:
      description: Requisições restantes na janela atual.
      schema: { type: integer }

  responses:
    Erro401:
      description: API key ausente ou inválida.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Erro" }
          example:
            data: null
            errors:
              - { code: "missing_api_key", message: "Envie sua API key em \"Authorization: Bearer <key>\" ou no header \"X-API-Key\"." }
    Erro404:
      description: Recurso não encontrado.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Erro" }
          example:
            data: null
            errors:
              - { code: "not_found", message: "Clube não encontrado neste campeonato." }
    Erro422:
      description: Parâmetro obrigatório ausente ou inválido.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Erro" }
          example:
            data: null
            errors:
              - { code: "invalid_parameter", field: "cod_campeonato", message: "The cod campeonato field is required." }
    Erro429:
      description: Rate limit / quota do plano atingidos.
      headers:
        Retry-After:
          description: Segundos até liberar.
          schema: { type: integer }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Erro" }
          example:
            data: null
            errors:
              - { code: "rate_limit_exceeded", message: "Limite de requisições atingido para o seu plano." }

  schemas:
    MetaRodada:
      type: object
      properties:
        cod_campeonato: { type: integer }
        rodada: { type: integer }
        total: { type: integer }

    Erro:
      type: object
      properties:
        data: { type: "null" }
        errors:
          type: array
          items:
            type: object
            required: [code, message]
            properties:
              code: { type: string }
              message: { type: string }
              field: { type: string }

    Campeonato:
      type: object
      properties:
        cod_campeonato: { type: integer }
        tipo: { type: string, description: "Código da série/liga (ex.: BRA, BRB)." }
        nome: { type: string }
        sigla: { type: [string, "null"] }
        temporada: { type: [string, "null"] }
        rodada_atual: { type: integer }
        total_rodadas: { type: integer }
        status: { type: string }
        dat_fim: { type: [string, "null"], format: date }

    ClassificacaoLinha:
      type: object
      properties:
        cod_clube: { type: integer }
        nome: { type: string }
        sigla: { type: [string, "null"] }
        escudo: { type: string, format: uri, description: "URL do escudo (a camisa)." }
        posicao: { type: integer, description: "Posição na tabela oficial." }
        posicao_real: { type: [integer, "null"], description: "Posição pela lógica RealLeader." }
        pontos: { type: integer }
        jogos: { type: integer }
        vitorias: { type: integer }
        empates: { type: integer }
        derrotas: { type: integer }
        gols_pro: { type: integer }
        gols_contra: { type: integer }
        saldo_gols: { type: integer }
        aproveitamento: { type: number, description: "Percentual (0–100)." }
        zona: { type: [string, "null"], description: "Código da zona (ex.: sobe); null = meio de tabela." }

    LadoJogo:
      type: object
      properties:
        cod_clube: { type: integer }
        nome: { type: string }
        sigla: { type: [string, "null"] }
        escudo: { type: string, format: uri }
        gols: { type: [integer, "null"], description: "null se o jogo ainda não foi realizado." }
        posicao: { type: [integer, "null"] }
        fator: { type: [number, "null"], description: "Peso/força no mando." }

    Jogo:
      type: object
      properties:
        cod_jogo: { type: integer }
        rodada: { type: integer }
        data: { type: [string, "null"], description: "Data/hora do jogo (Y-m-d H:i:s)." }
        status: { type: string, description: "Código de status (FIN, AGN, EAN, ...)." }
        mandante: { $ref: "#/components/schemas/LadoJogo" }
        visitante: { $ref: "#/components/schemas/LadoJogo" }

    PesoRealLinha:
      type: object
      properties:
        cod_clube: { type: integer }
        nome: { type: string }
        sigla: { type: [string, "null"] }
        escudo: { type: string, format: uri }
        mando: { type: string, enum: [casa, fora] }
        peso_rl: { type: number, description: "Peso RealLeader composto." }
        peso_adv: { type: number, description: "Força média dos adversários enfrentados." }
        merito: { type: number, description: "Ajuste (mérito) do fator." }
        aproveitamento: { type: number }
        jogos: { type: integer }
        tier: { type: string, enum: [top, med, min] }

    PontuacaoRealLinha:
      type: object
      properties:
        cod_clube: { type: integer }
        nome: { type: string }
        sigla: { type: [string, "null"] }
        escudo: { type: string, format: uri }
        mando: { type: string, enum: [casa, fora] }
        adversario: { type: [string, "null"] }
        sigla_adversario: { type: [string, "null"] }
        escudo_adversario: { type: [string, "null"], format: uri }
        gols_pro: { type: [integer, "null"] }
        gols_contra: { type: [integer, "null"] }
        resultado: { type: [string, "null"], enum: [V, E, D, null] }
        pontos: { type: [integer, "null"], description: "Pontos normais na rodada (0/1/3)." }
        peso: { type: [number, "null"], description: "Peso do adversário." }
        prl: { type: [number, "null"], description: "Pontuação RealLeader = pontos + peso." }
        status_jogo: { type: string }

    ProbabilidadePosicao:
      type: object
      properties:
        cod_clube: { type: integer }
        nome: { type: string }
        sigla: { type: [string, "null"] }
        escudo: { type: string, format: uri }
        prob_por_posicao:
          type: object
          description: "Mapa posição (1..N) => probabilidade %."
          additionalProperties: { type: number }

    CenarioLinha:
      type: object
      properties:
        posicao: { type: integer }
        cod_clube: { type: integer }
        nome: { type: string }
        sigla: { type: [string, "null"] }
        escudo: { type: string, format: uri }
        pontos: { type: integer }
        jogos: { type: integer }
        vitorias: { type: integer }
        saldo_gols: { type: integer }
        gols_pro: { type: integer }

    Campanha:
      type: object
      properties:
        pontos: { type: integer }
        pontos_reais: { type: number }
        posicao: { type: [integer, "null"] }
        posicao_real: { type: [integer, "null"] }
        peso: { type: number }

    TimeResumo:
      type: object
      properties:
        cod_clube: { type: integer }
        nome: { type: string }
        sigla: { type: [string, "null"] }
        slug: { type: string }
        escudo: { type: string, format: uri }
        posicao: { type: integer }
        posicao_real: { type: [integer, "null"] }
        pontos: { type: integer }
        pontos_reais: { type: number }
        jogos: { type: integer }
        vitorias: { type: integer }
        empates: { type: integer }
        derrotas: { type: integer }
        gols_pro: { type: integer }
        gols_contra: { type: integer }
        saldo_gols: { type: integer }
        casa: { $ref: "#/components/schemas/Campanha" }
        fora: { $ref: "#/components/schemas/Campanha" }
