Skip to main content

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:

  1. Short shelf life - may expire before gifting
  2. Personal taste issues - recipient may not like specific foods
  3. Dietary restrictions - allergies, preferences unknown
  4. 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"):

CategoryProduct TypeDescription
EhtedIlu ja stiilJewelry - universally giftable
IlutarvikudIlu ja stiilBeauty accessories
Kirja ja kirjutusvahendite kinkekomplektidKingitusedWriting gift sets
PeotarbedKingitusedParty supplies
KruusidKodu ja aedMugs - everyday usable
Pehmed mänguasjadMängudSoft toys
Pere- ja seltskonnamängudMängudFamily/party games
Pusled 2-99 tükkiMängudSmall puzzles
Pusled 100-999 tükkiMängudMedium puzzles
Pusled 1000-1499 tükkiMängudLarge puzzles
Pusled 1500+ tükkiMängudExtra large puzzles
ra kinkekaardidKinkekaartStore gift cards

Book Gift Categories (BOOK_GIFT_CATEGORIES)

These categories are only included when user explicitly requests books as gifts:

CategoryProduct TypeDescription
LastekirjandusRaamatChildren's literature
MuinasjutudRaamatFairy tales
Pisipõnnidele, vanus 0-3RaamatBooks for toddlers
Suhted ja seksuaalsusRaamatRelationships & intimacy

Fallback Categories (FALLBACK_GIFT_CATEGORY_HINTS)

Safe, neutral options used when primary categories return insufficient results:

CategoryDescription
KruusidEveryday usable
Pere- ja seltskonnamängudWorks for most ages/contexts
Küünlad ja lõhnastajadSafe, atmospheric
KinkeraamatudGift books - neutral
EhtedJewelry - 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:

  1. Generic gift rule - Uses PRIORITIZED_GIFT_CATEGORIES
  2. Book gift rule - Uses BOOK_GIFT_CATEGORIES
  3. 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 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?

  1. Flexibility - Users may want unexpected categories
  2. Discovery - Lower-priority items can still surface
  3. Fallback - If priority categories have no stock, others fill in

Why Separate Book Categories?

  1. Intent clarity - Books are appropriate for book-specific requests
  2. Result relevance - Generic gift searches shouldn't return books
  3. User expectation - "kingitus" implies physical gift items, not books

Why Exclude Edibles Entirely?

  1. Shelf life - Food/drinks may expire before gifting
  2. Personal taste - Recipient preferences unknown
  3. Dietary restrictions - Can't assume no allergies
  4. Market fit - Not typical gift items in Estonian retail

Modifying Configuration

Adding Prioritized Categories

  1. Edit PRIORITIZED_GIFT_CATEGORIES in gift-defaults.ts
  2. Categories must match exact names in category_product_type.csv
  3. Order matters - higher in list = higher priority

Adding Book Gift Categories

  1. Edit BOOK_GIFT_CATEGORIES in gift-defaults.ts
  2. Only add Raamat product type categories
  3. Categories will be auto-filtered from non-book searches

Adding Excluded Edible Categories

  1. Edit EXCLUDED_EDIBLE_CATEGORIES in gift-defaults.ts
  2. 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.

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