package outreach import ( "fmt" "strings" libkg "haixun-backend/internal/library/knowledge" libplacement "haixun-backend/internal/library/placement" brandentity "haixun-backend/internal/model/brand/domain/entity" branddomain "haixun-backend/internal/model/brand/domain/usecase" scanpostusecase "haixun-backend/internal/model/scan_post/domain/usecase" ) func FindGraphNode(nodes []libkg.Node, nodeID string) *libkg.Node { nodeID = strings.TrimSpace(nodeID) if nodeID == "" { return nil } for i := range nodes { if nodes[i].ID == nodeID { return &nodes[i] } } return nil } func BuildPlacementReason(post *scanpostusecase.ScanPostSummary) string { if post == nil { return "" } parts := []string{} if post.Priority == "gold" { parts = append(parts, "雙軌命中(相關+近期)") } else if post.Priority == "recent" { parts = append(parts, "近期軌命中") } if post.SolvedByProduct { parts = append(parts, "產品可解此需求") } if post.ProductFitScore > 0 { parts = append(parts, fmt.Sprintf("產品匹配 %d", post.ProductFitScore)) } return strings.Join(parts, ";") } func MergePlacementReason(base string, node *libkg.Node, post *scanpostusecase.ScanPostSummary) string { parts := []string{} if strings.TrimSpace(base) != "" { parts = append(parts, strings.TrimSpace(base)) } if node != nil { if strings.TrimSpace(node.PlacementValue) != "" { parts = append(parts, "置入價值 "+strings.TrimSpace(node.PlacementValue)) } if node.ProductFitScore > 0 && (post == nil || post.ProductFitScore == 0) { parts = append(parts, fmt.Sprintf("節點產品匹配 %d", node.ProductFitScore)) } } return strings.Join(parts, ";") } func ProductBriefForBrand(brand *branddomain.BrandSummary, productID, searchTag, postText string) string { if brand == nil { return "" } if product := PickProductForOutreach(brand, productID, searchTag, postText); product != nil { merged := libplacement.BuildMergedProductContext(brand.DisplayName, product.ProductContext, product.Label) merged = libplacement.ApplyPlacementURL(merged, product.PlacementURL) if pb := libplacement.ProductBriefFromContext(merged); pb != "" { return pb } } if pb := libplacement.ProductBriefFromContext(brand.ProductContext); pb != "" { return pb } return strings.TrimSpace(brand.ProductBrief) } func PickProductForOutreach(brand *branddomain.BrandSummary, preferredProductID, searchTag, postText string) *branddomain.ProductSummary { if brand == nil || len(brand.Products) == 0 { return nil } entities := make([]brandentity.Product, len(brand.Products)) for i := range brand.Products { entities[i] = brandentity.Product{ ID: brand.Products[i].ID, Label: brand.Products[i].Label, ProductContext: brand.Products[i].ProductContext, MatchTags: append([]string(nil), brand.Products[i].MatchTags...), } } if picked := libplacement.RecommendProductForPost(entities, searchTag, postText, preferredProductID); picked != nil { for i := range brand.Products { if brand.Products[i].ID == picked.ID { return &brand.Products[i] } } } if id := strings.TrimSpace(preferredProductID); id != "" { for i := range brand.Products { if brand.Products[i].ID == id { return &brand.Products[i] } } } return &brand.Products[0] }