Gift Category Prioritization System
Overview
The gift category prioritization system ensures that vague gift queries return the most relevant product categories while maintaining the ability to show all available products. This is achieved through prioritization (reordering), not exclusion.
Key Principles
Prioritize, don't exclude - Categories are reordered so high-priority items appear first, but all other categories remain available lower in the results.
Filter edibles - Edible products (food, drinks, sweets) are always excluded from gift recommendations due to shelf life and personal taste issues.
Configuration Location
app/api/chat/orchestrators/context-orchestrator/gift-defaults.ts
Edible Product Exclusion
Why Exclude Edibles?
Edible products are always excluded from gift searches because:
- Short shelf life - may expire before gifting
- Personal taste issues - recipient may not like specific foods
- Dietary restrictions - allergies, preferences unknown
- Not traditional "gifts" in Estonian retail context
Excluded Edible Product Type
export const EXCLUDED_EDIBLE_PRODUCT_TYPE = 'Joodav ja söödav';
Excluded Edible Categories
export const EXCLUDED_EDIBLE_CATEGORIES: readonly string[] = [
'Maiustused', // Sweets/candy
'Tee, kohv ja kakao', // Tea, coffee, cocoa
'Šokolaad', // Chocolate
'Kommid', // Candies
'Küpsised', // Cookies
'Joogid', // Drinks
'Toiduained', // Food items
'Kohv', // Coffee
'Tee', // Tea
];
Edible Filtering Functions
isExcludedEdibleProductType(productType)
Checks if a product type is an excluded edible type.
isExcludedEdibleProductType('Joodav ja söödav'); // true
isExcludedEdibleProductType('Kingitused'); // false
isExcludedEdibleCategory(category)
Checks if a category is an excluded edible category.
isExcludedEdibleCategory('Maiustused'); // true
isExcludedEdibleCategory('Šokolaad'); // true
isExcludedEdibleCategory('Kruusid'); // false
filterOutEdibleProducts(products)
Filters out edible products from search results.
const products = [
{ title: 'Mug', product_type: 'Kodu ja aed' },
{ title: 'Chocolate', product_type: 'Joodav ja söödav' },
{ title: 'Candle', product_type: 'Kodu ja aed' }
];
const filtered = filterOutEdibleProducts(products);
// Returns: [{ title: 'Mug', ... }, { title: 'Candle', ... }]
Product Type Configuration
Vague Gift Product Types
For queries with no specific recipient, occasion, or hobbies, only these product types are appropriate:
export const VAGUE_GIFT_PRODUCT_TYPES: ReadonlySet<string> = new Set([
'Kingitused', // Generic gift items - first priority
'Kodu ja aed', // Home items - universally useful
'Mängud', // Games/puzzles - safe gift choice
'Kinkekaart', // Gift cards - always appropriate
]);
isVagueGiftAppropriateType(productType)
Used to override LLM decisions that return inappropriate types for generic gift requests.
isVagueGiftAppropriateType('Kingitused'); // true
isVagueGiftAppropriateType('Raamat'); // false (books need explicit request)
Default Gift Hints
Broad product type categories for gift searches:
export const DEFAULT_GIFT_HINTS: readonly string[] = [
'Kingitused', // Cards, packaging, souvenirs
'Kodu ja aed', // Candles, mugs, home items
'Ilu ja stiil', // Jewelry, cosmetics, accessories
'Mängud', // Games, puzzles, toys
'Kinkekaart', // Gift cards
'Kontorikaup', // Notebooks, office gifts
'Tehnika' // Tech accessories
];
Category Definitions
Prioritized Gift Categories (PRIORITIZED_GIFT_CATEGORIES)
These categories are prioritized for general gift queries (e.g., "otsin kingitust", "gift ideas"):
| Category | Product Type | Description |
|---|---|---|
Ehted | Ilu ja stiil | Jewelry - universally giftable |
Ilutarvikud | Ilu ja stiil | Beauty accessories |
Kirja ja kirjutusvahendite kinkekomplektid | Kingitused | Writing gift sets |
Peotarbed | Kingitused | Party supplies |
Kruusid | Kodu ja aed | Mugs - everyday usable |
Pehmed mänguasjad | Mängud | Soft toys |
Pere- ja seltskonnamängud | Mängud | Family/party games |
Pusled 2-99 tükki | Mängud | Small puzzles |
Pusled 100-999 tükki | Mängud | Medium puzzles |
Pusled 1000-1499 tükki | Mängud | Large puzzles |
Pusled 1500+ tükki | Mängud | Extra large puzzles |
ra kinkekaardid | Kinkekaart | Store gift cards |
Book Gift Categories (BOOK_GIFT_CATEGORIES)
These categories are only included when user explicitly requests books as gifts:
| Category | Product Type | Description |
|---|---|---|
Lastekirjandus | Raamat | Children's literature |
Muinasjutud | Raamat | Fairy tales |
Pisipõnnidele, vanus 0-3 | Raamat | Books for toddlers |
Suhted ja seksuaalsus | Raamat | Relationships & intimacy |
Fallback Categories (FALLBACK_GIFT_CATEGORY_HINTS)
Safe, neutral options used when primary categories return insufficient results:
| Category | Description |
|---|---|
Kruusid | Everyday usable |
Pere- ja seltskonnamängud | Works for most ages/contexts |
Küünlad ja lõhnastajad | Safe, atmospheric |
Kinkeraamatud | Gift books - neutral |
Ehted | Jewelry - elegant option |
Helper Functions
getPrioritizedGiftCategories(includeBooks: boolean)
Returns prioritized gift categories based on whether books are wanted.
// Non-book gift search
const categories = getPrioritizedGiftCategories(false);
// Returns: ['Ehted', 'Ilutarvikud', 'Kruusid', ...]
// Book gift search
const categoriesWithBooks = getPrioritizedGiftCategories(true);
// Returns: [...PRIORITIZED_GIFT_CATEGORIES, ...BOOK_GIFT_CATEGORIES]
isBookGiftCategory(category: string)
Checks if a category is book-related.
isBookGiftCategory('Lastekirjandus'); // true
isBookGiftCategory('Kruusid'); // false
filterOutBookCategories(categories: string[])
Filters out book categories from a list. Used for non-book gift searches.
const allCategories = ['Kruusid', 'Lastekirjandus', 'Ehted', 'Muinasjutud'];
const filtered = filterOutBookCategories(allCategories);
// Returns: ['Kruusid', 'Ehted']
cloneDefaultGiftHints()
Returns a mutable copy of DEFAULT_GIFT_HINTS.
const hints = cloneDefaultGiftHints();
// Returns: ['Kingitused', 'Kodu ja aed', 'Ilu ja stiil', ...]
cloneFallbackGiftCategoryHints()
Returns a mutable copy of FALLBACK_GIFT_CATEGORY_HINTS.
const fallbacks = cloneFallbackGiftCategoryHints();
// Returns: ['Kruusid', 'Pere- ja seltskonnamängud', ...]
How Prioritization Works
The applyHintPriorityList function reorders categories without excluding:
Input: ['Küünlad', 'Maiustused', 'Ehted', 'Kruusid', 'Tee']
Priority: ['Ehted', 'Kruusid']
Output: ['Ehted', 'Kruusid', 'Küünlad', 'Maiustused', 'Tee']
↑ Priority items first ↑ Rest preserved in original order
Query Examples
Generic Gift Query
User: "otsin kingitust" (looking for a gift)
Triggered Rule: Generic gift keywords
Applied Categories: PRIORITIZED_GIFT_CATEGORIES
Book Categories: FILTERED OUT (no book keyword detected)
Edible Products: FILTERED OUT (always excluded)
Result: Jewelry, mugs, games, puzzles prioritized (no books, no food)
Book Gift Query
User: "kinkeraamat lapsele" (gift book for child)
Triggered Rule: Book gift keywords
Applied Categories: BOOK_GIFT_CATEGORIES + Kinkeraamatud, Luule
Edible Products: FILTERED OUT (always excluded)
Result: Children's literature, fairy tales, gift books
Occasion-Specific Gift Query
User: "sünnipäevakingitus" (birthday gift)
Triggered Rule: Birthday occasion
Applied Categories: Occasion-specific priorities
Book Categories: May be included (context-dependent)
Edible Products: FILTERED OUT (always excluded)
Result: Games, cosmetics, puzzles, etc. based on occasion rules
Integration Points
hint-prioritization.ts
The prioritization rules are applied in:
app/api/chat/services/context-understanding/hint-prioritization.ts
Key integration points:
- Generic gift rule - Uses
PRIORITIZED_GIFT_CATEGORIES - Book gift rule - Uses
BOOK_GIFT_CATEGORIES - Book filtering - Calls
filterOutBookCategories()for non-book searches
Book Filtering Logic
// In applyRules function
const hasBookKeyword = normalizedValue.includes('raamat') ||
normalizedValue.includes('book') ||
normalizedValue.includes('kinkeraamat');
if (!hasBookKeyword && context.categoryHints) {
context.categoryHints = filterOutBookCategories(context.categoryHints);
}
Edible Filtering in Search
Edible products are filtered at the search result level:
// In product search handler
const results = await searchProducts(query);
const filtered = filterOutEdibleProducts(results);
Design Rationale
Why Prioritize Instead of Exclude?
- Flexibility - Users may want unexpected categories
- Discovery - Lower-priority items can still surface
- Fallback - If priority categories have no stock, others fill in
Why Separate Book Categories?
- Intent clarity - Books are appropriate for book-specific requests
- Result relevance - Generic gift searches shouldn't return books
- User expectation - "kingitus" implies physical gift items, not books
Why Exclude Edibles Entirely?
- Shelf life - Food/drinks may expire before gifting
- Personal taste - Recipient preferences unknown
- Dietary restrictions - Can't assume no allergies
- Market fit - Not typical gift items in Estonian retail
Modifying Configuration
Adding Prioritized Categories
- Edit
PRIORITIZED_GIFT_CATEGORIESingift-defaults.ts - Categories must match exact names in
category_product_type.csv - Order matters - higher in list = higher priority
Adding Book Gift Categories
- Edit
BOOK_GIFT_CATEGORIESingift-defaults.ts - Only add Raamat product type categories
- Categories will be auto-filtered from non-book searches
Adding Excluded Edible Categories
- Edit
EXCLUDED_EDIBLE_CATEGORIESingift-defaults.ts - All gift searches will automatically exclude these
Logging
Book Category Filtering
📚 BOOK CATEGORIES FILTERED (non-book gift search):
filteredCount: 2
remainingCategories: ['Ehted', 'Kruusid', 'Pere- ja seltskonnamängud', ...]
reason: 'No book keyword detected - filtering book gift categories'
Edible Product Filtering
Edible filtering happens silently at the search layer but can be observed in product counts.
Related Files
app/api/chat/
├── orchestrators/context-orchestrator/
│ └── gift-defaults.ts # Category definitions & filters
├── services/context-understanding/
│ └── hint-prioritization.ts # Priority rule application
└── handlers/
└── product-search-handler.ts # Edible filtering in results