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

83 lines
2.0 KiB
Go
Raw Permalink Normal View History

package knowledge
import (
"sort"
"strings"
)
// SelectBestSearchKeywords pools research map, graph, product, and optional hints,
// then returns the most intent-relevant phrases for Search API (not UI display order).
func SelectBestSearchKeywords(explicit, saved []string, in PatrolTagInput, nodes []Node, limit int) []string {
if limit <= 0 {
limit = MaxScanPatrolKeywords
}
profile := BuildIntentProfile(in)
candidates := BuildPatrolCandidates(in, nodes)
for i, tag := range NormalizePatrolKeywordList(explicit) {
addPatrolCandidate(&candidates, tag, 156-i, "掃描指定", patrolIntentRelevance)
}
for i, tag := range NormalizePatrolKeywordList(saved) {
if containsNormalizedTag(explicit, tag) {
continue
}
addPatrolCandidate(&candidates, tag, 88-i, "研究地圖儲存", patrolIntentRelevance)
}
type ranked struct {
tag string
score int
}
scored := make([]ranked, 0, len(candidates))
seen := map[string]struct{}{}
for _, item := range selectTopPatrolCandidates(candidates, len(candidates)+32) {
tag := strings.TrimSpace(item.Tag)
if tag == "" {
continue
}
key := patrolTagDedupeKey(tag)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
if IsTangentialToIntent(tag, profile) {
continue
}
intent := ScoreIntentSimilarity(tag, profile)
if intent < 8 && item.Score < 65 {
continue
}
final := (item.Score*2 + intent*4) / 6
if final <= 0 {
continue
}
scored = append(scored, ranked{tag: tag, score: final})
}
sort.SliceStable(scored, func(i, j int) bool {
if scored[i].score == scored[j].score {
return scored[i].tag < scored[j].tag
}
return scored[i].score > scored[j].score
})
out := make([]string, 0, limit)
for _, item := range scored {
out = append(out, item.tag)
if len(out) >= limit {
break
}
}
return out
}
func containsNormalizedTag(items []string, target string) bool {
targetKey := patrolTagDedupeKey(target)
for _, item := range items {
if patrolTagDedupeKey(item) == targetKey {
return true
}
}
return false
}