symian 0.1.0
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.
- 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
|
+
|