haixunMaster/haixun-backend/internal/library/placement/patrol_queries.go

103 lines
2.7 KiB
Go

package placement
import (
"strings"
libkg "haixun-backend/internal/library/knowledge"
"haixun-backend/internal/library/websearch"
)
const defaultPatrolProductFit = 78
// CollectPatrolTagQueries builds dual-track crawl jobs from user-edited patrol keywords only.
func CollectPatrolTagQueries(keywords []string, nodes []libkg.Node, provider websearch.Provider) []TagQuery {
keywords = libkg.NormalizePatrolKeywordList(keywords)
if len(keywords) == 0 {
return nil
}
out := make([]TagQuery, 0, len(keywords)*3)
for _, tag := range keywords {
fit := productFitForPatrolTag(tag, nodes)
if q := BuildRelevanceQuery(provider, tag); q != "" {
out = append(out, TagQuery{
Tag: tag,
Query: q,
Dimension: QueryRelevance,
ProductFitScore: fit,
})
}
if q7 := BuildRecencyQuery(provider, tag, IdealMaxPostAgeDays); q7 != "" {
out = append(out, TagQuery{
Tag: tag,
Query: q7,
Dimension: QueryRecency,
ProductFitScore: fit,
RecencyDays: IdealMaxPostAgeDays,
})
}
if q30 := BuildRecencyQuery(provider, tag, MaxPostAgeDays); q30 != "" {
if q7 := BuildRecencyQuery(provider, tag, IdealMaxPostAgeDays); q30 != q7 {
out = append(out, TagQuery{
Tag: tag,
Query: q30,
Dimension: QueryRecency,
ProductFitScore: fit,
RecencyDays: MaxPostAgeDays,
})
}
}
}
return out
}
func productFitForPatrolTag(tag string, nodes []libkg.Node) int {
tagKey := patrolTagMatchKey(tag)
best := 0
bestNodeID := ""
for _, node := range nodes {
score := node.ProductFitScore
if score <= 0 {
continue
}
matched := false
for _, candidate := range append(append([]string{}, node.DerivedTags.Relevance...), node.DerivedTags.Recency...) {
if patrolTagMatchKey(candidate) == tagKey {
matched = true
break
}
}
if !matched && patrolTagMatchKey(node.Label) != tagKey {
continue
}
if score > best {
best = score
bestNodeID = node.ID
}
}
if best > 0 {
return best
}
_ = bestNodeID
return defaultPatrolProductFit
}
func patrolTagMatchKey(tag string) string {
tag = strings.TrimSpace(tag)
for _, suffix := range []string{" 推薦", " 請問", " 怎麼辦", " 好用嗎", " 有人用過嗎", " 有推薦嗎", " 請益"} {
if strings.HasSuffix(tag, suffix) {
tag = strings.TrimSuffix(tag, suffix)
break
}
}
return strings.TrimSpace(tag)
}
// ResolveTagQueries prefers explicit patrol keywords over graph node selection.
func ResolveTagQueries(nodes []libkg.Node, patrolKeywords []string, provider websearch.Provider) []TagQuery {
if len(patrolKeywords) > 0 {
return CollectPatrolTagQueries(patrolKeywords, nodes, provider)
}
return CollectTagQueries(nodes, provider)
}