thread-master/backend/internal/library/placement/recency_cascade.go

89 lines
1.8 KiB
Go
Raw Normal View History

2026-07-01 08:42:51 +00:00
package placement
import (
"haixun-backend/internal/library/websearch"
)
type tagCascadeState struct {
Tag string
GraphNodeID string
ProductFitScore int
RanWindows map[int]bool
}
func buildTagCascadeStates(queries []TagQuery) []*tagCascadeState {
states := map[string]*tagCascadeState{}
for _, q := range queries {
key := patrolTagMatchKey(q.Tag)
if key == "" {
continue
}
st, ok := states[key]
if !ok {
st = &tagCascadeState{
Tag: q.Tag,
RanWindows: map[int]bool{},
}
states[key] = st
}
if q.GraphNodeID != "" {
st.GraphNodeID = q.GraphNodeID
}
if q.ProductFitScore > st.ProductFitScore {
st.ProductFitScore = q.ProductFitScore
}
if q.Dimension == QueryRecency && q.RecencyDays > 0 {
st.RanWindows[q.RecencyDays] = true
}
}
out := make([]*tagCascadeState, 0, len(states))
for _, st := range states {
out = append(out, st)
}
return out
}
func countCandidatesForTag(merged map[string]*ScanCandidate, tag string) int {
key := patrolTagMatchKey(tag)
if key == "" {
return 0
}
count := 0
for _, item := range merged {
if item == nil {
continue
}
if patrolTagMatchKey(item.SearchTag) == key {
count++
}
}
return count
}
func nextRecencyCascadeWindow(ran map[int]bool) int {
for _, days := range RecencyCascadeDays() {
if ran[days] {
continue
}
return days
}
return 0
}
func buildRecencyCascadeQuery(st *tagCascadeState, days int, provider websearch.Provider) (TagQuery, bool) {
if st == nil || days <= 0 {
return TagQuery{}, false
}
query := BuildRecencyQuery(provider, st.Tag, days)
if query == "" {
return TagQuery{}, false
}
return TagQuery{
Tag: st.Tag,
Query: query,
Dimension: QueryRecency,
GraphNodeID: st.GraphNodeID,
ProductFitScore: st.ProductFitScore,
RecencyDays: days,
}, true
}