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/bin/schedule b/bin/schedule
index 6962b423..5d387ca0 100755
--- a/bin/schedule
+++ b/bin/schedule
@@ -1,5 +1,7 @@
#!/bin/bash
+set -e
+
if [ "$#" -ne 2 ]; then
echo "usage: $0 "
exit 1
@@ -23,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
@@ -36,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:"
@@ -44,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."
diff --git a/lib/tasks/app.rake b/lib/tasks/app.rake
index 8c7494c2..21a339f8 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:
@@ -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{
@@ -461,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,