thread-master/old/backend/internal/library/viral/mission_topic.go

132 lines
3.1 KiB
Go
Raw 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 viral
import (
"strings"
"unicode"
"haixun-backend/internal/library/placement"
)
type topicMatchTerms struct {
Anchors []string
Terms []string
}
func missionTopicMatchTerms(seed, label string, hints []string) topicMatchTerms {
seenAnchors := map[string]struct{}{}
seenTerms := map[string]struct{}{}
out := topicMatchTerms{}
addTerm := func(term string, anchor bool) {
term = normaliseTopicToken(term)
if term == "" || isGenericTopicToken(term) {
return
}
if _, ok := seenTerms[term]; !ok {
seenTerms[term] = struct{}{}
out.Terms = append(out.Terms, term)
}
if anchor {
if _, ok := seenAnchors[term]; !ok {
seenAnchors[term] = struct{}{}
out.Anchors = append(out.Anchors, term)
}
}
}
addSource := func(raw string, anchor bool) {
raw = strings.TrimSpace(raw)
if raw == "" {
return
}
compact := compactTopicPhrase(raw)
addTerm(compact, anchor)
for _, token := range splitTopicTokens(raw) {
addTerm(token, anchor)
}
}
addSource(seed, true)
addSource(label, true)
anchorHints := len(out.Anchors) == 0
for _, hint := range hints {
addSource(hint, anchorHints)
}
return out
}
// missionPostMatchesTopic is intentionally stricter than topicTopicHits:
// mission scan candidates must match the post body, not only the query/tag
// that produced the crawl result. This prevents crawler/search drift from
// admitting high-engagement but unrelated posts.
func missionPostMatchesTopic(post placement.ScanCandidate, terms topicMatchTerms) bool {
text := strings.ToLower(strings.TrimSpace(post.Text))
if len(terms.Anchors) == 0 && len(terms.Terms) == 0 {
return text != ""
}
if text == "" {
return false
}
for _, term := range terms.Anchors {
if term != "" && strings.Contains(text, term) {
return true
}
}
hits := 0
for _, term := range terms.Terms {
if term != "" && strings.Contains(text, term) {
hits++
if hits >= 2 {
return true
}
}
}
return false
}
func splitTopicTokens(raw string) []string {
return strings.FieldsFunc(raw, func(r rune) bool {
if unicode.IsSpace(r) || unicode.IsPunct(r) || unicode.IsSymbol(r) {
return true
}
switch r {
case '', '。', '、', '', '', '', '', '「', '」', '『', '』', '', '', '【', '】':
return true
default:
return false
}
})
}
func compactTopicPhrase(raw string) string {
var b strings.Builder
for _, r := range raw {
if unicode.IsSpace(r) || unicode.IsPunct(r) || unicode.IsSymbol(r) {
continue
}
b.WriteRune(r)
}
return b.String()
}
func normaliseTopicToken(raw string) string {
token := strings.ToLower(strings.TrimSpace(raw))
if token == "" {
return ""
}
token = strings.Trim(token, "#,.。:;!?()[]【】\"'「」『』")
if len([]rune(token)) < 2 {
return ""
}
return token
}
func isGenericTopicToken(token string) bool {
switch token {
case "分享", "心得", "推薦", "請問", "求助", "問題", "方法", "技巧", "經驗",
"熱門", "話題", "最近", "大家", "有人", "可以", "如何", "怎麼", "為什麼",
"品質", "閱讀", "更多", "多閱讀", "老公", "老婆", "男友", "女友":
return true
default:
return false
}
}