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:
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;statusemactiveouapproved;- 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:
- preserve
offerIde a ordem na resposta; - versiona a estratégia em
algorithmVersion; - atualize testes de
functions/tests/feed_functions.test.js; - verifique se novas queries exigem índice;
- se incluir campanhas, valide explicitamente os estados e janelas da campanha.