import { Language } from '../types' import { FAQEntry } from './types' import { PRESENTER_FAQ } from './presenter-faq' /** * Match a user query against pre-cached FAQ entries. * Returns the best match if score exceeds threshold, or null for LLM fallback. */ export function matchFAQ(query: string, lang: Language): FAQEntry | null { const normalized = query.toLowerCase().trim() const queryWords = normalized.split(/\s+/) let bestMatch: FAQEntry | null = null let bestScore = 0 for (const entry of PRESENTER_FAQ) { let score = 0 // Check keyword matches for (const keyword of entry.keywords) { const kwLower = keyword.toLowerCase() if (kwLower.includes(' ')) { // Multi-word keyword: check if phrase appears in query if (normalized.includes(kwLower)) { score += 3 * entry.priority / 10 } } else { // Single keyword: check word-level match if (queryWords.some(w => w === kwLower || w.startsWith(kwLower) || kwLower.startsWith(w))) { score += 1 } // Also check if keyword appears anywhere in query (partial match) if (normalized.includes(kwLower)) { score += 0.5 } } } // Check if query matches the question text closely const questionText = lang === 'de' ? entry.question_de : entry.question_en const questionWords = questionText.toLowerCase().split(/\s+/) const overlapCount = queryWords.filter(w => w.length > 2 && questionWords.some(qw => qw.includes(w) || w.includes(qw)) ).length if (overlapCount >= 2) { score += overlapCount * 0.5 } // Weight by priority score *= (entry.priority / 10) if (score > bestScore) { bestScore = score bestMatch = entry } } // Threshold: need meaningful match to avoid false positives // Require at least 2 keyword hits or strong phrase match if (bestScore < 1.5) { return null } return bestMatch } /** * Get FAQ answer text in the requested language */ export function getFAQAnswer(entry: FAQEntry, lang: Language): string { return lang === 'de' ? entry.answer_de : entry.answer_en }