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 }