purchase 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,7 @@ Feature: Purchase Committee Calculations
2
2
  The purchase model should generate correct committee calculations
3
3
 
4
4
  Scenario Outline: Adjusted cost committee from cost and date
5
- Given a purchase emitter
5
+ Given a purchase emitter
6
6
  And a characteristic "cost" of "<cost>"
7
7
  And characteristic "date" of "<date>"
8
8
  When the "adjusted_cost" committee is calculated
@@ -10,23 +10,24 @@ Feature: Purchase Committee Calculations
10
10
  And the conclusion of the committee should be "<adjusted_cost>"
11
11
  Examples:
12
12
  | cost | date | adjusted_cost |
13
- | 831.23 | 2010-08-01 | 831.23 |
14
- | 11.00 | 2005-07-14 | 11.0 |
13
+ | 831.23 | 2010-08-01 | 688.67439 |
14
+ | 11.00 | 2005-07-14 | 9.11350 |
15
15
 
16
16
  Scenario Outline: Adjusted cost committee from purchase amount and date
17
- Given a purchase emitter
17
+ Given a purchase emitter
18
18
  And a characteristic "purchase_amount" of "<amount>"
19
19
  And characteristic "date" of "<date>"
20
- When the "adjusted_cost" committee is calculated
21
- Then the committee should have used quorum "from purchase amount and date"
20
+ When the "cost" committee is calculated
21
+ And the "adjusted_cost" committee is calculated
22
+ Then the committee should have used quorum "from cost and date"
22
23
  And the conclusion of the committee should be "<adjusted_cost>"
23
24
  Examples:
24
25
  | amount | date | adjusted_cost |
25
- | 831.23 | 2010-08-01 | 748.107 |
26
- | 11.00 | 2005-07-14 | 9.9 |
26
+ | 831.23 | 2010-08-01 | 619.80695 |
27
+ | 11.00 | 2005-07-14 | 8.20215 |
27
28
 
28
29
  Scenario Outline: Merchant category committee from merchant
29
- Given a purchase emitter
30
+ Given a purchase emitter
30
31
  And a characteristic "merchant.id" of "<id>"
31
32
  When the "merchant_category" committee is calculated
32
33
  Then the conclusion of the committee should have "mcc" of "<mcc>"
@@ -35,94 +36,114 @@ Feature: Purchase Committee Calculations
35
36
  | 1 | 5111 |
36
37
  | 2 | 5732 |
37
38
 
38
- Scenario Outline: Industry shares committee
39
- Given a purchase emitter
39
+ Scenario Outline: Industry shares committee from merchant category
40
+ Given a purchase emitter
40
41
  And a characteristic "merchant_category.mcc" of "<mcc>"
41
42
  When the "industry_shares" committee is calculated
42
- Then the conclusion of the committee should have a record identified with "naics_code" of "<naics>" and having "ratio" of "<ratio>"
43
+ Then the conclusion of the committee should have a record identified with "naics_code" of "<naics>" and having "ratio" of "<share>"
43
44
  Examples:
44
- | mcc | naics | ratio |
45
- | 5111 | 45321 | 1.0 |
46
- | 5732 | 443112 | 1.0 |
47
- | 5172 | 32411 | 0.8 |
48
- | 5172 | 324121 | 0.05 |
49
- | 5172 | 324122 | 0.05 |
50
- | 5172 | 324191 | 0.05 |
51
- | 5172 | 324199 | 0.05 |
45
+ | mcc | naics | share |
46
+ | 5111 | 45321 | 1.0 |
47
+ | 5172 | 32411 | 0.8 |
48
+ | 5172 | 324121 | 0.05 |
49
+ | 5172 | 324122 | 0.05 |
50
+ | 5172 | 324191 | 0.05 |
51
+ | 5172 | 324199 | 0.05 |
52
52
 
53
- Scenario Outline: Product line shares committee
54
- Given a purchase emitter
53
+ Scenario Outline: Product line shares committee from merchant category
54
+ Given a purchase emitter
55
55
  And a characteristic "merchant_category.mcc" of "<mcc>"
56
56
  When the "industry_shares" committee is calculated
57
57
  And the "product_line_shares" committee is calculated
58
- Then the conclusion of the committee should include a key of <ps_code> and value <ratio>
58
+ Then the conclusion of the committee should have a record identified with "ps_code" of "<ps_code>" and having "ratio" of "<share>"
59
59
  Examples:
60
- | mcc | ps_code | ratio |
60
+ | mcc | ps_code | share |
61
61
  | 5111 | 20370 | 0.6 |
62
- | 5732 | 20375 | 0.5 |
63
- | 5172 | 30860 | 0.32 |
64
- | 5172 | 30861 | 0.0225 |
65
- | 5172 | 30862 | 0.0175 |
66
- | 5172 | 30863 | 0.018 |
67
- | 5172 | 30864 | 0.019 |
68
-
69
- Scenario Outline: Sector shares committee from industry shares
70
- Given a purchase emitter
71
- And a characteristic "merchant_category.mcc" of "<mcc>"
72
- When the "industry_shares" committee is calculated
73
- And the "sector_shares" committee is calculated
74
- Then the conclusion of the committee should include a key of "<io_code>" and subvalue "share" of "<share>" and subvalue "emission_factor" of "<emission_factor>"
75
- Examples:
76
- | mcc | io_code | emission_factor | share |
77
- | 5111 | | | |
78
- | 5732 | | | |
79
- | 5172 | 324110 | 2.0 | 0.8 |
80
- | 5172 | 324121 | 1.3 | 0.05 |
81
- | 5172 | 324122 | 0.9 | 0.05 |
82
- | 5172 | 324191 | 0.2 | 0.05 |
83
- | 5172 | 324199 | 1.2 | 0.05 |
62
+ | 5111 | 20852 | 0.2 |
63
+ | 5111 | 20853 | 0.2 |
84
64
 
85
- Scenario Outline: Sector shares committee from industry shares and product line shares
86
- Given a purchase emitter
65
+ Scenario Outline: Sector shares committee from merchant category
66
+ Given a purchase emitter
87
67
  And a characteristic "merchant_category.mcc" of "<mcc>"
88
68
  When the "industry_shares" committee is calculated
89
69
  And the "product_line_shares" committee is calculated
90
70
  And the "sector_shares" committee is calculated
91
- Then the conclusion of the committee should include a key of "<io_code>" and subvalue "share" of "<share>" and subvalue "emission_factor" of "<emission_factor>"
71
+ Then the conclusion of the committee should have a record identified with "io_code" of "<io_code>" and having "emission_factor" of "<emission_factor>"
72
+ And the conclusion of the committee should have a record identified with "io_code" of "<io_code>" and having "share" of "<share>"
92
73
  Examples:
93
74
  | mcc | io_code | emission_factor | share |
94
75
  | 5111 | 334111 | 1.3 | 0.24 |
95
- | 5111 | 33411A | 0.5 | 0.18 |
96
76
  | 5111 | 511200 | 1.0 | 0.18 |
97
- | 5111 | 339940 | 1.1 | 0.2 |
98
- | 5111 | 322230 | 1.4 | 0.2 |
99
- | 5732 | 33411A | 0.5 | 0.5 |
77
+ | 5111 | 33411A | 0.5 | 0.18 |
78
+ | 5111 | 322230 | 1.4 | 0.2 |
79
+ | 5111 | 339940 | 1.1 | 0.2 |
100
80
  | 5732 | 334300 | 1.2 | 0.25 |
101
- | 5732 | 334210 | 1.6 | 0.2 |
102
- | 5172 | 324110 | 2.0 | 0.256 |
81
+ | 5732 | 33411A | 0.5 | 0.5 |
82
+ | 5732 | 334210 | 1.6 | 0.2 |
83
+ | 5812 | 722000 | 0.8 | 1.0 |
84
+ | 5172 | 324110 | 2.0 | 0.8 |
103
85
  | 5172 | 324121 | 1.3 | 0.05 |
104
86
  | 5172 | 324122 | 0.9 | 0.05 |
105
- | 5172 | 324191 | 0.2 | 0.32 |
87
+ | 5172 | 324191 | 0.2 | 0.05 |
106
88
  | 5172 | 324199 | 1.2 | 0.05 |
107
- | 8225 | 722000 | 0.8 | 0.15 |
108
89
 
109
- Scenario Outline: Emission factor from sector shares
110
- Given a purchase emitter
90
+ Scenario Outline: Emission factor from merchant category
91
+ Given a purchase emitter
111
92
  And a characteristic "merchant_category.mcc" of "<mcc>"
112
93
  When the "industry_shares" committee is calculated
113
94
  And the "product_line_shares" committee is calculated
114
95
  And the "sector_shares" committee is calculated
115
- And the "emission_factor" committee is calculated
116
- Then the conclusion of the committee should be "<emission_factor>"
96
+ And the "emission_factors" committee is calculated
97
+ Then the conclusion of the committee should have a record identified with "io_code" of "<io_code>" and having "factor" of "<emission_factor_share>"
117
98
  Examples:
118
- | mcc | emission_factor |
119
- | 5111 | 1.082 |
120
- | 5732 | 0.87 |
121
- | 5172 | 0.799205 |
122
- | 8225 | 0.12 |
99
+ | mcc | io_code | emission_factor_share |
100
+ | 5111 | 334111 | 0.312 |
101
+ | 5111 | 33411A | 0.09 |
102
+ | 5111 | 511200 | 0.18 |
103
+ | 5111 | 339940 | 0.22 |
104
+ | 5111 | 322230 | 0.28 |
105
+ | 5732 | 33411A | 0.25 |
106
+ | 5732 | 334300 | 0.3 |
107
+ | 5732 | 334210 | 0.32 |
108
+ | 5172 | 324110 | 1.6 |
109
+ | 5172 | 324121 | 0.065 |
110
+ | 5172 | 324122 | 0.045 |
111
+ | 5172 | 324191 | 0.01 |
112
+ | 5172 | 324199 | 0.06 |
113
+ | 5812 | 722000 | 0.8 |
123
114
 
124
- Scenario Outline: Emission factor from default
115
+ Scenario: Emission factor from default
116
+ Given a purchase emitter
117
+ When the "emission_factors" committee is calculated
118
+ Then the conclusion of the committee should have a record identified with "io_code" of "0" and having "factor" of "1"
119
+
120
+ Scenario Outline: Sector emissions from merchant id, cost, and date
125
121
  Given a purchase emitter
126
- And a characteristic "merchant_category.mcc" of "<mcc>"
127
- When the "emission_factor" committee is calculated
128
- Then the conclusion of the committee should be "100"
122
+ And a characteristic "merchant.id" of "<merchant>"
123
+ And a characteristic "cost" of "<cost>"
124
+ And a characteristic "date" of "<date>"
125
+ When the "adjusted_cost" committee is calculated
126
+ And the "merchant_category" committee is calculated
127
+ And the "industry_shares" committee is calculated
128
+ And the "product_line_shares" committee is calculated
129
+ And the "sector_shares" committee is calculated
130
+ And the "emission_factors" committee is calculated
131
+ And the "sector_emissions" committee is calculated
132
+ Then the conclusion of the committee should include "<emission>"
133
+ Examples:
134
+ | merchant | io_code | cost | date | emission |
135
+ | 1 | 322230 | 100.00 | 2010-07-28 | 23.19801 |
136
+ | 1 | 334111 | 100.00 | 2010-07-28 | 25.84921 |
137
+ | 1 | 339940 | 100.00 | 2010-07-28 | 18.22701 |
138
+ | 1 | 511200 | 100.00 | 2010-07-28 | 14.91301 |
139
+ | 1 | 33411A | 100.00 | 2010-07-28 | 7.45650 |
140
+ | 2 | 334210 | 100.00 | 2010-07-28 | 26.51201 |
141
+ | 2 | 334300 | 100.00 | 2010-07-28 | 24.85501 |
142
+ | 2 | 33411A | 100.00 | 2010-07-28 | 20.71251 |
143
+ | 3 | 722000 | 100.00 | 2010-07-28 | 66.28003 |
144
+ | 4 | 7211A0 | 100.00 | 2010-07-28 | 82.85004 |
145
+ | 5 | 324110 | 100.00 | 2010-07-28 | 132.56007 |
146
+ | 5 | 324121 | 100.00 | 2010-07-28 | 5.38525 |
147
+ | 5 | 324122 | 100.00 | 2010-07-28 | 3.72825 |
148
+ | 5 | 324191 | 100.00 | 2010-07-28 | 0.82850 |
149
+ | 5 | 324199 | 100.00 | 2010-07-28 | 4.97100 |
@@ -1,7 +1,7 @@
1
1
  Feature: Purchase Emissions Calculations
2
2
  The purchase model should generate correct emission calculations
3
3
 
4
- Scenario Outline: Calculations for a merchant
4
+ Scenario Outline: Calculations starting from a merchant
5
5
  Given a purchase has "merchant.id" of "<id>"
6
6
  And it has "cost" of "<cost>"
7
7
  And it has "date" of "<date>"
@@ -9,22 +9,51 @@ Feature: Purchase Emissions Calculations
9
9
  Then the emission value should be within 1 kgs of <emission>
10
10
  Examples:
11
11
  | id | cost | date | emission |
12
- | 1 | 100.00 | 2010-07-28 | 108.2 |
13
- | 2 | 100.00 | 2010-07-28 | 87.0 |
14
- | 3 | 100.00 | 2010-07-28 | 80.0 |
15
- | 4 | 100.00 | 2010-07-28 | 121.0 |
16
- | 5 | 100.00 | 2010-07-28 | 79.9205 |
12
+ | 1 | 100.00 | 2010-07-28 | 89.64 |
13
+ | 2 | 100.00 | 2010-07-28 | 72.08 |
14
+ | 3 | 100.00 | 2010-07-28 | 66.28 |
15
+ | 4 | 100.00 | 2010-07-28 | 82.85 |
16
+ | 5 | 100.00 | 2010-07-28 | 147.47 |
17
17
 
18
- Scenario Outline: Calculations for a merchant category
18
+ Scenario Outline: Calculations starting from a merchant category
19
19
  Given a purchase has "merchant_category.mcc" of "<mcc>"
20
20
  And it has "cost" of "<cost>"
21
21
  And it has "date" of "<date>"
22
22
  When emissions are calculated
23
- Then the emission value should be within 1 kgs of <emission>
23
+ Then the emission value should be within 0.1 kgs of <emission>
24
24
  Examples:
25
25
  | mcc | cost | date | emission |
26
- | 5111 | 100.00 | 2010-07-28 | 108.2 |
27
- | 5732 | 100.00 | 2010-07-28 | 87.0 |
28
- | 5812 | 100.00 | 2010-07-28 | 80.0 |
29
- | 3504 | 100.00 | 2010-07-28 | 121.0 |
30
- | 5172 | 100.00 | 2010-07-28 | 79.9205 |
26
+ | 5111 | 100.00 | 2010-07-28 | 89.64 |
27
+ | 5732 | 100.00 | 2010-07-28 | 72.08 |
28
+ | 5812 | 100.00 | 2010-07-28 | 66.28 |
29
+ | 3504 | 100.00 | 2010-07-28 | 82.85 |
30
+ | 5172 | 100.00 | 2010-07-28 | 147.47 |
31
+
32
+ Scenario Outline: Calculations starting from industry
33
+ Given a purchase has "naics_code" of "<naics>"
34
+ And it has "cost" of "<cost>"
35
+ And it has "date" of "<date>"
36
+ When emissions are calculated
37
+ Then the emission value should be within 0.1 kgs of <emission>
38
+ Examples:
39
+ | naics | cost | date | emission |
40
+ | 45321 | 100.00 | 2010-07-28 | 89.64 |
41
+ | 443112 | 100.00 | 2010-07-28 | 72.08 |
42
+ | 72211 | 100.00 | 2010-07-28 | 66.28 |
43
+ | 72111 | 100.00 | 2010-07-28 | 82.85 |
44
+ | 32411 | 100.00 | 2010-07-28 | 132.56 |
45
+ | 324121 | 100.00 | 2010-07-28 | 5.39 |
46
+ | 324122 | 100.00 | 2010-07-28 | 3.73 |
47
+ | 324191 | 100.00 | 2010-07-28 | 0.83 |
48
+ | 324199 | 100.00 | 2010-07-28 | 4.97 |
49
+
50
+ Scenario Outline: Calculations without merchant, merchant category, or industry
51
+ Given a purchase has "cost" of "<cost>"
52
+ And it has "date" of "<date>"
53
+ When emissions are calculated
54
+ Then the emission value should be within 0.1 kgs of <emission>
55
+ Examples:
56
+ | cost | date | emission |
57
+ | 100.00 | 2010-07-28 | 82.85 |
58
+ | 100.00 | 2010-08-28 | 82.85 |
59
+ | 120.00 | 2010-08-28 | 99.42 |
@@ -5,4 +5,4 @@ require 'cucumber'
5
5
  require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
6
6
 
7
7
  require 'sniff'
8
- Sniff.init File.join(File.dirname(__FILE__), '..', '..'), :earth => :industry
8
+ Sniff.init File.join(File.dirname(__FILE__), '..', '..'), :earth => :industry, :cucumber => true
@@ -1,5 +1,6 @@
1
1
  require 'leap'
2
2
  require 'timeframe'
3
+ require 'date'
3
4
 
4
5
  module BrighterPlanet
5
6
  module Purchase
@@ -11,96 +12,92 @@ module BrighterPlanet
11
12
  def self.included(base)
12
13
  base.extend ::Leap::Subject
13
14
  base.extend FastTimestamp
15
+
14
16
  base.decide :emission, :with => :characteristics do
15
17
  committee :emission do
16
- quorum 'from emissions factor and adjusted cost', :needs => [:emission_factor, :adjusted_cost] do |characteristics|
17
- # lbs CO2e / 2002 US $ 2002 US $
18
- characteristics[:emission_factor] * characteristics[:adjusted_cost]
18
+ quorum 'from emissions factor and adjusted cost', :needs => :sector_emissions do |characteristics|
19
+ characteristics[:sector_emissions].inject(0) { |sum, emission| sum += emission }
19
20
  end
20
-
21
- quorum 'default' do
22
- raise "The purchase's default emission quorum should never be called"
21
+ end
22
+
23
+ committee :sector_emissions do
24
+ quorum 'from emissions factors and adjusted cost', :needs => [:emission_factors, :adjusted_cost] do |characteristics|
25
+ characteristics[:emission_factors].map do |emission_factor|
26
+ emission_factor.factor * characteristics[:adjusted_cost]
27
+ end
23
28
  end
24
29
  end
25
-
26
- committee :emission_factor do
27
- quorum 'from sector_shares', :needs => [:sector_shares] do |characteristics|
28
- characteristics[:sector_shares].inject(0) do |sum, (io_code, data)|
29
- if data[:emission_factor].nil?
30
+
31
+ committee :emission_factors do
32
+ quorum 'from sector shares', :needs => [:sector_shares] do |characteristics|
33
+ characteristics[:sector_shares].inject([]) do |list, sector_share|
34
+ if sector_share.emission_factor.nil?
30
35
  raise MissingEmissionFactor,
31
- "Missing emission factor for sector #{io_code}"
36
+ "Missing emission factor for sector #{sector_share.io_code}"
32
37
  end
33
- sum + data[:emission_factor] * data[:share]
38
+ factor = sector_share.emission_factor * sector_share.share
39
+ list << EmissionFactor.new(sector_share.io_code, factor)
34
40
  end
35
41
  end
36
42
 
43
+ # FIXME TODO figure out if we really want a fallback and get a real one
37
44
  quorum 'default' do
38
- # FIXME TODO figure out a real fallback emission factor
39
- 100
45
+ [EmissionFactor.new(0, 1)]
40
46
  end
41
47
  end
42
48
 
43
49
  committee :sector_shares do
44
50
  quorum 'from industry shares and product line shares', :needs => [:industry_shares, :product_line_shares] do |characteristics|
45
- industry_sector_shares = sector_shares_from_industry_shares(characteristics[:industry_shares])
51
+ industry_shares = characteristics[:industry_shares]
52
+ industry_sector_shares = industry_shares.inject([]) do |list, industry_share|
53
+ industry_share.industries_sectors.each do |industry_sector|
54
+ io_code = industry_sector.io_code
55
+ unless ['420000','4A0000'].include?(io_code.to_s)
56
+ calculated_share = industry_share.ratio * industry_sector.ratio
57
+ sector = industry_sector.sector
58
+ list << SectorShare.new(sector, calculated_share)
59
+ end
60
+ end
61
+ list
62
+ end
46
63
 
47
64
  product_line_shares = characteristics[:product_line_shares]
48
- product_lines_sectors = ProductLinesSectors.find :all,
49
- :conditions => { :ps_code => product_line_shares.keys }
50
- product_sector_shares = product_lines_sectors.inject({}) do |hash, product_line_sector|
51
- io_code = product_line_sector.io_code
52
- ps_code = product_line_sector.ps_code
53
- product_line_share = product_line_shares[ps_code]
54
-
55
- share = product_line_sector.ratio * product_line_share
56
- sector = product_line_sector.sector
57
- if sector.nil?
58
- raise MissingSectorForProductLineSector,
59
- "Missing a related sector for ProductLineSector #{product_line_sector.inspect}"
65
+ product_sector_shares = product_line_shares.inject([]) do |list, product_line_share|
66
+ product_line_share.product_lines_sectors.each do |product_line_sector|
67
+ calculated_share = product_line_sector.ratio * product_line_share.ratio
68
+ sector = product_line_sector.sector
69
+ list << SectorShare.new(sector, calculated_share)
60
70
  end
61
- hash[io_code] = {
62
- :share => share,
63
- :emission_factor => sector.emission_factor
64
- }
65
- hash
71
+ list
66
72
  end
67
73
 
68
- industry_sector_shares.merge product_sector_shares
69
- end
70
-
71
- # TODO Do we need this?
72
- quorum 'from industry shares', :needs => [:industry_shares] do |characteristics|
73
- sector_shares_from_industry_shares(characteristics[:industry_shares])
74
- end
75
-
76
- quorum 'default' do
77
- raise "We need a merchant, merchant category, industry, or product_line"
74
+ industry_sector_shares + product_sector_shares
78
75
  end
79
76
  end
80
-
77
+
81
78
  committee :product_line_shares do
82
- quorum 'from industry shares', :needs => [:industry_shares] do |characteristics|
79
+ quorum 'from industry shares', :needs => :industry_shares do |characteristics|
83
80
  industry_shares = characteristics[:industry_shares]
84
- industries_product_lines = industry_shares.
85
- map(&:industries_product_lines).flatten
86
-
87
- industries_product_lines.inject({}) do |hash, industry_product_line|
88
- ps_code = industry_product_line.ps_code
89
- naics_code = industry_product_line.naics_code
90
- industry_share = industry_shares.find_by_naics_code naics_code
91
- hash[ps_code] =
92
- industry_product_line.ratio * industry_share.ratio
93
- hash
81
+ industry_shares.inject([]) do |list, industry_share|
82
+ industry_share.industries_product_lines.each do |industry_product_line|
83
+ ratio = industry_product_line.ratio * industry_share.ratio
84
+ list << ProductLineShare.new(industry_product_line.ps_code,
85
+ ratio)
86
+ end
87
+ list
94
88
  end
95
89
  end
96
90
  end
97
91
 
98
92
  committee :industry_shares do
99
- quorum 'from merchant category', :needs => [:merchant_category] do |characteristics|
100
- characteristics[:merchant_category].merchant_categories_industries
93
+ quorum 'from merchant category', :needs => :merchant_category do |characteristics|
94
+ IndustryShare.find_all_by_merchant_category characteristics[:merchant_category]
95
+ end
96
+ quorum 'from industry', :needs => :naics_code do |characteristics|
97
+ IndustryShare.find_all_by_naics_code characteristics[:naics_code]
101
98
  end
102
99
  end
103
-
100
+
104
101
  committee :merchant_category do
105
102
  quorum 'from merchant', :needs => [:merchant] do |characteristics|
106
103
  characteristics[:merchant].merchant_category
@@ -109,42 +106,101 @@ module BrighterPlanet
109
106
 
110
107
  committee :adjusted_cost do
111
108
  quorum 'from cost and date', :needs => [:cost, :date] do |characteristics|
112
- # FIXME TODO convert cost to 2002 dollars based on date
113
- characteristics[:cost]
109
+ # TODO: Come up with a way to fetch real CPI conversions
110
+ @cpi_lookup ||= {
111
+ 2009 => 1.189, 2010 => 1.207, 2011 => 1.225, 2012 => 1.245,
112
+ 2013 => 1.265 }
113
+
114
+ date = parse_date characteristics[:date]
115
+ conversion_factor = @cpi_lookup[date.year] || 1.207
116
+
117
+ characteristics[:cost].to_f / conversion_factor
114
118
  end
119
+ end
115
120
 
116
- quorum 'from purchase amount and date', :needs => [:purchase_amount, :date] do |characteristics|
121
+ committee :cost do
122
+ quorum 'from purchase amount', :needs => :purchase_amount do |characteristics|
117
123
  # FIXME TODO take out tax, then convert to 2002 US $ based on date and cost
118
124
  characteristics[:purchase_amount] * 0.9
119
125
  end
126
+ end
120
127
 
128
+ committee :date do
121
129
  quorum 'default' do
122
- raise "We need either a cost or purchase amount"
130
+ Date.today
123
131
  end
124
132
  end
125
133
  end
126
134
  # FIXME TODO make other committees to report emissions by gas, by io sector, etc.
127
135
  end
128
136
 
129
- def self.sector_shares_from_industry_shares(industry_shares)
130
- industry_sectors = industry_shares.map(&:industries_sectors).flatten
131
- industry_sectors.inject({}) do |hash, industry_sector|
132
- io_code = industry_sector.io_code
133
- unless ['420000','4A0000'].include?(io_code.to_s)
134
- naics_code = industry_sector.naics_code
135
- industry_share = industry_shares.find_by_naics_code naics_code
136
- calculated_share = industry_share.ratio * industry_sector.ratio
137
- sector = industry_sector.sector
138
- if sector.nil?
139
- raise MissingSectorForIndustrySector,
140
- "Missing a related sector for IndustrySector #{industry_sector.inspect}"
137
+ def self.parse_date(date)
138
+ date = date.is_a?(Date) ? date : Date.parse(date)
139
+ end
140
+
141
+ class IndustryShare
142
+ class << self
143
+ def find_all_by_naics_code(naics_code)
144
+ industry = Industry.find_by_naics_code naics_code
145
+ from_merchant_categories_industries industry.merchant_categories_industries
146
+ end
147
+ def find_all_by_merchant_category(merchant_category)
148
+ from_merchant_categories_industries merchant_category.merchant_categories_industries
149
+ end
150
+
151
+ private
152
+ def from_merchant_categories_industries(merchant_categories_industries)
153
+ merchant_categories_industries.map do |mci|
154
+ new mci.naics_code, mci.ratio
141
155
  end
142
- hash[io_code] = {
143
- :share => calculated_share,
144
- :emission_factor => sector.emission_factor
145
- }
146
156
  end
147
- hash
157
+ end
158
+
159
+ attr_accessor :naics_code, :ratio
160
+
161
+ def initialize(naics_code, ratio)
162
+ self.naics_code = naics_code
163
+ self.ratio = ratio
164
+ end
165
+
166
+ def industries_product_lines
167
+ IndustriesProductLines.find_all_by_naics_code naics_code
168
+ end
169
+
170
+ def industries_sectors
171
+ IndustriesSectors.find_all_by_naics_code naics_code
172
+ end
173
+ end
174
+
175
+ class ProductLineShare
176
+ attr_accessor :ps_code, :ratio
177
+
178
+ def initialize(ps_code, ratio)
179
+ self.ps_code = ps_code
180
+ self.ratio = ratio
181
+ end
182
+
183
+ def product_lines_sectors
184
+ ProductLinesSectors.find_all_by_ps_code ps_code
185
+ end
186
+ end
187
+
188
+ class SectorShare
189
+ attr_accessor :io_code, :share, :emission_factor
190
+
191
+ def initialize(sector, share)
192
+ self.io_code = sector.io_code
193
+ self.share = share
194
+ self.emission_factor = sector.emission_factor
195
+ end
196
+ end
197
+
198
+ class EmissionFactor
199
+ attr_accessor :io_code, :factor
200
+
201
+ def initialize(io_code, factor)
202
+ self.io_code = io_code
203
+ self.factor = factor
148
204
  end
149
205
  end
150
206
  end
@@ -8,15 +8,10 @@ module BrighterPlanet
8
8
  base.characterize do
9
9
  has :merchant
10
10
  has :merchant_category
11
- has :industry_shares
12
- has :product_line_shares
13
- has :sector_shares
14
- has :purchase_amount # full purchase amount
11
+ has :naics_code
12
+ has :total # full purchase amount
15
13
  has :tax # tax portion of purchase
16
14
  has :cost # cost before tax
17
- has :line_item # text describing what was purchased
18
- has :customer_code
19
- has :zip_code
20
15
  has :date
21
16
  end
22
17
  end
@@ -4,7 +4,6 @@ Sniff::Database.define_schema do
4
4
  create_table "purchase_records", :force => true do |t|
5
5
  t.integer 'merchant_id'
6
6
  t.string 'mcc'
7
- t.string 'naics_code'
8
7
  t.string 'ps_code'
9
8
  t.string 'io_code'
10
9
  t.float 'purchase_amount'
@@ -9,7 +9,6 @@ class PurchaseRecord < ActiveRecord::Base
9
9
 
10
10
  belongs_to :merchant, :foreign_key => 'merchant_id'
11
11
  belongs_to :merchant_category, :foreign_key => 'mcc'
12
- # belongs_to :industry, :foreign_key => 'naics_code'
13
- # belongs_to :product_line, :foreign_key => 'ps_code'
14
- # belongs_to :sector, :foreign_key => 'io_code'
12
+
13
+ attr_accessor :naics_code
15
14
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: purchase
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 4
10
- version: 0.0.4
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andy Rossmeissl
@@ -19,7 +19,7 @@ autorequire:
19
19
  bindir: bin
20
20
  cert_chain: []
21
21
 
22
- date: 2010-08-02 00:00:00 -04:00
22
+ date: 2010-08-11 00:00:00 -04:00
23
23
  default_executable:
24
24
  dependencies:
25
25
  - !ruby/object:Gem::Dependency
@@ -144,12 +144,12 @@ dependencies:
144
144
  requirements:
145
145
  - - "="
146
146
  - !ruby/object:Gem::Version
147
- hash: 57
147
+ hash: 23
148
148
  segments:
149
149
  - 0
150
- - 0
151
- - 19
152
- version: 0.0.19
150
+ - 1
151
+ - 6
152
+ version: 0.1.6
153
153
  requirement: *id008
154
154
  - !ruby/object:Gem::Dependency
155
155
  type: :runtime
@@ -207,12 +207,12 @@ dependencies:
207
207
  requirements:
208
208
  - - ">="
209
209
  - !ruby/object:Gem::Version
210
- hash: 57
210
+ hash: 55
211
211
  segments:
212
212
  - 0
213
213
  - 0
214
- - 19
215
- version: 0.0.19
214
+ - 20
215
+ version: 0.0.20
216
216
  requirement: *id012
217
217
  - !ruby/object:Gem::Dependency
218
218
  type: :runtime
@@ -255,12 +255,12 @@ dependencies:
255
255
  requirements:
256
256
  - - "="
257
257
  - !ruby/object:Gem::Version
258
- hash: 13
258
+ hash: 11
259
259
  segments:
260
260
  - 0
261
261
  - 4
262
- - 1
263
- version: 0.4.1
262
+ - 2
263
+ version: 0.4.2
264
264
  requirement: *id015
265
265
  - !ruby/object:Gem::Dependency
266
266
  type: :runtime
@@ -302,7 +302,6 @@ extensions: []
302
302
 
303
303
  extra_rdoc_files:
304
304
  - LICENSE
305
- - README.html
306
305
  - README.markdown
307
306
  files:
308
307
  - LICENSE
@@ -314,8 +313,6 @@ files:
314
313
  - lib/purchase/summarization.rb
315
314
  - lib/test_support/db/schema.rb
316
315
  - lib/test_support/purchase_record.rb
317
- - README.html
318
- - features/step_definitions/committee_steps.rb
319
316
  - features/support/env.rb
320
317
  - features/purchase_committees.feature
321
318
  - features/purchase_emissions.feature
@@ -354,7 +351,6 @@ signing_key:
354
351
  specification_version: 3
355
352
  summary: A carbon model
356
353
  test_files:
357
- - features/step_definitions/committee_steps.rb
358
354
  - features/support/env.rb
359
355
  - features/purchase_committees.feature
360
356
  - features/purchase_emissions.feature
data/README.html DELETED
@@ -1,102 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <!DOCTYPE html PUBLIC
3
- "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"
4
- "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">
5
- <html xmlns:svg='http://www.w3.org/2000/svg' xml:lang='en' xmlns='http://www.w3.org/1999/xhtml'>
6
- <head><meta content='application/xhtml+xml;charset=utf-8' http-equiv='Content-type' /><title>Purchase</title></head>
7
- <body>
8
- <h1 id='purchase'>Purchase</h1>
9
-
10
- <p>The purchase gem is used by Brighter Planet&#8217;s Carbon Middleware platform to define a mathematical emissions model for a purchase activity. In addition, this gem defines how the model&#8217;s calculation is displayed in a human readable format.</p>
11
-
12
- <p>Contributions are welcome and encouraged, as we believe that help from the scientific and developer communities will help us provide correct and transparent models of greenhouse gas-emitting activities. See the section on the Collaboration Cycle below.</p>
13
-
14
- <h2 id='usage'>Usage</h2>
15
-
16
- <p>The purchase gem defines a module, BrighterPlanet::Purchase that should be included into an ActiveRecord model. Calling #emission on an instance of the model will determine the amount of emissions (CO2e) generated by the activity.</p>
17
-
18
- <pre><code>class PurchaseRecord &lt; ActiveRecord::Base
19
- include BrighterPlanet::Purchase
20
- ...
21
- end
22
-
23
- p = PurchaseRecord.new :merchant_category_code =&gt; 2324, :amount =&gt; 873.45
24
- p.emission
25
- # =&gt; 2472</code></pre>
26
-
27
- <h2 id='getting_ready_for_development'>Getting Ready for Development</h2>
28
-
29
- <p>Purchase uses bundler to manage dependencies. To prepare your environment, perform the following: git clone git://github.com/brighterplanet/purchase.git cd purchase gem install bundler (note: prior to bundler 1.0 release, add &#8211;pre) bundle install</p>
30
-
31
- <h3 id='a_note_to_rvm_users_and_nonusers'>A Note to RVM Users (and Non-Users)</h3>
32
-
33
- <p><a href='http://rvm.beginrescueend.com/'>RVM</a> is a great tool for testing and development as it can create independent gem environments.</p>
34
-
35
- <p>Included in the repository is a .rvmrc that will automatically create a ruby 1.8.7 gemset for you named purchase. If you already have a gemset with the same name, simply replace the `pwd` within the .rvmrc to whatever name you want.</p>
36
-
37
- <h2 id='testing'>Testing</h2>
38
-
39
- <p>Our goal is to have sufficient testing coverage for our emissions models. We have chosen to write our tests using <a href='http://cukes.info'>cucumber</a> as the table-formatted testing fits well with our calculations tests and the plain-english stories lower the barrier to entry for programmers of all skill levels.</p>
40
-
41
- <p>There are two main features:</p>
42
-
43
- <ul>
44
- <li>purchase_emissions.feature, which tests emission values for various inputs</li>
45
-
46
- <li>purchase_committees.feature, which tests sub-calculations used within the model</li>
47
- </ul>
48
-
49
- <p>To run all tests: <code>rake</code> or <code>cucumber</code></p>
50
-
51
- <p>To run individual tests: <code>cucumber features/name_of_feature.feature</code></p>
52
-
53
- <p>To run a specific scenario: <code>cucumber features/name_of_feature.feature:line_number_of_scenario</code></p>
54
-
55
- <h3 id='sniff'>Sniff</h3>
56
-
57
- <p>The sniff gem is used to provide a testing environment that includes dependent data models and a database.</p>
58
-
59
- <p>See: <a href='http://github.com/brighterplanet/sniff'>http://github.com/brighterplanet/sniff</a></p>
60
-
61
- <h3 id='earth'>Earth</h3>
62
-
63
- <p>The earth gem contains the model definitions for dependent data models. For instance, this is zip code data or fuel prices used in the emissions calculations. Earth will look for any test fixtures defined in lib/test_support/db/fixtures. These sample records are in csv format and are imported into the testing database.</p>
64
-
65
- <h2 id='implementation'>Implementation</h2>
66
-
67
- <p>See the README for the sniff gem: <a href='http://github.com/brighterplanet/sniff/blob/master/README.markdown'>http://github.com/brighterplanet/sniff/blob/master/README.markdown</a></p>
68
-
69
- <h2 id='collaboration_cycle'>Collaboration cycle</h2>
70
-
71
- <p>Brighter Planet vigorously encourages collaborative improvement of its emitter libraries. Collaboration requires a (free) GitHub account.</p>
72
-
73
- <h3 id='you'>You</h3>
74
-
75
- <ol>
76
- <li>Fork the emitter repository on GitHub.</li>
77
-
78
- <li>Write a test proving the existing implementation&#8217;s inadequacy. Ensure that the test fails. Commit the test.</li>
79
-
80
- <li>Improve the code until your new test passes and commit your changes.</li>
81
-
82
- <li>Push your changes to your GitHub fork.</li>
83
-
84
- <li>Submit a pull request to brighterplanet.</li>
85
- </ol>
86
-
87
- <h3 id='brighter_planet'>Brighter Planet</h3>
88
-
89
- <ol>
90
- <li>Receive a pull request.</li>
91
-
92
- <li>Pull changes from forked repository.</li>
93
-
94
- <li>Ensure tests pass.</li>
95
-
96
- <li>Review changes for scientific accuracy.</li>
97
-
98
- <li>Merge changes to master repository and publish.</li>
99
-
100
- <li>Direct production environment to use new emitter version.</li>
101
- </ol>
102
- </body></html>
@@ -1,93 +0,0 @@
1
- def coerce_value(value)
2
- if value =~ /\d+\.\d+/
3
- value.to_f
4
- elsif value =~ /^\d+$/
5
- value.to_i
6
- else
7
- value
8
- end
9
- end
10
-
11
- def compare_values(a, b)
12
- if b =~ /\d+\.\d+/
13
- b = b.to_f
14
- a.should be_close(b, 0.00001)
15
- elsif b =~ /^\d+$/
16
- b = b.to_i
17
- a.should == b
18
- else
19
- a.should == b
20
- end
21
- end
22
-
23
- Given /^a purchase emitter$/ do
24
- @activity = PurchaseRecord
25
- end
26
-
27
- Given /^(a )?characteristic "(.*)" of "(.*)"$/ do |_, name, value|
28
- @characteristics ||= {}
29
-
30
- if name =~ /\./
31
- model_name, attribute = name.split /\./
32
- model = model_name.singularize.camelize.constantize
33
- value = model.send "find_by_#{attribute}", value
34
- @characteristics[model_name.to_sym] = value
35
- else
36
- value = coerce_value(value)
37
- @characteristics[name.to_sym] = value
38
- end
39
- end
40
-
41
- When /^the "(.*)" committee is calculated$/ do |committee_name|
42
- @decision ||= @activity.decisions[:emission]
43
- @committee = @decision.committees.find { |c| c.name.to_s == committee_name }
44
- @report = @committee.report(@characteristics, [])
45
- @characteristics[committee_name.to_sym] = @report.conclusion
46
- end
47
-
48
- Then /^the committee should have used quorum "(.*)"$/ do |quorum|
49
- @report.quorum.name.should == quorum
50
- end
51
-
52
- Then /^the conclusion of the committee should be "(.+)"$/ do |conclusion|
53
- compare_values(@report.conclusion, conclusion)
54
- end
55
-
56
- Then /^the conclusion of the committee should include a key of (.*) and value (.*)$/ do |key, value|
57
- if key.present?
58
- @report.conclusion.keys.map(&:to_s).should include(key)
59
- else
60
- @report.conclusion.keys.map(&:to_s).should be_empty
61
- end
62
-
63
- if value.present?
64
- @report.conclusion.each do |k, v|
65
- @report.conclusion[k.to_s] == v
66
- end
67
- compare_values(@report.conclusion[key.to_s], value)
68
- end
69
- end
70
-
71
- Then /^the conclusion of the committee should have "(.*)" of "(.*)"$/ do |attribute, value|
72
- report_value = coerce_value @report.conclusion.send(attribute)
73
- report_value.should == coerce_value(value)
74
- end
75
-
76
- Then /^the conclusion of the committee should have a record identified with "(.*)" of "(.*)" and having "(.*)" of "(.*)"$/ do |id_field, id, field, value|
77
- id_field = id_field.to_sym
78
- records = @report.conclusion
79
- record = records.send("find_by_#{id_field}", id)
80
- coerce_value(record.send(field)).should == coerce_value(value)
81
- end
82
-
83
- Then /^the conclusion of the committee should include a key of "(.*)" and subvalue "(.*)" of "(.*)" and subvalue "(.*)" of "(.*)"$/ do |key, subkey1, subvalue1, subkey2, subvalue2|
84
- if key.present?
85
- @report.conclusion.keys.map(&:to_s).should include(key)
86
- actual_subvalue1 = coerce_value(@report.conclusion[key.to_s][subkey1.to_sym].to_s)
87
- compare_values(actual_subvalue1, subvalue1)
88
- actual_subvalue2 = coerce_value(@report.conclusion[key.to_s][subkey2.to_sym].to_s)
89
- compare_values(actual_subvalue2, subvalue2)
90
- else
91
- @report.conclusion.keys.map(&:to_s).should be_empty
92
- end
93
- end