We value transparency, so we have no issue showing our code on how we calculate the rankings.

Ruby Code: RankCalculation Module

    
    
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