# Computes the country leaderboard using a blended score that balances overall # impact with per-employee efficiency, so bigger teams don't win on size alone. # # For each country: # tomatoes = number of finished play sessions (players where is_done) # per_employee = tomatoes / employee headcount (nil if headcount unknown) # impact_index = tomatoes / max(tomatoes) * 100 # efficiency = per_employee / max(per_employee) * 100 # score = impact_index * IMPACT_WEIGHT + efficiency * EFFICIENCY_WEIGHT # # Tune the balance here. The proposal's default is an even 50/50 split; raise # EFFICIENCY_WEIGHT to lean more toward fairness across team sizes. class LeaderboardScore IMPACT_WEIGHT = 0.5 EFFICIENCY_WEIGHT = 1.0 - IMPACT_WEIGHT def self.call new.call end def call rows = build_rows max_tomatoes = rows.map { |r| r[:tomatoes] }.max.to_f max_per_emp = rows.filter_map { |r| r[:per_employee] }.max.to_f rows.map { |r| score_row(r, max_tomatoes, max_per_emp) } .sort_by { |r| [ -r[:score], r[:country_code] ] } end private # One row per known country, so the API always lists every country. Only # players that are done AND tagged with a known country contribute tomatoes; # countries with no finished players still appear, with 0 tomatoes / 0 score. def build_rows known = I18n.t("countries").keys.map(&:to_s) counts = Player.where(is_done: true, country: known).group(:country).count headcounts = self.class.headcounts known.map do |country| tomatoes = counts[country].to_i employees = headcounts[country].to_i per_employee = employees.positive? ? tomatoes.to_f / employees : nil { country: country, tomatoes: tomatoes, per_employee: per_employee } end end def score_row(row, max_tomatoes, max_per_emp) impact = max_tomatoes.positive? ? row[:tomatoes] / max_tomatoes * 100 : 0.0 efficiency = if row[:per_employee] && max_per_emp.positive? row[:per_employee] / max_per_emp * 100 else # No headcount for this country: fall back to impact only rather than # penalising it with a zero efficiency score. impact end blended = impact * IMPACT_WEIGHT + efficiency * EFFICIENCY_WEIGHT { country_code: row[:country].to_s.upcase, score: blended.round, tomatoes: row[:tomatoes], per_employee: row[:per_employee]&.round(2) } end def self.headcounts @headcounts ||= YAML.safe_load_file( Rails.root.join("config", "country_headcounts.yml"), permitted_classes: [], aliases: false ) || {} end end