Merge pull request 'Redo TablesArrangements#show to display arrangement ID and discomfort breakdown' (#188) from table-discomfort-breakdown into main
All checks were successful
Check usage of free licenses / check-licenses (push) Successful in 1m47s
Run unit tests / unit_tests (push) Successful in 3m49s
Build Nginx-based docker image / build-static-assets (push) Successful in 24m52s

Reviewed-on: #188
This commit is contained in:
bustikiller 2024-12-16 22:18:23 +00:00
commit 2c6a05ee06
5 changed files with 96 additions and 47 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 }
}
}
}
}
}
}

View File

@ -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