wedding-planner/app/services/tables/discomfort_calculator.rb

65 lines
1.9 KiB
Ruby
Raw Normal View History

2024-10-27 21:42:45 +00:00
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module Tables
class DiscomfortCalculator
class << self
def cohesion_discomfort(id_a:, id_b:)
distance = AffinityGroupsHierarchy.instance.distance(id_a, id_b)
return 1 if distance.nil?
Rational(distance, distance + 1)
end
end
private attr_reader :table
def initialize(table:)
@table = table
end
def calculate
breakdown.values.sum
end
def breakdown
@breakdown ||= { table_size_penalty:, cohesion_penalty: }
end
private
#
# Calculates the penalty associated with violating the table size constraints. The penalty is
# zero when the limits are honored, and it increases linearly as the number of guests deviates
# from the limits. Overcapacity is penalized more severely than undercapacity.
#
# @return [Number] The penalty associated with violating the table size constraints.
#
def table_size_penalty
case table.size
when 0...table.min_per_table then 5 * (table.min_per_table - table.size)
when table.min_per_table..table.max_per_table then 0
else 5 * (table.size - table.max_per_table)
end
end
def cohesion_penalty
10 * (cohesion_discomfort * 1.0 / table.size)
end
#
# Calculates the discomfort of the table based on the cohesion of the guests. The total discomfort
# is calculated as the sum of the discomfort of each pair of guests. The discomfort of a pair of
# guests is a rational number between 1 (unrelated groups) and 0 (same group).
#
# @return [Number] Total discomfort of the table.
#
def cohesion_discomfort
table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
count_a * count_b * self.class.cohesion_discomfort(id_a: a, id_b: b)
end
end
end
end