class Trends::Base
include Redisable
+ class_attribute :default_options
+
+ attr_reader :options
+
+ # @param [Hash] options
+ # @option options [Integer] :threshold Minimum amount of uses by unique accounts to begin calculating the score
+ # @option options [Integer] :review_threshold Minimum rank (lower = better) before requesting a review
+ # @option options [ActiveSupport::Duration] :max_score_cooldown For this amount of time, the peak score (if bigger than current score) is decayed-from
+ # @option options [ActiveSupport::Duration] :max_score_halflife How quickly a peak score decays
+ def initialize(options = {})
+ @options = self.class.default_options.merge(options)
+ end
+
def register(_status)
raise NotImplementedError
end
class Trends::Links < Trends::Base
PREFIX = 'trending_links'
- # Minimum amount of uses by unique accounts to begin calculating the score
- THRESHOLD = 15
-
- # Minimum rank (lower = better) before requesting a review
- REVIEW_THRESHOLD = 10
-
- # For this amount of time, the peak score (if bigger than current score) is decayed-from
- MAX_SCORE_COOLDOWN = 2.days.freeze
-
- # How quickly a peak score decays
- MAX_SCORE_HALFLIFE = 8.hours.freeze
+ self.default_options = {
+ threshold: 15,
+ review_threshold: 10,
+ max_score_cooldown: 2.days.freeze,
+ max_score_halflife: 8.hours.freeze,
+ }
def register(status, at_time = Time.now.utc)
original_status = status.reblog? ? status.reblog : status
observed = preview_card.history.get(at_time).accounts.to_f
max_time = preview_card.max_score_at
max_score = preview_card.max_score
- max_score = 0 if max_time.nil? || max_time < (at_time - MAX_SCORE_COOLDOWN)
+ max_score = 0 if max_time.nil? || max_time < (at_time - options[:max_score_cooldown])
score = begin
- if expected > observed || observed < THRESHOLD
+ if expected > observed || observed < options[:threshold]
0
else
((observed - expected)**2) / expected
preview_card.update_columns(max_score: max_score, max_score_at: max_time)
end
- decaying_score = max_score * (0.5**((at_time.to_f - max_time.to_f) / MAX_SCORE_HALFLIFE.to_f))
+ decaying_score = max_score * (0.5**((at_time.to_f - max_time.to_f) / options[:max_score_halflife].to_f))
if decaying_score.zero?
redis.zrem("#{PREFIX}:all", preview_card.id)
end
def would_be_trending?(id)
- score(id) > score_at_rank(REVIEW_THRESHOLD - 1)
+ score(id) > score_at_rank(options[:review_threshold] - 1)
end
end
class Trends::Tags < Trends::Base
PREFIX = 'trending_tags'
- # Minimum amount of uses by unique accounts to begin calculating the score
- THRESHOLD = 15
-
- # Minimum rank (lower = better) before requesting a review
- REVIEW_THRESHOLD = 10
-
- # For this amount of time, the peak score (if bigger than current score) is decayed-from
- MAX_SCORE_COOLDOWN = 2.days.freeze
-
- # How quickly a peak score decays
- MAX_SCORE_HALFLIFE = 4.hours.freeze
+ self.default_options = {
+ threshold: 15,
+ review_threshold: 10,
+ max_score_cooldown: 2.days.freeze,
+ max_score_halflife: 4.hours.freeze,
+ }
def register(status, at_time = Time.now.utc)
original_status = status.reblog? ? status.reblog : status
observed = tag.history.get(at_time).accounts.to_f
max_time = tag.max_score_at
max_score = tag.max_score
- max_score = 0 if max_time.nil? || max_time < (at_time - MAX_SCORE_COOLDOWN)
+ max_score = 0 if max_time.nil? || max_time < (at_time - options[:max_score_cooldown])
score = begin
- if expected > observed || observed < THRESHOLD
+ if expected > observed || observed < options[:threshold]
0
else
((observed - expected)**2) / expected
tag.update_columns(max_score: max_score, max_score_at: max_time)
end
- decaying_score = max_score * (0.5**((at_time.to_f - max_time.to_f) / MAX_SCORE_HALFLIFE.to_f))
+ decaying_score = max_score * (0.5**((at_time.to_f - max_time.to_f) / options[:max_score_halflife].to_f))
if decaying_score.zero?
redis.zrem("#{PREFIX}:all", tag.id)
end
def would_be_trending?(id)
- score(id) > score_at_rank(REVIEW_THRESHOLD - 1)
+ score(id) > score_at_rank(options[:review_threshold] - 1)
end
end
require 'rails_helper'
RSpec.describe Trends::Tags do
- subject { described_class.new }
+ subject { described_class.new(threshold: 5, review_threshold: 10) }
let!(:at_time) { DateTime.new(2021, 11, 14, 10, 15, 0) }
- before do
- stub_const 'Trends::Tags::THRESHOLD', 5
- stub_const 'Trends::Tags::REVIEW_THRESHOLD', 10
- end
-
describe '#add' do
let(:tag) { Fabricate(:tag) }
subject.refresh(yesterday + 12.hours)
original_score = subject.score(tag3.id)
expect(original_score).to eq 144.0
- subject.refresh(yesterday + 12.hours + described_class::MAX_SCORE_HALFLIFE)
+ subject.refresh(yesterday + 12.hours + subject.options[:max_score_halflife])
decayed_score = subject.score(tag3.id)
expect(decayed_score).to be <= original_score / 2
end