diff --git a/app/controllers/tables_arrangements_controller.rb b/app/controllers/tables_arrangements_controller.rb index 87bcc4c..00ba8b1 100644 --- a/app/controllers/tables_arrangements_controller.rb +++ b/app/controllers/tables_arrangements_controller.rb @@ -6,18 +6,33 @@ class TablesArrangementsController < ApplicationController end def show - Seat.joins(guest: :group) - .where(tables_arrangement_id: params[:id]) - .order('guests.group_id') - .pluck( - :table_number, - 'guests.name', - 'guests.id', - 'groups.color' - ) - .group_by(&:first) - .transform_values { |table| table.map { |(_, name, id, color)| { id:, name:, color: } } } - .map { |number, guests| { number:, guests: } } - .then { |result| render json: result } + Guest.joins(:seats, :group) + .where(seats: { tables_arrangement_id: params[:id] }) + .select('guests.*', 'groups.color', 'seats.table_number') + .group_by(&:table_number) + .map { |number, guests| format(number:, guests:) } + .then { |result| render json: { id: params[:id], tables: result } } + end + + private + + def format(number:, guests:) + { + number: number, + discomfort: discomfort(guests: guests), + guests: guests.as_json(only: %i[id name color]) + } + end + + def discomfort(guests:) + table = Tables::Table.new(guests) + + table.min_per_table = TableSimulatorJob::MIN_PER_TABLE + table.max_per_table = TableSimulatorJob::MAX_PER_TABLE + calculator = Tables::DiscomfortCalculator.new(table:) + { + discomfort: calculator.calculate, + breakdown: calculator.breakdown + } end end diff --git a/app/jobs/table_simulator_job.rb b/app/jobs/table_simulator_job.rb index ced9b2d..7cbe659 100644 --- a/app/jobs/table_simulator_job.rb +++ b/app/jobs/table_simulator_job.rb @@ -3,6 +3,9 @@ class TableSimulatorJob < ApplicationJob queue_as :default + MIN_PER_TABLE = 8 + MAX_PER_TABLE = 10 + def perform(wedding_id) ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do engine = VNS::Engine.new @@ -10,7 +13,7 @@ class TableSimulatorJob < ApplicationJob engine.add_perturbation(Tables::Swap) engine.add_perturbation(Tables::Shift) - initial_solution = Tables::Distribution.new(min_per_table: 8, max_per_table: 10) + initial_solution = Tables::Distribution.new(min_per_table: MIN_PER_TABLE, max_per_table: MAX_PER_TABLE) initial_solution.random_distribution(Guest.potential.shuffle) engine.initial_solution = initial_solution diff --git a/app/services/tables/discomfort_calculator.rb b/app/services/tables/discomfort_calculator.rb index c21796a..35da8ce 100644 --- a/app/services/tables/discomfort_calculator.rb +++ b/app/services/tables/discomfort_calculator.rb @@ -8,7 +8,11 @@ module Tables end def calculate - table_size_penalty + 10 * (cohesion_penalty * 1.0 / table.size) + breakdown.values.sum + end + + def breakdown + @breakdown ||= { table_size_penalty:, cohesion_penalty: } end private @@ -28,6 +32,10 @@ module Tables 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 @@ -35,7 +43,7 @@ module Tables # # @return [Number] Total discomfort of the table. # - def cohesion_penalty + def cohesion_discomfort table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)| distance = AffinityGroupsHierarchy.instance.distance(a, b) diff --git a/spec/requests/tables_arrangements_spec.rb b/spec/requests/tables_arrangements_spec.rb index 1581d9a..c48c127 100644 --- a/spec/requests/tables_arrangements_spec.rb +++ b/spec/requests/tables_arrangements_spec.rb @@ -3,7 +3,6 @@ require 'swagger_helper' RSpec.describe 'tables_arrangements', type: :request do - path '/{slug}/tables_arrangements' do get('list tables arrangements') do tags 'Tables Arrangements' @@ -33,21 +32,44 @@ RSpec.describe 'tables_arrangements', type: :request do parameter Swagger::Schema::SLUG parameter Swagger::Schema::ID response(200, 'successful') do - schema type: :array, - items: { - type: :object, - required: %i[number guests], - properties: { - number: { type: :integer }, - guests: { - type: :array, - items: { - type: :object, - required: %i[id name color], - properties: { - id: { type: :string, format: :uuid }, - name: { type: :string }, - color: { type: :string } + schema type: :object, + required: %i[id tables], + properties: { + id: { type: :string, format: :uuid }, + tables: { + + type: :array, + items: { + type: :object, + required: %i[number guests discomfort], + properties: { + number: { type: :integer }, + guests: { + type: :array, + items: { + type: :object, + required: %i[id name color], + properties: { + id: { type: :string, format: :uuid }, + name: { type: :string }, + color: { type: :string } + } + } + }, + discomfort: { + type: :object, + required: %i[discomfort breakdown], + properties: { + discomfort: { type: :number }, + breakdown: { + type: :object, + required: %i[table_size_penalty cohesion_penalty], + properties: { + table_size_penalty: { type: :number }, + cohesion_penalty: { type: :number } + } + } + } } } } diff --git a/spec/services/tables/discomfort_calculator_spec.rb b/spec/services/tables/discomfort_calculator_spec.rb index 99fb3d0..f5da2d1 100644 --- a/spec/services/tables/discomfort_calculator_spec.rb +++ b/spec/services/tables/discomfort_calculator_spec.rb @@ -13,13 +13,14 @@ module Tables describe '#calculate' do before do allow(calculator).to receive(:table_size_penalty).and_return(2) - allow(calculator).to receive(:cohesion_penalty).and_return(3) + allow(calculator).to receive(:cohesion_discomfort).and_return(3) end let(:table) { Table.new(create_list(:guest, 6)) } - it 'returns the sum of the table size penalty and the average cohesion penalty' do - expect(calculator.calculate).to eq(2 + 10 * 3 / 6.0) + 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) end end @@ -71,7 +72,7 @@ module Tables end end - describe '#cohesion_penalty' do + describe '#cohesion_discomfort' do before do # Overridden in each test except trivial cases allow(AffinityGroupsHierarchy.instance).to receive(:distance).and_call_original @@ -91,7 +92,7 @@ module Tables context 'when they belong to the same group' do let(:table) { create_list(:guest, 2, group: family) } - it { expect(calculator.send(:cohesion_penalty)).to eq(0) } + it { expect(calculator.send(:cohesion_discomfort)).to eq(0) } end context 'when they belong to completely unrelated groups' do @@ -101,7 +102,7 @@ module Tables create(:guest, group: friends) ] end - it { expect(calculator.send(:cohesion_penalty)).to eq(1) } + it { expect(calculator.send(:cohesion_discomfort)).to eq(1) } end context 'when they belong to groups at a distance of 1' do @@ -112,7 +113,7 @@ module Tables ] end - it { expect(calculator.send(:cohesion_penalty)).to eq(0.5) } + it { expect(calculator.send(:cohesion_discomfort)).to eq(0.5) } end context 'when they belong to groups at a distance of 2' do @@ -123,7 +124,7 @@ module Tables ] end - it { expect(calculator.send(:cohesion_penalty)).to eq(Rational(2, 3)) } + it { expect(calculator.send(:cohesion_discomfort)).to eq(Rational(2, 3)) } end context 'when they belong to groups at a distance of 3' do @@ -134,7 +135,7 @@ module Tables ] end - it { expect(calculator.send(:cohesion_penalty)).to eq(Rational(3, 4)) } + it { expect(calculator.send(:cohesion_discomfort)).to eq(Rational(3, 4)) } end end @@ -148,7 +149,7 @@ module Tables end it 'returns the sum of the penalties for each pair of guests' do - expect(calculator.send(:cohesion_penalty)).to eq(1 + Rational(1, 2) + Rational(2, 3)) + expect(calculator.send(:cohesion_discomfort)).to eq(1 + Rational(1, 2) + Rational(2, 3)) end end @@ -163,7 +164,7 @@ module Tables end it 'returns the sum of the penalties for each pair of guests' do - expect(calculator.send(:cohesion_penalty)) + expect(calculator.send(:cohesion_discomfort)) .to eq(1 + Rational(1, 2) + Rational(2, 3) + Rational(3, 4) + Rational(4, 5) + Rational(5, 6)) end end @@ -177,7 +178,7 @@ module Tables end it 'returns the sum of the penalties for each pair of guests' do - expect(calculator.send(:cohesion_penalty)).to eq(4) + expect(calculator.send(:cohesion_discomfort)).to eq(4) end end @@ -190,7 +191,7 @@ module Tables end it 'returns the sum of the penalties for each pair of guests' do - expect(calculator.send(:cohesion_penalty)).to eq(8) + expect(calculator.send(:cohesion_discomfort)).to eq(8) end end @@ -204,7 +205,7 @@ module Tables end it 'returns the sum of the penalties for each pair of guests' do - expect(calculator.send(:cohesion_penalty)).to eq(4 * 1 + 4 * Rational(1, 2) + 4 * Rational(2, 3)) + expect(calculator.send(:cohesion_discomfort)).to eq(4 * 1 + 4 * Rational(1, 2) + 4 * Rational(2, 3)) end end @@ -218,7 +219,7 @@ module Tables end it 'returns the sum of the penalties for each pair of guests' do - expect(calculator.send(:cohesion_penalty)).to eq(6 * 1 + 2 * Rational(1, 2) + 3 * Rational(2, 3)) + expect(calculator.send(:cohesion_discomfort)).to eq(6 * 1 + 2 * Rational(1, 2) + 3 * Rational(2, 3)) end end end