schedsolver2 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +23 -0
- data/Gemfile +4 -0
- data/LICENCE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/features/schedsolver_T1_initializes.feature +11 -0
- data/features/schedsolver_T2_seed.feature +16 -0
- data/features/schedsolver_T3_constraint_checker.feature +29 -0
- data/features/schedsolver_T3v2_constraints.feature +8 -0
- data/features/schedsolver_T4_print_attributes.feature +19 -0
- data/features/schedsolver_T5_crud_attributes.feature +26 -0
- data/features/schedsolver_T6_generate_schedules.feature +14 -0
- data/features/schedsolver_T7_log_output.feature +9 -0
- data/features/schedsolver_T8_big_example.feature +10 -0
- data/features/schedsolver_heuristic.feature +10 -0
- data/features/step_definitions/T1_steps.rb +19 -0
- data/features/step_definitions/T3_steps.rb +97 -0
- data/features/step_definitions/T3v2_steps.rb +14 -0
- data/features/step_definitions/T4_steps.rb +19 -0
- data/features/step_definitions/T6_steps.rb +62 -0
- data/features/step_definitions/heuristic_steps.rb +49 -0
- data/features/step_definitions/shared_steps.rb +10 -0
- data/features/support/env.rb +4 -0
- data/features/support/shared_contexts.rb +47 -0
- data/lib/schedsolver2.rb +84 -0
- data/lib/schedsolver2/ClassCounter.rb +42 -0
- data/lib/schedsolver2/constraint.rb +133 -0
- data/lib/schedsolver2/schedule.rb +107 -0
- data/lib/schedsolver2/school.rb +176 -0
- data/lib/schedsolver2/teacher.rb +49 -0
- data/schedsolver2.gemspec +23 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/ClassCounter_spec.rb +38 -0
- data/spec/constraint_spec.rb +83 -0
- data/spec/schedsolver2_spec.rb +4 -0
- data/spec/schedule_spec.rb +53 -0
- data/spec/school_spec.rb +56 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/masters/2ETs_8_to_14_by_1.schedule +12 -0
- data/spec/support/shared_contexts.rb +41 -0
- data/spec/teacher_spec.rb +43 -0
- metadata +151 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
module Schedsolver2
|
2
|
+
|
3
|
+
class School
|
4
|
+
include Schedsolver2
|
5
|
+
attr_accessor :cs
|
6
|
+
attr_reader :scheds, :consts, :si, :ts
|
7
|
+
|
8
|
+
def initialize si, ts, cs
|
9
|
+
@si, @ts, @cs = si, ts, cs
|
10
|
+
@consts = generate_constants
|
11
|
+
@scheds = []
|
12
|
+
verify_initialize_args
|
13
|
+
parse_part_time_constraints
|
14
|
+
end
|
15
|
+
|
16
|
+
def print attr
|
17
|
+
case attr
|
18
|
+
when :schedules
|
19
|
+
@scheds.each {|s| log.info("\n" + s.to_s) }
|
20
|
+
when :school_info
|
21
|
+
log.info("SCHOOL INFO #{@si}")
|
22
|
+
when :consts
|
23
|
+
log.info("CONSTS #{@consts}")
|
24
|
+
when :teachers
|
25
|
+
log.info("TEACHERS #{@ts}")
|
26
|
+
when :constraints
|
27
|
+
log.info("CONSTRAINTS #{@cs}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_part_time_constraints
|
32
|
+
@cs[:part_time] ||= {}
|
33
|
+
@ts[:e].each do |et|
|
34
|
+
not_work_days = (days - et.work_days)
|
35
|
+
if not_work_days == [] then next end
|
36
|
+
s_d = Constraint.days_descriptor(not_work_days, times, [et.id])
|
37
|
+
#log.debug("School#parse_part_time constraints\nS_D = #{s_d}\n")
|
38
|
+
new_pt_c = Constraint.new(:type => :part_time,
|
39
|
+
:et_ids => [et.id],
|
40
|
+
:s_ds => [s_d],
|
41
|
+
:name => "et#{et.id} part time")
|
42
|
+
@cs[:part_time][et.id] = new_pt_c
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def verify_initialize_args
|
47
|
+
check = ((@si[:end_time] - @si[:start_time]) == (@si[:num_blocks] * @si[:block_size]))
|
48
|
+
raise ArgumentError, "si: end-start != num*size" unless check == true
|
49
|
+
|
50
|
+
@ts[:e].each do |t|
|
51
|
+
raise ArgumentError, "ts:e: has non-teacher" unless t.class == Teacher
|
52
|
+
end
|
53
|
+
|
54
|
+
@ts[:h].each do |t|
|
55
|
+
raise ArgumentError, "ts:h: has non-teacher" unless t.class == Teacher
|
56
|
+
end
|
57
|
+
#add check for cs?
|
58
|
+
end
|
59
|
+
|
60
|
+
def generate_constants
|
61
|
+
h = Hash.new
|
62
|
+
h[:days] = [:mon, :tue, :wed, :thu, :fri]
|
63
|
+
h[:times] = generate_times
|
64
|
+
h[:et_ids] = @ts[:e].map {|t| t.id}
|
65
|
+
h[:ht_ids] = @ts[:h].map {|t| t.id}
|
66
|
+
return h
|
67
|
+
end
|
68
|
+
|
69
|
+
def generate_times
|
70
|
+
ts, t = [], @si[:start_time]
|
71
|
+
@si[:num_blocks].times do
|
72
|
+
ts << t
|
73
|
+
t += @si[:block_size]
|
74
|
+
end
|
75
|
+
return ts
|
76
|
+
end
|
77
|
+
|
78
|
+
def days
|
79
|
+
@consts[:days]
|
80
|
+
end
|
81
|
+
|
82
|
+
def times
|
83
|
+
@consts[:times]
|
84
|
+
end
|
85
|
+
|
86
|
+
def et_ids
|
87
|
+
@consts[:et_ids]
|
88
|
+
end
|
89
|
+
|
90
|
+
def ht_ids
|
91
|
+
@consts[:ht_ids]
|
92
|
+
end
|
93
|
+
|
94
|
+
def add type, *args
|
95
|
+
case type
|
96
|
+
when :schedules
|
97
|
+
raise ArgumentError unless (args.size == 1 and args[0] > 0)
|
98
|
+
args[0].times { @scheds << Schedule.new(@consts) }
|
99
|
+
when :hard_constraint
|
100
|
+
@cs[:h] << args[0]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate_schedule(index)
|
105
|
+
#log.debug("GENERATE SCHEDULE")
|
106
|
+
i = index
|
107
|
+
@ts[:h].shuffle.each do |ht|
|
108
|
+
ht_id = ht.id
|
109
|
+
#log.debug("Assigning for ht #{ht_id}")
|
110
|
+
count = nil
|
111
|
+
count = ClassCounter.new(@consts[:et_ids])
|
112
|
+
|
113
|
+
@ts[:e].shuffle.each do |et|
|
114
|
+
et_id = et.id
|
115
|
+
w_remaining = @cs[:ecs_per_week] - count.by_et_id(et_id)
|
116
|
+
#log.debug("\tLOOKING at et #{et_id} with #{w_remaining} remaining this week")
|
117
|
+
if w_remaining == 0
|
118
|
+
#log.debug("\tBREAK because remaining is 0 (top of days loop) ")
|
119
|
+
break
|
120
|
+
end
|
121
|
+
|
122
|
+
@consts[:times].shuffle.each do |time|
|
123
|
+
#log.debug("\tTIME: #{time}")
|
124
|
+
w_remaining = @cs[:ecs_per_week] - count.by_et_id(et_id)
|
125
|
+
|
126
|
+
if w_remaining == 0
|
127
|
+
#log.debug("\tBREAK because w_remaining is 0 (top of #{time} loop) ")
|
128
|
+
break
|
129
|
+
end
|
130
|
+
|
131
|
+
@consts[:days].shuffle.each do |day|
|
132
|
+
|
133
|
+
d_remaining = @cs[:max_ecs_per_day] - count.by_day(day)
|
134
|
+
w_remaining = @cs[:ecs_per_week] - count.by_et_id(et_id)
|
135
|
+
|
136
|
+
if d_remaining == 0
|
137
|
+
#log.debug("\tBREAK because d_remaining is 0 (top of #{day} loop) ")
|
138
|
+
next
|
139
|
+
elsif w_remaining == 0
|
140
|
+
#log.debug("\tBREAK because w_remaining is 0 (top of #{day} loop) ")
|
141
|
+
break
|
142
|
+
elsif @scheds[i][day, time, et_id] != nil
|
143
|
+
#log.debug("\tNEXT because slot (#{day}, #{time}) is full")
|
144
|
+
next
|
145
|
+
elsif count[day, et_id] > 0
|
146
|
+
#log.debug("\tNEXT because this EC is already assigned to #{day}")
|
147
|
+
next
|
148
|
+
elsif not et.working?(day)
|
149
|
+
#log.debug("\tNEXT because et #{et_id} doesn't work on #{day}")
|
150
|
+
next
|
151
|
+
end
|
152
|
+
|
153
|
+
begin
|
154
|
+
@scheds[i][day, time, et_id] = ht_id
|
155
|
+
rescue InsanityError
|
156
|
+
#log.debug("\tNEXT because of insanity")
|
157
|
+
next
|
158
|
+
else
|
159
|
+
count[day, et_id] += 1
|
160
|
+
d_remaining = @cs[:max_ecs_per_day] - count.by_day(day)
|
161
|
+
w_remaining = @cs[:ecs_per_week] - count.by_et_id(et_id)
|
162
|
+
#log.debug("\tASSIGNED to (#{day},#{time}) with #{d_remaining} remaining today and #{w_remaining} for week")
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def db
|
172
|
+
@scheds[0][:mon, 8, :Art] = 'TEST'
|
173
|
+
@scheds[1][:wed, 12, :Music] = 'TEST'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Schedsolver2
|
2
|
+
class Teacher
|
3
|
+
attr_accessor :name, :id, :work_days
|
4
|
+
|
5
|
+
@@ids ||= []
|
6
|
+
def Teacher::ids
|
7
|
+
@@ids
|
8
|
+
end
|
9
|
+
|
10
|
+
def Teacher::reset_ids
|
11
|
+
@@ids = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize name, id=nil
|
15
|
+
@name = name.to_s
|
16
|
+
@work_days = [:mon, :tue, :wed, :thu, :fri]
|
17
|
+
unless id == nil then
|
18
|
+
raise ArgumentError, "Dupicate id" unless Teacher.uniq_id?(id)
|
19
|
+
@id = id
|
20
|
+
else
|
21
|
+
@id = Teacher.generate_id
|
22
|
+
end
|
23
|
+
@@ids << @id
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
@name.to_s + "(#{self.id})"
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.generate_id
|
31
|
+
id = 0
|
32
|
+
until Teacher.uniq_id?(id); id += 1; end
|
33
|
+
return id
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.uniq_id? id
|
37
|
+
if @@ids.include?(id)
|
38
|
+
return false
|
39
|
+
else
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def working? day
|
45
|
+
@work_days.include? day
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'schedsolver2/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "schedsolver2"
|
8
|
+
gem.version = Schedsolver2::VERSION
|
9
|
+
gem.authors = ["Julian Irwin"]
|
10
|
+
gem.email = ["julian.irwin@gmail.com"]
|
11
|
+
gem.description = %q{Create schedules for elementary schools}
|
12
|
+
gem.summary = ""
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.add_dependency('pp')
|
16
|
+
gem.add_dependency('text-table')
|
17
|
+
gem.add_dependency('logger')
|
18
|
+
|
19
|
+
gem.files = `git ls-files`.split($/)
|
20
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
21
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
22
|
+
gem.require_paths = ["lib"]
|
23
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/schedsolver2.rb'}"
|
9
|
+
puts "Loading schedsolver2 gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
module Schedsolver2
|
4
|
+
|
5
|
+
describe ClassCounter do
|
6
|
+
include_context "main"
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@count = ClassCounter.new [0 ,1, 2]
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#by_et_id" do
|
13
|
+
it "counts all ets of a certain type" do
|
14
|
+
@count[:mon, 0] = 1
|
15
|
+
@count[:fri, 0] = 2
|
16
|
+
@count[:mon, 1] = 4
|
17
|
+
@count[:wed, 1] = 8
|
18
|
+
@count.by_et_id(0).should == 3
|
19
|
+
@count.by_et_id(1).should == 12
|
20
|
+
@count.by_et_id(2).should == 0
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#by_day" do
|
25
|
+
it "counts all days of a certain type" do
|
26
|
+
@count[:mon, 0] = 1
|
27
|
+
@count[:fri, 0] = 2
|
28
|
+
@count[:mon, 1] = 4
|
29
|
+
@count[:wed, 1] = 8
|
30
|
+
@count.by_day(:mon).should == 5
|
31
|
+
@count.by_day(:tue).should == 0
|
32
|
+
@count.by_day(:wed).should == 8
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
module Schedsolver2
|
4
|
+
|
5
|
+
describe Constraint do
|
6
|
+
include_context "main"
|
7
|
+
|
8
|
+
|
9
|
+
describe "#hard_assert" do
|
10
|
+
it "takes a constraint and a schedule" do
|
11
|
+
@school.add :schedules, 1
|
12
|
+
@school.add :hard_constraint, @c_true
|
13
|
+
expect {
|
14
|
+
Constraint.hard_assert @school.sched[0], @school.cs[:h][0]
|
15
|
+
}.to_not raise_error(ArgumentError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "throws an arg error upon getting bad input" do
|
19
|
+
expect {
|
20
|
+
Constraint.hard_assert :not_schec, @c_true
|
21
|
+
}.to raise_error(ArgumentError)
|
22
|
+
@school.add :schedules, 1
|
23
|
+
expect {
|
24
|
+
Constraint.hard_assert @school.scheds[0], :not_constraint
|
25
|
+
}.to raise_error(ArgumentError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns false for an impossible-to-meet constraint" do
|
29
|
+
@school.add :schedules, 1
|
30
|
+
Constraint.hard_assert(@school.scheds[0], @c_false).should == false
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns true for an always-met constraint" do
|
34
|
+
@school.add :schedules, 1
|
35
|
+
Constraint.hard_assert(@school.scheds[0], @c_true).should == true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "Constraint.make_slots_ary", :wip=>true do
|
40
|
+
it "returns nil for nil input" do
|
41
|
+
expect {
|
42
|
+
Constraint.make_slots_ary(nil)
|
43
|
+
}.to raise_error(ArgumentError)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "turns {:mon => {8 => [:Art]}} into [:mon, 8, :Art]" do
|
47
|
+
h = {:mon => {8 => [:Art]}}
|
48
|
+
a = [[:mon, 8, :Art]]
|
49
|
+
Constraint.make_slots_ary(h).should == a
|
50
|
+
end
|
51
|
+
|
52
|
+
it "works for a bigger hash too" do
|
53
|
+
h = {:mon => {8 => [:Art, :Music], 9 => [:Music]}, :wed => {13 => [:Art]}}
|
54
|
+
a = [[:mon, 8, :Art], [:mon, 8, :Music], [:mon, 9, :Music], [:wed, 13, :Art]]
|
55
|
+
Constraint.make_slots_ary(h).should == a
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "Constraint.day_descriptor", :wip => true do
|
60
|
+
it "gives a slot_descriptor for a whole day" do
|
61
|
+
s_d = {:mon => {8 => [:One],
|
62
|
+
9 => [:One],
|
63
|
+
10 => [:One],
|
64
|
+
11 => [:One],
|
65
|
+
12 => [:One],
|
66
|
+
13 => [:One],
|
67
|
+
14 => [:One]}}
|
68
|
+
Constraint.day_descriptor(:mon, @school.times, [:One]).should == s_d
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "Constraint.week_descriptor", :wip => true do
|
73
|
+
it "gives a slot_descriptor for a whole week" do
|
74
|
+
s_d = {:mon => {8 => [0]},
|
75
|
+
:tue => {8 => [0]},
|
76
|
+
:wed => {8 => [0]},
|
77
|
+
:thu => {8 => [0]},
|
78
|
+
:fri => {8 => [0]}}
|
79
|
+
Constraint.week_descriptor([8], [0]).should == s_d
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
module Schedsolver2
|
4
|
+
|
5
|
+
describe Schedule do
|
6
|
+
include_context "main"
|
7
|
+
|
8
|
+
describe "#[]=, #[]" do
|
9
|
+
before(:all) do
|
10
|
+
@et = @ts[:e][0]
|
11
|
+
@et2 = @ts[:e][1]
|
12
|
+
@ht = @ts[:h][0]
|
13
|
+
@ht2 = @ts[:h][1]
|
14
|
+
@school.add :schedules, 1
|
15
|
+
@sched_class = @school.scheds[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "adds teachers to slots in the schedule" do
|
19
|
+
@sched_class[:mon, 8, @et.id] = @ht.id
|
20
|
+
@sched_class.sched[0][0][0].should == @ht.id
|
21
|
+
|
22
|
+
@sched_class[:wed, 12, @et.id] = @ht.id
|
23
|
+
@sched_class.sched[0][0][0].should == @ht.id
|
24
|
+
end
|
25
|
+
|
26
|
+
it "blows up when it is given bad arguments" do
|
27
|
+
expect{@sched_class[:not_a_day, 8, @et.id]}.to raise_error(ArgumentError)
|
28
|
+
expect{@sched_class[:mon, -1, @et.id]}.to raise_error(ArgumentError)
|
29
|
+
expect{@sched_class[:mon, 8, :Not_a_teacher]}.to raise_error(ArgumentError)
|
30
|
+
|
31
|
+
expect{@sched_class[:not_a_day, 8, @et.id]=nil}.to raise_error(ArgumentError)
|
32
|
+
expect{@sched_class[:mon, -1, @et.id]=nil}.to raise_error(ArgumentError)
|
33
|
+
expect{@sched_class[:mon, 8, :Not_a_teacher]=nil}.to raise_error(ArgumentError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "throws InsanityError if one HC gets simultaneous ECs" do
|
37
|
+
@sched_class[:mon, 8, @et.id] = @ht.id
|
38
|
+
expect{
|
39
|
+
@sched_class[:mon, 8, @et2.id] = @ht.id
|
40
|
+
}.to raise_error(InsanityError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "doesn't throw Insanity error if distinct HCs get simultaneous ECs" do
|
44
|
+
@sched_class[:mon, 8, @et.id] = @ht.id
|
45
|
+
expect{
|
46
|
+
@sched_class[:mon, 8, @et2.id] = @ht2.id
|
47
|
+
}.to_not raise_error(InsanityError)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|