flight 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,33 +2,47 @@
2
2
  # See LICENSE for details.
3
3
  # Contact Brighter Planet for dual-license arrangements.
4
4
 
5
+ require 'leap'
6
+ require 'timeframe'
7
+ require 'date'
5
8
  require 'weighted_average'
6
9
  require 'builder'
7
10
 
8
- ## Flight:carbon model
9
- # This module is used by [Brighter Planet](http://brighterplanet.com)'s carbon emission [web service](http://carbon.brighterplanet.com) to estimate the **greenhouse gas emissions of passenger air travel**.
11
+ ## Flight carbon model
12
+ # This model is used by [Brighter Planet](http://brighterplanet.com)'s carbon emission [web service](http://carbon.brighterplanet.com) to estimate the **greenhouse gas emissions of passenger air travel**.
10
13
  #
11
- # The final estimate is the result of the **calculations** detailed below.
12
- # These calculations are performed in reverse order, starting with the last calculation listed and finishing with the `emission` calculation.
14
+ ##### Timeframe and date
15
+ # The model estimates the emissions that occur during a particular `timeframe`. To do this it needs to know the `date` on which the flight occurred. For example, if the `timeframe` is January 2010, a flight that occured on January 5, 2010 will have emissions but a flight that occured on February 1, 2010 will not. If no `timeframe` is specified, the default is the current year. If no `date` is specified, the default is the first day of the `timeframe`.
13
16
  #
14
- # To accomodate varying input data, each calculation may have one or more **methods**. These are listed under each calculation in order from most to least preferred.
17
+ ##### Calculations
18
+ # The final estimate is the result of the **calculations** detailed below. These calculations are performed in reverse order, starting with the last calculation listed and finishing with the `emission` calculation. Each calculation is named according to the value it returns.
19
+ #
20
+ ##### Methods
21
+ # To accomodate varying client input, each calculation may have one or more **methods**. These are listed under each calculation in order from most to least preferred. Each method is named according to the values it requires. If any of these values is not available the method will be ignored. If all the methods for a calculation are ignored, the calculation will not return a value. "Default" methods do not require any values, and so a calculation with a default method will always return a value.
22
+ #
23
+ ##### Standard compliance
24
+ # Each method lists any established calculation standards with which it **complies**. When compliance with a standard is requested, all methods that do not comply with that standard are ignored. This means that any values a particular method requires will have been calculated using a compliant method, because those are the only methods available. If any value did not have a compliant method in its calculation then it would be undefined, and the current method would have been ignored.
25
+ #
26
+ ##### Collaboration
27
+ # Contributions to this carbon model are actively encouraged and warmly welcomed. This library includes a comprehensive test suite to ensure that your changes do not cause regressions. All changes shold include test coverage for new functionality. Please see [sniff](http://github.com/brighterplanet/sniff#readme), our emitter testing framework, for more information.
15
28
  module BrighterPlanet
16
29
  module Flight
17
30
  module CarbonModel
18
31
  def self.included(base)
19
32
  base.extend FastTimestamp
20
33
  base.decide :emission, :with => :characteristics do
21
- ### Emission
22
- # This calculation returns the `emission` estimate in *kg CO<sub>2</sub>e*.
23
- # The `emission` estimate is the passenger's share of the total flight emissions that occured during the `timeframe`.
34
+ ### Emission calculation
35
+ # Returns the `emission` estimate in *kg CO<sub>2</sub>e*.
36
+ # This is the passenger's share of the total flight emissions that occured during the `timeframe`.
24
37
  committee :emission do
25
- ##### From fuel, emission factor, freight share, passengers, and multipliers
26
- # This method:
38
+ #### Emission from fuel, emission factor, freight share, passengers, multipliers, and date
39
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
27
40
  #
28
- # 1. Checks that the flight occured during the `timeframe`
29
- # 2. Multiplies `fuel use` (*kg fuel*) by an `emission factor` (*kg CO<sub>2</sub>e / kg fuel*) and an `aviation multiplier` to give total flight emissions in *kg CO<sub>2</sub>e*
30
- # 3. Multiplies by (1 - `freight share`) to take out emissions attributed to freight cargo and mail, leaving emissions attributed to passengers and their baggage
31
- # 4. Divides by the number of `passengers` and multiplies by a `seat class multiplier` to give `emission` for the passenger
41
+ # - Checks whether the flight occured during the `timeframe`
42
+ # - Multiplies `fuel use` (*kg fuel*) by an `emission factor` (*kg CO<sub>2</sub>e / kg fuel*) and an `aviation multiplier` to give total flight emissions in *kg CO<sub>2</sub>e*
43
+ # - Multiplies by (1 - `freight share`) to take out emissions attributed to freight cargo and mail, leaving emissions attributed to passengers and their baggage
44
+ # - Divides by the number of `passengers` and multiplies by a `seat class multiplier` to give `emission` for the passenger
45
+ # - If the flight did not occur during the `timeframe`, `emission` is zero
32
46
  quorum 'from fuel, emission factor, freight share, passengers, multipliers, and date',
33
47
  :needs => [:fuel, :emission_factor, :freight_share, :passengers, :seat_class_multiplier, :aviation_multiplier, :date], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics, timeframe|
34
48
  date = characteristics[:date].is_a?(Date) ?
@@ -36,52 +50,60 @@ module BrighterPlanet
36
50
  Date.parse(characteristics[:date].to_s)
37
51
  if timeframe.include? date
38
52
  characteristics[:fuel] * characteristics[:emission_factor] * characteristics[:aviation_multiplier] * (1 - characteristics[:freight_share]) / characteristics[:passengers] * characteristics[:seat_class_multiplier]
39
- # If the flight did not occur during the `timeframe`, `emission` is zero.
40
53
  else
41
54
  0
42
55
  end
43
56
  end
44
-
45
- quorum 'default' do
46
- raise "The emission committee's default quorum should never be called"
47
- end
48
57
  end
49
58
 
50
- ### Emission factor
51
- # This calculation returns the `emission factor` in *kg CO<sub>2</sub>e / kg fuel*.
59
+ ### Emission factor calculation
60
+ # Returns the `emission factor` in *kg CO<sub>2</sub>e / kg fuel*.
52
61
  committee :emission_factor do
53
- ##### From fuel type
54
- # This method looks up data on [fuel types](http://data.brighterplanet.com/fuel_types) and divides the `fuel type` `emission factor` (*kg CO<sub>2</sub> / litre fuel*) by the `fuel type` `density` (*kg fuel / litre fuel*) to give *kg CO<sub>2</sub>e / kg fuel*.
62
+ #### Emission factor from fuel type
63
+ # Complies: GHG Protocol, ISO-14064-1, Climate Registry Protocol
64
+ #
65
+ # Looks up the [fuel type](http://data.brighterplanet.com/fuel_types) and divides its `emission factor` (*kg CO<sub>2</sub> / litre fuel*) by its `density` (*kg fuel / litre fuel*) to give *kg CO<sub>2</sub>e / kg fuel*.
55
66
  quorum 'from fuel type', :needs => :fuel_type, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
56
67
  characteristics[:fuel_type].emission_factor.to_f / characteristics[:fuel_type].density.to_f
57
68
  end
58
69
  end
59
70
 
60
- ### Aviation multiplier
61
- # This calculation returns the `aviation multiplier`, which approximates the extra climate impact of emissions high in the atmosphere.
71
+ ### Aviation multiplier calculation
72
+ # Returns the `aviation multiplier`. This approximates the extra climate impact of emissions high in the atmosphere.
62
73
  committee :aviation_multiplier do
63
- ##### Default
64
- # This method uses an `aviation multiplier` of **2.0** after [Kolmuss and Crimmins (2009)](http://sei-us.org/publications/id/13).
74
+ #### Aviation multiplier from client input
75
+ # **Complies:** All
76
+ #
77
+ # Uses the client-input `aviation multiplier`.
78
+
79
+ #### Default aviation multiplier
80
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
81
+ #
82
+ # Uses an `aviation multiplier` of **2.0** after [Kolmuss and Crimmins (2009)](http://sei-us.org/publications/id/13).
65
83
  quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
66
- base.fallback.aviation_multiplier
84
+ 2.0
67
85
  end
68
86
  end
69
87
 
70
- ### Fuel
71
- # This calculation returns the flight's total `fuel` use in *kg fuel*.
88
+ ### Fuel calculation
89
+ # Returns the flight's total `fuel` use in *kg fuel*.
72
90
  committee :fuel do
73
- ##### From fuel per segment and segments per trip and trips
74
- # This method multiplies the `fuel per segment` (*kg fuel*) by the `segments per trip` and the number of `trips` to give *kg fuel*.
91
+ #### Fuel from fuel per segment and segments per trip and trips
92
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
93
+ #
94
+ # Multiplies the `fuel per segment` (*kg fuel*) by the `segments per trip` and the number of `trips` to give *kg fuel*.
75
95
  quorum 'from fuel per segment and segments per trip and trips', :needs => [:fuel_per_segment, :segments_per_trip, :trips], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
76
96
  characteristics[:fuel_per_segment] * characteristics[:segments_per_trip].to_f * characteristics[:trips].to_f
77
97
  end
78
98
  end
79
99
 
80
- ### Fuel per segment
81
- # This calculation returns the `fuel per segment` in *kg fuel*.
100
+ ### Fuel per segment calculation
101
+ # Returns the `fuel per segment` in *kg fuel*.
82
102
  committee :fuel_per_segment do
83
- ##### From adjusted distance per segment and fuel use coefficients
84
- # This method uses a third-order polynomial equation to calculate the fuel used per segment:
103
+ #### Fuel per segment from adjusted distance per segment and fuel use coefficients
104
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
105
+ #
106
+ # Uses a third-order polynomial equation to calculate the fuel used per segment:
85
107
  #
86
108
  # (m<sub>3</sub> * d^3 ) + (m<sub>2</sub> * d^2 ) + (m<sub>1</sub> * d) + endpoint fuel
87
109
  #
@@ -94,32 +116,38 @@ module BrighterPlanet
94
116
  end
95
117
  end
96
118
 
97
- ### Adjusted distance per segment
98
- # This calculation returns the `adjusted distance per segment` in *nautical miles*.
119
+ ### Adjusted distance per segment calculation
120
+ # Returns the `adjusted distance per segment` in *nautical miles*.
99
121
  committee :adjusted_distance_per_segment do
100
- ##### From adjusted distance and segments per trip
101
- # This method divides the `adjusted distance` (*nautical miles*) by `segments per trip` to give *nautical miles*.
122
+ #### Adjusted distance per segment from adjusted distance and segments per trip
123
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
124
+ #
125
+ # Divides the `adjusted distance` (*nautical miles*) by `segments per trip` to give *nautical miles*.
102
126
  quorum 'from adjusted distance and segments per trip', :needs => [:adjusted_distance, :segments_per_trip], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
103
127
  characteristics[:adjusted_distance] / characteristics[:segments_per_trip]
104
128
  end
105
129
  end
106
130
 
107
- ### Adjusted distance
108
- # This calculation returns the `adjusted distance` in *nautical miles*.
131
+ ### Adjusted distance calculation
132
+ # Returns the `adjusted distance` in *nautical miles*.
109
133
  # The `adjusted distance` accounts for factors that increase the actual distance traveled by real world flights.
110
134
  committee :adjusted_distance do
111
- ##### From distance, route inefficiency factor, and dogleg factor
112
- # This method multiplies `distance` (*nautical miles*) by a `route inefficiency factor` and a `dogleg factor` to give *nautical miles*.
135
+ #### Adjusted distance from distance, route inefficiency factor, and dogleg factor
136
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
137
+ #
138
+ # Multiplies `distance` (*nautical miles*) by a `route inefficiency factor` and a `dogleg factor` to give *nautical miles*.
113
139
  quorum 'from distance, route inefficiency factor, and dogleg factor', :needs => [:distance, :route_inefficiency_factor, :dogleg_factor], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
114
140
  characteristics[:distance] * characteristics[:route_inefficiency_factor] * characteristics[:dogleg_factor]
115
141
  end
116
142
  end
117
143
 
118
- ### Distance
119
- # This calculation returns the flight's base `distance` in *nautical miles*.
144
+ ### Distance calculation
145
+ # Returns the flight's base `distance` in *nautical miles*.
120
146
  committee :distance do
121
- ##### From airports
122
- # This first-tier method calculates the great circle distance between the `origin airport` and `destination airport` and converts from *km* to *nautical miles*.
147
+ #### Distance from airports
148
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
149
+ #
150
+ # Calculates the great circle distance between the `origin airport` and `destination airport` and converts from *km* to *nautical miles*.
123
151
  quorum 'from airports', :needs => [:origin_airport, :destination_airport], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
124
152
  if characteristics[:origin_airport].latitude and
125
153
  characteristics[:origin_airport].longitude and
@@ -129,70 +157,88 @@ module BrighterPlanet
129
157
  end
130
158
  end
131
159
 
132
- ##### From distance estimate
133
- # This second-tier method converts the `distance_estimate` in *km* to *nautical miles*.
160
+ #### Distance from distance estimate
161
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
162
+ #
163
+ # Converts the `distance_estimate` in *km* to *nautical miles*.
134
164
  quorum 'from distance estimate', :needs => :distance_estimate, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
135
165
  characteristics[:distance_estimate].kilometres.to :nautical_miles
136
166
  end
137
167
 
138
- ##### From distance class
139
- # This third-tier method looks up the [distance class](http://data.brighterplanet.com/flight_distance_classes)'s `distance` and converts from *km* to *nautical miles*.
168
+ #### Distance from distance class
169
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
170
+ #
171
+ # Looks up the [distance class](http://data.brighterplanet.com/flight_distance_classes)' `distance` and converts from *km* to *nautical miles*.
140
172
  quorum 'from distance class', :needs => :distance_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
141
173
  characteristics[:distance_class].distance.kilometres.to :nautical_miles
142
174
  end
143
175
 
144
- ##### From cohort
145
- # This fourth-tier method calculates the average `distance` of the `cohort` segments, weighted by their passengers, and converts from *km* to *nautical miles*.
176
+ #### Distance from cohort
177
+ # **Complies:**
178
+ #
179
+ # Calculates the average `distance` of the `cohort` segments, weighted by their passengers, and converts from *km* to *nautical miles*.
146
180
  quorum 'from cohort', :needs => :cohort do |characteristics| # cohort here will be some combo of origin, airline, and aircraft
147
181
  distance = characteristics[:cohort].weighted_average(:distance, :weighted_by => :passengers).kilometres.to(:nautical_miles)
148
182
  distance > 0 ? distance : nil
149
183
  end
150
184
 
151
- ##### Default
152
- # This default method calculates the average `distance` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers, and converts from *km* to *nautical miles*.
185
+ #### Default distance
186
+ # **Complies:**
187
+ #
188
+ # Calculates the average `distance` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers, and converts from *km* to *nautical miles*.
153
189
  quorum 'default' do
154
190
  FlightSegment.fallback.distance.kilometres.to :nautical_miles
155
191
  end
156
192
  end
157
193
 
158
- ### Route inefficiency factor
159
- # This calculation returns the `route inefficiency factor`, a measure of how much farther real world flights travel than the great circle distance between their origin and destination.
194
+ ### Route inefficiency factor calculation
195
+ # This calculation returns the `route inefficiency factor`.
196
+ # This is a measure of how much farther real world flights travel than the great circle distance between their origin and destination.
160
197
  # It accounts for factors like flight path routing around controlled airspace and circling while waiting for clearance to land.
161
198
  committee :route_inefficiency_factor do
162
- ##### From country
163
- # This first-tier method looks up the `route inefficiency factor` for the [country](http://data.brighterplanet.com/countries) in which the flight occurs.
199
+ #### Route inefficiency factor from country
200
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
201
+ #
202
+ # Looks up the `route inefficiency factor` for the [country](http://data.brighterplanet.com/countries) in which the flight occurs.
164
203
  quorum 'from country', :needs => :country, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
165
204
  characteristics[:country].andand.flight_route_inefficiency_factor
166
205
  end
167
206
 
168
- ##### Default
169
- # This default method uses a `route inefficiency factor` of **10%** based on [Kettunen et al. (2005)](http://www.atmseminar.org/seminarContent/seminar6/papers/p_055_MPM.pdf)
207
+ #### Default route inefficiency factor
208
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
209
+ #
210
+ # Uses a `route inefficiency factor` of **10%** based on [Kettunen et al. (2005)](http://www.atmseminar.org/seminarContent/seminar6/papers/p_055_MPM.pdf)
170
211
  quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
171
212
  Country.fallback.flight_route_inefficiency_factor
172
213
  end
173
214
  end
174
215
 
175
- ### Dogleg factor
176
- # This calculation returns the `dogleg factor`, a measure of how far out of the way the average layover is compared to a direct flight.
216
+ ### Dogleg factor calculation
217
+ # Returns the `dogleg factor`.
218
+ # This is a measure of how far out of the way the average layover is compared to a direct flight.
177
219
  committee :dogleg_factor do
178
- ##### From segments per trip
179
- # This method assumes that each layover increases the total flight distance by **25%**.
220
+ #### Dogleg factor from segments per trip
221
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
222
+ #
223
+ # Assumes that each layover increases the total flight distance by **25%**.
180
224
  quorum 'from segments per trip', :needs => :segments_per_trip, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
181
- base.fallback.dogleg_factor ** (characteristics[:segments_per_trip] - 1)
225
+ 1.25 ** (characteristics[:segments_per_trip] - 1)
182
226
  end
183
227
  end
184
228
 
185
- ### Distance estimate
186
- # This implied calculation returns the client-input 'distance estimate' in *km*.
229
+ ### Distance estimate calculation
230
+ # Returns the client-input `distance estimate` in *km*.
187
231
 
188
- ### Distance class
189
- # This implied calculation returns the client-input [distance class](http://data.brighterplanet.com/distance_classes).
232
+ ### Distance class calculation
233
+ # Returns the client-input [distance class](http://data.brighterplanet.com/distance_classes).
190
234
 
191
- ### Fuel use coefficients
192
- # This calculation returns the `fuel use coefficients`, the coefficients of the third-order polynomial equation that describes aircraft fuel use.
235
+ ### Fuel use coefficients calculation
236
+ # Returns the `fuel use coefficients`. These are the coefficients of the third-order polynomial equation that describes aircraft fuel use.
193
237
  committee :fuel_use_coefficients do
194
- ##### From aircraft
195
- # This first-tier method looks up the [aircraft](http://data.brighterplanet.com/aircraft)'s `fuel use coefficients`.
238
+ #### Fuel use coefficients from aircraft
239
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
240
+ #
241
+ # Looks up the [aircraft](http://data.brighterplanet.com/aircraft)'s `fuel use coefficients`.
196
242
  quorum 'from aircraft', :needs => :aircraft, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
197
243
  aircraft = characteristics[:aircraft]
198
244
  fuel_use = FuelUseEquation.new aircraft.m3, aircraft.m2, aircraft.m1, aircraft.endpoint_fuel
@@ -203,15 +249,20 @@ module BrighterPlanet
203
249
  end
204
250
  end
205
251
 
206
- ##### From aircraft class
207
- # This second-tier method looks up the [aircraft class](http://data.brighterplanet.com/aircraft_classes)'s `fuel use coefficients`.
252
+ #### Fuel use coefficients from aircraft class
253
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
254
+ #
255
+ # Looks up the [aircraft class](http://data.brighterplanet.com/aircraft_classes)' `fuel use coefficients`.
208
256
  quorum 'from aircraft class', :needs => :aircraft_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
209
257
  aircraft_class = characteristics[:aircraft_class]
210
258
  FuelUseEquation.new aircraft_class.m3, aircraft_class.m2, aircraft_class.m1, aircraft_class.endpoint_fuel
211
259
  end
212
260
 
213
- ##### From cohort
214
- # This third-tier method calculates the average `fuel use coefficients` of the `cohort` segments, weighted by their passengers.
261
+ #### Fuel use coefficients from cohort
262
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
263
+ #
264
+ # Calculates the average `fuel use coefficients` of the aircraft used by the `cohort` segments, weighted by the segment passengers.
265
+ # If an aircraft does not have `fuel use coefficients`, it takes the `fuel use coefficients` for the aircraft's [aircraft class](http://data.brighterplanet.com/aircraft_classes).
215
266
  quorum 'from cohort', :needs => :cohort, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
216
267
  flight_segments = characteristics[:cohort]
217
268
 
@@ -219,119 +270,102 @@ module BrighterPlanet
219
270
  passengers + flight_segment.passengers
220
271
  end
221
272
 
222
- bts_codes = flight_segments.map(&:bts_aircraft_type_code).uniq
223
- relevant_aircraft = Aircraft.find_all_by_bts_aircraft_type_code(bts_codes).inject({}) do |hsh, aircraft|
224
- hsh[aircraft.bts_aircraft_type_code] = aircraft
273
+ bts_codes = flight_segments.map(&:aircraft_bts_code).uniq
274
+ relevant_aircraft = Aircraft.find_all_by_bts_code(bts_codes).inject({}) do |hsh, aircraft|
275
+ hsh[aircraft.bts_code] = aircraft
225
276
  hsh
226
277
  end
227
- flight_segment_aircraft = flight_segments.inject({}) do |hsh, flight_segment|
228
- bts_code = flight_segment.bts_aircraft_type_code
229
- key = flight_segment.row_hash
230
- aircraft = relevant_aircraft[bts_code.to_s]
231
- hsh[key] = aircraft
232
- hsh
233
- end
234
-
235
- if flight_segment_aircraft.values.map(&:m3).any?
236
- m3 = flight_segments.inject(0) do |m3, flight_segment|
237
- aircraft = flight_segment_aircraft[flight_segment.row_hash]
238
- aircraft_m3 = aircraft.andand.m3 || 0
239
- m3 + (aircraft_m3 * flight_segment.passengers)
240
- end
241
- else
242
- m3 = Aircraft.fallback.m3
243
- end
244
-
245
- if flight_segment_aircraft.values.map(&:m2).any?
246
- m2 = flight_segments.inject(0) do |m2, flight_segment|
247
- aircraft = flight_segment_aircraft[flight_segment.row_hash]
248
- aircraft_m2 = aircraft.andand.m2 || 0
249
- m2 + (aircraft_m2 * flight_segment.passengers)
250
- end
251
- else
252
- m2 = Aircraft.fallback.m2
253
- end
254
-
255
- if flight_segment_aircraft.values.map(&:m1).any?
256
- m1 = flight_segments.inject(0) do |m1, flight_segment|
257
- aircraft = flight_segment_aircraft[flight_segment.row_hash]
258
- aircraft_m1 = aircraft.andand.m1 || 0
259
- m1 + (aircraft_m1 * flight_segment.passengers)
260
- end
261
- else
262
- m1 = Aircraft.fallback.m1
263
- end
264
-
265
- if flight_segment_aircraft.values.map(&:endpoint_fuel).any?
266
- endpoint_fuel = flight_segments.inject(0) do |endpoint_fuel, flight_segment|
267
- aircraft = flight_segment_aircraft[flight_segment.row_hash]
268
- aircraft_epfuel = aircraft.andand.endpoint_fuel || 0
269
- endpoint_fuel + (aircraft_epfuel * flight_segment.passengers)
278
+
279
+ sum_coefficients = lambda do |name|
280
+ flight_segments.inject(0) do |coefficient, flight_segment|
281
+ bts_code = flight_segment.aircraft_bts_code.to_s
282
+ aircraft = relevant_aircraft[bts_code]
283
+ aircraft_coefficient = aircraft.send(name)
284
+ if aircraft_coefficient.nil? or aircraft_coefficient.zero?
285
+ aircraft_coefficient = aircraft.aircraft_class.send(name)
286
+ end
287
+ coefficient + (aircraft_coefficient * flight_segment.passengers)
270
288
  end
271
- else
272
- endpoint_fuel = Aircraft.fallback.endpoint_fuel
273
289
  end
290
+
291
+ m3 = sum_coefficients.call(:m3) / passengers
292
+ m2 = sum_coefficients.call(:m2) / passengers
293
+ m1 = sum_coefficients.call(:m1) / passengers
294
+ endpoint_fuel = sum_coefficients.call(:endpoint_fuel) / passengers
274
295
 
275
- if [m3, m2, m1, endpoint_fuel, passengers].any?(&:nonzero?)
276
- m3 = m3 / passengers
277
- m2 = m2 / passengers
278
- m1 = m1 / passengers
279
- endpoint_fuel = endpoint_fuel / passengers
280
-
281
- FuelUseEquation.new m3, m2, m1, endpoint_fuel
282
- end
296
+ FuelUseEquation.new m3, m2, m1, endpoint_fuel
283
297
  end
284
298
 
285
- ##### Default
286
- # This default method calculates the average `fuel use coefficients` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers.
299
+ #### Default fuel use coefficients
300
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
301
+ #
302
+ # Calculates the average `fuel use coefficients` of the aircraft used by [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by the segment passengers.
303
+ # If an aircraft does not have `fuel use coefficients`, it takes the `fuel use coefficients` for the aircraft's [aircraft class](http://data.brighterplanet.com/aircraft_classes).
287
304
  quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
288
- fallback = Aircraft.fallback
289
- if fallback
290
- FuelUseEquation.new fallback.m3, fallback.m2, fallback.m1, fallback.endpoint_fuel
291
- end
305
+ FuelUseEquation.new Aircraft.fallback.m3, Aircraft.fallback.m2, Aircraft.fallback.m1, Aircraft.fallback.endpoint_fuel
292
306
  end
293
307
  end
294
308
 
295
- ### Fuel type
296
- # This calculation returns the `fuel type`.
309
+ ### Fuel type calculation
310
+ # Returns the `fuel type`.
297
311
  committee :fuel_type do
298
- ##### From client input
299
- # This implied first-tier method uses the client-input [fuel type](http://data.brighterplanet.com/fuel_types).
312
+ #### Fuel type from client input
313
+ # **Complies:** All
314
+ #
315
+ # Uses the client-input [fuel type](http://data.brighterplanet.com/fuel_types).
300
316
 
301
- ##### Default
302
- # This method assumes the flight uses **Jet Fuel**.
303
- quorum 'default' do
317
+ #### Default fuel type
318
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
319
+ #
320
+ # Assumes the flight uses **Jet Fuel**.
321
+ quorum 'default', :complies => [:ghg_protocol, :ico, :tcr] do
304
322
  FuelType.find_by_name 'Jet Fuel'
305
323
  end
306
324
  end
307
325
 
308
- ### Passengers
309
- # This calculation returns the number of `passengers`.
326
+ ### Passengers calculation
327
+ # Returns the number of `passengers`.
310
328
  committee :passengers do
311
- ##### From seats and load factor
312
- # This method multiplies the number of `seats` by the `load factor`.
329
+ #### Passengers from seats and load factor
330
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
331
+ #
332
+ # Multiplies the number of `seats` by the `load factor`.
313
333
  quorum 'from seats and load factor', :needs => [:seats, :load_factor], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
314
334
  (characteristics[:seats] * characteristics[:load_factor]).round
315
335
  end
316
336
  end
317
337
 
318
- ### Seats
319
- # This calculation returns the number of `seats`.
338
+ ### Seats calculation
339
+ # Returns the number of `seats`.
320
340
  committee :seats do
321
- ##### From aircraft
322
- # This first-tier method looks up the [aircraft](http://data.brighterplanet.com/aircraft)'s average number of `seats`.
341
+ #### Seats from seats estimate
342
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
343
+ #
344
+ # Uses the client-input estimate of the number of `seats`.
345
+ quorum 'from seats estimate', :needs => :seats_estimate, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
346
+ characteristics[:seats_estimate]
347
+ end
348
+
349
+ #### Seats from aircraft
350
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
351
+ #
352
+ # Looks up the [aircraft](http://data.brighterplanet.com/aircraft)'s average number of `seats`.
323
353
  quorum 'from aircraft', :needs => :aircraft, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
324
354
  characteristics[:aircraft].seats
325
355
  end
326
356
 
327
- ##### From seats estimate
328
- # This second-tier method uses the input estimate of the number of `seats`.
329
- quorum 'from seats estimate', :needs => :seats_estimate, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
330
- characteristics[:seats_estimate]
357
+ #### Seats from aircraft class
358
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
359
+ #
360
+ # Looks up the [aircraft class](http://data.brighterplanet.com/aircraft_classes)' average number of `seats`.
361
+ quorum 'from aircraft class', :needs => :aircraft_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
362
+ characteristics[:aircraft_class].seats
331
363
  end
332
364
 
333
- ##### From cohort
334
- # This third-tier method calculates the average number of `seats` of the `cohort` segments, weighted by their passengers.
365
+ #### Seats from cohort
366
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
367
+ #
368
+ # Calculates the average number of `seats` of the `cohort` segments, weighted by their passengers.
335
369
  quorum 'from cohort', :needs => :cohort, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
336
370
  seats = characteristics[:cohort].weighted_average :seats, :weighted_by => :passengers
337
371
  if seats.nil? or seats.zero?
@@ -341,121 +375,156 @@ module BrighterPlanet
341
375
  end
342
376
  end
343
377
 
344
- ##### From aircraft class
345
- # This fourth-tier method looks up the [aircraft class](http://data.brighterplanet.com/aircraft_classes)'s average number of `seats`.
346
- quorum 'from aircraft class', :needs => :aircraft_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
347
- characteristics[:aircraft_class].seats
348
- end
349
-
350
- ##### Default
351
- # This default method calculates the average number of `seats` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers.
352
- quorum 'default' do
353
- FlightSegment.fallback.seats.to_f # need before_type_cast b/c seats is an integer but the fallback value is a float
378
+ #### Default seats
379
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
380
+ #
381
+ # Calculates the average number of `seats` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers.
382
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
383
+ FlightSegment.fallback.seats
354
384
  end
355
385
  end
356
386
 
357
- ### Seats estimate
358
- # This implied calculation returns the client-input `seats estimate`.
387
+ ### Seats estimate calculation
388
+ # Returns the client-input `seats estimate`.
359
389
 
360
- ### Load factor
361
- # This calculation returns the `load factor`.
362
- # The `load factor` is the portion of available seats that are occupied.
390
+ ### Load factor calculation
391
+ # Returns the `load factor`.
392
+ # This is the portion of available seats that are occupied.
363
393
  committee :load_factor do
364
- ##### From client input
365
- # This implied first-tier method uses the client-input `load factor`.
394
+ #### Load factor from client input
395
+ # **Complies:** All
396
+ #
397
+ # Uses the client-input `load factor`.
366
398
 
367
- ##### From cohort
368
- # This second-tier method calculates the average `load factor` of the `cohort` segments, weighted by their passengers.
399
+ #### Load factor from cohort
400
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
401
+ #
402
+ # Calculates the average `load factor` of the `cohort` segments, weighted by their passengers.
369
403
  quorum 'from cohort', :needs => :cohort, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
370
404
  characteristics[:cohort].weighted_average(:load_factor, :weighted_by => :passengers)
371
405
  end
372
406
 
373
- ##### Default
374
- # This default method calculates the average `load factor` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers.
407
+ #### Default load factor
408
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
409
+ #
410
+ # Calculates the average `load factor` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers.
375
411
  quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
376
412
  FlightSegment.fallback.load_factor
377
413
  end
378
414
  end
379
415
 
380
- ### Freight share
381
- # This calculation returns the `freight share`.
382
- # The `freight share` is the percent of the total aircraft weight that is freight cargo and mail (as opposed to passengers and their baggage).
416
+ ### Freight share calculation
417
+ # Returns the `freight share`.
418
+ # This is the percent of the total aircraft weight that is freight cargo and mail (as opposed to passengers and their baggage).
383
419
  committee :freight_share do
384
- ##### From cohort
385
- # This first-tier method calculates the average `freight share` of the `cohort` segments, weighted by their passengers
420
+ #### Freight share from cohort
421
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
422
+ #
423
+ # Calculates the average `freight share` of the `cohort` segments, weighted by their passengers.
386
424
  quorum 'from cohort', :needs => :cohort, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
387
425
  characteristics[:cohort].weighted_average(:freight_share, :weighted_by => :passengers)
388
426
  end
389
427
 
390
- ##### Default
391
- # This default method calculates the average `freight share` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers.
428
+ #### Default freight share
429
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
430
+ #
431
+ # Calculates the average `freight share` of [all segments in the T-100 database](http://data.brighterplanet.com/flight_segments), weighted by their passengers.
392
432
  quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
393
433
  FlightSegment.fallback.freight_share
394
434
  end
395
435
  end
396
436
 
397
- ### Trips
398
- # This calculation returns the number of `trips`.
437
+ ### Trips calculation
438
+ # Returns the number of `trips`.
399
439
  # A one-way flight has one trip; a round-trip flight has two trips.
400
440
  committee :trips do
401
- ##### From client input
402
- # This implied first-tier method uses the client-input number of `trips`.
441
+ #### Trips from client input
442
+ # **Complies:** All
443
+ #
444
+ # Uses the client-input number of `trips`.
403
445
 
404
- ##### Default
405
- # This default method calculates the average number of `trips` from the [U.S. National Household Travel Survey](http://www.bts.gov/publications/america_on_the_go/long_distance_transportation_patterns/html/table_07.html).
446
+ #### Default trips
447
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
448
+ #
449
+ # Uses an average number of `trips` of **1.941**, taken from the [U.S. National Household Travel Survey](http://www.bts.gov/publications/america_on_the_go/long_distance_transportation_patterns/html/table_07.html).
406
450
  quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
407
- base.fallback.trips_before_type_cast # need before_type_cast b/c trips is an integer but fallback value is a float
451
+ 1.941
408
452
  end
409
453
  end
410
454
 
411
- ### Seat class multiplier
412
- # This calculation returns the `seat class multiplier`, which reflects the amount of cabin space occupied by the passenger's seat.
455
+ ### Seat class multiplier calculation
456
+ # Returns the `seat class multiplier`. This reflects the amount of cabin space occupied by the passenger's seat.
413
457
  committee :seat_class_multiplier do
414
- ##### From seat class
415
- # This first-tier method looks up the [seat class](http://data.brighterplanet.com/flight_seat_classes)'s `seat class multiplier`.
458
+ #### Seat class multiplier from seat class
459
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
460
+ #
461
+ # Looks up the [seat class](http://data.brighterplanet.com/flight_seat_classes)' `seat class multiplier`.
416
462
  quorum 'from seat class', :needs => :seat_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
417
463
  characteristics[:seat_class].multiplier
418
464
  end
419
465
 
420
- ##### Default
421
- # This default method uses a `seat class multiplier` of **1**.
466
+ #### Default seat class multiplier
467
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
468
+ #
469
+ # Uses a `seat class multiplier` of **1**.
422
470
  quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
423
471
  FlightSeatClass.fallback.multiplier
424
472
  end
425
473
  end
426
474
 
427
- ### Seat class
428
- # This implied calculation returns the client-input [seat class](http://data.brighterplanet.com/seat_classes).
475
+ ### Seat class calculation
476
+ # Returns the client-input [seat class](http://data.brighterplanet.com/seat_classes).
429
477
 
430
- ### Country
431
- # This calculation returns the [country](http://data.brighterplanet.com/countries) in which a flight occurs.
478
+ ### Country calculation
479
+ # Returns the [country](http://data.brighterplanet.com/countries) in which a flight occurs.
432
480
  committee :country do
433
- ##### From origin airport and destination airport
434
- # This method checks that the flight's `origin airport` and `destination airport` are within the same country.
481
+ #### Country from client input
482
+ # **Complies:** All
483
+ #
484
+ # Uses the client-input [country](http://data.brighterplanet.com/countries).
485
+
486
+ #### Country from origin airport and destination airport
487
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
488
+ #
489
+ # Checks whether the flight's `origin airport` and `destination airport` are within the same country.
435
490
  # If so, that country is the `country`.
436
491
  quorum 'from origin airport and destination airport', :needs => [:origin_airport, :destination_airport], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
437
492
  if characteristics[:origin_airport].country == characteristics[:destination_airport].country
438
493
  characteristics[:origin_airport].country
439
494
  end
440
495
  end
496
+ end
497
+
498
+ ### Aircraft Class calculation
499
+ # This calculation returns the [aircraft class](http://data.brighterplanet.com/aircraft_classes).
500
+ committee :aircraft_class do
501
+ #### Aircraft class from client input
502
+ # **Complies:** All
503
+ #
504
+ # Uses the client-input [aircraft_class](http://data.brighterplanet.com/aircraft_classes).
441
505
 
442
- ##### From client input
443
- # This implied method uses the client-input [country](http://data.brighterplanet.com/countries).
506
+ #### Aircraft class from aircraft
507
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
508
+ #
509
+ # Looks up the [aircraft](http://data.brighterplanet.com/aircraft)'s [aircraft_class](http://data.brighterplanet.com/aircraft_classes).
510
+ quorum 'from aircraft', :needs => :aircraft, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
511
+ characteristics[:aircraft].aircraft_class
512
+ end
444
513
  end
445
514
 
446
- ### Cohort
447
- # This calculation returns the `cohort`, which is a set of flight segment records in the [T-100 database](http://data.brighterplanet.com/flight_segments) that match certain client-input values.
515
+ ### Cohort calculation
516
+ # Returns the `cohort`.
517
+ # This is a set of flight segment records in the [T-100 database](http://data.brighterplanet.com/flight_segments) that match certain client-input values.
448
518
  committee :cohort do
449
- ##### From segments per trip and input
450
- # This method:
451
- #
452
- # 1. Checks that the flight is direct
453
- # 2. Takes the input values for `origin airport`, `destination airport`, `aircraft`, and `airline`
454
- # 3. Selects all the records in the T-100 database that match the available input values
455
- # 4. Drops the last input value (initially `airline`, then `aircraft`, etc.) if no records match all of the available input values
456
- # 5. Repeats steps 3 and 4 until some records match or no input values remain
519
+ #### Cohort from segments per trip and input
520
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
457
521
  #
458
- # If no records match any of the input values, or if the flight is indirect, then `cohort` is undefined.
522
+ # - Checks whether the flight is direct
523
+ # - Takes the input values for `origin airport`, `destination airport`, `aircraft`, and `airline`
524
+ # - Selects all the records in the T-100 database that match the available input values
525
+ # - Drops the last input value (initially `airline`, then `aircraft`, etc.) if no records match all of the available input values
526
+ # - Repeats steps 3 and 4 until some records match or no input values remain
527
+ # - If no records match any of the input values, or if the flight is indirect, then `cohort` is undefined.
459
528
  quorum 'from segments per trip and input', :needs => :segments_per_trip, :appreciates => [:origin_airport, :destination_airport, :aircraft, :airline], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
460
529
  cohort = {}
461
530
  if characteristics[:segments_per_trip] == 1
@@ -474,53 +543,76 @@ module BrighterPlanet
474
543
  end
475
544
  end
476
545
 
477
- ### Origin airport
478
- # This implied calculation returns the client-input [origin airport](http://data.brighterplanet.com/airports).
546
+ ### Origin airport calculation
547
+ # Returns the client-input [origin airport](http://data.brighterplanet.com/airports).
479
548
 
480
- ### Destination airport
481
- # This implied calculation returns the client-input [destination airport](http://data.brighterplanet.com/airports).
549
+ ### Destination airport calculation
550
+ # Returns the client-input [destination airport](http://data.brighterplanet.com/airports).
482
551
 
483
- ### Aircraft
484
- # This implied calculation returns the client-input type of [aircraft](http://data.brighterplanet.com/aircraft).
552
+ ### Aircraft calculation
553
+ # Returns the client-input type of [aircraft](http://data.brighterplanet.com/aircraft).
485
554
 
486
- ### Aircraft class
487
- # This implied calculation returns the client-input [aircraft_class](http://data.brighterplanet.com/aircraft_classes).
555
+ ### Airline calculation
556
+ # Returns the client-input [airline](http://data.brighterplanet.com/airlines) operating the flight.
488
557
 
489
- ### Airline
490
- # This implied calculation returns the client-input [airline](http://data.brighterplanet.com/airlines) operating the flight.
491
-
492
- ### Segments per trip
493
- # This calculation returns the `segments per trip`.
558
+ ### Segments per trip calculation
559
+ # Returns the `segments per trip`.
494
560
  # Direct flights have a single segment per trip. Indirect flights with one or more layovers have two or more segments per trip.
495
561
  committee :segments_per_trip do
496
- ##### From client input
497
- # This implied first-tier method uses the client-input `segments per trip`.
562
+ #### Segments per trip from client input
563
+ # **Complies:** All
564
+ #
565
+ # Uses the client-input `segments per trip`.
498
566
 
499
- ##### Default
500
- # This default method calculates the average `segments per trip` from the [U.S. National Household Travel Survey](http://nhts.ornl.gov/).
567
+ #### Default segments per trip
568
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
569
+ #
570
+ # Uses an average `segments per trip` of **1.67**, calculated from the [U.S. National Household Travel Survey](http://nhts.ornl.gov/).
501
571
  quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
502
- base.fallback.segments_per_trip_before_type_cast # need before_type_cast b/c segments_per_trip is an integer but fallback value is a float
572
+ 1.67
503
573
  end
504
574
  end
505
575
 
506
- ### Date
507
- # This calculation returns the `date` on which the flight occured.
576
+ ### Date calculation
577
+ # Returns the `date` on which the flight occured.
508
578
  committee :date do
509
- ##### From client input
510
- # This implied first-tier method uses the client-input value for `date`.
579
+ #### Date from client input
580
+ # **Complies:** All
581
+ #
582
+ # Uses the client-input value for `date`.
511
583
 
512
- ##### From timeframe
513
- # This second-tier method assumes the flight occured on the first day of the `timeframe`.
584
+ #### Date from timeframe
585
+ # **Complies:** GHG Protocol, ISO-14064-1, Climate Registry Protocol
586
+ #
587
+ # Assumes the flight occured on the first day of the `timeframe`.
514
588
  quorum 'from timeframe', :complies => [:ghg_protocol, :iso, :tcr] do |characteristics, timeframe|
515
589
  timeframe.from
516
590
  end
517
591
  end
592
+
593
+ ### Timeframe calculation
594
+ # Returns the `timeframe`.
595
+ # This is the period over which to calculate emissions.
596
+
597
+ #### Timeframe from client input
598
+ # **Complies:** All
599
+ #
600
+ # Uses the client-input value for `timeframe`.
601
+
602
+ #### Default timeframe
603
+ # **Complies:** All
604
+ #
605
+ # Uses the current calendar year.
518
606
  end
519
607
  end
520
608
 
521
609
  class FuelUseEquation < Struct.new(:m3, :m2, :m1, :endpoint_fuel)
522
610
  def empty?
523
- m3.nil? and m2.nil? and m1.nil? and endpoint_fuel.nil?
611
+ values.all?(&:nil?) or values.all?(&:zero?)
612
+ end
613
+
614
+ def values
615
+ [m3, m2, m1, endpoint_fuel]
524
616
  end
525
617
 
526
618
  def to_xml(options = {})