github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/opensearch/scoring.go (about) 1 package opensearch 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/keybase/client/go/protocol/keybase1" 9 ) 10 11 const ( 12 minScoringMemberCount = 0 13 maxScoringMemberCount = 100000 14 minScoringActivityHours = 7 * 24 // one week 15 maxScoringActivityHours = 4 * 30 * 24 // one month 16 memberCountWeight = 400 17 lastActiveWeight = 20 18 ) 19 20 type RankedSearchItem interface { 21 Score(query string) float64 22 String() string 23 } 24 25 type rankedSearchItem struct { 26 item keybase1.TeamSearchItem 27 score float64 28 } 29 30 func (i rankedSearchItem) String() string { 31 description := "" 32 if i.item.Description != nil { 33 description = *i.item.Description 34 } 35 return fmt.Sprintf( 36 "Name: %s Description: %s MemberCount: %d LastActive: %v Score: %.2f isDemoted: %v", 37 i.item.Name, description, i.item.MemberCount, 38 i.item.LastActive.Time(), i.score, i.item.IsDemoted) 39 } 40 41 func (i rankedSearchItem) Score(query string) (score float64) { 42 query = strings.ToLower(query) 43 name := strings.ToLower(i.item.Name) 44 // demoted teams require an exact name match to be returned 45 if i.item.IsDemoted && query != name { 46 return 0 47 } 48 for _, qtok := range strings.Split(query, " ") { 49 score += ScoreName(name, qtok) 50 if i.item.Description != nil { 51 score += ScoreDescription(*i.item.Description, qtok) 52 } 53 } 54 if FilterScore(score) { 55 return score 56 } 57 return score + normalizeMemberCount(i.item.MemberCount)*memberCountWeight + 58 NormalizeLastActive(minScoringActivityHours, 59 maxScoringActivityHours, i.item.LastActive)*lastActiveWeight 60 } 61 62 func normalizeMemberCount(memberCount int) float64 { 63 if memberCount < minScoringMemberCount { 64 return 0 65 } else if memberCount > maxScoringMemberCount { 66 return 1 67 } 68 return float64(memberCount) / float64(maxScoringMemberCount-minScoringMemberCount) 69 } 70 71 func NormalizeLastActive(minHrs, maxHrs float64, lastActive keybase1.Time) float64 { 72 hours := time.Since(lastActive.Time()).Hours() 73 if hours > maxHrs { 74 return 0 75 } else if hours < minHrs { 76 return 1 77 } 78 return 1 - hours/(maxHrs-minHrs) 79 } 80 81 func FilterScore(score float64) bool { 82 return score-.0001 < 0 83 } 84 85 func ScoreName(name, qtok string) (score float64) { 86 name = strings.ToLower(name) 87 if qtok == name || strings.HasPrefix(name, qtok) || strings.HasSuffix(name, qtok) { 88 score += 1000 89 } else if strings.Contains(name, qtok) { 90 score += 100 91 } 92 return score 93 } 94 95 func ScoreDescription(desc, qtok string) (score float64) { 96 desc = strings.ToLower(desc) 97 for _, dtok := range strings.Split(desc, " ") { 98 if dtok == qtok { 99 score += 25 100 } 101 } 102 return score 103 }