Skip to main content

Popular Products Filter

The popular products filter enables users to request prioritized , trending, bestselling, or popular items within any product category. The system supports both Estonian and English queries, as well as mixed-language prompts.

Overview

When users search for "popular books" or "populaarseid mänge", the system:

  1. Detects the popularity intent via LLM classification
  2. Propagates the isPopularQuery flag through the pipeline
  3. Filters products by in_popular_list === true in Convex

Language Support

Estonian Keywords

KeywordExample Query
populaarseid"Näita populaarseid raamatuid"
populaarne"Populaarne kingitus"
populaarsemaid"Otsin populaarsemaid mänge"
populaarsemad"Kõige populaarsemad kokaraamatud"
top"Top raamatud"
bestseller"Bestseller krimi"
enim ostetud"Enim ostetud pusled"
parimad müügid"Parimad müügid mängudest"
kõige ostetumad"Kõige ostetumad mängud"

English Keywords

KeywordExample Query
popular"Show me popular books"
bestseller"Bestseller crime novels"
trending"Trending games"
best selling"Best selling puzzles"
top rated"Top rated cookbooks"
most purchased"Most purchased items"

Mixed Language

Mixed language queries work automatically:

QueryDetection
"Popular raamatud"✅ English keyword detected
"Bestseller mängud"✅ Both patterns match
"Trending pusled"✅ English keyword detected

System Flow

1. LLM Detection

The fast classifier (app/api/chat/services/context-understanding/prompts.ts) includes explicit rules:

POPULAARSUSE TUVASTAMINE:
- "populaarseid", "populaarne", "top", "bestseller", "enim ostetud", "parimad müügid" → isPopularQuery: true
- "popular", "bestseller", "trending", "best selling", "top rated" → isPopularQuery: true
- Tavalised päringud ilma populaarsuse viiteta → isPopularQuery: false

2. Context Propagation

The isPopularQuery flag flows through multiple layers:

Key files:

  • app/api/chat/services/context-understanding/fast-classifier.ts - LLM response parsing
  • app/api/chat/services/context-understanding/classifier-context.ts - Context building
  • app/api/chat/services/query-rewriting/index.ts - Query variation metadata

3. Query Rewriting

In query-rewriting/index.ts, the popular flag is propagated to all query variations:

// POPULAR QUERY FLAG PROPAGATION
if (context.isPopularQuery) {
finalVariations.forEach(v => {
v.metadata.isPopular = true;
});
}

4. Product Search Service

The ProductSearchService.searchMulti() passes the flag to Convex:

// product-search.ts line 166
...(variation.metadata.isPopular ? { isPopular: true } : {}),

5. Convex Database Filter

Two locations apply the popular filter:

Text Search Path (convex/actions/searchProducts.ts)

// Lines 2287-2313
if (isPopular && !allowGiftAccessories && pool.length > 0) {
const beforePopularFilter = pool.length;
const popularPool = pool.filter(p => p.in_popular_list === true);

// Graceful fallback if no popular products
if (popularPool.length === 0) {
console.warn('🌟 No popular products in pool, using all');
} else {
pool = popularPool;
poolSource = 'popular';
}
}

Category Search Path (convex/queries/getProduct.ts)

// Lines 195-211
if (csv_category) {
let queryBuilder = ctx.db
.query('products')
.withIndex('by_csv_category', (q) => q.eq('csv_category', csv_category));

if (applyPopularFilter) {
// Use proper Convex filter builder syntax
queryBuilder = queryBuilder.filter((q) => q.eq(q.field('in_popular_list'), true));
}

const results = await queryBuilder.take(effectiveLimit);
return results;
}

Database Schema

The products table includes the in_popular_list field:

// convex/schema.ts
products: defineTable({
// ... other fields
in_popular_list: v.optional(v.boolean()),
// ...
})
.index("by_in_popular_list", ["in_popular_list"])

The index by_in_popular_list enables efficient queries when filtering by popularity without a category constraint.

Search Orchestrator Behavior

Book Category Path Bypass

When isPopularQuery === true, the optimized Book Category Path is bypassed:

// search-orchestrator.ts lines 524-525
const isBookCategoryPath = (isExplicitBookRoute || (hasBookProductType && hasBookCategory)) &&
giftContext.isPopularQuery !== true; // Popular queries use standard path

This ensures popular book queries go through the standard text search path where the isPopular flag is properly handled via ProductSearchService.searchMulti().

Category Search Path

For category-specific popular queries (via CategorySearchService):

// search-orchestrator.ts line 571
isPopular: giftContext.isPopularQuery === true,

Multi-Turn Preservation

The isPopularQuery flag is automatically preserved across follow-up turns, including "show more" requests and page reloads.

Preservation Flow

Key Files

FilePurpose
persist-context.tsSaves isPopularQuery to Convex
fetch-stored-context.tsRestores isPopularQuery after page reload
apply-preservation.tsMerges flag from lastSearchParams OR storedGiftContext
setConversationContext.tsConvex mutation with isPopularQuery field

Example Scenario

Turn 1: "Show me popular crime books"
→ isPopularQuery: true (from LLM)
→ Returns popular crime books
→ Persists isPopularQuery to Convex

Turn 2: "Show more"
→ Restores isPopularQuery: true from lastSearchParams
→ Returns MORE popular crime books (not just any crime books)

[Page Reload]

Turn 3: "Show more"
→ Restores isPopularQuery: true from storedGiftContext
→ Still returns only popular crime books ✅

Graceful Fallback

If no popular products exist for a category, the system falls back to showing all products:

This ensures users always see results, even if no products are marked as popular in that category.

Testing

A test script exists at scripts/test-popular-filter.ts to verify:

  1. Database statistics (how many products are marked popular)
  2. Filter effectiveness with/without the isPopular flag
  3. API integration via quick prompt simulation

Run with:

npx tsx scripts/test-popular-filter.ts

Bug Fix History

December 2024: Convex Filter Syntax Fix

Issue: The popular filter returned 0 results when combined with a category filter.

Root Cause: Incorrect Convex filter syntax in getProduct.ts:

// ❌ WRONG - JavaScript arrow function doesn't work with Convex filter
queryBuilder = queryBuilder.filter((p: any) => p.in_popular_list === true);

Fix: Use proper Convex filter builder syntax:

// ✅ CORRECT - Convex filter builder
queryBuilder = queryBuilder.filter((q) => q.eq(q.field('in_popular_list'), true));

Impact:

  • Before: "Popular crime books" returned 0 products (broken)
  • After: "Popular crime books" correctly returns only popular crime books