diff --git a/app/services/affinity_groups_hierarchy.rb b/app/services/affinity_groups_hierarchy.rb index cd448de..ca5ddc3 100644 --- a/app/services/affinity_groups_hierarchy.rb +++ b/app/services/affinity_groups_hierarchy.rb @@ -16,6 +16,7 @@ class AffinityGroupsHierarchy < Array end discomforts + invitation_counts freeze end @@ -54,8 +55,16 @@ class AffinityGroupsHierarchy < Array Rational(dist, dist + 1) end + def guest_count(invitation_id) + @invitation_counts[invitation_id] || 0 + end + private + def invitation_counts + @invitation_counts = Guest.where.not(invitation_id: nil).group(:invitation_id).count + end + def discomforts @discomforts ||= GroupAffinity.pluck(:group_a_id, :group_b_id, :discomfort).each_with_object({}) do |(id_a, id_b, discomfort), acc| diff --git a/app/services/tables/discomfort_calculator.rb b/app/services/tables/discomfort_calculator.rb index 6a7ebcf..e9da343 100644 --- a/app/services/tables/discomfort_calculator.rb +++ b/app/services/tables/discomfort_calculator.rb @@ -15,7 +15,7 @@ module Tables end def breakdown - @breakdown ||= { table_size_penalty:, cohesion_penalty: } + @breakdown ||= { table_size_penalty:, cohesion_penalty:, invitations_penalty: } end private @@ -39,6 +39,12 @@ module Tables 10 * (cohesion_discomfort * 1.0 / table.size) end + def invitations_penalty + 2 * table.map(&:invitation_id) + .tally + .sum { |invitation_id, guests_in_table| hierarchy.guest_count(invitation_id) - guests_in_table } + 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 diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index 3c37279..1945f51 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -14,14 +14,14 @@ module Tables describe '#calculate' do before do - allow(calculator).to receive_messages(table_size_penalty: 2, cohesion_discomfort: 3) + allow(calculator).to receive_messages(table_size_penalty: 2, cohesion_penalty: 5, invitations_penalty: 4) end let(:table) { Table.new(create_list(:guest, 6)) } it 'returns the sum of the table size penalty and the average cohesion penalty', :aggregate_failures do - expect(calculator.calculate).to eq(7) - expect(calculator.breakdown).to eq(table_size_penalty: 2, cohesion_penalty: 5) + expect(calculator.calculate).to eq(11) + expect(calculator.breakdown).to eq(table_size_penalty: 2, cohesion_penalty: 5, invitations_penalty: 4) end end @@ -131,5 +131,44 @@ module Tables end end end + + describe '#invitations_penalty' do + let(:invitation_a) { create(:invitation) } + let(:invitation_b) { create(:invitation) } + let(:invitation_c) { create(:invitation) } + + let(:table) do + create_list(:guest, 2, invitation: invitation_a) + + create_list(:guest, 3, invitation: invitation_b) + + create_list(:guest, 4, invitation: invitation_c) + end + + context 'when the table contains all members of an invitation' do + it 'returns 0 as penalty' do + expect(calculator.send(:invitations_penalty)).to eq(0) + end + end + + context 'when there is an additional guest of one of the invitations that is not included' do + before do + create(:guest, invitation: invitation_a) + end + + it 'returns the penalty for the missing guest' do + expect(calculator.send(:invitations_penalty)).to eq(2) + end + end + + context 'when there are multiple guests missing from different invitations' do + before do + create(:guest, invitation: invitation_b) + create(:guest, invitation: invitation_c) + end + + it 'returns 2x # of guests left out as the total penalty for all missing guests' do + expect(calculator.send(:invitations_penalty)).to eq(4) + end + end + end end end