2026-07-01 08:42:51 +00:00
|
|
|
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
|
2026-07-03 14:42:19 +00:00
|
|
|
}
|