differential 1.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.
- 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
|