We value transparency, so we have no issue showing our code on how we calculate the rankings.
module RankCalculation
extend ActiveSupport::Concern
def weighted_rating(r, v, m, c)
(v / (v.to_f + m)) * r + (m / (v.to_f + m)) * c
end
def overall_score(google_avg:, treatwell_avg:, m_google:, m_treatwell:)
google_score = if google_rating.present? && google_rating_count.present?
weighted_rating(google_rating, google_rating_count, m_google, google_avg)
end
treatwell_score = if treatwell_rating.present? && treatwell_rating_count.present?
weighted_rating(treatwell_rating, treatwell_rating_count, m_treatwell, treatwell_avg)
end
if google_score && treatwell_score
(google_score + treatwell_score) / 2.0
else
google_score || treatwell_score || 0
end
end
def total_reviews
(google_rating_count || 0) + (treatwell_rating_count || 0)
end
def normalize_rating(rating)
return 0.0 if rating.nil?
scaled = Math.exp(rating - 5.0)
penalty = rating < 4.8 ? 0.7 : 1.0
(scaled * penalty).clamp(0.0, 1.0)
end
def reputation_factor(count, max_count)
return 0 if count.to_f <= 0 || max_count.to_f <= 0
Math.log(1 + count.to_f) / Math.log(1 + max_count.to_f)
end
def adjusted_overall_score(google_avg:, treatwell_avg:, m_google:, m_treatwell:)
google_weight = 0.40
google_count_weight = 0.08
google_rep_weight = 0.08
google_growth_weight = 0.04
treatwell_weight = 0.18
treatwell_count_weight = 0.04
treatwell_rep_weight = 0.02
treatwell_growth_weight = 0.01
trip_weight = 0.10
trip_count_weight = 0.03
trip_rep_weight = 0.02
capped_google_growth = [google_review_growth.to_f, 5].min
capped_treatwell_growth = [treatwell_review_growth.to_f, 5].min
max_google_reviews = Salon.maximum(:google_rating_count).to_f.nonzero? || 1.0
max_treatwell_reviews = Salon.maximum(:treatwell_rating_count).to_f.nonzero? || 1.0
max_trip_reviews = Salon.maximum(:tripadvisor_rating_count).to_f.nonzero? || 1.0
max_google_growth = 5.0
max_treatwell_growth = 5.0
g_rating = normalize_rating(google_rating.to_f)
g_count = google_rating_count.to_f / max_google_reviews
g_rep = reputation_factor(google_rating_count, max_google_reviews)
g_growth = (google_rating.to_f >= 4.8) ? (capped_google_growth / max_google_growth) : 0.0
t_rating = normalize_rating(treatwell_rating.to_f)
t_count = treatwell_rating_count.to_f / max_treatwell_reviews
t_rep = reputation_factor(treatwell_rating_count, max_treatwell_reviews)
t_growth = (treatwell_rating.to_f >= 4.8) ? (capped_treatwell_growth / max_treatwell_growth) : 0.0
trip_rating = normalize_rating(tripadvisor_rating.to_f)
trip_count = tripadvisor_rating_count.to_f / max_trip_reviews
trip_rep = reputation_factor(tripadvisor_rating_count, max_trip_reviews)
total = (
g_rating * google_weight +
g_count * google_count_weight +
g_rep * google_rep_weight +
g_growth * google_growth_weight +
t_rating * treatwell_weight +
t_count * treatwell_count_weight +
t_rep * treatwell_rep_weight +
t_growth * treatwell_growth_weight +
trip_rating * trip_weight +
trip_count * trip_count_weight +
trip_rep * trip_rep_weight
) * 100.0
total
end
module ClassMethods
def update_rankings!(m_google: 50.0, m_treatwell: 100.0)
google_avg = where.not(google_rating: nil).average(:google_rating).to_f
treatwell_avg = where.not(treatwell_rating: nil).average(:treatwell_rating).to_f
salons = all.to_a
scores = salons.map do |salon|
adjusted = salon.adjusted_overall_score(
google_avg: google_avg,
treatwell_avg: treatwell_avg,
m_google: m_google,
m_treatwell: m_treatwell
)
[salon, adjusted]
end
sorted = scores.sort_by { |_, score| -score }
sorted.each_with_index do |(salon, score), index|
salon.update!(ranking: index + 1)
computed_percentage = if score >= 95
100
else
score.round
end
salon.update_column(:ranking_percentage, computed_percentage)
end
end
end
end