Pular para conteúdo

Ofertas e feed

Modelo de oferta

A coleção offers é a fonte atual para ofertas regulares e de campanha. Campos importantes:

  • vínculo: partnerId, restaurantId, campaignId, source;
  • conteúdo: title, description, preços, imagem e tipo;
  • ciclo: status, startsAt, endsAt;
  • curadoria: visibilityTier, curatedBy, curatedAt, motivos/notas;
  • autoria: createdBy, createdByRole, createdByAdmin, createdOnBehalfOf*;
  • quota: couponUsageLimit, couponReservedCount, couponUsedCount.

Feed offer-first

O app chama OfferRepositoryImpl.getFeedOffers. Com latitude/longitude disponíveis, o repository chama getOfferFeed e preserva a ordem retornada pelo backend.

sequenceDiagram
  participant UI as HomeFeedCubit/UI
  participant R as OfferRepositoryImpl
  participant F as getOfferFeed
  participant DB as Firestore
  UI->>R: getFeedOffers(lat,lng)
  R->>F: userLat,userLng,limit
  F->>DB: stores + partners + offers regulares
  F-->>R: IDs ordenados + distância + ranking metadata
  R->>DB: busca offers por IDs (chunks <= 30)
  R-->>UI: OfferEntity[] na ordem do backend

Contrato atual de getOfferFeed

Entrada

Obrigatórios:

{
  "userLat": -7.0,
  "userLng": -35.0
}

Opcionais: radiusKm (default 30), limit (default 30), categoryFilter, filterIds, searchText e excludeOfferIds.

Default comprovado no código

Documentos de planejamento antigos sugerem raio inicial de 8 km. A Function atual usa radiusKm = 30; esta documentação segue a implementação.

Elegibilidade atual

A Function seleciona ofertas com:

  • source == regular;
  • status em active ou approved;
  • quota ainda disponível;
  • vínculo válido com store;
  • coordenadas válidas;
  • datas de início/fim compatíveis;
  • distância dentro do raio.

Campanhas possuem lógica e peso previstos no algoritmo, mas a query atual do feed busca somente ofertas regulares. Não presuma que ofertas source=campaign entram no feed principal sem alterar essa consulta.

Ranking geo_weighted_balanced_v1

Score base 100, com boosts observados:

Critério Peso
até 1 km +40
1–3 km +30
3–8 km +15
desconto >= 25% +25
premium +20
campanha +15
criada nos últimos 7 dias +10
trending/redemption recente +10
store com categorias +8

A seleção é aleatória ponderada, não uma ordenação determinística por score.

Diversidade

Em janela de 10 itens, o algoritmo tenta limitar:

  • até 2 ofertas do mesmo restaurante;
  • até 4 da mesma categoria;
  • no máximo 2 restaurantes iguais consecutivos;
  • no máximo 3 categorias iguais consecutivas.

Se nenhum candidato respeitar as travas, elas são relaxadas para evitar feed vazio.

Fallback local

Se a Function falhar ou não houver coordenadas, OfferRepositoryImpl consulta ofertas regulares ativas/aprovadas diretamente no Firestore, remove esgotadas e ordena por createdAt decrescente. Esse fallback é operacional, não equivalente ao ranking geográfico.

Pontos de extensão

Para mudar ranking sem quebrar o contrato do app:

  1. preserve offerId e a ordem na resposta;
  2. versiona a estratégia em algorithmVersion;
  3. atualize testes de functions/tests/feed_functions.test.js;
  4. verifique se novas queries exigem índice;
  5. se incluir campanhas, valide explicitamente os estados e janelas da campanha.