package knowledge import ( "encoding/json" "strings" branddomain "haixun-backend/internal/model/brand/domain/usecase" ) // PatrolTagInput grounds海巡 tag in研究地圖與品牌/產品輸入。 type PatrolTagInput struct { BrandName string ProductName string ProductFeatures string AudienceSummary string MatchTags []string Questions []string Pillars []string PatrolKeywords []string } type productContextFields struct { Brand string `json:"brand"` Product string `json:"product"` Features string `json:"features"` } func parseProductContextFields(raw string) productContextFields { raw = strings.TrimSpace(raw) if raw == "" { return productContextFields{} } var parsed productContextFields if err := json.Unmarshal([]byte(raw), &parsed); err != nil { return productContextFields{Features: raw} } parsed.Brand = strings.TrimSpace(parsed.Brand) parsed.Product = strings.TrimSpace(parsed.Product) parsed.Features = strings.TrimSpace(parsed.Features) return parsed } func PatrolTagInputFromBrand(brand *branddomain.BrandSummary, productBrief string) PatrolTagInput { if brand == nil { return PatrolTagInput{} } fields := parseProductContextFields(brand.ProductContext) productName := productLabelFromBrand(brand) if productName == "" { productName = fields.Product } brandName := strings.TrimSpace(brand.DisplayName) if brandName == "" { brandName = fields.Brand } features := fields.Features if features == "" { features = strings.TrimSpace(productBrief) } if features == "" { features = strings.TrimSpace(brand.ProductBrief) } return PatrolTagInput{ BrandName: brandName, ProductName: productName, ProductFeatures: features, AudienceSummary: strings.TrimSpace(brand.ResearchMap.AudienceSummary), MatchTags: matchTagsFromBrand(brand), Questions: append([]string{}, brand.ResearchMap.Questions...), Pillars: append([]string{}, brand.ResearchMap.Pillars...), PatrolKeywords: append([]string{}, brand.ResearchMap.PatrolKeywords...), } } func productLabelFromBrand(brand *branddomain.BrandSummary) string { if brand == nil { return "" } productID := strings.TrimSpace(brand.ProductID) if productID == "" { return "" } for _, item := range brand.Products { if item.ID == productID { label := strings.TrimSpace(item.Label) if label != "" { return label } return parseProductContextFields(item.ProductContext).Product } } return "" } func matchTagsFromBrand(brand *branddomain.BrandSummary) []string { if brand == nil { return nil } productID := strings.TrimSpace(brand.ProductID) seen := map[string]struct{}{} out := []string{} add := func(tag string) { tag = strings.TrimSpace(tag) if tag == "" { return } if _, ok := seen[tag]; ok { return } seen[tag] = struct{}{} out = append(out, tag) } for _, item := range brand.Products { if productID != "" && item.ID != productID { continue } for _, tag := range item.MatchTags { add(tag) } if productID != "" { break } } return out } func (in PatrolTagInput) HasResearchMap() bool { return len(in.Questions) > 0 || len(in.Pillars) > 0 || strings.TrimSpace(in.AudienceSummary) != "" } func (in PatrolTagInput) HasProductContext() bool { return strings.TrimSpace(in.ProductName) != "" || strings.TrimSpace(in.ProductFeatures) != "" || len(in.MatchTags) > 0 } func productCategoryHint(productName, features string) string { combined := productName + " " + features for _, hint := range []string{ "沐浴乳", "沐浴露", "洗髮精", "洗髮乳", "洗面乳", "洗面奶", "乳液", "精華", "防曬", "卸妝", "潔面", "身體乳", "洗手乳", "清潔", } { if strings.Contains(combined, hint) { return hint } } return "" }