carbon 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,15 @@
1
1
  = Carbon
2
2
 
3
- Carbon is a Ruby API wrapper for the {Brighter Planet emission estimate web service}[http://carbon.brighterplanet.com], which is located at http://carbon.brighterplanet.com.
4
-
5
- By querying the web service, it can estimate the carbon emissions of many real-life objects, such as cars and houses, based on particular attributes that they may have.
3
+ Carbon is a Ruby API wrapper for the {Brighter Planet emission estimate web service}[http://carbon.brighterplanet.com], which is located at http://carbon.brighterplanet.com. By querying the web service, it can estimate the carbon emissions of many real-life objects, such as cars and houses, based on particular attributes that they may have.
6
4
 
7
5
  == Full documentation
8
6
 
9
7
  Give the quick start a try! Once you've exhausted that, read the {carbon gem RDoc}[http://rdoc.info/projects/brighterplanet/carbon] for more.
10
8
 
9
+ == Real-life integration guide
10
+
11
+ In addition to the quick start and full documentation, check out the {integration guide}[http://github.com/brighterplanet/carbon/blob/master/doc/INTEGRATION_GUIDE.rdoc]. It has code examples from our real-life migration followed by brief developer commentary.
12
+
11
13
  == Quick start
12
14
 
13
15
  <b>You'll need a Brighter Planet API key. See the "API keys" section below for details.</b>
@@ -65,7 +67,7 @@ You should get an API key from http://keys.brighterplanet.com and set it globall
65
67
 
66
68
  Now all of your queries will use that key.
67
69
 
68
- == A note on <tt>#to_param</tt>
70
+ == A note on <tt>#to_characteristic</tt> and <tt>#to_param</tt>
69
71
 
70
72
  Once you understand how to map attributes using the DSL, you might still be confused about what the gem actually calls on your objects.
71
73
 
@@ -75,12 +77,41 @@ Let's say <tt>RentalCar#make</tt> returns a <tt>Make</tt> object. You should def
75
77
 
76
78
  == A note on asynchronous queries
77
79
 
78
- To request an emission estimate asynchronously, simply pass an URL as the <tt>:callback</tt> option to <tt>#emission_estimate</tt>:
80
+ To specify that the result of a query should be POSTed back to you, simply pass an URL as the <tt>:callback</tt> option to <tt>#emission_estimate</tt>:
79
81
 
80
- > RentalCar.new.emission_estimate :callback => http://example.com/my/callback/handler
82
+ > RentalCar.new.emission_estimate :callback => 'http://example.com/my/callback/handler'
81
83
 
82
84
  A good way to test this is to set up a {PostBin}[http://postbin.org].
83
85
 
86
+ You can specify that the result be stored on S3 so that future identical requests can use the same estimate:
87
+
88
+ > RentalCar.new.emission_estimate :guid => 'A_GLOBALLY_UNIQUE_IDENTIFIER_FOR_THIS_EMISSION_ESTIMATE'
89
+
90
+ You might think of <tt>:guid</tt> as a hash key or a cache key, but it is not necessarily unique to the emitter object... rather it should identify the particular set of characteristics on that emitter. That way other emitters, if they have the same characteristics, can use the same estimate.
91
+
92
+ If you combine <tt>:guid</tt> and <tt>:defer => true</tt>, you have an easy, cheap 2-pass reporting system:
93
+
94
+ # pass 1: cron job runs a whole bunch of these
95
+ > car51.emission_estimate :guid => '[...]', :defer => true
96
+ > car52.emission_estimate :guid => '[...]', :defer => true
97
+ > car96.emission_estimate :guid => '[...]', :defer => true
98
+
99
+ # pass 2: a while later, cron job runs these
100
+ > car51.emission_estimate :guid => '[...]'
101
+ > car52.emission_estimate :guid => '[...]'
102
+ > car96.emission_estimate :guid => '[...]'
103
+
104
+ Most of these will be pre-calculated by the time you run pass #2. If any of them aren't done processing yet, it will revert to a real-time estimate... no big deal!
105
+
106
+ == Possible combinations
107
+
108
+ * () Runs a normal realtime request.
109
+ * (timeout) Runs a realtime request but allows timing out the network call. Raises <tt>Timeout::Error</tt> if timeout is exceeded.
110
+ * (guid) Runs a realtime request, but first checks if there is a result pre-calculated on S3. Stores result to S3 if there isn't.
111
+ * (callback) Runs an async request (so you don't immediately get the result back). The result will be POSTed to the URL you specify.
112
+ * (guid,defer) Runs an async request (so you don't immediately get the result back). The result will be stored to S3 as <tt>hxxp://storage.carbon.brighterplanet.com/SHA1-hexdigest-of-key+guid</tt>
113
+ * (guid,timeout) (NOT IMPLEMENTED) A hybrid "best effort" request that is as cheap as an async request but will try to return the full result data immediately. It keeps trying S3 until your timeout is exceeded, at which point it's just a normal async request.
114
+
84
115
  == A note on exceptions
85
116
 
86
117
  Since this gem connects to a web service, you need to be ready for network problems and latency. For example:
data/Rakefile CHANGED
@@ -13,6 +13,8 @@ begin
13
13
  gemspec.add_dependency 'activesupport', '>=2.3.5'
14
14
  gemspec.add_dependency 'nap', '>=0.4'
15
15
  gemspec.add_dependency 'timeframe', '>=0.0.7'
16
+ # This is still only on Rubyforge
17
+ gemspec.add_dependency 'SystemTimer', '>=1.2'
16
18
  gemspec.add_dependency 'blockenspiel', '>=0.3.2'
17
19
 
18
20
  gemspec.add_development_dependency 'fakeweb', '>=1.2.8'
@@ -55,6 +57,7 @@ Rake::RDocTask.new do |rdoc|
55
57
 
56
58
  rdoc.rdoc_dir = 'rdoc'
57
59
  rdoc.title = "carbon #{version}"
58
- rdoc.rdoc_files.include('README*')
60
+ rdoc.rdoc_files.include('README.rdoc')
61
+ rdoc.rdoc_files.include('doc/*.rdoc')
59
62
  rdoc.rdoc_files.include('lib/**/*.rb')
60
63
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.2.5
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{carbon}
8
- s.version = "0.2.4"
8
+ s.version = "0.2.5"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Derek Kastner", "Seamus Abshere", "Andy Rossmeissl"]
12
- s.date = %q{2010-07-22}
12
+ s.date = %q{2010-08-02}
13
13
  s.description = %q{Carbon is a Ruby API wrapper for the Brighter Planet emission estimate web service (http://carbon.brighterplanet.com). By querying the web service, it can estimate the carbon emissions of many real-life objects, such as cars and houses, based on particular attributes that they may have.}
14
14
  s.email = %q{derek.kastner@brighterplanet.com}
15
15
  s.extra_rdoc_files = [
@@ -22,11 +22,17 @@ Gem::Specification.new do |s|
22
22
  "Rakefile",
23
23
  "VERSION",
24
24
  "carbon.gemspec",
25
+ "doc/INTEGRATION_GUIDE.rdoc",
26
+ "doc/examining-response-with-jsonview.png",
27
+ "doc/timeout-error.png",
28
+ "doc/with-committee-reports.png",
29
+ "doc/without-committee-reports.png",
25
30
  "lib/carbon.rb",
26
31
  "lib/carbon/base.rb",
27
32
  "lib/carbon/emission_estimate.rb",
28
33
  "lib/carbon/emission_estimate/request.rb",
29
34
  "lib/carbon/emission_estimate/response.rb",
35
+ "lib/carbon/emission_estimate/storage.rb",
30
36
  "spec/lib/carbon_spec.rb",
31
37
  "spec/spec_helper.rb",
32
38
  "spec/specwatchr"
@@ -49,6 +55,7 @@ Gem::Specification.new do |s|
49
55
  s.add_runtime_dependency(%q<activesupport>, [">= 2.3.5"])
50
56
  s.add_runtime_dependency(%q<nap>, [">= 0.4"])
51
57
  s.add_runtime_dependency(%q<timeframe>, [">= 0.0.7"])
58
+ s.add_runtime_dependency(%q<SystemTimer>, [">= 1.2"])
52
59
  s.add_runtime_dependency(%q<blockenspiel>, [">= 0.3.2"])
53
60
  s.add_development_dependency(%q<fakeweb>, [">= 1.2.8"])
54
61
  s.add_development_dependency(%q<rspec>, [">= 2.0.0.beta.17"])
@@ -56,6 +63,7 @@ Gem::Specification.new do |s|
56
63
  s.add_dependency(%q<activesupport>, [">= 2.3.5"])
57
64
  s.add_dependency(%q<nap>, [">= 0.4"])
58
65
  s.add_dependency(%q<timeframe>, [">= 0.0.7"])
66
+ s.add_dependency(%q<SystemTimer>, [">= 1.2"])
59
67
  s.add_dependency(%q<blockenspiel>, [">= 0.3.2"])
60
68
  s.add_dependency(%q<fakeweb>, [">= 1.2.8"])
61
69
  s.add_dependency(%q<rspec>, [">= 2.0.0.beta.17"])
@@ -64,6 +72,7 @@ Gem::Specification.new do |s|
64
72
  s.add_dependency(%q<activesupport>, [">= 2.3.5"])
65
73
  s.add_dependency(%q<nap>, [">= 0.4"])
66
74
  s.add_dependency(%q<timeframe>, [">= 0.0.7"])
75
+ s.add_dependency(%q<SystemTimer>, [">= 1.2"])
67
76
  s.add_dependency(%q<blockenspiel>, [">= 0.3.2"])
68
77
  s.add_dependency(%q<fakeweb>, [">= 1.2.8"])
69
78
  s.add_dependency(%q<rspec>, [">= 2.0.0.beta.17"])
@@ -0,0 +1,1002 @@
1
+ = Emission estimate integration guide
2
+
3
+ Using real examples from the July 2010 integration of http://brighterplanet.com with the carbon middleware emission estimate web service.
4
+
5
+ == Step 1: Get an early win
6
+
7
+ Convert just one legacy class to use the web service. All you want is a number back, any number!
8
+
9
+ Here's what we changed:
10
+
11
+ vidalia:~/app1 (practice_migration) $ git diff master..practice_migration
12
+ diff --git a/Gemfile b/Gemfile
13
+ index 1d582d4..acccc7e 100644
14
+ --- a/Gemfile
15
+ +++ b/Gemfile
16
+ @@ -15,6 +15,7 @@ gem 'tzinfo', '0.3.20' # otherwise doesn't recognize timezone
17
+ # gem 'mime-types', '1.16', :require => 'mime/types' # twitter_oauth
18
+ # --
19
+
20
+ +gem 'carbon', '0.2.4'
21
+ gem 'simple-rss', '1.2.2'
22
+ gem 'httparty', '0.6.1'
23
+ gem 'thinking-sphinx', '1.3.16', :require => 'thinking_sphinx'
24
+ diff --git a/app/models/automobile_fuel_type.rb b/app/models/automobile_fuel_type.rb
25
+ index 30bb0c7..aaa4f49 100644
26
+ --- a/app/models/automobile_fuel_type.rb
27
+ +++ b/app/models/automobile_fuel_type.rb
28
+ @@ -7,6 +7,10 @@ class AutomobileFuelType < ActiveRecord::Base
29
+ attributes.slice 'code'
30
+ end
31
+
32
+ + def to_characteristic
33
+ + code
34
+ + end
35
+ +
36
+ class << self
37
+ # don't show electricity (plug-in) per Matt https://brighterplanet.sifterapp.com/projects/30/issues/341/comments
38
+ def for_collection_select(*args)
39
+ diff --git a/app/models/automobile_make.rb b/app/models/automobile_make.rb
40
+ index f3e7798..3133406 100644
41
+ --- a/app/models/automobile_make.rb
42
+ +++ b/app/models/automobile_make.rb
43
+ @@ -17,6 +17,10 @@ class AutomobileMake < ActiveRecord::Base
44
+ def to_s
45
+ name
46
+ end
47
+ +
48
+ + def to_characteristic
49
+ + name
50
+ + end
51
+
52
+ # leave this for later if we need it
53
+ SUBSIDIARIES = {
54
+ diff --git a/app/models/automobile_model.rb b/app/models/automobile_model.rb
55
+ index 1620b3a..870a6c5 100644
56
+ --- a/app/models/automobile_model.rb
57
+ +++ b/app/models/automobile_model.rb
58
+ @@ -24,6 +24,10 @@ class AutomobileModel < ActiveRecord::Base
59
+ name
60
+ end
61
+
62
+ + def to_characteristic
63
+ + "#{make.to_characteristic} #{name}"
64
+ + end
65
+ +
66
+ def fuel_efficiency(options = {})
67
+ urbanity = options[:urbanity] || Automobile.fallback.urbanity
68
+ urbanity * AutomobileVariant.average(:fuel_efficiency_city, :conditions => { :automobile_model_id => id }) +
69
+ diff --git a/app/models/automobile_model_year.rb b/app/models/automobile_model_year.rb
70
+ index 36a1714..32b5c1f 100644
71
+ --- a/app/models/automobile_model_year.rb
72
+ +++ b/app/models/automobile_model_year.rb
73
+ @@ -29,4 +29,9 @@ class AutomobileModelYear < ActiveRecord::Base
74
+ def to_i
75
+ year.to_i
76
+ end
77
+ +
78
+ + def to_characteristic
79
+ + year
80
+ + end
81
+ +
82
+ end
83
+ diff --git a/app/models/automobile_size_class.rb b/app/models/automobile_size_class.rb
84
+ index e62b80d..5ed1e2a 100644
85
+ --- a/app/models/automobile_size_class.rb
86
+ +++ b/app/models/automobile_size_class.rb
87
+ @@ -7,6 +7,10 @@ class AutomobileSizeClass < ActiveRecord::Base
88
+ attributes.slice 'name'
89
+ end
90
+
91
+ + def to_characteristic
92
+ + name
93
+ + end
94
+ +
95
+ class << self
96
+ def for_collection_select(*args)
97
+ all :order => :id
98
+ diff --git a/app/models/emitters/automobile.rb b/app/models/emitters/automobile.rb
99
+ index d2d852c..e7aa8a6 100644
100
+ --- a/app/models/emitters/automobile.rb
101
+ +++ b/app/models/emitters/automobile.rb
102
+ @@ -1,4 +1,42 @@
103
+ class Automobile < Emitter
104
+ + include Carbon
105
+ +
106
+ + emit_as :automobile do
107
+ + provide :make
108
+ + provide :model
109
+ + provide :model_year_proxy, :as => :model_year
110
+ + # provide :variant - what to key on?
111
+ + provide :fuel_type
112
+ + provide :size_class
113
+ + provide :fuel_efficiency
114
+ + provide :urbanity
115
+ + provide :hybridity
116
+ + provide :daily_distance_estimate
117
+ + provide :daily_duration
118
+ + provide :weekly_distance_estimate
119
+ + provide :annual_distance_estimate
120
+ + provide :acquisition
121
+ + provide :retirement
122
+ + end
123
+ +
124
+ + # app1 model years are just years - 1999
125
+ + # cm1 model years are composites of year and model - Acura TSX 1999
126
+ + # so we have to proxy it
127
+ + class ModelYearProxy
128
+ + attr_reader :parent
129
+ + def initialize(parent)
130
+ + @parent = parent
131
+ + end
132
+ + def to_characteristic
133
+ + if parent.model and parent.model_year
134
+ + "#{parent.model.to_characteristic} #{parent.model_year.to_characteristic}"
135
+ + end
136
+ + end
137
+ + end
138
+ + def model_year_proxy
139
+ + @model_year_proxy ||= ModelYearProxy.new self
140
+ + end
141
+ +
142
+ diff --git a/config/initializers/carbon_middleware.rb b/config/initializers/carbon_middleware.rb
143
+ new file mode 100644
144
+ index 0000000..8becc5f
145
+ --- /dev/null
146
+ +++ b/config/initializers/carbon_middleware.rb
147
+ @@ -0,0 +1 @@
148
+ +::Carbon.key = '[Get your own at http://keys.brighterplanet.com]'
149
+
150
+
151
+ And then we could do...
152
+
153
+ >> a = Automobile.find 710
154
+ => #<Automobile [...]>
155
+ >> a.emission(Timeframe.this_year) # legacy call
156
+ => 7040.88102017319
157
+ >> a.emission_estimate(:timeframe => Timeframe.this_year).to_f # outsourced (new) call
158
+ => 7040.88102017319
159
+
160
+ An early win!
161
+
162
+ === Comments on step 1
163
+
164
+ * We're using Ruby on Rails and so we can use the official {carbon gem}[http://rubygems.org/gems/carbon]. If you're not on Ruby on Rails, please contact us for extra support in other languages or frameworks. If we don't have a library for it, we will help shoulder the burden of developing one.
165
+
166
+ * We mapped all of the attributes of an Automobile that the web service recognizes.
167
+
168
+ * One characteristic causes us trouble: model_year. The web service expects "Acura TSX 1999", but if you just called <tt>Automobile#model_year</tt> in the legacy app, you would get "1999". So, we map the characteristic "model_year" to <tt>Automobile#model_year_proxy</tt>, which responds with a <tt>ModelYearProxy</tt> object, which, in turn, responds to <tt>ModelYearProxy#to_characteristic</tt>.
169
+
170
+ * Note the similarity of the legacy call <tt>Automobile#emission</tt> to the outsourced call <tt>Automobile#emission_estimate</tt>. That's because the carbon gem and our website have a lot of shared code history.
171
+
172
+ == Step 2: Apply the outsourced method call in interface code
173
+
174
+ Rather than getting really deep into replacing the business logic of your legacy emitter classes, try to hit interface problems early.
175
+
176
+ Out of all the emitter classes, we've only converted the Automobile class, so for now we have to make sure the interface knows how to render both kinds: outsourced and legacy.
177
+
178
+ Here's what we changed:
179
+
180
+ vidalia:~/app1 (practice_migration) $ git diff master..practice_migration
181
+ diff --git a/app/models/emitter.rb b/app/models/emitter.rb
182
+ index 8603f22..93971b7 100644
183
+ --- a/app/models/emitter.rb
184
+ +++ b/app/models/emitter.rb
185
+ @@ -1,5 +1,10 @@
186
+ # todo dry emblem
187
+ class Emitter < ActiveRecord::Base
188
+ + # Whether emission estimates for this class have been outsourced to Brighter Planet
189
+ + def outsourced?
190
+ + respond_to? :emission_estimate
191
+ + end
192
+ +
193
+ self.abstract_class = true
194
+
195
+ extend ActiveSupport::Memoizable if Switches.memoization?
196
+ @@ -304,7 +313,11 @@ class Emitter < ActiveRecord::Base
197
+ if respond_to? :emission_date
198
+ timeframe.include? emission_date
199
+ elsif self.class.characterization.characteristics.include? :active_subtimeframe
200
+ - timeframe & committee_reports[:active_subtimeframe]
201
+ + if outsourced?
202
+ + nil # don't know how to get active_subtimeframe
203
+ + else
204
+ + timeframe & committee_reports[:active_subtimeframe]
205
+ + end
206
+ end
207
+ end
208
+ diff --git a/app/models/component.rb b/app/models/component.rb
209
+ index 24e359a..b00ba5d 100644
210
+ --- a/app/models/component.rb
211
+ +++ b/app/models/component.rb
212
+ @@ -232,7 +232,13 @@ class Component
213
+ def emission_from_imperfect_emitters(timeframe, perspective)
214
+ # unlike preterite components, we have to leave data interpolation up to the emitter, so this is all-or-nothing
215
+ if emitters_present_during? timeframe
216
+ - emitters.values.flatten.sum { |emitter| emitter.as_of(perspective).emission(timeframe, perspective).to_f }
217
+ + emitters.values.flatten.sum do |emitter|
218
+ + if emitter.outsourced?
219
+ + emitter.as_of(perspective).emission_estimate(:timeframe => timeframe).to_f # dropping perspective
220
+ + else
221
+ + emitter.as_of(perspective).emission(timeframe, perspective).to_f
222
+ + end
223
+ + end
224
+ else
225
+ typical_emission_from_emitters(timeframe)
226
+ end
227
+ @@ -248,7 +254,13 @@ class Component
228
+ puts " Looking at subtimeframe #{subtimeframe.from} - #{subtimeframe.to}" if Component::DEBUG
229
+ if emitters_present_during? subtimeframe # we want to use emissions committee for the period BETWEEN profile creation and the end of last month for which we have emitters
230
+ puts " --> Emitters present!" if Component::DEBUG
231
+ - e = preterite_emitters.sum { |emitter| emitter.emission(subtimeframe, perspective).to_f }
232
+ + e = preterite_emitters.sum do |emitter|
233
+ + if emitter.outsourced?
234
+ + emitter.emission_estimate(:timeframe => subtimeframe).to_f # dropping perspective
235
+ + else
236
+ + emitter.emission(subtimeframe, perspective).to_f
237
+ + end
238
+ + end
239
+ puts " --> Adding #{e}" if Component::DEBUG
240
+ e
241
+ elsif emitters_present? and profile.created_at.to_date <= subtimeframe.from and subtimeframe.to < Date.new(Time.now.year, Time.now.month, 1) # we want to use 0 for the period BETWEEN profile creation and the end of the last month for which we have no emitters IF there are any emitters at all, ever.
242
+ @@ -261,7 +273,14 @@ class Component
243
+ e
244
+ end
245
+ end
246
+ - emission_from_patterns = (patterns.sum { |pattern| pattern.emission(timeframe, perspective).to_f }) || 0
247
+ + emission_from_patterns = patterns.sum do |pattern|
248
+ + if pattern.outsourced?
249
+ + pattern.emission_estimate(:timeframe => timeframe).to_f # dropping perspective
250
+ + else
251
+ + pattern.emission(timeframe, perspective).to_f
252
+ + end
253
+ + end
254
+ + emission_from_patterns ||= 0
255
+ if emitters_present_during? timeframe # normally pattern emissions are *added to* preterite emitter emissions
256
+ emission_during_timeframe += emission_from_patterns
257
+ elsif emission_from_patterns.nonzero? # if there's no real emitters, but there are patterns established, the patterns *replace* placeholders
258
+ @@ -270,6 +289,7 @@ class Component
259
+ emission_during_timeframe
260
+ end
261
+
262
+ + # Component#emission, not to be confused with Emitter#emission
263
+ def emission(timeframe, perspective = Time.zone.today)
264
+ return 0 if disabled?
265
+ unless @emission and @emission[timeframe.hash] and @emission[timeframe.hash][perspective.hash]
266
+ diff --git a/app/helpers/emitters_helper.rb b/app/helpers/emitters_helper.rb
267
+ index c1b75ee..e526ba1 100755
268
+ --- a/app/helpers/emitters_helper.rb
269
+ +++ b/app/helpers/emitters_helper.rb
270
+ @@ -3,7 +3,7 @@
271
+
272
+ module EmittersHelper
273
+ def render_characteristic(emitter, characteristic)
274
+ - characteristic_value = emitter.characteristics.has_key?(characteristic) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
275
+ + characteristic_value = (emitter.outsourced? or emitter.characteristics.has_key?(characteristic)) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
276
+ locals = { :emitter => emitter, :characteristic => characteristic, characteristic => characteristic_value }
277
+
278
+ begin
279
+ @@ -14,7 +14,7 @@ module EmittersHelper
280
+ end
281
+
282
+ def render_edit_characteristic(emitter, characteristic)
283
+ - characteristic_value = emitter.characteristics.has_key?(characteristic) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
284
+ + characteristic_value = (emitter.outsourced? or emitter.characteristics.has_key?(characteristic)) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
285
+ locals = { :emitter => emitter, :characteristic => characteristic, characteristic => characteristic_value }
286
+ template = pick_edit_partial(emitter, characteristic)
287
+ if template == 'characteristics/controls/collection_select'
288
+ @@ -80,7 +80,7 @@ module EmittersHelper
289
+ end
290
+
291
+ def render_placeholder_characteristic(emitter, characteristic)
292
+ - characteristic_value = emitter.characteristics.has_key?(characteristic) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
293
+ + characteristic_value = (emitter.outsourced? or emitter.characteristics.has_key?(characteristic)) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
294
+ locals = { :emitter => emitter, :characteristic => characteristic, characteristic => characteristic_value }
295
+ render :partial => "#{emitter.common_plural}/#{characteristic}/placeholder", :layout => 'characteristics/layouts/placeholder', :locals => locals
296
+ rescue ActionView::MissingTemplate
297
+ diff --git a/app/views/characteristics/_characteristic.html.erb b/app/views/characteristics/_characteristic.html.erb
298
+ index c08003d..5e5ff1f 100644
299
+ --- a/app/views/characteristics/_characteristic.html.erb
300
+ +++ b/app/views/characteristics/_characteristic.html.erb
301
+ @@ -1,7 +1,7 @@
302
+ <%
303
+ known = !(known == false)
304
+ expanded = (expanded == true)
305
+ -reported = emitter.committee_reports.keys.include? characteristic # this should never happen if known
306
+ +reported = !emitter.outsourced? and emitter.committee_reports.keys.include? characteristic # this should never happen if known
307
+ element_class = [characteristic]
308
+ element_class << 'reported' if reported
309
+ element_class << 'next' if expanded
310
+ diff --git a/app/views/carbon_offsets/_emitter.html.erb b/app/views/carbon_offsets/_emitter.html.erb
311
+ index 61cd499..b006892 100644
312
+ --- a/app/views/carbon_offsets/_emitter.html.erb
313
+ +++ b/app/views/carbon_offsets/_emitter.html.erb
314
+ @@ -1,5 +1,9 @@
315
+ <%
316
+ -emission = emitter.emission timeframe
317
+ +emission = if emitter.outsourced?
318
+ + emitter.emission_estimate(:timeframe => timeframe).to_f
319
+ +else
320
+ + emitter.emission timeframe
321
+ +end
322
+ return unless emission.kilograms.to(:tons) > CustomPackage::MINIMUM_CARBON_OFFSET_IN_TONS
323
+ custom_package = {
324
+ :carbon_offset => emission,
325
+ diff --git a/app/views/components/_gaps.html.erb b/app/views/components/_gaps.html.erb
326
+ index 203d261..6e13b6f 100644
327
+ --- a/app/views/components/_gaps.html.erb
328
+ +++ b/app/views/components/_gaps.html.erb
329
+ @@ -1,5 +1,5 @@
330
+ <% if component.tense == :imperfect %>
331
+ - <% covered_timeframes = component.emitters.values.flatten.collect { |e| e.committee_reports(timeframe)[:active_subtimeframe] } %>
332
+ + <% covered_timeframes = component.emitters.values.flatten.collect { |e| e.outsourced? ? nil : e.committee_reports(timeframe)[:active_subtimeframe] } %>
333
+ <% unless timeframe.covered_by? *covered_timeframes %>
334
+ <% gaps = timeframe.gaps_left_by(*covered_timeframes) %>
335
+ <p><strong>Note:</strong> you haven't told us about any <%= component.class.component_name %> emissions during <%= gaps.to_sentence %>. If you <%= component.verb[:past] %> <%= component.class.component_name.pluralize %> during these times, please tell us about it.</p>
336
+ diff --git a/app/views/components/show.html.erb b/app/views/components/show.html.erb
337
+ index 3a89238..1479f2e 100644
338
+ --- a/app/views/components/show.html.erb
339
+ +++ b/app/views/components/show.html.erb
340
+ @@ -75,7 +75,11 @@
341
+ <% @component.emitters.values.flatten.partition(&:retired?).reverse.flatten.each do |e| %>
342
+ <li class="<%= e.emblem %><%= ' retired' if e.retired? %>">
343
+ <p class="size">
344
+ - <%= e.emission(@timeframe).kilograms.to(:tons, 2) %> tons
345
+ + <% if e.outsourced? %>
346
+ + <%= e.emission_estimate(:timeframe => @timeframe).to_f.kilograms.to(:tons, 2) %> tons
347
+ + <% else %>
348
+ + <%= e.emission(@timeframe).kilograms.to(:tons, 2) %> tons
349
+ + <% end %>
350
+ </p>
351
+ <h5><%= link_to e.name, e %><%= ' (retired)' if e.retired? %></h5>
352
+ <%= render :partial => 'carbon_offsets/emitter', :locals => { :emitter => e, :timeframe => @timeframe, :show_purchasing => viewing_self?(@user), :show_granting => false } %>
353
+ diff --git a/app/views/emitters/update.js.rjs b/app/views/emitters/update.js.rjs
354
+ index b325e97..5967500 100644
355
+ --- a/app/views/emitters/update.js.rjs
356
+ +++ b/app/views/emitters/update.js.rjs
357
+ @@ -15,7 +15,11 @@ if @wizard
358
+ page.visual_effect :fade, "#{common_name}_#{@characteristic}"
359
+ page.visual_effect :appear, "#{common_name}_#{next_characteristic}"
360
+ else
361
+ - emission = @emitter.emission Timeframe.new(Time.zone.today, Time.zone.today.tomorrow.tomorrow)
362
+ + emission = if @emitter.outsourced?
363
+ + @emitter.emission Timeframe.new(Time.zone.today, Time.zone.today.tomorrow.tomorrow)
364
+ + else
365
+ + @emitter.emission_estimate(:timeframe => Timeframe.new(Time.zone.today, Time.zone.today.tomorrow.tomorrow)).to_f
366
+ + end
367
+ custom_package = {
368
+ :carbon_offset => emission,
369
+ :short_title => 'Travel emissions',
370
+ diff --git a/app/views/emissions/_footprint.html.erb b/app/views/emissions/_footprint.html.erb
371
+ index 89bcfe5..69c27a4 100644
372
+ --- a/app/views/emissions/_footprint.html.erb
373
+ +++ b/app/views/emissions/_footprint.html.erb
374
+ @@ -5,7 +5,7 @@
375
+ <%= render :partial => "#{footprintable.common_plural}/footprint", :locals => { :emitter => footprintable, :timeframe => timeframe } %>
376
+ <% end %>
377
+
378
+ - <span class="footprint"><span class="number"><%= footprintable.emission(timeframe).kilograms.to(:tons).adaptive_round %></span> tons CO<sub>2</sub></span>
379
+ + <span class="footprint"><span class="number"><%= ((footprintable.is_a?(Emitter) and footprintable.outsourced?) ? footprintable.emission_estimate(:timeframe => timeframe) : footprintable.emission(timeframe)).to_f.kilograms.to(:tons).adaptive_round %></span> tons CO<sub>2</sub></span>
380
+
381
+ <%= render :partial => 'emissions/extraneous', :locals => { :timeframe => timeframe } if footprintable.is_a?(Emitter) and !footprintable.emitted_during_timeframe?(timeframe) %>
382
+ diff --git a/app/views/emitters/_sidebar.html.erb b/app/views/emitters/_sidebar.html.erb
383
+ index 7f0c8d0..2da2214 100644
384
+ --- a/app/views/emitters/_sidebar.html.erb
385
+ +++ b/app/views/emitters/_sidebar.html.erb
386
+ @@ -4,6 +4,11 @@
387
+ <%= render :partial => 'emissions/footprint', :locals => { :footprintable => emitter, :timeframe => timeframe } %>
388
+ <%#= render :partial => 'introduction', :locals => { :emitter => emitter, :component => component } %>
389
+ <%= render :partial => 'carbon_offsets/emitter', :locals => { :emitter => emitter, :timeframe => timeframe, :show_purchasing => true, :show_granting => true } %>
390
+ +<% if emitter.outsourced? %>
391
+ + <p class="committee_reports">
392
+ + <%= link_to 'Show calculation methods', emitter.emission_estimate.methodology %>
393
+ + </p>
394
+ +<% else %>
395
+ <p class="committee_reports">
396
+ <%=
397
+ link_to_function 'Show calculation methods' do |page|
398
+ @@ -28,6 +33,7 @@ end
399
+ <% end %>
400
+ </ol>
401
+ <%= render :partial => 'emissions/methodology' %>
402
+ - </div>
403
+ +<% end %>
404
+ +</div>
405
+ <% end %>
406
+
407
+ === Comments on step 2
408
+
409
+ * The legacy method <tt>Automobile#committee_reports</tt> is disappearing. We're going to have to look into that.
410
+
411
+ * We're don't have any way to pass in "perspective" for the outsourced emitters. Also going to have to deal with that.
412
+
413
+ * One big win is to be found in <tt>app/views/emitters/_sidebar.html.erb</tt>. If we're dealing with an outsourced emitter class, we can link to the web service's methodology report instead of trying to explain the calculations locally. If you outsource the hard science to Brighter Planet, it's nice to know that we'll also explain it for you!
414
+
415
+ == Step 3: Decide if you will be able to finish the job
416
+
417
+ You know you can get a number (step 1) and apply it to the interface (step 2). But does the web service provide everything you need?
418
+
419
+ For us, the potential show-stoppers were "committee reports", byproducts of the local calculation process. <b>Once we outsourced calculation, these values weren't being generated locally.</b>
420
+
421
+ In step 2, we cheated by ignoring them and showing nothing. We used to exploit them as helpful defaults for interface elements:
422
+
423
+ link:../doc/with-committee-reports.png
424
+
425
+ After step two, it looked like this:
426
+
427
+ link:../doc/without-committee-reports.png
428
+
429
+ Let's take a closer look at what the emission estimate web service returns.
430
+
431
+ >> a = Automobile.find 710
432
+ => #<Automobile id [...] >
433
+ >> pp a.emission_estimate.response.data
434
+ {"emission_units"=>"kilograms",
435
+ "timeframe"=>"2010-01-01/2011-01-01",
436
+ "acquisition"=>Wed, 01 Jul 2009,
437
+ "adjusted_fuel_efficiency"=>8.50287,
438
+ "fuel_efficiency_multiplier"=>1.0,
439
+ "annual_distance"=>24140.2,
440
+ "fuel_type"=>
441
+ {"automobile_fuel_type"=>
442
+ {"name"=>"regular gasoline",
443
+ "created_at"=>"2010-05-15T01:31:58Z",
444
+ "code"=>"R",
445
+ "annual_distance"=>18161.0,
446
+ "emission_factor_units"=>"kilograms_per_litre",
447
+ "updated_at"=>"2010-05-27T01:26:14Z",
448
+ "annual_distance_units"=>"kilometres",
449
+ "emission_factor"=>2.48}},
450
+ "errors"=>[],
451
+ "nominal_fuel_efficiency"=>7.88765178259689,
452
+ "retirement"=>Sat, 01 Jan 2011,
453
+ "methodology"=>
454
+ "http://carbon.brighterplanet.com/automobiles.html?acquisition=2009-07-01&annual_distance_estimate=24140.2&fuel_efficiency=8.50287&fuel_type=R&key=3fca1403c1a561769a78ba462390ff01&make=Ford&size_class=Midsize+Pickup&timeframe=2010-01-01%2F2011-01-01",
455
+ "speed"=>50.9438793670604,
456
+ "emission_factor"=>2.48,
457
+ "emission"=>7040.88102017319,
458
+ "active_subtimeframe"=>
459
+ <Timeframe(-629293078) 365 days starting 2010-01-01 ending 2011-01-01>,
460
+ "make"=>
461
+ {"automobile_make"=>
462
+ {"name"=>"Ford",
463
+ "created_at"=>nil,
464
+ "fuel_efficiency_units"=>"kilometres_per_litre",
465
+ "updated_at"=>"2010-05-27T01:21:08Z",
466
+ "major"=>true,
467
+ "fuel_efficiency"=>10.1994}},
468
+ "fuel_consumed"=>2839.06492748919,
469
+ "urbanity"=>0.43,
470
+ "size_class"=>
471
+ {"automobile_size_class"=>
472
+ {"name"=>"Midsize Pickup",
473
+ "created_at"=>"2010-05-14T22:55:43Z",
474
+ "fuel_efficiency_city"=>6.8,
475
+ "fuel_efficiency_highway"=>8.97,
476
+ "annual_distance"=>20918.0,
477
+ "hybrid_fuel_efficiency_highway_multiplier"=>1.2,
478
+ "updated_at"=>"2010-05-14T22:55:44Z",
479
+ "fuel_efficiency_highway_units"=>"kilometres_per_litre",
480
+ "emblem"=>"",
481
+ "annual_distance_units"=>"kilometres",
482
+ "conventional_fuel_efficiency_city_multiplier"=>0.98,
483
+ "conventional_fuel_efficiency_highway_multiplier"=>0.99,
484
+ "hybrid_fuel_efficiency_city_multiplier"=>1.57,
485
+ "fuel_efficiency_city_units"=>"kilometres_per_litre"}},
486
+ "annual_distance_estimate"=>24140.2,
487
+ "reports"=>
488
+ [{"conclusion"=>7040.88102017319,
489
+ "committee"=>
490
+ {"name"=>"emission",
491
+ "quorums"=>
492
+ [{"name"=>"from fuel",
493
+ "process"=>{},
494
+ "supplements"=>["fuel_type"],
495
+ "compliance"=>[],
496
+ "requirements"=>["fuel_consumed", "emission_factor"]},
497
+ {"name"=>"default",
498
+ "process"=>{},
499
+ "supplements"=>[],
500
+ "compliance"=>[],
501
+ "requirements"=>[]}]},
502
+ "quorum"=>
503
+ {"name"=>"from fuel",
504
+ "process"=>{},
505
+ "supplements"=>["fuel_type"],
506
+ "compliance"=>[],
507
+ "requirements"=>["fuel_consumed", "emission_factor"]}},
508
+ {"conclusion"=>2.48,
509
+ "committee"=>
510
+ {"name"=>"emission_factor",
511
+ "quorums"=>
512
+ [{"name"=>"from fuel type",
513
+ "process"=>{},
514
+ "supplements"=>[],
515
+ "compliance"=>[],
516
+ "requirements"=>["fuel_type"]},
517
+ {"name"=>"default",
518
+ "process"=>{},
519
+ "supplements"=>[],
520
+ "compliance"=>[],
521
+ "requirements"=>[]}]},
522
+ "quorum"=>
523
+ {"name"=>"from fuel type",
524
+ "process"=>{},
525
+ "supplements"=>[],
526
+ "compliance"=>[],
527
+ "requirements"=>["fuel_type"]}},
528
+ {"conclusion"=>2839.06492748919,
529
+ "committee"=>
530
+ {"name"=>"fuel_consumed",
531
+ "quorums"=>
532
+ [{"name"=>"from adjusted fuel_efficiency and distance",
533
+ "process"=>{},
534
+ "supplements"=>[],
535
+ "compliance"=>[],
536
+ "requirements"=>["adjusted_fuel_efficiency", "distance"]}]},
537
+ "quorum"=>
538
+ {"name"=>"from adjusted fuel_efficiency and distance",
539
+ "process"=>{},
540
+ "supplements"=>[],
541
+ "compliance"=>[],
542
+ "requirements"=>["adjusted_fuel_efficiency", "distance"]}},
543
+ {"conclusion"=>24140.2,
544
+ "committee"=>
545
+ {"name"=>"distance",
546
+ "quorums"=>
547
+ [{"name"=>"from annual distance",
548
+ "process"=>{},
549
+ "supplements"=>[],
550
+ "compliance"=>[],
551
+ "requirements"=>["annual_distance", "active_subtimeframe"]}]},
552
+ "quorum"=>
553
+ {"name"=>"from annual distance",
554
+ "process"=>{},
555
+ "supplements"=>[],
556
+ "compliance"=>[],
557
+ "requirements"=>["annual_distance", "active_subtimeframe"]}},
558
+ {"conclusion"=>24140.2,
559
+ "committee"=>
560
+ {"name"=>"annual_distance",
561
+ "quorums"=>
562
+ [{"name"=>"from annual distance estimate",
563
+ "process"=>{},
564
+ "supplements"=>[],
565
+ "compliance"=>[],
566
+ "requirements"=>["annual_distance_estimate"]},
567
+ {"name"=>"from weekly distance estimate",
568
+ "process"=>{},
569
+ "supplements"=>[],
570
+ "compliance"=>[],
571
+ "requirements"=>["weekly_distance_estimate"]},
572
+ {"name"=>"from daily distance",
573
+ "process"=>{},
574
+ "supplements"=>[],
575
+ "compliance"=>[],
576
+ "requirements"=>["daily_distance"]},
577
+ {"name"=>"from size class",
578
+ "process"=>{},
579
+ "supplements"=>[],
580
+ "compliance"=>[],
581
+ "requirements"=>["size_class"]},
582
+ {"name"=>"from fuel type",
583
+ "process"=>{},
584
+ "supplements"=>[],
585
+ "compliance"=>[],
586
+ "requirements"=>["fuel_type"]},
587
+ {"name"=>"default",
588
+ "process"=>{},
589
+ "supplements"=>[],
590
+ "compliance"=>[],
591
+ "requirements"=>[]}]},
592
+ "quorum"=>
593
+ {"name"=>"from annual distance estimate",
594
+ "process"=>{},
595
+ "supplements"=>[],
596
+ "compliance"=>[],
597
+ "requirements"=>["annual_distance_estimate"]}},
598
+ {"conclusion"=>8.50287,
599
+ "committee"=>
600
+ {"name"=>"adjusted_fuel_efficiency",
601
+ "quorums"=>
602
+ [{"name"=>"from fuel efficiency",
603
+ "process"=>{},
604
+ "supplements"=>[],
605
+ "compliance"=>[],
606
+ "requirements"=>["fuel_efficiency"]},
607
+ {"name"=>"from variant",
608
+ "process"=>{},
609
+ "supplements"=>[],
610
+ "compliance"=>[],
611
+ "requirements"=>["variant", "urbanity"]},
612
+ {"name"=>"from nominal fuel efficiency and multiplier",
613
+ "process"=>{},
614
+ "supplements"=>[],
615
+ "compliance"=>[],
616
+ "requirements"=>
617
+ ["nominal_fuel_efficiency", "fuel_efficiency_multiplier"]}]},
618
+ "quorum"=>
619
+ {"name"=>"from fuel efficiency",
620
+ "process"=>{},
621
+ "supplements"=>[],
622
+ "compliance"=>[],
623
+ "requirements"=>["fuel_efficiency"]}},
624
+ {"conclusion"=>1.0,
625
+ "committee"=>
626
+ {"name"=>"fuel_efficiency_multiplier",
627
+ "quorums"=>
628
+ [{"name"=>"from_size_class_and_hybridity",
629
+ "process"=>{},
630
+ "supplements"=>[],
631
+ "compliance"=>[],
632
+ "requirements"=>["size_class", "hybridity", "urbanity"]},
633
+ {"name"=>"from hybridity",
634
+ "process"=>{},
635
+ "supplements"=>[],
636
+ "compliance"=>[],
637
+ "requirements"=>["hybridity", "urbanity"]},
638
+ {"name"=>"default",
639
+ "process"=>{},
640
+ "supplements"=>[],
641
+ "compliance"=>[],
642
+ "requirements"=>[]}]},
643
+ "quorum"=>
644
+ {"name"=>"default",
645
+ "process"=>{},
646
+ "supplements"=>[],
647
+ "compliance"=>[],
648
+ "requirements"=>[]}},
649
+ {"conclusion"=>7.88765178259689,
650
+ "committee"=>
651
+ {"name"=>"nominal_fuel_efficiency",
652
+ "quorums"=>
653
+ [{"name"=>"from model",
654
+ "process"=>{},
655
+ "supplements"=>[],
656
+ "compliance"=>[],
657
+ "requirements"=>["model", "urbanity"]},
658
+ {"name"=>"from make and model year",
659
+ "process"=>{},
660
+ "supplements"=>[],
661
+ "compliance"=>[],
662
+ "requirements"=>["model_year"]},
663
+ {"name"=>"from size class",
664
+ "process"=>{},
665
+ "supplements"=>[],
666
+ "compliance"=>[],
667
+ "requirements"=>["size_class", "urbanity"]},
668
+ {"name"=>"from model year",
669
+ "process"=>{},
670
+ "supplements"=>[],
671
+ "compliance"=>[],
672
+ "requirements"=>["model_year"]},
673
+ {"name"=>"from make",
674
+ "process"=>{},
675
+ "supplements"=>[],
676
+ "compliance"=>[],
677
+ "requirements"=>["make"]},
678
+ {"name"=>"default",
679
+ "process"=>{},
680
+ "supplements"=>[],
681
+ "compliance"=>[],
682
+ "requirements"=>[]}]},
683
+ "quorum"=>
684
+ {"name"=>"from size class",
685
+ "process"=>{},
686
+ "supplements"=>[],
687
+ "compliance"=>[],
688
+ "requirements"=>["size_class", "urbanity"]}},
689
+ {"conclusion"=>50.9438793670604,
690
+ "committee"=>
691
+ {"name"=>"speed",
692
+ "quorums"=>
693
+ [{"name"=>"from urbanity",
694
+ "process"=>{},
695
+ "supplements"=>[],
696
+ "compliance"=>[],
697
+ "requirements"=>["urbanity"]}]},
698
+ "quorum"=>
699
+ {"name"=>"from urbanity",
700
+ "process"=>{},
701
+ "supplements"=>[],
702
+ "compliance"=>[],
703
+ "requirements"=>["urbanity"]}},
704
+ {"conclusion"=>0.43,
705
+ "committee"=>
706
+ {"name"=>"urbanity",
707
+ "quorums"=>
708
+ [{"name"=>"default",
709
+ "process"=>{},
710
+ "supplements"=>[],
711
+ "compliance"=>[],
712
+ "requirements"=>[]}]},
713
+ "quorum"=>
714
+ {"name"=>"default",
715
+ "process"=>{},
716
+ "supplements"=>[],
717
+ "compliance"=>[],
718
+ "requirements"=>[]}},
719
+ {"conclusion"=>"2010-01-01/2011-01-01",
720
+ "committee"=>
721
+ {"name"=>"active_subtimeframe",
722
+ "quorums"=>
723
+ [{"name"=>"from acquisition and retirement",
724
+ "process"=>{},
725
+ "supplements"=>[],
726
+ "compliance"=>[],
727
+ "requirements"=>["acquisition", "retirement"]}]},
728
+ "quorum"=>
729
+ {"name"=>"from acquisition and retirement",
730
+ "process"=>{},
731
+ "supplements"=>[],
732
+ "compliance"=>[],
733
+ "requirements"=>["acquisition", "retirement"]}},
734
+ {"conclusion"=>Sat, 01 Jan 2011,
735
+ "committee"=>
736
+ {"name"=>"retirement",
737
+ "quorums"=>
738
+ [{"name"=>"from acquisition",
739
+ "process"=>{},
740
+ "supplements"=>["acquisition"],
741
+ "compliance"=>[],
742
+ "requirements"=>[]}]},
743
+ "quorum"=>
744
+ {"name"=>"from acquisition",
745
+ "process"=>{},
746
+ "supplements"=>["acquisition"],
747
+ "compliance"=>[],
748
+ "requirements"=>[]}}],
749
+ "fuel_efficiency"=>8.50287,
750
+ "distance"=>24140.2}
751
+
752
+ An easier way to look is using the {JSONView add-on}[https://addons.mozilla.org/en-US/firefox/addon/10869/] for Firefox. Just take the methodology URL:
753
+
754
+ "methodology"=>
755
+ "http://carbon.brighterplanet.com/automobiles.html?acquisition=2009-07-01&annual_distance_estimate=24140.2&fuel_efficiency=8.50287&fuel_type=R&key=3fca1403c1a561769a78ba462390ff01&make=Ford&size_class=Midsize+Pickup&timeframe=2010-01-01%2F2011-01-01",
756
+
757
+ ...and say you want JSON instead of HTML...
758
+
759
+ http://carbon.brighterplanet.com/automobiles.html?acquisition=2009-07-01&annual_distance_estimate=24140.2&fuel_efficiency=8.50287&fuel_type=R&key=3fca1403c1a561769a78ba462390ff01&make=Ford&size_class=Midsize+Pickup&timeframe=2010-01-01%2F2011-01-01
760
+ |
761
+ change this
762
+ |
763
+ http://carbon.brighterplanet.com/automobiles.json?acquisition=2009-07-01&annual_distance_estimate=24140.2&fuel_efficiency=8.50287&fuel_type=R&key=3fca1403c1a561769a78ba462390ff01&make=Ford&size_class=Midsize+Pickup&timeframe=2010-01-01%2F2011-01-01
764
+
765
+ ...and look what we found!
766
+
767
+ link:../doc/examining-response-with-jsonview.png
768
+
769
+ As you can see, the byproducts (really the internal variables) of the calculation are sent along with the response. Your legacy app may never use these values, but we do! We clarified things with a few helper methods:
770
+
771
+ diff --git a/app/models/emitter.rb b/app/models/emitter.rb
772
+ index 93971b7..c5d5709 100644
773
+ --- a/app/models/emitter.rb
774
+ +++ b/app/models/emitter.rb
775
+ @@ -5,6 +5,35 @@ class Emitter < ActiveRecord::Base
776
+ respond_to? :emission_estimate
777
+ end
778
+
779
+ + def real_or_assumed_characteristic_value(key)
780
+ + return characteristics[key] if characteristics.has_key? key
781
+ + if outsourced?
782
+ + emission_estimate.response.data[key.to_s]
783
+ + else
784
+ + committee_reports[key]
785
+ + end
786
+ + end
787
+ +
788
+ + def assumed_characteristic_value?(key)
789
+ + if outsourced?
790
+ + emission_estimate.response.data.keys.include? key.to_s
791
+ + else
792
+ + committee_reports.keys.include? key
793
+ + end
794
+ + end
795
+ +
796
+ + def reporting_committee_name(key)
797
+ + if outsourced?
798
+ + begin
799
+ + emission_estimate.response.data['reports'].detect { |i| i['committee']['name'] == key.to_s }['quorum']['name']
800
+ + rescue NoMethodError
801
+ + # couldn't find it
802
+ + end
803
+ + else
804
+ + self.class.characterization.committees[key].called_quorum.name
805
+ + end.to_s.dasherize.downcase
806
+ + end
807
+ +
808
+ self.abstract_class = true
809
+
810
+ extend ActiveSupport::Memoizable if Switches.memoization?
811
+ @@ -54,6 +83,8 @@ class Emitter < ActiveRecord::Base
812
+ value = options[:value]
813
+ elsif characteristics.has_key?(name)
814
+ value = characteristics[name]
815
+ + elsif outsourced? and emission_estimate.response.data.has_key? name.to_s
816
+ + value = emission_estimate.response.data[name.to_s]
817
+ elsif committee_reports.has_key?(name)
818
+ value = committee_reports[name]
819
+ else
820
+ @@ -314,7 +345,7 @@ class Emitter < ActiveRecord::Base
821
+ timeframe.include? emission_date
822
+ elsif self.class.characterization.characteristics.include? :active_subtimeframe
823
+ if outsourced?
824
+ - timeframe & emission_estimate(:timeframe => timeframe).active_subtimeframe
825
+ + timeframe & emission_estimate(:timeframe => timeframe).response.data['active_subtimeframe']
826
+ else
827
+ timeframe & committee_reports[:active_subtimeframe]
828
+ end
829
+
830
+ Then we brought the internal variables back to life:
831
+
832
+ diff --git a/app/helpers/emitters_helper.rb b/app/helpers/emitters_helper.rb
833
+ index c1b75ee..b5c3a1c 100755
834
+ --- a/app/helpers/emitters_helper.rb
835
+ +++ b/app/helpers/emitters_helper.rb
836
+ @@ -3,7 +3,7 @@
837
+
838
+ module EmittersHelper
839
+ def render_characteristic(emitter, characteristic)
840
+ - characteristic_value = (emitter.outsourced? or emitter.characteristics.has_key?(characteristic)) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
841
+ + characteristic_value = emitter.real_or_assumed_characteristic_value(characteristic)
842
+ locals = { :emitter => emitter, :characteristic => characteristic, characteristic => characteristic_value }
843
+
844
+ begin
845
+ @@ -14,7 +14,7 @@ module EmittersHelper
846
+ end
847
+
848
+ def render_edit_characteristic(emitter, characteristic)
849
+ - characteristic_value = (emitter.outsourced? or emitter.characteristics.has_key?(characteristic)) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
850
+ + characteristic_value = emitter.real_or_assumed_characteristic_value(characteristic)
851
+ locals = { :emitter => emitter, :characteristic => characteristic, characteristic => characteristic_value }
852
+ template = pick_edit_partial(emitter, characteristic)
853
+ if template == 'characteristics/controls/collection_select'
854
+ @@ -80,7 +80,7 @@ module EmittersHelper
855
+ end
856
+
857
+ def render_placeholder_characteristic(emitter, characteristic)
858
+ - characteristic_value = (emitter.outsourced? or emitter.characteristics.has_key?(characteristic)) ? emitter.characteristics[characteristic] : emitter.committee_reports[characteristic]
859
+ + characteristic_value = emitter.real_or_assumed_characteristic_value(characteristic)
860
+ locals = { :emitter => emitter, :characteristic => characteristic, characteristic => characteristic_value }
861
+ render :partial => "#{emitter.common_plural}/#{characteristic}/placeholder", :layout => 'characteristics/layouts/placeholder', :locals => locals
862
+ rescue ActionView::MissingTemplate
863
+ diff --git a/app/views/characteristics/_characteristic.html.erb b/app/views/characteristics/_characteristic.html.erb
864
+ index c08003d..b9bb073 100644
865
+ --- a/app/views/characteristics/_characteristic.html.erb
866
+ +++ b/app/views/characteristics/_characteristic.html.erb
867
+ @@ -1,7 +1,7 @@
868
+ <%
869
+ known = !(known == false)
870
+ expanded = (expanded == true)
871
+ -reported = !emitter.outsourced? and emitter.committee_reports.keys.include? characteristic # this should never happen if known
872
+ +reported = emitter.assumed_characteristic_value? characteristic # this should never happen if known
873
+ element_class = [characteristic]
874
+ element_class << 'reported' if reported
875
+ element_class << 'next' if expanded
876
+ @@ -16,7 +16,7 @@ element_class = element_class.join ' '
877
+ <%= render_edit_characteristic emitter, characteristic if expanded %>
878
+ <%= render_help_for_characteristic(emitter.common_symbol, characteristic) if expanded %>
879
+ <% if reported %>
880
+ - <span class="assumption">Assuming <%= render_characteristic emitter, characteristic %><span class="method"> via the <i><%= emitter.class.characterization.committees[characteristic].called_quorum.name.to_s.dasherize %></i> method</span></span>
881
+ + <span class="assumption">Assuming <%= render_characteristic emitter, characteristic %><span class="method"> via the <i><%= emitter.reporting_committee_name(characteristic) %></i> method</span></span>
882
+ <% end %>
883
+ <% end %>
884
+ </dd>
885
+ diff --git a/app/views/components/_gaps.html.erb b/app/views/components/_gaps.html.erb
886
+ index 203d261..4e0fa04 100644
887
+ --- a/app/views/components/_gaps.html.erb
888
+ +++ b/app/views/components/_gaps.html.erb
889
+ @@ -1,5 +1,5 @@
890
+ <% if component.tense == :imperfect %>
891
+ - <% covered_timeframes = component.emitters.values.flatten.collect { |e| e.outsourced? ? nil : e.committee_reports(timeframe)[:active_subtimeframe] } %>
892
+ + <% covered_timeframes = component.emitters.values.flatten.collect { |e| e.real_or_assumed_characteristic_value :active_subtimeframe } %>
893
+ <% unless timeframe.covered_by? *covered_timeframes %>
894
+ <% gaps = timeframe.gaps_left_by(*covered_timeframes) %>
895
+ <p><strong>Note:</strong> you haven't told us about any <%= component.class.component_name %> emissions during <%= gaps.to_sentence %>. If you <%= component.verb[:past] %> <%= component.class.component_name.pluralize %> during these times, please tell us about it.</p>
896
+
897
+ === Comments on step 3
898
+
899
+ * A lot of data comes back from the emission estimate web service. You can inspect it by grabbing the methodology URL and changing it to JSON.
900
+
901
+ * We realized that the "perspective" argument was mostly for caching. We don't need it anymore because the expensive computations are outsourced!
902
+
903
+ == Step 4: Prepare for network problems (WORKING DRAFT)
904
+
905
+ If you've decided that the data returned by the Brighter Planet emission estimate web service is sufficient for your needs, it's time to deal with network problems.
906
+
907
+ link:../doc/timeout-error.png
908
+
909
+ You don't want your application to go offline if there is a network problem between carbon middleware and you. Using the carbon gem, that means rescuing from exceptions.
910
+
911
+ It's easy to fake a simple problem by adding this to <tt>/etc/hosts</tt>: (remember to take it out later)
912
+
913
+ 10.0.22.22 carbon.brighterplanet.com
914
+
915
+ Now everything will time out:
916
+
917
+ >> a = Automobile.find 710
918
+ => #<Automobile [...] >
919
+ >> a.emission_estimate.to_f
920
+ Errno::ETIMEDOUT: Connection timed out - connect(2)
921
+ from /usr/lib/ruby/1.8/net/http.rb:560:in `initialize'
922
+ from /usr/lib/ruby/1.8/net/http.rb:560:in `open'
923
+ from /usr/lib/ruby/1.8/net/http.rb:560:in `connect'
924
+ from /usr/lib/ruby/1.8/timeout.rb:53:in `timeout'
925
+ from /usr/lib/ruby/1.8/timeout.rb:93:in `timeout'
926
+ from /usr/lib/ruby/1.8/net/http.rb:560:in `connect'
927
+ from /usr/lib/ruby/1.8/net/http.rb:553:in `do_start'
928
+ from /usr/lib/ruby/1.8/net/http.rb:542:in `start'
929
+ from /usr/lib/ruby/gems/1.8/gems/nap-0.4/lib/rest/request.rb:133:in `perform'
930
+ from /usr/lib/ruby/gems/1.8/gems/carbon-0.2.4/lib/carbon/emission_estimate/response.rb:43:in `perform'
931
+ from /usr/lib/ruby/gems/1.8/gems/carbon-0.2.4/lib/carbon/emission_estimate/response.rb:16:in `load_realtime_data'
932
+ from /usr/lib/ruby/gems/1.8/gems/carbon-0.2.4/lib/carbon/emission_estimate/response.rb:11:in `send'
933
+ from /usr/lib/ruby/gems/1.8/gems/carbon-0.2.4/lib/carbon/emission_estimate/response.rb:11:in `initialize'
934
+ from /usr/lib/ruby/gems/1.8/gems/carbon-0.2.4/lib/carbon/emission_estimate.rb:66:in `new'
935
+ from /usr/lib/ruby/gems/1.8/gems/carbon-0.2.4/lib/carbon/emission_estimate.rb:66:in `response'
936
+ from /usr/lib/ruby/gems/1.8/gems/carbon-0.2.4/lib/carbon/emission_estimate.rb:41:in `method_missing'
937
+ from (irb):5>>
938
+
939
+ Here's an idea:
940
+
941
+ diff --git a/app/views/emitters/_sidebar.html.erb b/app/views/emitters/_sidebar.html.erb
942
+ index 2da2214..9190387 100644
943
+ --- a/app/views/emitters/_sidebar.html.erb
944
+ +++ b/app/views/emitters/_sidebar.html.erb
945
+ @@ -6,7 +6,9 @@
946
+ <%= render :partial => 'carbon_offsets/emitter', :locals => { :emitter => emitter, :timeframe => timeframe, :show_purchasing => true, :show_granting => true } %>
947
+ <% if emitter.outsourced? %>
948
+ <p class="committee_reports">
949
+ - <%= link_to 'Show calculation methods', emitter.emission_estimate.methodology %>
950
+ + <% outsourced_content do %>
951
+ + <%= link_to 'Show calculation methods', emitter.emission_estimate.methodology %>
952
+ + <% end %>
953
+ </p>
954
+ <% else %>
955
+ <p class="committee_reports">
956
+ diff --git a/app/helpers/emitters_helper.rb b/app/helpers/emitters_helper.rb
957
+ index c1b75ee..bd0c78f 100755
958
+ --- a/app/helpers/emitters_helper.rb
959
+ +++ b/app/helpers/emitters_helper.rb
960
+ @@ -2,8 +2,47 @@
961
+ # doc/partials_that_use_local_variables_named_after_characteristics.rb
962
+
963
+ module EmittersHelper
964
+ + def outsourced_content(failsafe = '', &block)
965
+ + failed = request.instance_variable_get :@outsourced_request_failed
966
+ + str = failed ? failsafe : capture(&block)
967
+ + if block_called_from_erb? block
968
+ + # sabshere 7/28/10 use of #concat is specific to Rails 2, I believe
969
+ + concat str
970
+ + else
971
+ + str
972
+ + end
973
+ + rescue ::SocketError, ::Timeout::Error, ::Errno::ETIMEDOUT, ::Errno::ENETUNREACH, ::Errno::ECONNRESET, ::Errno::ECONNREFUSED
974
+ + # These are general network errors raised by Net::HTTP.
975
+ + # Your internet connection might be down, or our servers might be down.
976
+ + request.instance_variable_set :@outsourced_request_failed, true
977
+ + retry
978
+ + rescue ::Carbon::RateLimited
979
+ + # Realtime mode only.
980
+ + # In order to prevent denial-of-service attacks, our servers rate limit requests.
981
+ + # The gem will try up to three times to get an answer back from the server, waiting slightly longer each time.
982
+ + # If you still get this exception, please contact us at staff@brighterplanet.com and we'll lift your rate.
983
+ + request.instance_variable_set :@outsourced_request_failed, true
984
+ + retry
985
+ + rescue ::Carbon::RealtimeEstimateFailed
986
+ + # Realtime mode only.
987
+ + # Our server returned a 4XX or 5XX error.
988
+ + # Please contact us at staff@brighterplanet.com if you get these more than a couple times.
989
+ + retry
990
+ + rescue ::Carbon::QueueingFailed
991
+ + # Async mode only.
992
+ + # The gem connects directly to Amazon SQS in order to provide maximum throughput. If that service returns anything other than success, you get this exception.
993
+ + # Please contact us at staff@brighterplanet.com if you see too many of these.
994
+ + request.instance_variable_set :@outsourced_request_failed, true
995
+ + retry
996
+ + end
997
+
998
+ === Comments on step 4
999
+
1000
+ * This is a very Rails-specific solution. It might not be as easy in your language of choice.
1001
+
1002
+ * Even if this is easy, it has to go in a lot of places in our app. Maybe we should proxy the response object (::Carbon::EmissionEstimate) and add useful methods there?