From f4dc25dd57171ccc3bbefab106ec569fc99f0783 Mon Sep 17 00:00:00 2001 From: Paul Cantrell Date: Fri, 25 Apr 2025 12:55:02 -0500 Subject: [PATCH 1/5] Fixed CLI instructions for schedule constraint import --- lib/tasks/app.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/app.rake b/lib/tasks/app.rake index 8c7494c2..95fa8d48 100644 --- a/lib/tasks/app.rake +++ b/lib/tasks/app.rake @@ -102,7 +102,7 @@ namespace :app do if args[:config_file].blank? STDERR.puts 'Usage: - rake app:configure_sessions[path/to/constraints.csv] + rails "app:configure_sessions[path/to/constraints.csv]" The CSV file must open with a header line with the following columns: From 2672ad807e98ded3f8470a10b4af2ec9b4ac722f Mon Sep 17 00:00:00 2001 From: Paul Cantrell Date: Fri, 25 Apr 2025 12:56:34 -0500 Subject: [PATCH 2/5] Revamped constraint syntax for including/excluding specific slots Was misdocumented! Yikes! --- app/models/participant.rb | 44 +++++++++++++++++++++------------------ lib/tasks/app.rake | 16 ++++++++------ 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/app/models/participant.rb b/app/models/participant.rb index 724e52dd..9583e0c8 100644 --- a/app/models/participant.rb +++ b/app/models/participant.rb @@ -37,38 +37,40 @@ def self.ransackable_associations(auth_object = nil) end def restrict_after(datetime, weight=1, event=Event.current_event) - event.timeslots.each do |timeslot| - if timeslot.ends_at > datetime - self.presenter_timeslot_restrictions.create!( - timeslot: timeslot, - weight: weight) - end + create_restrictions do |timeslot| + timeslot.ends_at > datetime end end def restrict_before(datetime, weight=1, event=Event.current_event) - event.timeslots.each do |timeslot| - if timeslot.starts_at < datetime - self.presenter_timeslot_restrictions.create!( - timeslot: timeslot, - weight: weight) - end + create_restrictions do |timeslot| + timeslot.starts_at < datetime end end def restrict_not_at(datetime, weight=1, event=Event.current_event) - event.timeslots.each do |timeslot| - if timeslot.ends_at < datetime || timeslot.starts_at > datetime - self.presenter_timeslot_restrictions.create!( - timeslot: timeslot, - weight: weight) - end + create_restrictions do |timeslot| + timeslot.ends_at < datetime || timeslot.starts_at > datetime + end + end + + def restrict_all_except(allowed_slots, weight=1, event=Event.current_event) + create_restrictions do |timeslot| + !allowed_slots.include?(timeslot) end end - def restrict_to_only(allowed_slots, weight=1, event=Event.current_event) + def restrict_only(disallowed_slots, weight=1, event=Event.current_event) + create_restrictions do |timeslot| + disallowed_slots.include?(timeslot) + end + end + +private + + def create_restrictions(weight=1, &is_restricted) event.timeslots.each do |timeslot| - unless allowed_slots.include?(timeslot) + if timeslot.schedulable && yield(timeslot) self.presenter_timeslot_restrictions.create!( timeslot: timeslot, weight: weight) @@ -76,6 +78,8 @@ def restrict_to_only(allowed_slots, weight=1, event=Event.current_event) end end +public + def deliver_password_reset_instructions! reset_perishable_token! Notifier.password_reset_instructions(self).deliver_now! diff --git a/lib/tasks/app.rake b/lib/tasks/app.rake index 95fa8d48..85b8b852 100644 --- a/lib/tasks/app.rake +++ b/lib/tasks/app.rake @@ -118,7 +118,8 @@ namespace :app do <12:00pm Session must end at or before the given time >1:00pm, <3:00pm Session must fall entirely within give time range @2:00pm Session must be in the timeslot that includes the given time - # 1 2 3 Session must be in one of these specific timeslot ids + #= 1 2 3 Session must be in one of these specific timeslot ids + #! 1 2 3 Session must NOT be in one of these specific timeslot ids manual Do not let sessionizer schedule this session delete Soft-delete session by assigning to a nonexistent event @@ -192,11 +193,14 @@ namespace :app do constraints.split(',').map(&:strip).each do |constraint| puts " #{constraint}" - if /^#(?(\s*\d+\s*)+)$/ =~ constraint - presenter.restrict_to_only( - Timeslot.find( - ids.split)) - next + if /^#(?.)(?(\s*\d+\s*)+)$/ =~ constraint + specific_slots = Timeslot.find(ids.split) + case include_exclude + when '=' then presenter.restrict_all_except(specific_slots) + when '!' then presenter.restrict_only(specific_slots) + else raise "Unknown include/exclude symbol `#{include_exclude}` in `#{constraint}`" + end + next # done with this rule! end unless %r{ From cf298c80917460485a5d72e592ab66bb2476f70a Mon Sep 17 00:00:00 2001 From: Paul Cantrell Date: Fri, 25 Apr 2025 12:57:36 -0500 Subject: [PATCH 3/5] Ensured that schedule script fail if any step fails! (Would previous continue to schedule even if schedule constraints not found) --- bin/schedule | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bin/schedule b/bin/schedule index 6962b423..3fc24eec 100755 --- a/bin/schedule +++ b/bin/schedule @@ -1,5 +1,7 @@ #!/bin/bash +set -e + if [ "$#" -ne 2 ]; then echo "usage: $0 " exit 1 From 7bb609891fcc965727f4c5f8dddcd0ae3fe3a8af Mon Sep 17 00:00:00 2001 From: Paul Cantrell Date: Fri, 25 Apr 2025 12:58:00 -0500 Subject: [PATCH 4/5] Added ability to rerun schedule script from previous best --- bin/schedule | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bin/schedule b/bin/schedule index 3fc24eec..5d387ca0 100755 --- a/bin/schedule +++ b/bin/schedule @@ -25,6 +25,11 @@ function section { section "Pulling latest data from production..." bin/pull-database-from-production +if [ -e "$schedule_file" ]; then + section "Re-importing previous best schedule..." + rails app:import_schedule <"$schedule_file" +fi + section "Scheduler input quality analysis" rails db:migrate && rails app:analyze_scheduler_input_quality @@ -38,7 +43,7 @@ for q in 0.01 0.05 0.1 0.2 0.3 0.5 0.7 0.9 1.0 1.5; do done section "Exporting generated schedule to $schedule_file" -rails app:export_schedule >$schedule_file || exit +rails app:export_schedule >"$schedule_file" || exit section "Congratulations. You have a schedule!" echo "Check the schedule locally. If it looks good, you can upload it to prod with:" @@ -46,3 +51,7 @@ echo echo " heroku run rails app:import_schedule < $schedule_file" echo -e " ${red_bold}⬆︎⬆︎⬆︎ DANGER: overwrites current live schedule ⬆︎⬆︎⬆︎${normal}" echo +echo "If you want to try for an even better schedule, you can run this script again." +echo "It will restart from the previous best schedule in $schedule.json." +echo "This is a good idea if you were still seeing “New best solution” even at the" +echo "highest cooling times, late in the process." From 364bc76276f4c35388f7262d10258d08c7226d68 Mon Sep 17 00:00:00 2001 From: Paul Cantrell Date: Thu, 23 Apr 2026 20:42:50 -0500 Subject: [PATCH 5/5] Fixed session popularity column formatting --- lib/tasks/app.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/app.rake b/lib/tasks/app.rake index 85b8b852..21a339f8 100644 --- a/lib/tasks/app.rake +++ b/lib/tasks/app.rake @@ -465,7 +465,7 @@ namespace :app do puts "#{' ' * timeslots.count} vot exp ID title presenters" puts "#{' ' * timeslots.count} --- --- ---------------------------------------- ----------" Session.largest_attendance_first(event.sessions).each do |session| - puts "%s %3d %3s%s %-40.40s %s" % [ + puts "%s %3d %3.0f%s %-40.40s %s" % [ timeslots.map { |slot| slot.id == session.timeslot_id ? '•' : ' ' }.join, session.attendance_count, session.expected_attendance,