import type { SearchResult } from "./types"; const NEGATIVE_WORDS = [ "不好用", "雷", "踩雷", "失望", "問題", "壞掉", "客服", "退款", "詐騙", "不推", "有人遇過嗎", "怎麼辦", ]; const QUESTION_WORDS = [ "有人用過", "好用嗎", "推薦嗎", "值得買嗎", "怎麼選", "求推薦", "請問", "疑問", ]; export interface NotifyRuleContext { brandTerms?: string[]; productTerms?: string[]; accountTerms?: string[]; competitorTerms?: string[]; manualHighPriority?: boolean; } export interface NotifyMatch { matched: boolean; rules: string[]; } function blob(result: SearchResult): string { return `${result.title} ${result.snippet} ${result.url}`.toLowerCase(); } export function evaluateNotifyRules( result: SearchResult, ctx: NotifyRuleContext ): NotifyMatch { const text = blob(result); const rules: string[] = []; if (ctx.manualHighPriority) rules.push("high_priority_keyword"); for (const term of ctx.brandTerms ?? []) { if (term && text.includes(term.toLowerCase())) rules.push("brand"); } for (const term of ctx.productTerms ?? []) { if (term && text.includes(term.toLowerCase())) rules.push("product"); } for (const term of ctx.accountTerms ?? []) { if (term && text.includes(term.toLowerCase().replace(/^@/, ""))) rules.push("account"); } for (const term of ctx.competitorTerms ?? []) { if (term && text.includes(term.toLowerCase())) rules.push("competitor"); } for (const w of NEGATIVE_WORDS) { if (text.includes(w)) rules.push("negative_word"); } for (const w of QUESTION_WORDS) { if (text.includes(w)) rules.push("question_word"); } return { matched: rules.length > 0, rules: [...new Set(rules)] }; } export function filterNotifiableResults( results: SearchResult[], ctx: NotifyRuleContext ): Array { return results .map((r) => { const verdict = evaluateNotifyRules(r, ctx); return verdict.matched ? { ...r, matchedRules: verdict.rules } : null; }) .filter((r): r is SearchResult & { matchedRules: string[] } => r !== null); }