package app import ( "strings" "unicode" "unicode/utf8" ) const ( QuestionLayerL1 = "L1" QuestionLayerL2 = "L2" QuestionLayerL3 = "L3" QuestionLayerL4 = "L4" QuestionLayerL5 = "L5" QuestionIntentInformational = "informational" QuestionIntentEvaluative = "evaluative" QuestionIntentDecisional = "decisional" QuestionSourceManual = "manual" QuestionSourceCombination = "combination" QuestionSourceAIDistill = "ai_distill" ) type ClassifiedQuestion struct { Text string `json:"text"` Layer string `json:"layer"` Intent string `json:"intent"` } func normalizeQuestionText(text string) string { trimmed := strings.TrimSpace(text) if trimmed == "" { return "" } return strings.Map(func(r rune) rune { if unicode.IsPunct(r) { return -1 } return r }, trimmed) } func normalizeQuestionKey(text string) string { return strings.ToLower(normalizeQuestionText(text)) } func isValidQuestionLayer(value string) bool { switch strings.TrimSpace(value) { case QuestionLayerL1, QuestionLayerL2, QuestionLayerL3, QuestionLayerL4, QuestionLayerL5: return true default: return false } } func isValidQuestionIntent(value string) bool { switch strings.TrimSpace(value) { case QuestionIntentInformational, QuestionIntentEvaluative, QuestionIntentDecisional: return true default: return false } } func isValidExternalQuestionSource(value string) bool { switch strings.TrimSpace(value) { case QuestionSourceManual, QuestionSourceCombination, QuestionSourceAIDistill: return true default: return false } } func inferQuestionIntent(text string) string { normalized := strings.ToLower(strings.TrimSpace(text)) if containsAny(normalized, []string{"哪家好", "哪个好", "怎么选", "推荐", "哪里买", "适合谁", "多少钱", "报价", "购买"}) { return QuestionIntentDecisional } if containsAny(normalized, []string{"对比", "区别", "哪个更", "优劣", "测评", "排名", "值不值", "替代", "竞品"}) { return QuestionIntentEvaluative } return QuestionIntentInformational } func inferQuestionLayer(text, brandName string, competitorNames []string) string { normalized := strings.ToLower(strings.TrimSpace(text)) brandHits := 0 if containsFold(normalized, brandName) { brandHits++ } for _, competitor := range competitorNames { if containsFold(normalized, competitor) { brandHits++ } } if brandHits >= 2 || containsAny(normalized, []string{"对比", "区别", "哪个更", "优劣", "竞品", "替代"}) { return QuestionLayerL5 } if brandHits == 1 { return QuestionLayerL1 } if containsAny(normalized, []string{"北京", "上海", "广州", "深圳", "杭州", "成都", "合肥", "华东", "华南", "华北", "中小企业", "企业", "团队", "老板", "新手", "预算", "价格", "费用", "场景", "本地"}) { return QuestionLayerL4 } if containsAny(normalized, []string{"痛点", "问题", "踩坑", "解决", "失败", "难", "风险", "怎么办"}) { return QuestionLayerL3 } return QuestionLayerL2 } func classifyQuestionText(text, brandName string, competitorNames []string) ClassifiedQuestion { trimmed := normalizeQuestionText(text) return ClassifiedQuestion{ Text: trimmed, Layer: inferQuestionLayer(trimmed, brandName, competitorNames), Intent: inferQuestionIntent(trimmed), } } func questionLooksValid(text string) bool { trimmed := normalizeQuestionText(text) if utf8.RuneCountInString(trimmed) < 4 { return false } hasLetterOrNumber := false for _, r := range trimmed { if unicode.IsLetter(r) || unicode.IsNumber(r) { hasLetterOrNumber = true break } } if !hasLetterOrNumber { return false } return containsAny(strings.ToLower(trimmed), []string{"?", "?", "什么", "如何", "怎么", "哪", "为什么", "是否", "能不能", "适合", "区别", "对比", "推荐"}) } func containsAny(value string, needles []string) bool { for _, needle := range needles { if strings.Contains(value, strings.ToLower(strings.TrimSpace(needle))) { return true } } return false } func containsFold(value, needle string) bool { needle = strings.ToLower(strings.TrimSpace(needle)) return needle != "" && strings.Contains(value, needle) }