thread-master/backend/internal/library/knowledge/patrol_input.go

192 lines
5.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
TargetAudience 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),
TargetAudience: strings.TrimSpace(brand.TargetAudience),
MatchTags: matchTagsFromBrand(brand),
Questions: append([]string{}, brand.ResearchMap.Questions...),
Pillars: append([]string{}, brand.ResearchMap.Pillars...),
PatrolKeywords: append([]string{}, brand.ResearchMap.PatrolKeywords...),
}
}
// OverlayResearchMap copies topic-level research map fields onto patrol input.
func OverlayResearchMap(in PatrolTagInput, audienceSummary string, questions, pillars, patrolKeywords []string) PatrolTagInput {
if strings.TrimSpace(audienceSummary) != "" {
in.AudienceSummary = strings.TrimSpace(audienceSummary)
}
if len(questions) > 0 {
in.Questions = append([]string{}, questions...)
}
if len(pillars) > 0 {
in.Pillars = append([]string{}, pillars...)
}
if len(patrolKeywords) > 0 {
in.PatrolKeywords = append([]string{}, patrolKeywords...)
}
return in
}
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
}
var patrolCoreValueTokens = []string{
"敏感", "無香", "抗敏", "低敏", "香精", "香料", "香味", "過敏", "皮膚",
"化療", "癌症", "乳癌", "荷爾蒙", "標靶", "病友",
}
func primaryPatrolValueAnchor(in PatrolTagInput) string {
blob := strings.ToLower(strings.TrimSpace(in.ProductName + " " + in.ProductFeatures + " " +
strings.Join(in.MatchTags, " ") + " " + strings.Join(in.Pillars, " ") + " " +
in.AudienceSummary + " " + in.TargetAudience))
priority := []string{"無香", "抗敏", "敏感", "化療", "癌症", "乳癌", "荷爾蒙", "過敏", "香味", "香精"}
for _, token := range priority {
if strings.Contains(blob, token) {
return token
}
}
for _, token := range patrolCoreValueTokens {
if strings.Contains(blob, token) {
return token
}
}
return ""
}
func productCategoryHint(productName, features string) string {
combined := productName + " " + features
for _, hint := range []string{
"洗衣精", "洗衣粉", "衣物", "洗碗精", "洗碗機", "碗盤",
"沐浴乳", "沐浴露", "洗髮精", "洗髮乳", "洗面乳", "洗面奶", "乳液", "精華",
"防曬", "卸妝", "潔面", "身體乳", "洗手乳", "清潔",
} {
if strings.Contains(combined, hint) {
return hint
}
}
return ""
}