Compare commits

...

5 Commits

Author SHA1 Message Date
Renovate Bot
9c1fe13063 Update dependency factory_bot_rails to v6.4.4
All checks were successful
Add copyright notice / copyright_notice (pull_request) Successful in 6m21s
Run unit tests / unit_tests (pull_request) Successful in 8m22s
Check usage of free licenses / check-licenses (pull_request) Successful in 1m27s
2024-12-17 01:05:13 +00:00
2c6a05ee06 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
2024-12-16 22:18:23 +00:00
3bfe889747 Redo TablesArrangements#show to display arrangement ID and discomfort breakdown
All checks were successful
Check usage of free licenses / check-licenses (pull_request) Successful in 32s
Add copyright notice / copyright_notice (pull_request) Successful in 1m4s
Run unit tests / unit_tests (pull_request) Successful in 2m33s
2024-12-16 18:52:34 +01:00
Renovate Bot
e44043f572 Update dependency rubocop to v1.69.2
Some checks failed
Run unit tests / unit_tests (push) Failing after 17s
Check usage of free licenses / check-licenses (push) Failing after 19s
Build Nginx-based docker image / build-static-assets (push) Failing after 29m12s
2024-12-16 08:03:14 +00:00
28386df1d8 Merge pull request 'Update dependency factory_bot_rails to v6.4.4' (#182) from renovate/factory_bot_rails-6.x-lockfile into main
Some checks failed
Check usage of free licenses / check-licenses (push) Successful in 36s
Build Nginx-based docker image / build-static-assets (push) Has been cancelled
Run unit tests / unit_tests (push) Has been cancelled
Reviewed-on: #182
2024-12-16 08:01:57 +00:00
6 changed files with 98 additions and 49 deletions

View File

@ -327,7 +327,7 @@ GEM
rswag-ui (2.16.0) rswag-ui (2.16.0)
actionpack (>= 5.2, < 8.1) actionpack (>= 5.2, < 8.1)
railties (>= 5.2, < 8.1) railties (>= 5.2, < 8.1)
rubocop (1.69.1) rubocop (1.69.2)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (>= 3.17.0)
parallel (~> 1.10) parallel (~> 1.10)
@ -343,7 +343,7 @@ GEM
rubytree (2.1.0) rubytree (2.1.0)
json (~> 2.0, > 2.3.1) json (~> 2.0, > 2.3.1)
rubyzip (2.3.2) rubyzip (2.3.2)
securerandom (0.4.0) securerandom (0.4.1)
shoulda-matchers (6.4.0) shoulda-matchers (6.4.0)
activesupport (>= 5.2.0) activesupport (>= 5.2.0)
solid_queue (1.1.0) solid_queue (1.1.0)

View File

@ -6,18 +6,33 @@ class TablesArrangementsController < ApplicationController
end end
def show def show
Seat.joins(guest: :group) Guest.joins(:seats, :group)
.where(tables_arrangement_id: params[:id]) .where(seats: { tables_arrangement_id: params[:id] })
.order('guests.group_id') .select('guests.*', 'groups.color', 'seats.table_number')
.pluck( .group_by(&:table_number)
:table_number, .map { |number, guests| format(number:, guests:) }
'guests.name', .then { |result| render json: { id: params[:id], tables: result } }
'guests.id', end
'groups.color'
) private
.group_by(&:first)
.transform_values { |table| table.map { |(_, name, id, color)| { id:, name:, color: } } } def format(number:, guests:)
.map { |number, guests| { number:, guests: } } {
.then { |result| render json: result } 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
end end

View File

@ -3,6 +3,9 @@
class TableSimulatorJob < ApplicationJob class TableSimulatorJob < ApplicationJob
queue_as :default queue_as :default
MIN_PER_TABLE = 8
MAX_PER_TABLE = 10
def perform(wedding_id) def perform(wedding_id)
ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do ActsAsTenant.with_tenant(Wedding.find(wedding_id)) do
engine = VNS::Engine.new engine = VNS::Engine.new
@ -10,7 +13,7 @@ class TableSimulatorJob < ApplicationJob
engine.add_perturbation(Tables::Swap) engine.add_perturbation(Tables::Swap)
engine.add_perturbation(Tables::Shift) 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) initial_solution.random_distribution(Guest.potential.shuffle)
engine.initial_solution = initial_solution engine.initial_solution = initial_solution

View File

@ -8,7 +8,11 @@ module Tables
end end
def calculate 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 end
private private
@ -28,6 +32,10 @@ module Tables
end end
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 # 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 # 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. # @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)| table.map(&:group_id).tally.to_a.combination(2).sum do |(a, count_a), (b, count_b)|
distance = AffinityGroupsHierarchy.instance.distance(a, b) distance = AffinityGroupsHierarchy.instance.distance(a, b)

View File

@ -3,7 +3,6 @@
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'tables_arrangements', type: :request do RSpec.describe 'tables_arrangements', type: :request do
path '/{slug}/tables_arrangements' do path '/{slug}/tables_arrangements' do
get('list tables arrangements') do get('list tables arrangements') do
tags 'Tables Arrangements' tags 'Tables Arrangements'
@ -33,21 +32,44 @@ RSpec.describe 'tables_arrangements', type: :request do
parameter Swagger::Schema::SLUG parameter Swagger::Schema::SLUG
parameter Swagger::Schema::ID parameter Swagger::Schema::ID
response(200, 'successful') do response(200, 'successful') do
schema type: :array, schema type: :object,
items: { required: %i[id tables],
type: :object, properties: {
required: %i[number guests], id: { type: :string, format: :uuid },
properties: { tables: {
number: { type: :integer },
guests: { type: :array,
type: :array, items: {
items: { type: :object,
type: :object, required: %i[number guests discomfort],
required: %i[id name color], properties: {
properties: { number: { type: :integer },
id: { type: :string, format: :uuid }, guests: {
name: { type: :string }, type: :array,
color: { type: :string } 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 describe '#calculate' do
before do before do
allow(calculator).to receive(:table_size_penalty).and_return(2) 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 end
let(:table) { Table.new(create_list(:guest, 6)) } let(:table) { Table.new(create_list(:guest, 6)) }
it 'returns the sum of the table size penalty and the average cohesion penalty' do it 'returns the sum of the table size penalty and the average cohesion penalty', :aggregate_failures do
expect(calculator.calculate).to eq(2 + 10 * 3 / 6.0) expect(calculator.calculate).to eq(7)
expect(calculator.breakdown).to eq(table_size_penalty: 2, cohesion_penalty: 5)
end end
end end
@ -71,7 +72,7 @@ module Tables
end end
end end
describe '#cohesion_penalty' do describe '#cohesion_discomfort' do
before do before do
# Overridden in each test except trivial cases # Overridden in each test except trivial cases
allow(AffinityGroupsHierarchy.instance).to receive(:distance).and_call_original allow(AffinityGroupsHierarchy.instance).to receive(:distance).and_call_original
@ -91,7 +92,7 @@ module Tables
context 'when they belong to the same group' do context 'when they belong to the same group' do
let(:table) { create_list(:guest, 2, group: family) } 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 end
context 'when they belong to completely unrelated groups' do context 'when they belong to completely unrelated groups' do
@ -101,7 +102,7 @@ module Tables
create(:guest, group: friends) create(:guest, group: friends)
] ]
end end
it { expect(calculator.send(:cohesion_penalty)).to eq(1) } it { expect(calculator.send(:cohesion_discomfort)).to eq(1) }
end end
context 'when they belong to groups at a distance of 1' do context 'when they belong to groups at a distance of 1' do
@ -112,7 +113,7 @@ module Tables
] ]
end end
it { expect(calculator.send(:cohesion_penalty)).to eq(0.5) } it { expect(calculator.send(:cohesion_discomfort)).to eq(0.5) }
end end
context 'when they belong to groups at a distance of 2' do context 'when they belong to groups at a distance of 2' do
@ -123,7 +124,7 @@ module Tables
] ]
end end
it { expect(calculator.send(:cohesion_penalty)).to eq(Rational(2, 3)) } it { expect(calculator.send(:cohesion_discomfort)).to eq(Rational(2, 3)) }
end end
context 'when they belong to groups at a distance of 3' do context 'when they belong to groups at a distance of 3' do
@ -134,7 +135,7 @@ module Tables
] ]
end 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
end end
@ -148,7 +149,7 @@ module Tables
end end
it 'returns the sum of the penalties for each pair of guests' do 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
end end
@ -163,7 +164,7 @@ module Tables
end end
it 'returns the sum of the penalties for each pair of guests' do 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)) .to eq(1 + Rational(1, 2) + Rational(2, 3) + Rational(3, 4) + Rational(4, 5) + Rational(5, 6))
end end
end end
@ -177,7 +178,7 @@ module Tables
end end
it 'returns the sum of the penalties for each pair of guests' do 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
end end
@ -190,7 +191,7 @@ module Tables
end end
it 'returns the sum of the penalties for each pair of guests' do 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
end end
@ -204,7 +205,7 @@ module Tables
end end
it 'returns the sum of the penalties for each pair of guests' do 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
end end
@ -218,7 +219,7 @@ module Tables
end end
it 'returns the sum of the penalties for each pair of guests' do 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 end
end end