symian 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +63 -0
- data/Rakefile +9 -0
- data/bin/symian +24 -0
- data/lib/symian.rb +5 -0
- data/lib/symian/configuration.rb +97 -0
- data/lib/symian/cost_analyzer.rb +37 -0
- data/lib/symian/event.rb +53 -0
- data/lib/symian/generator.rb +89 -0
- data/lib/symian/incident.rb +135 -0
- data/lib/symian/operator.rb +97 -0
- data/lib/symian/performance_analyzer.rb +60 -0
- data/lib/symian/simulation.rb +171 -0
- data/lib/symian/sorted_array.rb +57 -0
- data/lib/symian/support/dsl_helper.rb +18 -0
- data/lib/symian/support/yaml_io.rb +9 -0
- data/lib/symian/support_group.rb +182 -0
- data/lib/symian/trace_collector.rb +107 -0
- data/lib/symian/transition_matrix.rb +168 -0
- data/lib/symian/version.rb +3 -0
- data/lib/symian/work_shift.rb +158 -0
- data/symian.gemspec +29 -0
- data/test/symian/configuration_test.rb +66 -0
- data/test/symian/cost_analyzer_test.rb +52 -0
- data/test/symian/generator_test.rb +27 -0
- data/test/symian/incident_test.rb +104 -0
- data/test/symian/operator_test.rb +60 -0
- data/test/symian/reference_configuration.rb +107 -0
- data/test/symian/support_group_test.rb +111 -0
- data/test/symian/trace_collector_test.rb +203 -0
- data/test/symian/transition_matrix_test.rb +88 -0
- data/test/symian/work_shift_test.rb +68 -0
- data/test/test_helper.rb +5 -0
- metadata +188 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'symian/reference_configuration'
|
4
|
+
|
5
|
+
|
6
|
+
describe Symian::Configuration do
|
7
|
+
|
8
|
+
context 'simulation-related parameters' do
|
9
|
+
|
10
|
+
it 'should correctly load simulation start' do
|
11
|
+
with_reference_config do |conf|
|
12
|
+
conf.start_time.must_equal START_TIME
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should correctly load simulation duration' do
|
17
|
+
with_reference_config do |conf|
|
18
|
+
conf.duration.must_equal DURATION
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should correctly load simulation end time' do
|
23
|
+
with_reference_config do |conf|
|
24
|
+
conf.end_time.must_equal START_TIME + DURATION
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should correctly load warmup phase duration' do
|
29
|
+
with_reference_config do |conf|
|
30
|
+
conf.warmup_duration.must_equal WARMUP_DURATION
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should initialize incident generation' do
|
35
|
+
with_reference_config do |conf|
|
36
|
+
conf.incident_generation.must_equal INCIDENT_GENERATION
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should initialize support groups' do
|
41
|
+
with_reference_config do |conf|
|
42
|
+
conf.support_groups.must_equal SUPPORT_GROUPS
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should initialize transition matrix' do
|
47
|
+
with_reference_config do |conf|
|
48
|
+
conf.transition_matrix.must_equal TRANSITION_MATRIX
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'cloning mechanism' do
|
55
|
+
it 'should correctly clone w/ other ops' do
|
56
|
+
with_reference_config do |conf|
|
57
|
+
new_ops = (1..conf.support_groups.size).to_a
|
58
|
+
new_conf = conf.reallocate_ops_and_clone(new_ops)
|
59
|
+
new_conf.support_groups.zip(new_ops) do |(k,v),num_ops|
|
60
|
+
v[:operators][:number].must_equal num_ops
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'symian/reference_configuration'
|
4
|
+
require 'symian/cost_analyzer'
|
5
|
+
|
6
|
+
|
7
|
+
describe Symian::CostAnalyzer do
|
8
|
+
|
9
|
+
context 'operations' do
|
10
|
+
it 'should correctly calculate daily operations' do
|
11
|
+
EXAMPLE_KPIS = { :mttr => 9000, :micd => 450 }
|
12
|
+
|
13
|
+
with_reference_config do |conf|
|
14
|
+
ca = Symian::CostAnalyzer.new(conf)
|
15
|
+
res = ca.evaluate(EXAMPLE_KPIS)
|
16
|
+
res[:operations].must_equal((25_000 + 30_000 + 40_000) / 365.0)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'contracting' do
|
22
|
+
it 'should work if no contracting function is provided' do
|
23
|
+
cost_analysis_wo_contracting = COST_ANALYSIS.reject {|x| x == :contracting }
|
24
|
+
with_reference_config(cost_analysis: cost_analysis_wo_contracting) do |conf|
|
25
|
+
Symian::CostAnalyzer.new(conf)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'drift' do
|
31
|
+
it 'should work if no drift function is provided' do
|
32
|
+
cost_analysis_wo_drift = COST_ANALYSIS.reject {|x| x == :drift }
|
33
|
+
with_reference_config(cost_analysis: cost_analysis_wo_drift) do |conf|
|
34
|
+
Symian::CostAnalyzer.new(conf)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# it 'should work if penalty function returns something' do
|
40
|
+
# evaluator = with_reference_config do |conf|
|
41
|
+
# SISFC::Evaluator.new(conf)
|
42
|
+
# end
|
43
|
+
# evaluator.evaluate_business_impact({ mttr: 0.075 }, nil, EXAMPLE_ALLOCATION)
|
44
|
+
# end
|
45
|
+
|
46
|
+
# it 'should work if penalty function returns nil' do
|
47
|
+
# evaluator = with_reference_config do |conf|
|
48
|
+
# SISFC::Evaluator.new(conf)
|
49
|
+
# end
|
50
|
+
# evaluator.evaluate_business_impact({ mttr: 0.025 }, nil, EXAMPLE_ALLOCATION)
|
51
|
+
# end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'symian/generator'
|
4
|
+
require 'symian/event'
|
5
|
+
|
6
|
+
|
7
|
+
describe Symian::IncidentGenerator do
|
8
|
+
|
9
|
+
let(:simulation) { MiniTest::Mock.new }
|
10
|
+
|
11
|
+
|
12
|
+
it 'should generate incidents with random arrival times' do
|
13
|
+
gen = Symian::IncidentGenerator.new(simulation,
|
14
|
+
:type => :sequential_random_variable,
|
15
|
+
:source => { :first_value => Time.now,
|
16
|
+
:seed => (Process.pid / rand).to_i,
|
17
|
+
:distribution => :discrete_uniform,
|
18
|
+
:max_value => 100 })
|
19
|
+
simulation.expect(:new_event, nil, [ Symian::Event::ET_INCIDENT_ARRIVAL, Symian::Incident, Time, nil ])
|
20
|
+
gen.generate
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
it 'should generate incidents with arrival times from traces'
|
25
|
+
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'symian/incident'
|
4
|
+
|
5
|
+
describe Symian::Incident do
|
6
|
+
|
7
|
+
it 'should ignore invalid parameters passed to the constructor' do
|
8
|
+
Symian::Incident.new(1, Time.now, :some_invalid_param => "dummy")
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not be closed unless closure time is provided' do
|
12
|
+
inc = Symian::Incident.new(1, Time.now)
|
13
|
+
inc.closed?.must_equal false
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should be closed if closure time is provided' do
|
17
|
+
inc = Symian::Incident.new(1, Time.now)
|
18
|
+
inc.closure_time = Time.now
|
19
|
+
inc.closed?.must_equal true
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should have a nil TTR if still open' do
|
23
|
+
inc = Symian::Incident.new(1, Time.now)
|
24
|
+
inc.ttr.must_be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should have a valid TTR if closed' do
|
28
|
+
arrival_time = Time.now
|
29
|
+
closure_time = Time.now + 1.hour
|
30
|
+
inc = Symian::Incident.new(1, arrival_time,
|
31
|
+
:closure_time => closure_time)
|
32
|
+
inc.ttr.must_equal 1.hour
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should correctly manage tracking information' do
|
36
|
+
inc = Symian::Incident.new(1, Time.now)
|
37
|
+
tis = [ { :type => :queue,
|
38
|
+
:at => Time.now,
|
39
|
+
:duration => 50.seconds,
|
40
|
+
:sg => 'SG1' },
|
41
|
+
{ :type => :work,
|
42
|
+
:at => Time.now + 1.hour,
|
43
|
+
:duration => 2.hours,
|
44
|
+
:sg => 'SG2' },
|
45
|
+
{ :type => :suspend,
|
46
|
+
:at => Time.now + 20.minutes,
|
47
|
+
:duration => 30.minutes,
|
48
|
+
:sg => 'SG3' } ]
|
49
|
+
tis.each do |ti|
|
50
|
+
inc.add_tracking_information(ti)
|
51
|
+
end
|
52
|
+
i = 0
|
53
|
+
inc.with_tracking_information do |ti|
|
54
|
+
ti.must_equal tis[i]
|
55
|
+
i += 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should correctly calculate time spent at last support group' do
|
60
|
+
inc = Symian::Incident.new(1, Time.now)
|
61
|
+
now = Time.now
|
62
|
+
|
63
|
+
tis = [ { :type => :queue,
|
64
|
+
:at => now,
|
65
|
+
:duration => 1.hour,
|
66
|
+
:sg => 'SG1' },
|
67
|
+
{ :type => :work,
|
68
|
+
:at => now + 1.hour,
|
69
|
+
:duration => 2.hours,
|
70
|
+
:sg => 'SG1' },
|
71
|
+
{ :type => :queue,
|
72
|
+
:at => now + 3.hours,
|
73
|
+
:duration => 1.hour,
|
74
|
+
:sg => 'SG2' },
|
75
|
+
{ :type => :work,
|
76
|
+
:at => now + 4.hours,
|
77
|
+
:duration => 2.hours,
|
78
|
+
:sg => 'SG2' },
|
79
|
+
{ :type => :queue,
|
80
|
+
:at => now + 6.hours,
|
81
|
+
:duration => 1.hour,
|
82
|
+
:sg => 'SG3' },
|
83
|
+
{ :type => :work,
|
84
|
+
:at => now + 7.hours,
|
85
|
+
:duration => 2.hours,
|
86
|
+
:sg => 'SG3' },
|
87
|
+
{ :type => :suspend,
|
88
|
+
:at => now + 9.hours,
|
89
|
+
:duration => 30.minutes,
|
90
|
+
:sg => 'SG3' },
|
91
|
+
{ :type => :work,
|
92
|
+
:at => now + 9.hours + 30.minutes,
|
93
|
+
:duration => 30.minutes,
|
94
|
+
:sg => 'SG3' } ]
|
95
|
+
|
96
|
+
tis.each do |ti|
|
97
|
+
inc.add_tracking_information(ti)
|
98
|
+
end
|
99
|
+
|
100
|
+
inc.total_time_at_last_sg.must_equal 4.hours
|
101
|
+
inc.queue_time_at_last_sg.must_equal 1.hour
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'symian/incident'
|
4
|
+
require 'symian/operator'
|
5
|
+
|
6
|
+
|
7
|
+
describe Symian::Operator do
|
8
|
+
|
9
|
+
it 'should support :workshift => :all_day_long shortcut' do
|
10
|
+
o = Symian::Operator.new(1, 1, :workshift => :all_day_long)
|
11
|
+
o.workshift.must_equal Symian::WorkShift::WORKSHIFT_24x7
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
it 'should work on minor incidents until escalation' do
|
16
|
+
assignment_time = Time.now # incident is assigned now
|
17
|
+
needed_work_time = 1.hour # incident requires 1 hour of work
|
18
|
+
arrival_time = assignment_time - 1.hour # incident arrived one hour ago
|
19
|
+
expected_escalation_time = assignment_time + 1.hour # incident should be closed in one hour
|
20
|
+
|
21
|
+
o = Symian::Operator.new(1, 1)
|
22
|
+
i = Symian::Incident.new(1, arrival_time)
|
23
|
+
|
24
|
+
o.assign(i, { :needed_work_time => needed_work_time }, assignment_time).must_equal [ :incident_escalation, expected_escalation_time ]
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
it 'should work on major incidents until time of shift' do
|
29
|
+
assignment_time = Time.now # incident is assigned now
|
30
|
+
needed_work_time = 2.hours # incident requires 2 hours of work
|
31
|
+
arrival_time = assignment_time - 1.hour # incident arrived one hour ago
|
32
|
+
workshift_start = assignment_time - 7.hours # operator workshift started 7 hours ago
|
33
|
+
workshift_end = assignment_time + 1.hour # operator workshift ends in 1 hour
|
34
|
+
|
35
|
+
o = Symian::Operator.new(1, 1,
|
36
|
+
:workshift => Symian::WorkShift.new(:custom,
|
37
|
+
:start_time => workshift_start,
|
38
|
+
:end_time => workshift_end))
|
39
|
+
i = Symian::Incident.new(1, arrival_time)
|
40
|
+
|
41
|
+
o.assign(i, { :needed_work_time => needed_work_time }, assignment_time).must_equal [ :operator_off_duty, workshift_end ]
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
it 'should have specialization factors skewing its productivity' do
|
46
|
+
assignment_time = Time.now # incident is assigned now
|
47
|
+
needed_work_time = 2.hours # incident requires 2 hours of work
|
48
|
+
arrival_time = assignment_time - 1.hour # incident arrived one hour ago
|
49
|
+
expected_escalation_time = assignment_time + 1.hour # incident should be closed in one hour
|
50
|
+
|
51
|
+
o = Symian::Operator.new(1, 1,
|
52
|
+
:specialization => { :web => 2.0 }) # 2x specialization on 'web' incidents
|
53
|
+
|
54
|
+
i = Symian::Incident.new(1, arrival_time, :category => :web)
|
55
|
+
|
56
|
+
o.assign(i, { :needed_work_time => needed_work_time }, assignment_time).must_equal [ :incident_escalation, expected_escalation_time ]
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'symian/configuration'
|
2
|
+
|
3
|
+
START_TIME = Time.utc(1978, 'Aug', 12, 14, 30, 0)
|
4
|
+
DURATION = 1.minute
|
5
|
+
WARMUP_DURATION = 10.seconds
|
6
|
+
SIMULATION_CHARACTERIZATION = <<END
|
7
|
+
start_time Time.utc(1978, 'Aug', 12, 14, 30, 0)
|
8
|
+
duration 1.minute
|
9
|
+
warmup_duration 10.seconds
|
10
|
+
END
|
11
|
+
|
12
|
+
INCIDENT_GENERATION_CHARACTERIZATION = <<END
|
13
|
+
incident_generation \
|
14
|
+
:type => :sequential_random_variable,
|
15
|
+
:source => {
|
16
|
+
:first_value => Time.utc(1978, 'Aug', 12, 14, 31, 0),
|
17
|
+
:distribution => :exponential,
|
18
|
+
:mean => 1/0.0015
|
19
|
+
}
|
20
|
+
END
|
21
|
+
|
22
|
+
SUPPORT_GROUPS_CHARACTERIZATION = <<END
|
23
|
+
support_groups \
|
24
|
+
'SG1' => { :work_time => { :distribution => :exponential, :mean => 227370 },
|
25
|
+
:operators => { :number => 1, :workshift => :all_day_long } },
|
26
|
+
'SG2' => { :work_time => { :distribution => :exponential, :mean => 1980 },
|
27
|
+
:operators => { :number => 1, :workshift => :all_day_long } },
|
28
|
+
'SG3' => { :work_time => { :distribution => :exponential, :mean => 360 },
|
29
|
+
:operators => { :number => 1, :workshift => :all_day_long } }
|
30
|
+
END
|
31
|
+
|
32
|
+
TRANSITION_MATRIX_CHARACTERIZATION = <<END
|
33
|
+
transition_matrix %q{
|
34
|
+
From/To,SG1,SG2,SG3,Out
|
35
|
+
In,25,50,25,0
|
36
|
+
SG1,0,10,70,20
|
37
|
+
SG2,5,0,45,45
|
38
|
+
SG3,10,20,0,70
|
39
|
+
}
|
40
|
+
END
|
41
|
+
|
42
|
+
COST_ANALYSIS_CHARACTERIZATION = <<END
|
43
|
+
cost_analysis \
|
44
|
+
:operations => [
|
45
|
+
{ :sg_name => 'SG1', :operator_salary => 30_000 },
|
46
|
+
{ :sg_name => 'SG2', :operator_salary => 40_000 },
|
47
|
+
{ :sg_name => 'SG3', :operator_salary => 25_000 },
|
48
|
+
],
|
49
|
+
:contracting => lambda { |kpis|
|
50
|
+
kpis[:mttr] > 9000 ? 1500 : 0.0
|
51
|
+
},
|
52
|
+
:drift => lambda { |kpis|
|
53
|
+
target = 500
|
54
|
+
delta = target - kpis[:micd]
|
55
|
+
if delta > 0.0
|
56
|
+
1500.0 * (2.0 / Math::PI) * Math::atan(10.0 * delta / target)
|
57
|
+
else
|
58
|
+
0.0
|
59
|
+
end
|
60
|
+
}
|
61
|
+
END
|
62
|
+
|
63
|
+
|
64
|
+
# this is the whole reference configuration
|
65
|
+
# (useful for spec'ing configuration.rb)
|
66
|
+
REFERENCE_CONFIGURATION =
|
67
|
+
SIMULATION_CHARACTERIZATION +
|
68
|
+
INCIDENT_GENERATION_CHARACTERIZATION +
|
69
|
+
SUPPORT_GROUPS_CHARACTERIZATION +
|
70
|
+
TRANSITION_MATRIX_CHARACTERIZATION +
|
71
|
+
COST_ANALYSIS_CHARACTERIZATION
|
72
|
+
|
73
|
+
evaluator = Object.new
|
74
|
+
evaluator.extend Symian::Configurable
|
75
|
+
evaluator.instance_eval(REFERENCE_CONFIGURATION)
|
76
|
+
|
77
|
+
# these are preprocessed portions of the reference configuration
|
78
|
+
# (useful for spec'ing everything else)
|
79
|
+
INCIDENT_GENERATION = evaluator.incident_generation
|
80
|
+
SUPPORT_GROUPS = evaluator.support_groups
|
81
|
+
TRANSITION_MATRIX = evaluator.transition_matrix
|
82
|
+
COST_ANALYSIS = evaluator.cost_analysis
|
83
|
+
|
84
|
+
|
85
|
+
def with_reference_config(opts={})
|
86
|
+
# create temporary file with reference configuration
|
87
|
+
tf = Tempfile.open('REFERENCE_CONFIGURATION')
|
88
|
+
begin
|
89
|
+
tf.write(REFERENCE_CONFIGURATION)
|
90
|
+
tf.close
|
91
|
+
|
92
|
+
# create a configuration object from the reference configuration file
|
93
|
+
conf = Symian::Configuration.load_from_file(tf.path)
|
94
|
+
|
95
|
+
# apply any change from the opts parameter and validate the modified configuration
|
96
|
+
opts.each do |k,v|
|
97
|
+
conf.send(k, v)
|
98
|
+
end
|
99
|
+
conf.validate
|
100
|
+
|
101
|
+
# pass the configuration object to the block
|
102
|
+
yield conf
|
103
|
+
ensure
|
104
|
+
# delete temporary file
|
105
|
+
tf.delete
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'symian/incident'
|
4
|
+
require 'symian/support_group'
|
5
|
+
require 'symian/work_shift'
|
6
|
+
|
7
|
+
|
8
|
+
describe Symian::SupportGroup do
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
@simulation = MiniTest::Mock.new
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
it 'should be creatable with one operator group' do
|
16
|
+
sg = Symian::SupportGroup.new('SG',
|
17
|
+
@simulation,
|
18
|
+
{ :distribution => :exponential, :mean => 5 },
|
19
|
+
{ :number => 3, :workshift => Symian::WorkShift.new(:all_day_long) })
|
20
|
+
start_time = Time.now
|
21
|
+
sg.initialize_at(start_time)
|
22
|
+
sg.operators.size.must_equal 3
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
it 'should be creatable with several operator groups' do
|
27
|
+
sg = Symian::SupportGroup.new('SG',
|
28
|
+
@simulation,
|
29
|
+
{ :distribution => :exponential, :mean => 5 },
|
30
|
+
[ { :number => 3, :workshift => Symian::WorkShift.new(:all_day_long) },
|
31
|
+
{ :number => 4, :workshift => Symian::WorkShift.new(:all_day_long) },
|
32
|
+
{ :number => 3, :workshift => Symian::WorkShift.new(:all_day_long) } ])
|
33
|
+
start_time = Time.now
|
34
|
+
sg.initialize_at(start_time)
|
35
|
+
sg.operators.size.must_equal 10
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
it 'should allow incident reassignments' do
|
40
|
+
ws = Symian::WorkShift.new(:custom,
|
41
|
+
:start_time => Time.utc(2009, 'Jan', 1, 8, 0, 0),
|
42
|
+
:end_time => Time.utc(2009, 'Jan', 1, 16, 0, 0))
|
43
|
+
sg = Symian::SupportGroup.new('SG',
|
44
|
+
@simulation,
|
45
|
+
{ :distribution => :constant, :value => 10.hours },
|
46
|
+
[ { :number => 1, :workshift => ws } ])
|
47
|
+
start_time = Time.utc(2009, 'Jan', 1, 8, 0, 0)
|
48
|
+
incident_arrival = start_time + 2.hours
|
49
|
+
|
50
|
+
@simulation.expect(:new_event,
|
51
|
+
nil,
|
52
|
+
[ Symian::Event::ET_OPERATOR_LEAVING, String, incident_arrival + 6.hours, 'SG'])
|
53
|
+
|
54
|
+
sg.initialize_at(start_time)
|
55
|
+
i = Symian::Incident.new(0, incident_arrival,
|
56
|
+
:category => 'normal',
|
57
|
+
:priority => 0) # not supported at the moment
|
58
|
+
|
59
|
+
# first increase queue size,...
|
60
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_SUPPORT_GROUP_QUEUE_SIZE_CHANGE, 1, incident_arrival, 'SG'])
|
61
|
+
# ...then decrease it, ...
|
62
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_SUPPORT_GROUP_QUEUE_SIZE_CHANGE, 0, incident_arrival, 'SG'])
|
63
|
+
# ...assign it to an operator, ...
|
64
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_INCIDENT_ASSIGNMENT, Array, incident_arrival, 'SG'])
|
65
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_OPERATOR_ACTIVITY_STARTS, Array, incident_arrival, 'SG'])
|
66
|
+
# ...and finally escalate the incident.
|
67
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_OPERATOR_ACTIVITY_FINISHES, Array, incident_arrival + 6.hours, 'SG'])
|
68
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_INCIDENT_RESCHEDULING, Array, incident_arrival + 6.hours, 'SG'])
|
69
|
+
|
70
|
+
sg.new_incident(i, incident_arrival)
|
71
|
+
|
72
|
+
i.visited_support_groups.must_equal 1
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
it 'should accept new incidents' do
|
77
|
+
sg = Symian::SupportGroup.new('SG',
|
78
|
+
@simulation,
|
79
|
+
{ :distribution => :constant, :value => 500 },
|
80
|
+
[ { :number => 3, :workshift => Symian::WorkShift.new(:all_day_long) } ])
|
81
|
+
start_time = Time.now
|
82
|
+
sg.initialize_at(start_time)
|
83
|
+
incident_arrival = start_time + 3600 # after 1 hour
|
84
|
+
i = Symian::Incident.new(0, incident_arrival,
|
85
|
+
:category => 'normal',
|
86
|
+
:priority => 0) # not supported at the moment
|
87
|
+
|
88
|
+
# first increase queue size,...
|
89
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_SUPPORT_GROUP_QUEUE_SIZE_CHANGE, 1, incident_arrival, 'SG'])
|
90
|
+
# ...then decrease it, ...
|
91
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_SUPPORT_GROUP_QUEUE_SIZE_CHANGE, 0, incident_arrival, 'SG'])
|
92
|
+
# ...assign it to an operator, ...
|
93
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_INCIDENT_ASSIGNMENT, Array, incident_arrival, 'SG'])
|
94
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_OPERATOR_ACTIVITY_STARTS, Array, incident_arrival, 'SG'])
|
95
|
+
# ...and finally escalate the incident.
|
96
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_OPERATOR_ACTIVITY_FINISHES, Array, incident_arrival + 500, 'SG'])
|
97
|
+
@simulation.expect(:new_event, nil, [Symian::Event::ET_INCIDENT_ESCALATION, Symian::Incident, incident_arrival + 500, 'SG'])
|
98
|
+
|
99
|
+
sg.new_incident(i, incident_arrival)
|
100
|
+
|
101
|
+
i.visited_support_groups.must_equal 1
|
102
|
+
end
|
103
|
+
|
104
|
+
# it 'should handle operators going home'
|
105
|
+
|
106
|
+
# it 'should handle operators coming back'
|
107
|
+
|
108
|
+
# it 'should handle operators finishing their work'
|
109
|
+
|
110
|
+
end
|
111
|
+
|