Bem-vinda/o, explorador de queries

QueryCurious - Porque ate dados merecem um pouco de sarcasmo

Se voce conecta dashboards antes mesmo do cafe, esta pagina foi feita para voce. Descubra como consultar o data warehouse da Lifeapps Solucoes via OData ou MCP sem perder o bom humor ou a governanca.

Descricao do Projeto

QueryCurious e o servico oficial que apresenta o data warehouse da Lifeapps Solucoes via OData. Ele entende filtros, agregacoes, ordenacao e paginacao, devolvendo as entidades que voce ja conhece: DimEmpresa, DimProduto e DimLoja. Tudo protegido por tenant: se o seu token diz que voce pertence a uma empresa, voce so enxerga os dados dela, mesmo que tente ser criativo no $filter.

Objetivo do Projeto

  • Entregar dados analiticos prontos para uso sem exigir manutencao extra.
  • Garantir isolamento e seguranca por tenant direto no token.
  • Oferecer interfaces OData e MCP padronizadas para conexoes plug-and-play.

Fluxo de Autenticacao

O teste definitivo para quem diz que domina APIs:

  1. Solicite um token JWT usando client credentials. Exemplo classico:
    curl -X POST \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "grant_type=client_credentials" \
      -d "client_id=<XXX>" \
      -d "client_secret=<xxxx>" \
      https://sso.lifeappsecommerce.com.br/realms/lifeapps/protocol/openid-connect/token
  2. Copie o valor de access_token devolvido.
  3. Envie em todas as requisicoes:
    Authorization: Bearer <seu_access_token>
  4. O servico extrai o claim empresa. Se ele faltar, voce recebe um aviso simpatico; se estiver presente, as consultas fluem.

Dica rapida: tokens expiram. Renove antes do dashboard reclamar.

Integracao MCP para agentes

O servidor remoto QueryCurious Lifeapps esta disponivel em /mcp para clientes compativeis com Model Context Protocol. Ele entrega o mesmo acesso analitico tenant-scoped da API OData, agora em formato natural para agentes de IA que precisam descobrir schemas, montar consultas e explicar resultados sem adivinhar o data warehouse inteiro.

A autenticacao MCP usa Bearer JWT emitido por uma service account do Keycloak. Cada cliente/agente recebe client_id e client_secret, busca o token com grant_type=client_credentials fora do QueryCurious e envia o header Authorization. O claim empresa continua definindo o tenant, e daily_query_limit continua valendo. Claims opcionais como permissions podem faltar em service accounts; nesse caso apenas nao ha bypass lifegrowth.

O recurso MCP publica um documento PRM em /.well-known/oauth-protected-resource/mcp. Clientes que seguem a especificacao MCP recebem esse link no desafio WWW-Authenticate quando chamam /mcp sem token, descobrem o realm Keycloak e entao usam o Bearer emitido pela service account. Se seu token ainda vier com aud=account, deixe MCP_AUDIENCE vazio ate configurar um audience mapper no Keycloak.

O servidor publica tools como list_entity_sets, describe_entity_set, query_entity_set, aggregate_entity_set e sample_entity_set. Tambem expoe resources de metadata, exemplos OData, politica de billing e prompts prontos como analyze_sales, build_kpi_query e explain_dataset.

Configuracao de cliente

Primeiro obtenha o token no Keycloak:

curl -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=<mcp-client-id>" \
  -d "client_secret=<mcp-client-secret>" \
  https://sso.lifeapps.com.br/realms/lifeapps/protocol/openid-connect/token

Depois use a URL publica do ambiente e envie o token como Bearer:

{
  "mcpServers": {
    "querycurious-lifeapps": {
      "url": "https://querycurious.lifeapps.com.br/mcp",
      "headers": {
        "Authorization": "Bearer ${QUERYCURIOUS_MCP_TOKEN}"
      }
    }
  }
}

Renove o token fora do QueryCurious. Nao existe endpoint nem tool MCP para gerar JWT.

OData e MCP compartilham o mesmo billing. O contador nao liga para a interface, so para os megabytes entregues.

Billing sem terrorismo

Cada resposta analitica em /odata ou via /mcp passa por um contador que mede o volume entregue em megabytes. O total de cada tenant soma ao longo de 24 horas e depois expira sozinho.

Se o token carregar daily_query_limit, usamos esse valor como limite diario. Estourou a cota? Retornamos 429 com a mensagem Daily query limit exceeded e paramos por ai. Precisa de ajuda ou quer ampliar o limite? Contate o suporte Lifeapps.

Tutorial Definitivo de OData

Tudo o que voce precisa para transformar curiosidade em dashboards. Os EntitySets publicados sao:

  • /odata/DimEmpresa
  • /odata/DimProduto
  • /odata/DimLoja

Resposta sempre em JSON, com ate 50 linhas por pagina e isolamento por tenant.

$select - dieta de colunas

Sem $select vem tudo. Use apenas o que importa.

GET /odata/DimEmpresa?$select=nome,erp

GET /odata/DimLoja?$select=nome,ativo&$filter=ativo eq true

Nomes invalidos resultam em 400. Com $apply, cite so colunas agrupadas ou aliases.

$filter - onde a logica brilha

Misture comparacoes, funcoes e operacoes sem perder o controle. Strings precisam de aspas simples, GUIDs devem ser validos, null e case-insensitive.

eq

eq - operador de igualdade. Retorna linhas em que os valores sao iguais.

GET /odata/DimEmpresa?$filter=erp eq 'ERP-X'
ne

ne - operador de diferenca. Remove linhas em que os valores coincidem.

GET /odata/DimEmpresa?$filter=erp ne 'ERP-X'
gt

gt - compara se o valor de uma coluna e maior que o alvo informado.

GET /odata/DimEmpresa?$filter=valorfixosuporte gt 1000
ge

ge - retorna linhas em que o valor e maior ou igual ao limite.

GET /odata/DimEmpresa?$filter=valorfixosuporte ge 1000
lt

lt - verifica valores menores que o parametro escolhido.

GET /odata/DimLoja?$filter=notaavaliacao lt 4
le

le - captura resultados com valor menor ou igual ao limite.

GET /odata/DimLoja?$filter=notaavaliacao le 4
in

in - verifica se o valor da coluna existe dentro de uma lista de literais.

GET /odata/DimEmpresa?$filter=erp in ('ERP-X','ERP-Y','ERP-Z')
contains

contains - encontra linhas onde o texto contem o trecho informado.

GET /odata/DimProduto?$filter=contains(descricao,'Combo')
startswith

startswith - filtra textos que iniciam com o prefixo desejado.

GET /odata/DimProduto?$filter=startswith(descricao,'Kit')
endswith

endswith - valida textos que terminam com o sufixo indicado.

GET /odata/DimProduto?$filter=endswith(descricao,'Premium')
year

year - extrai o ano de uma coluna de data para comparacoes diretas.

GET /odata/DimProduto?$filter=year(data_atualizacao) eq 2025
month

month - extrai o numero do mes (1 a 12) para filtragem.

GET /odata/DimProduto?$filter=month(data_atualizacao) ge 10
day

day - extrai o dia do mes para comparacoes pontuais.

GET /odata/DimLoja?$filter=day(data_inicio_faturamento) lt 15
add / sub / mul / div / mod

Operadores aritmeticos - permitem somar, subtrair, multiplicar, dividir ou calcular resto antes da comparacao.

GET /odata/DimEmpresa?$filter=valorfixosuporte sub percrepasse gt 50

$orderby - colocando ordem no caos

GET /odata/DimProduto?$orderby=data_atualizacao desc

GET /odata/DimLoja?$orderby=ativo desc,nome asc

GET /odata/DimProduto?$filter=disponivel eq true&$orderby=descricao asc

asc e padrao. Depois de $apply, ordene apenas por colunas agrupadas ou aliases.

$top e $skip - paginacao civilizada

GET /odata/DimProduto?$top=20&$orderby=data_atualizacao desc

GET /odata/DimProduto?$skip=50&$top=50

$top maior que 50 rende 400. Combine com $skip para navegar sem drama.

$count - conhecendo o tamanho da festa

GET /odata/DimEmpresa?$filter=erp eq 'ERP-X'&$count=true

A resposta inclui @odata.count com o total antes da paginacao. $apply nao altera o contador.

$apply - agregacoes com estilo

GET /odata/DimProduto?$apply=
  groupby((disponivel),
    aggregate(multiplo_incremento with sum as TotalMultiplo))

GET /odata/DimProduto?$apply=
  groupby((disponivel),
    aggregate(id with countdistinct as ProdutosUnicos))

GET /odata/FatoPedido?$filter=year(datapedido) eq 2026 and month(datapedido) eq 6&
  $apply=groupby((),
    aggregate(dim_cliente with countdistinct as ClientesDistintos))

GET /odata/DimLoja?$filter=ativo eq true&
  $apply=groupby((ativo),
    aggregate(notaavaliacao with avg as AvgRating))

GET /odata/DimProduto?
  $filter=disponivel eq true and multiplo_incremento gt 0&
  $apply=groupby((disponivel),
    aggregate(multiplo_incremento with sum as TotalMultiplo))&
  $select=disponivel,TotalMultiplo&
  $orderby=TotalMultiplo desc&
  $top=10

Use parenteses duplos em groupby, filtre antes de agregar e, apos $apply, selecione apenas colunas agrupadas ou aliases.

Combos dignos de daily

GET /odata/DimEmpresa?
  $select=nome,erp&
  $filter=erp eq 'ERP-X' or contains(nome,'Beta')&
  $orderby=nome asc&
  $top=25&
  $skip=25&
  $count=true
GET /odata/DimProduto?
  $filter=disponivel eq true and multiplo_incremento gt 0&
  $apply=groupby((disponivel),
    aggregate(multiplo_incremento with sum as TotalMultiplo))&
  $select=disponivel,TotalMultiplo&
  $orderby=TotalMultiplo desc&
  $top=10

Erros mais comuns? Coluna inexistente (400), sintaxe torta em $filter (400), $apply sem groupby valido (400) e $top acima de 50 (400). Tudo em modo somente leitura: o maximo que voce trava e o proprio script.

Links uteis

Swagger e ReDoc pedem o token Bearer diretamente nos formularios. Para MCP, configure a URL remota no cliente e envie Authorization: Bearer ${QUERYCURIOUS_MCP_TOKEN}.