Skip to content

Instantly share code, notes, and snippets.

@CGamesPlay
Created June 16, 2020 16:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CGamesPlay/7ff394bd1f810d206521f904763fae2b to your computer and use it in GitHub Desktop.
Save CGamesPlay/7ff394bd1f810d206521f904763fae2b to your computer and use it in GitHub Desktop.
package search
import (
"math"
"time"
"github.com/blevesearch/bleve/index"
"github.com/blevesearch/bleve/mapping"
"github.com/blevesearch/bleve/numeric"
"github.com/blevesearch/bleve/search"
"github.com/blevesearch/bleve/search/query"
"github.com/blevesearch/bleve/search/searcher"
)
type RecencyQuery struct {
// BoostVal represents the ratio of recency to preexisitng score. The
// default, 1.0, assigns equal importance to recency score and match score.
// A value of 2 would relatively rank recency twice as important as match
// score.
BoostVal *query.Boost
// Field is the ranked field
Field string
// RecentAge is the age at which the score reaches BoostVal / 2. The
// default value is 7 days.
RecentAge time.Duration
base query.Query
}
// NewRecencyQuery takes a base query, but mixes in a score based on how recent
// the passed field is.
func NewRecencyQuery(field string, base query.Query) *RecencyQuery {
return &RecencyQuery{Field: field, RecentAge: time.Hour * 24 * 7, base: base}
}
func (q *RecencyQuery) SetBoost(b float64) {
boost := query.Boost(b)
q.BoostVal = &boost
}
func (q *RecencyQuery) Boost() float64 {
return q.BoostVal.Value()
}
func (q *RecencyQuery) Searcher(i index.IndexReader, m mapping.IndexMapping, options search.SearcherOptions) (search.Searcher, error) {
bs, err := q.base.Searcher(i, m, options)
if err != nil {
return nil, err
}
dvReader, err := i.DocValueReader([]string{q.Field})
if err != nil {
return nil, err
}
return searcher.NewFilteringSearcher(bs, buildRecencyFilter(dvReader, q.Field, q.RecentAge, q.BoostVal.Value())), nil
}
func buildRecencyFilter(dvReader index.DocValueReader, field string, recentAge time.Duration, boost float64) searcher.FilterFunc {
return func(d *search.DocumentMatch) bool {
// score against the newest date
var selected int64
err := dvReader.VisitDocValues(d.IndexInternalID, func(field string, term []byte) {
// only consider the values which are shifted 0
prefixCoded := numeric.PrefixCoded(term)
shift, err := prefixCoded.Shift()
if err == nil && shift == 0 {
i64, err := prefixCoded.Int64()
if err == nil {
if i64 > selected {
selected = i64
}
}
}
})
if err != nil {
return true
}
// Rescore based on date
date := time.Unix(0, selected)
age := time.Now().Sub(date)
score := math.Min(1, math.Pow(2, -float64(age)/float64(recentAge)))
d.Score = (d.Score + score*boost) / (boost + 1)
return true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment