differential 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +87 -0
- data/Guardfile +16 -0
- data/LICENSE +7 -0
- data/README.md +223 -0
- data/differential.gemspec +29 -0
- data/lib/differential.rb +10 -0
- data/lib/differential/calculator/calculator.rb +15 -0
- data/lib/differential/calculator/group.rb +62 -0
- data/lib/differential/calculator/has_totals.rb +24 -0
- data/lib/differential/calculator/item.rb +57 -0
- data/lib/differential/calculator/report.rb +54 -0
- data/lib/differential/calculator/side.rb +19 -0
- data/lib/differential/calculator/totals.rb +44 -0
- data/lib/differential/differential.rb +31 -0
- data/lib/differential/parser/parser.rb +11 -0
- data/lib/differential/parser/reader.rb +73 -0
- data/lib/differential/parser/record.rb +25 -0
- data/lib/differential/version.rb +12 -0
- data/spec/differential/calculator/report_spec.rb +137 -0
- data/spec/differential/calculator/side_spec.rb +30 -0
- data/spec/differential/calculator/totals_spec.rb +40 -0
- data/spec/differential/differential_spec.rb +34 -0
- data/spec/differential/parser/reader_spec.rb +113 -0
- data/spec/differential/parser/record_spec.rb +28 -0
- data/spec/spec_helper.rb +10 -0
- metadata +127 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
module Calculator
|
12
|
+
# A Report has 0 or more Group objects and a Group has 0 or more Item objects.
|
13
|
+
# Report -> Group -> Item
|
14
|
+
# A Group is, as the name implies, a grouping of items. It is up to the consumer application
|
15
|
+
# to define how to group (i.e. group based on this attribute's value or
|
16
|
+
# group based on these two attributes' values.)
|
17
|
+
class Group
|
18
|
+
include ::Differential::Calculator::HasTotals
|
19
|
+
|
20
|
+
attr_reader :id
|
21
|
+
|
22
|
+
def initialize(id)
|
23
|
+
raise ArgumentError, 'id is required' unless id
|
24
|
+
|
25
|
+
@id = id
|
26
|
+
end
|
27
|
+
|
28
|
+
def items
|
29
|
+
items_by_id.values
|
30
|
+
end
|
31
|
+
|
32
|
+
def add(record, side)
|
33
|
+
raise ArgumentError, 'record is required' unless record
|
34
|
+
raise ArgumentError, 'side is required' unless side
|
35
|
+
raise ArgumentError, "mismatch: #{record.group_id} != #{id}" if id != record.group_id
|
36
|
+
|
37
|
+
totals.add(record.value, side)
|
38
|
+
|
39
|
+
upsert_item(record, side)
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def upsert_item(record, side)
|
47
|
+
item_id = record.id
|
48
|
+
|
49
|
+
# Create a new item if one does not exist
|
50
|
+
items_by_id[item_id] = Item.new(item_id) unless items_by_id.key?(item_id)
|
51
|
+
|
52
|
+
items_by_id[item_id].add(record, side)
|
53
|
+
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def items_by_id
|
58
|
+
@items_by_id ||= {}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
module Calculator
|
12
|
+
# There are multiple classes that all need calculation support (The Total class.)
|
13
|
+
# Instead of using inheritance, those classes can use this mix-in for composition.
|
14
|
+
module HasTotals
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
def_delegators :totals, :a_sigma, :b_sigma, :delta
|
18
|
+
|
19
|
+
def totals
|
20
|
+
@totals ||= ::Differential::Calculator::Totals.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
module Calculator
|
12
|
+
# Consider this as being line-level and is the lowest point of calculation.
|
13
|
+
# Ultimately a Report object will turn all added Record objects into Item objects (placed
|
14
|
+
# in Group objects.)
|
15
|
+
class Item
|
16
|
+
include ::Differential::Calculator::HasTotals
|
17
|
+
include ::Differential::Calculator::Side
|
18
|
+
|
19
|
+
attr_reader :id, :a_records, :b_records
|
20
|
+
|
21
|
+
def initialize(id)
|
22
|
+
raise ArgumentError, 'id is required' unless id
|
23
|
+
|
24
|
+
@id = id
|
25
|
+
@a_records = []
|
26
|
+
@b_records = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def add(record, side)
|
30
|
+
raise ArgumentError, 'record is required' unless record
|
31
|
+
raise ArgumentError, 'side is required' unless side
|
32
|
+
raise ArgumentError, "mismatch: #{record.id} != #{id}" if id != record.id
|
33
|
+
|
34
|
+
totals.add(record.value, side)
|
35
|
+
|
36
|
+
account_for_record(record, side)
|
37
|
+
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def account_for_record(record, side)
|
44
|
+
case side
|
45
|
+
when A
|
46
|
+
@a_records << record
|
47
|
+
when B
|
48
|
+
@b_records << record
|
49
|
+
else
|
50
|
+
raise ArgumentError, "unknown side: #{side}"
|
51
|
+
end
|
52
|
+
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
module Calculator
|
12
|
+
# This class is responsible for building an entire report. Usage:
|
13
|
+
# - Instantiate a Reader.
|
14
|
+
# - Instantiate a Report.
|
15
|
+
# - Feed in dataset(s) into the Reader to generate Record objects.
|
16
|
+
# - Feed in Record objects, generated by a Reader, by calling Report#add.
|
17
|
+
# The Report object will keep running sums and deltas of all added records.
|
18
|
+
class Report
|
19
|
+
include HasTotals
|
20
|
+
|
21
|
+
def groups
|
22
|
+
groups_by_id.values
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(record, side)
|
26
|
+
raise ArgumentError, 'record is required' unless record
|
27
|
+
raise ArgumentError, 'side is required' unless side
|
28
|
+
|
29
|
+
totals.add(record.value, side)
|
30
|
+
|
31
|
+
upsert_group(record, side)
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def upsert_group(record, side)
|
39
|
+
group_id = record.group_id
|
40
|
+
|
41
|
+
# Create a new group if one does not exist
|
42
|
+
groups_by_id[group_id] = Group.new(group_id) unless groups_by_id.key?(group_id)
|
43
|
+
|
44
|
+
groups_by_id[group_id].add(record, side)
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def groups_by_id
|
50
|
+
@groups_by_id ||= {}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
module Calculator
|
12
|
+
# Differential can currently only compute calculations for two datasets, represented as:
|
13
|
+
# A and B. Ultimately, how you define what A and B are up to the consuming application.
|
14
|
+
module Side
|
15
|
+
A = :a
|
16
|
+
B = :b
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
module Calculator
|
12
|
+
# Value object that can capture basic calculations:
|
13
|
+
# - a_sigma is the sum of data set A's values.
|
14
|
+
# - b_sigma is the sum of data set B's values.
|
15
|
+
# - delta is the difference: b_sigma - a_sigma.
|
16
|
+
class Totals
|
17
|
+
include ::Differential::Calculator::Side
|
18
|
+
|
19
|
+
attr_reader :a_sigma, :b_sigma
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@a_sigma = 0
|
23
|
+
@b_sigma = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def delta
|
27
|
+
b_sigma - a_sigma
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(value, side)
|
31
|
+
case side
|
32
|
+
when A
|
33
|
+
@a_sigma += value
|
34
|
+
when B
|
35
|
+
@b_sigma += value
|
36
|
+
else
|
37
|
+
raise ArgumentError, "unknown side: #{side}"
|
38
|
+
end
|
39
|
+
|
40
|
+
self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'forwardable'
|
11
|
+
|
12
|
+
require_relative 'calculator/calculator'
|
13
|
+
require_relative 'parser/parser'
|
14
|
+
|
15
|
+
# This module will serve as the top-level entry point for consumers.
|
16
|
+
# You can stick with the API provided here unless you know the internals behind this point.
|
17
|
+
module Differential
|
18
|
+
extend ::Differential::Calculator::Side
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def calculate(dataset_a: [], dataset_b: [], reader_config: {})
|
22
|
+
reader = ::Differential::Parser::Reader.new(reader_config)
|
23
|
+
report = ::Differential::Calculator::Report.new
|
24
|
+
|
25
|
+
reader.each(dataset_a) { |record| report.add(record, A) }
|
26
|
+
reader.each(dataset_b) { |record| report.add(record, B) }
|
27
|
+
|
28
|
+
report
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require_relative 'reader'
|
11
|
+
require_relative 'record'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
module Parser
|
12
|
+
# This class is used to parse incoming datasets.
|
13
|
+
# Usage:
|
14
|
+
# Instantiate new object with configuration options.
|
15
|
+
# Call read to parse individual hash objects into Record objects.
|
16
|
+
class Reader
|
17
|
+
attr_reader :record_id_key,
|
18
|
+
:value_key,
|
19
|
+
:group_id_key
|
20
|
+
|
21
|
+
# Params:
|
22
|
+
# +record_id_key+:: The hash key(s) to use to uniquely identify a record (required)
|
23
|
+
# +value_key+:: The hash key used to extract the value of the record.
|
24
|
+
# +group_id_key+:: The hash key(s) to use to identify which group the record belongs to.
|
25
|
+
def initialize(record_id_key:, value_key:, group_id_key: nil)
|
26
|
+
raise ArgumentError, 'record_id_key is required' unless record_id_key
|
27
|
+
raise ArgumentError, 'value_key is required' unless value_key
|
28
|
+
|
29
|
+
@record_id_key = record_id_key
|
30
|
+
@value_key = value_key
|
31
|
+
@group_id_key = group_id_key
|
32
|
+
end
|
33
|
+
|
34
|
+
def each(hashes)
|
35
|
+
return enum_for(:each) unless block_given?
|
36
|
+
|
37
|
+
hashes.each do |hash|
|
38
|
+
record = read(hash)
|
39
|
+
yield record
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def read(hash)
|
46
|
+
id = make_record_id(hash)
|
47
|
+
group_id = make_group_id(hash)
|
48
|
+
value = hash[value_key]
|
49
|
+
|
50
|
+
::Differential::Parser::Record.new(id: id,
|
51
|
+
group_id: group_id,
|
52
|
+
value: value,
|
53
|
+
data: hash)
|
54
|
+
end
|
55
|
+
|
56
|
+
def make_record_id(hash)
|
57
|
+
record_id_key_array.map { |k| hash[k] }.join(':')
|
58
|
+
end
|
59
|
+
|
60
|
+
def make_group_id(hash)
|
61
|
+
group_id_key_array.map { |k| hash[k] }.join(':')
|
62
|
+
end
|
63
|
+
|
64
|
+
def record_id_key_array
|
65
|
+
@record_id_key_array ||= Array(record_id_key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def group_id_key_array
|
69
|
+
@group_id_key_array ||= Array(group_id_key)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
module Parser
|
12
|
+
# Represents a parsed record object. This is ultimately what a Reader creates and
|
13
|
+
# is serves as input into the Calculator module.
|
14
|
+
class Record
|
15
|
+
attr_reader :id, :group_id, :value, :data
|
16
|
+
|
17
|
+
def initialize(id:, group_id:, value:, data:)
|
18
|
+
@id = id
|
19
|
+
@group_id = group_id
|
20
|
+
@value = value
|
21
|
+
@data = data
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
module Differential
|
11
|
+
VERSION = '1.0.1'
|
12
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright (c) 2018-present, Blue Marble Payroll, LLC
|
5
|
+
#
|
6
|
+
# This source code is licensed under the MIT license found in the
|
7
|
+
# LICENSE file in the root directory of this source tree.
|
8
|
+
#
|
9
|
+
|
10
|
+
require './spec/spec_helper'
|
11
|
+
|
12
|
+
describe ::Differential::Calculator::Report do
|
13
|
+
it 'should initialize correctly' do
|
14
|
+
report = ::Differential::Calculator::Report.new
|
15
|
+
|
16
|
+
expect(report.a_sigma).to eq(0)
|
17
|
+
expect(report.b_sigma).to eq(0)
|
18
|
+
expect(report.delta).to eq(0)
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:group_1_plus8) do
|
22
|
+
::Differential::Parser::Record.new(id: '1', group_id: '1', value: 8, data: { id: '1' })
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:group_1_minus9) do
|
26
|
+
::Differential::Parser::Record.new(id: '1', group_id: '1', value: -9, data: { id: '1' })
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:group_2_plus3) do
|
30
|
+
::Differential::Parser::Record.new(id: '2', group_id: '2', value: 3, data: { id: '1' })
|
31
|
+
end
|
32
|
+
|
33
|
+
let(:group_2_minus2) do
|
34
|
+
::Differential::Parser::Record.new(id: '3', group_id: '2', value: -2, data: { id: '1' })
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when totaling sigma and deltas at report level' do
|
38
|
+
it 'should compute sigma & delta correctly' do
|
39
|
+
report = ::Differential::Calculator::Report.new
|
40
|
+
|
41
|
+
report.add(group_1_plus8, ::Differential::Calculator::Side::A)
|
42
|
+
|
43
|
+
expect(report.a_sigma).to eq(group_1_plus8.value)
|
44
|
+
expect(report.b_sigma).to eq(0)
|
45
|
+
expect(report.delta).to eq(-group_1_plus8.value)
|
46
|
+
|
47
|
+
report.add(group_1_minus9, ::Differential::Calculator::Side::B)
|
48
|
+
|
49
|
+
expect(report.a_sigma).to eq(group_1_plus8.value)
|
50
|
+
expect(report.b_sigma).to eq(group_1_minus9.value)
|
51
|
+
expect(report.delta).to eq(group_1_minus9.value - group_1_plus8.value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when totaling sigma and deltas at group level' do
|
56
|
+
it 'should compute sigma & delta correctly' do
|
57
|
+
report = ::Differential::Calculator::Report.new
|
58
|
+
|
59
|
+
report.add(group_1_plus8, ::Differential::Calculator::Side::A)
|
60
|
+
|
61
|
+
group1 = report.groups.first
|
62
|
+
|
63
|
+
expect(report.groups.length).to eq(1)
|
64
|
+
expect(group1.id).to eq(group_1_plus8.group_id)
|
65
|
+
expect(group1.a_sigma).to eq(group_1_plus8.value)
|
66
|
+
expect(group1.b_sigma).to eq(0)
|
67
|
+
expect(group1.delta).to eq(-group_1_plus8.value)
|
68
|
+
|
69
|
+
report.add(group_1_minus9, ::Differential::Calculator::Side::B)
|
70
|
+
|
71
|
+
group1 = report.groups.first
|
72
|
+
|
73
|
+
expect(report.groups.length).to eq(1)
|
74
|
+
expect(group1.id).to eq(group_1_plus8.group_id)
|
75
|
+
expect(group1.a_sigma).to eq(group_1_plus8.value)
|
76
|
+
expect(group1.b_sigma).to eq(group_1_minus9.value)
|
77
|
+
expect(group1.delta).to eq(group_1_minus9.value - group_1_plus8.value)
|
78
|
+
|
79
|
+
report.add(group_2_plus3, ::Differential::Calculator::Side::A)
|
80
|
+
|
81
|
+
group2 = report.groups.last
|
82
|
+
|
83
|
+
expect(report.groups.length).to eq(2)
|
84
|
+
expect(group2.id).to eq(group_2_plus3.group_id)
|
85
|
+
expect(group2.a_sigma).to eq(group_2_plus3.value)
|
86
|
+
expect(group2.b_sigma).to eq(0)
|
87
|
+
expect(group2.delta).to eq(-group_2_plus3.value)
|
88
|
+
|
89
|
+
report.add(group_2_minus2, ::Differential::Calculator::Side::B)
|
90
|
+
|
91
|
+
group2 = report.groups.last
|
92
|
+
|
93
|
+
expect(report.groups.length).to eq(2)
|
94
|
+
expect(group2.id).to eq(group_2_plus3.group_id)
|
95
|
+
expect(group2.a_sigma).to eq(group_2_plus3.value)
|
96
|
+
expect(group2.b_sigma).to eq(group_2_minus2.value)
|
97
|
+
expect(group2.delta).to eq(group_2_minus2.value - group_2_plus3.value)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when totaling sigma and deltas at item level' do
|
102
|
+
it 'should compute sigma & delta correctly' do
|
103
|
+
report = ::Differential::Calculator::Report.new
|
104
|
+
|
105
|
+
report.add(group_1_plus8, ::Differential::Calculator::Side::A)
|
106
|
+
report.add(group_1_minus9, ::Differential::Calculator::Side::B)
|
107
|
+
report.add(group_2_plus3, ::Differential::Calculator::Side::A)
|
108
|
+
report.add(group_2_minus2, ::Differential::Calculator::Side::B)
|
109
|
+
|
110
|
+
group1 = report.groups.first
|
111
|
+
group2 = report.groups.last
|
112
|
+
|
113
|
+
expect(group1.items.length).to eq(1)
|
114
|
+
expect(group2.items.length).to eq(2)
|
115
|
+
|
116
|
+
group1_item1 = group1.items.first
|
117
|
+
|
118
|
+
expect(group1_item1.id).to eq(group_1_plus8.id)
|
119
|
+
expect(group1_item1.a_sigma).to eq(group_1_plus8.value)
|
120
|
+
expect(group1_item1.b_sigma).to eq(group_1_minus9.value)
|
121
|
+
expect(group1_item1.delta).to eq(group_1_minus9.value - group_1_plus8.value)
|
122
|
+
|
123
|
+
group2_item1 = group2.items.first
|
124
|
+
group2_item2 = group2.items.last
|
125
|
+
|
126
|
+
expect(group2_item1.id).to eq(group_2_plus3.id)
|
127
|
+
expect(group2_item1.a_sigma).to eq(group_2_plus3.value)
|
128
|
+
expect(group2_item1.b_sigma).to eq(0)
|
129
|
+
expect(group2_item1.delta).to eq(-group_2_plus3.value)
|
130
|
+
|
131
|
+
expect(group2_item2.id).to eq(group_2_minus2.id)
|
132
|
+
expect(group2_item2.a_sigma).to eq(0)
|
133
|
+
expect(group2_item2.b_sigma).to eq(group_2_minus2.value)
|
134
|
+
expect(group2_item2.delta).to eq(group_2_minus2.value)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|