flight 0.0.23 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,107 @@
1
1
  Feature: Flight Emissions Calculations
2
2
  The flight model should generate correct emission calculations
3
3
 
4
- Scenario Outline: Standard Calculations for origin/destination airport, airline, and aircraft
5
- Given a flight has "origin_airport.iata_code" of "<source>"
6
- And it has "destination_airport.iata_code" of "<dest>"
7
- And it has "airline.iata_code" of "<airline>"
8
- And it has "date" of "<date>"
9
- And it has "aircraft.bts_aircraft_type_code" of "<aircraft>"
4
+ Scenario: Calculations from default
5
+ Given a flight has nothing
6
+ When emissions are calculated
7
+ Then the emission value should be within "0.1" kgs of "28.3"
8
+
9
+ Scenario Outline: Calculations from date
10
+ Given a flight has "date" of "<date>"
10
11
  When emissions are calculated
11
12
  Then the emission value should be within "0.1" kgs of "<emission>"
12
13
  Examples:
13
- | source | dest | airline | date | aircraft | emission |
14
- | DCA | JFK | AA | 2010-06-25 | 1 | 148.4 |
14
+ | date | emission |
15
+ | 2009-06-25 | 0.0 |
16
+ | 2010-06-25 | 28.3 |
17
+ | 2011-06-25 | 0.0 |
18
+
19
+ Scenario Outline: Calculations from date and timeframe
20
+ Given a flight has "date" of "<date>"
21
+ And it has "timeframe" of "<timeframe>"
22
+ When emissions are calculated
23
+ Then the emission value should be within "0.1" kgs of "<emission>"
24
+ Examples:
25
+ | date | timeframe | emission |
26
+ | 2009-06-25 | 2009-01-01/2009-01-31 | 0 |
27
+ | 2009-06-25 | 2009-01-01/2009-12-31 | 28.3 |
28
+ | 2009-06-25 | 2009-12-01/2009-12-31 | 0 |
29
+
30
+ Scenario: Calculations from cohort based on origin
31
+ Given a flight has "segments_per_trip" of "1"
32
+ And it has "origin_airport.iata_code" of "ADA"
33
+ When emissions are calculated
34
+ Then the emission value should be within "0.1" kgs of "4.2"
35
+
36
+ Scenario: Calculations from cohort based on destination
37
+ Given a flight has "segments_per_trip" of "1"
38
+ And it has "destination_airport.iata_code" of "AIA"
39
+ When emissions are calculated
40
+ Then the emission value should be within "0.1" kgs of "24.4"
41
+
42
+ Scenario: Calculations from cohort based on aircraft
43
+ Given a flight has "segments_per_trip" of "1"
44
+ And it has "aircraft.icao_code" of "FM1"
45
+ When emissions are calculated
46
+ Then the emission value should be within "0.1" kgs of "4.2"
47
+
48
+ Scenario: Calculations from cohort based on airline
49
+ Given a flight has "segments_per_trip" of "1"
50
+ And it has "airline.iata_code" of "DA"
51
+ When emissions are calculated
52
+ Then the emission value should be within "0.1" kgs of "4.2"
53
+
54
+ Scenario: Calculations from segments per trip
55
+ Given a flight has "segments_per_trip" of "2"
56
+ When emissions are calculated
57
+ Then the emission value should be within "0.1" kgs of "30.5"
58
+
59
+ Scenario: Calculations from seat class
60
+ Given a flight has "seat_class.name" of "economy"
61
+ When emissions are calculated
62
+ Then the emission value should be within "0.1" kgs of "25.5"
63
+
64
+ Scenario: Calculations from trips
65
+ Given a flight has "trips" of "2"
66
+ When emissions are calculated
67
+ Then the emission value should be within "0.1" kgs of "29.2"
68
+
69
+ Scenario: Calculations from load factor
70
+ Given a flight has "load_factor" of "1"
71
+ When emissions are calculated
72
+ Then the emission value should be within "0.1" kgs of "23.2"
73
+
74
+ Scenario: Calculations from seats estimate
75
+ Given a flight has "seats_estimate" of "100"
76
+ When emissions are calculated
77
+ Then the emission value should be within "0.1" kgs of "34.6"
78
+
79
+ Scenario: Calculations from aircraft class
80
+ Given a flight has "aircraft_class.brighter_planet_aircraft_class_code" of "BX"
81
+ When emissions are calculated
82
+ Then the emission value should be within "0.1" kgs of "41.5"
83
+
84
+ Scenario: Calculations from country
85
+ Given a flight has "country.iso_3166_code" of "UK"
86
+ When emissions are calculated
87
+ Then the emission value should be within "0.1" kgs of "30.9"
88
+
89
+ Scenario: Calculations from distance estimate
90
+ Given a flight has "distance_estimate" of "185.2"
91
+ When emissions are calculated
92
+ Then the emission value should be within "0.1" kgs of "7.7"
93
+
94
+ Scenario: Calculations from distance class
95
+ Given a flight has "distance_class.name" of "petite"
96
+ When emissions are calculated
97
+ Then the emission value should be within "0.1" kgs of "7.7"
98
+
99
+ Scenario: Calculations from aviation multiplier
100
+ Given a flight has "aviation_multiplier" of "1"
101
+ When emissions are calculated
102
+ Then the emission value should be within "0.1" kgs of "14.2"
103
+
104
+ Scenario: Calculations from fuel type
105
+ Given a flight has "fuel_type.name" of "Aviation Gasoline"
106
+ When emissions are calculated
107
+ Then the emission value should be within "0.1" kgs of "56.7"
@@ -5,4 +5,4 @@ require 'cucumber'
5
5
  require 'cucumber/formatter/unicode' # Remove this line if you don't want Cucumber Unicode support
6
6
 
7
7
  require 'sniff'
8
- Sniff.init File.join(File.dirname(__FILE__), '..', '..'), :earth => :air, :cucumber => true
8
+ Sniff.init File.join(File.dirname(__FILE__), '..', '..'), :earth => [:air, :locality, :fuel], :cucumber => true
@@ -3,15 +3,5 @@ require 'emitter'
3
3
  module BrighterPlanet
4
4
  module Flight
5
5
  extend BrighterPlanet::Emitter
6
-
7
- def self.flight_model
8
- if Object.const_defined? 'Flight'
9
- ::Flight
10
- elsif Object.const_defined? 'FlightRecord'
11
- FlightRecord
12
- else
13
- raise 'There is no flight model'
14
- end
15
- end
16
6
  end
17
7
  end
@@ -1,22 +1,41 @@
1
- require 'timeframe'
1
+ # Copyright © 2010 Brighter Planet.
2
+ # See LICENSE for details.
3
+ # Contact Brighter Planet for dual-license arrangements.
4
+
2
5
  require 'weighted_average'
3
6
 
7
+ ## Flight:carbon model
8
+ # 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**.
9
+ #
10
+ # The final estimate is the result of the **calculations** detailed below.
11
+ # These calculations are performed in reverse order, starting with the last calculation listed and finishing with the `emission` calculation.
12
+ #
13
+ # 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.
4
14
  module BrighterPlanet
5
15
  module Flight
6
16
  module CarbonModel
7
17
  def self.included(base)
8
18
  base.extend FastTimestamp
9
19
  base.decide :emission, :with => :characteristics do
20
+ ### Emission
21
+ # This calculation returns the `emission` estimate in *kg CO<sub>2</sub>e*.
22
+ # The `emission` estimate is the passenger's share of the total flight emissions that occured during the `timeframe`.
10
23
  committee :emission do
11
- quorum 'from fuel and passengers with coefficients',
12
- :needs => [:fuel, :passengers, :seat_class_multiplier, :emission_factor,
13
- :radiative_forcing_index, :freight_share, :date] do |characteristics, timeframe|
24
+ ##### From fuel, emission factor, freight share, passengers, and multipliers
25
+ # This method:
26
+ #
27
+ # 1. Checks that the flight occured during the `timeframe`
28
+ # 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*
29
+ # 3. Multiplies by (1 - `freight share`) to take out emissions attributed to freight cargo and mail, leaving emissions attributed to passengers and their baggage
30
+ # 4. Divides by the number of `passengers` and multiplies by a `seat class multiplier` to give `emission` for the passenger
31
+ quorum 'from fuel, emission factor, freight share, passengers, multipliers, and date',
32
+ :needs => [:fuel, :emission_factor, :freight_share, :passengers, :seat_class_multiplier, :aviation_multiplier, :date], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics, timeframe|
14
33
  date = characteristics[:date].is_a?(Date) ?
15
34
  characteristics[:date] :
16
35
  Date.parse(characteristics[:date].to_s)
17
36
  if timeframe.include? date
18
- #( kg fuel ) * ( kg CO2 / kg fuel ) = kg CO2
19
- (characteristics[:fuel] / characteristics[:passengers] * characteristics[:seat_class_multiplier]) * characteristics[:emission_factor] * characteristics[:radiative_forcing_index] * (1 - characteristics[:freight_share])
37
+ characteristics[:fuel] * characteristics[:emission_factor] * characteristics[:aviation_multiplier] * (1 - characteristics[:freight_share]) / characteristics[:passengers] * characteristics[:seat_class_multiplier]
38
+ # If the flight did not occur during the `timeframe`, `emission` is zero.
20
39
  else
21
40
  0
22
41
  end
@@ -27,14 +46,46 @@ module BrighterPlanet
27
46
  end
28
47
  end
29
48
 
30
- committee :fuel do # returns kg fuel
31
- quorum 'from fuel per segment and emplanements and trips', :needs => [:fuel_per_segment, :emplanements_per_trip, :trips] do |characteristics|
32
- characteristics[:fuel_per_segment] * characteristics[:emplanements_per_trip].to_f * characteristics[:trips].to_f
49
+ ### Emission factor
50
+ # This calculation returns the `emission factor` in *kg CO<sub>2</sub>e / kg fuel*.
51
+ committee :emission_factor do
52
+ ##### From fuel type
53
+ # 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*.
54
+ quorum 'from fuel type', :needs => :fuel_type, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
55
+ characteristics[:fuel_type].emission_factor / characteristics[:fuel_type].density
33
56
  end
34
57
  end
35
58
 
36
- committee :fuel_per_segment do # returns kg fuel
37
- quorum 'from adjusted distance per segment and fuel use coefficients', :needs => [:adjusted_distance_per_segment, :fuel_use_coefficients] do |characteristics|
59
+ ### Aviation multiplier
60
+ # This calculation returns the `aviation multiplier`, which approximates the extra climate impact of emissions high in the atmosphere.
61
+ committee :aviation_multiplier do
62
+ ##### Default
63
+ # This method uses an `aviation multiplier` of **2.0** after [Kolmuss and Crimmins (2009)](http://sei-us.org/publications/id/13).
64
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
65
+ base.fallback.aviation_multiplier
66
+ end
67
+ end
68
+
69
+ ### Fuel
70
+ # This calculation returns the flight's total `fuel` use in *kg fuel*.
71
+ committee :fuel do
72
+ ##### From fuel per segment and segments per trip and trips
73
+ # This method multiplies the `fuel per segment` (*kg fuel*) by the `segments per trip` and the number of `trips` to give *kg fuel*.
74
+ 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|
75
+ characteristics[:fuel_per_segment] * characteristics[:segments_per_trip].to_f * characteristics[:trips].to_f
76
+ end
77
+ end
78
+
79
+ ### Fuel per segment
80
+ # This calculation returns the `fuel per segment` in *kg fuel*.
81
+ committee :fuel_per_segment do
82
+ ##### From adjusted distance per segment and fuel use coefficients
83
+ # This method uses a third-order polynomial equation to calculate the fuel used per segment:
84
+ #
85
+ # (m<sub>3</sub> * d^3 ) + (m<sub>2</sub> * d^2 ) + (m<sub>1</sub> * d) + endpoint fuel
86
+ #
87
+ # Where d is the `adjusted distance per segment` and m<sub>3</sub>, m<sub>2</sub>, m<sub>2</sub>, and endpoint fuel are the `fuel use coefficients`.
88
+ quorum 'from adjusted distance per segment and fuel use coefficients', :needs => [:adjusted_distance_per_segment, :fuel_use_coefficients], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
38
89
  characteristics[:fuel_use_coefficients].m3.to_f * characteristics[:adjusted_distance_per_segment].to_f ** 3 +
39
90
  characteristics[:fuel_use_coefficients].m2.to_f * characteristics[:adjusted_distance_per_segment].to_f ** 2 +
40
91
  characteristics[:fuel_use_coefficients].m1.to_f * characteristics[:adjusted_distance_per_segment].to_f +
@@ -42,30 +93,131 @@ module BrighterPlanet
42
93
  end
43
94
  end
44
95
 
96
+ ### Adjusted distance per segment
97
+ # This calculation returns the `adjusted distance per segment` in *nautical miles*.
45
98
  committee :adjusted_distance_per_segment do
46
- quorum 'from adjusted distance and emplanements', :needs => [:adjusted_distance, :emplanements_per_trip] do |characteristics|
47
- characteristics[:adjusted_distance] / characteristics[:emplanements_per_trip]
99
+ ##### From adjusted distance and segments per trip
100
+ # This method divides the `adjusted distance` (*nautical miles*) by `segments per trip` to give *nautical miles*.
101
+ quorum 'from adjusted distance and segments per trip', :needs => [:adjusted_distance, :segments_per_trip], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
102
+ characteristics[:adjusted_distance] / characteristics[:segments_per_trip]
48
103
  end
49
104
  end
50
105
 
106
+ ### Adjusted distance
107
+ # This calculation returns the `adjusted distance` in *nautical miles*.
108
+ # The `adjusted distance` accounts for factors that increase the actual distance traveled by real world flights.
109
+ committee :adjusted_distance do
110
+ ##### From distance, route inefficiency factor, and dogleg factor
111
+ # This method multiplies `distance` (*nautical miles*) by a `route inefficiency factor` and a `dogleg factor` to give *nautical miles*.
112
+ quorum 'from distance, route inefficiency factor, and dogleg factor', :needs => [:distance, :route_inefficiency_factor, :dogleg_factor], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
113
+ characteristics[:distance] * characteristics[:route_inefficiency_factor] * characteristics[:dogleg_factor]
114
+ end
115
+ end
116
+
117
+ ### Distance
118
+ # This calculation returns the flight's base `distance` in *nautical miles*.
119
+ committee :distance do
120
+ ##### From airports
121
+ # This first-tier method calculates the great circle distance between the `origin airport` and `destination airport` and converts from *km* to *nautical miles*.
122
+ quorum 'from airports', :needs => [:origin_airport, :destination_airport], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
123
+ if characteristics[:origin_airport].latitude and
124
+ characteristics[:origin_airport].longitude and
125
+ characteristics[:destination_airport].latitude and
126
+ characteristics[:destination_airport].longitude
127
+ characteristics[:origin_airport].distance_to(characteristics[:destination_airport], :units => :kms).kilometres.to :nautical_miles
128
+ end
129
+ end
130
+
131
+ ##### From distance estimate
132
+ # This second-tier method converts the `distance_estimate` in *km* to *nautical miles*.
133
+ quorum 'from distance estimate', :needs => :distance_estimate, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
134
+ characteristics[:distance_estimate].kilometres.to :nautical_miles
135
+ end
136
+
137
+ ##### From distance class
138
+ # 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*.
139
+ quorum 'from distance class', :needs => :distance_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
140
+ characteristics[:distance_class].distance.kilometres.to :nautical_miles
141
+ end
142
+
143
+ ##### From cohort
144
+ # This fourth-tier method calculates the average `distance` of the `cohort` segments, weighted by their passengers, and converts from *km* to *nautical miles*.
145
+ quorum 'from cohort', :needs => :cohort do |characteristics| # cohort here will be some combo of origin, airline, and aircraft
146
+ distance = characteristics[:cohort].weighted_average(:distance, :weighted_by => :passengers).kilometres.to(:nautical_miles)
147
+ distance > 0 ? distance : nil
148
+ end
149
+
150
+ ##### Default
151
+ # 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*.
152
+ quorum 'default' do
153
+ FlightSegment.fallback.distance.kilometres.to :nautical_miles
154
+ end
155
+ end
156
+
157
+ ### Route inefficiency factor
158
+ # 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.
159
+ # It accounts for factors like flight path routing around controlled airspace and circling while waiting for clearance to land.
160
+ committee :route_inefficiency_factor do
161
+ ##### From country
162
+ # This first-tier method looks up the `route inefficiency factor` for the [country](http://data.brighterplanet.com/countries) in which the flight occurs.
163
+ quorum 'from country', :needs => :country, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
164
+ characteristics[:country].andand.flight_route_inefficiency_factor
165
+ end
166
+
167
+ ##### Default
168
+ # 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)
169
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
170
+ Country.fallback.flight_route_inefficiency_factor
171
+ end
172
+ end
173
+
174
+ ### Dogleg factor
175
+ # This calculation returns the `dogleg factor`, a measure of how far out of the way the average layover is compared to a direct flight.
176
+ committee :dogleg_factor do
177
+ ##### From segments per trip
178
+ # This method assumes that each layover increases the total flight distance by **25%**.
179
+ quorum 'from segments per trip', :needs => :segments_per_trip, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
180
+ base.fallback.dogleg_factor ** (characteristics[:segments_per_trip] - 1)
181
+ end
182
+ end
183
+
184
+ ### Distance estimate
185
+ # This implied calculation returns the client-input 'distance estimate' in *km*.
186
+
187
+ ### Distance class
188
+ # This implied calculation returns the client-input [distance class](http://data.brighterplanet.com/distance_classes).
189
+
190
+ ### Fuel use coefficients
191
+ # This calculation returns the `fuel use coefficients`, the coefficients of the third-order polynomial equation that describes aircraft fuel use.
51
192
  committee :fuel_use_coefficients do
52
- quorum 'from aircraft', :needs => :aircraft do |characteristics|
193
+ ##### From aircraft
194
+ # This first-tier method looks up the [aircraft](http://data.brighterplanet.com/aircraft)'s `fuel use coefficients`.
195
+ quorum 'from aircraft', :needs => :aircraft, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
53
196
  aircraft = characteristics[:aircraft]
54
- FuelUseEquation.new aircraft.m3, aircraft.m2, aircraft.m1, aircraft.endpoint_fuel
197
+ fuel_use = FuelUseEquation.new aircraft.m3, aircraft.m2, aircraft.m1, aircraft.endpoint_fuel
198
+ if fuel_use.empty?
199
+ nil
200
+ else
201
+ fuel_use
202
+ end
55
203
  end
56
204
 
57
- quorum 'from aircraft class', :needs => :aircraft_class do |characteristics|
205
+ ##### From aircraft class
206
+ # This second-tier method looks up the [aircraft class](http://data.brighterplanet.com/aircraft_classes)'s `fuel use coefficients`.
207
+ quorum 'from aircraft class', :needs => :aircraft_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
58
208
  aircraft_class = characteristics[:aircraft_class]
59
209
  FuelUseEquation.new aircraft_class.m3, aircraft_class.m2, aircraft_class.m1, aircraft_class.endpoint_fuel
60
210
  end
61
211
 
62
- quorum 'from cohort', :needs => :cohort do |characteristics|
212
+ ##### From cohort
213
+ # This third-tier method calculates the average `fuel use coefficients` of the `cohort` segments, weighted by their passengers.
214
+ quorum 'from cohort', :needs => :cohort, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
63
215
  flight_segments = characteristics[:cohort]
64
216
 
65
217
  passengers = flight_segments.inject(0) do |passengers, flight_segment|
66
218
  passengers + flight_segment.passengers
67
219
  end
68
-
220
+
69
221
  flight_segment_aircraft = flight_segments.inject({}) do |hsh, flight_segment|
70
222
  bts_code = flight_segment.bts_aircraft_type_code
71
223
  key = flight_segment.row_hash
@@ -93,7 +245,7 @@ module BrighterPlanet
93
245
  else
94
246
  m2 = Aircraft.fallback.m2
95
247
  end
96
-
248
+
97
249
  if flight_segment_aircraft.values.map(&:m1).any?
98
250
  m1 = flight_segments.inject(0) do |m1, flight_segment|
99
251
  aircraft = flight_segment_aircraft[flight_segment.row_hash]
@@ -103,7 +255,7 @@ module BrighterPlanet
103
255
  else
104
256
  m1 = Aircraft.fallback.m1
105
257
  end
106
-
258
+
107
259
  if flight_segment_aircraft.values.map(&:endpoint_fuel).any?
108
260
  endpoint_fuel = flight_segments.inject(0) do |endpoint_fuel, flight_segment|
109
261
  aircraft = flight_segment_aircraft[flight_segment.row_hash]
@@ -113,18 +265,20 @@ module BrighterPlanet
113
265
  else
114
266
  endpoint_fuel = Aircraft.fallback.endpoint_fuel
115
267
  end
116
-
268
+
117
269
  if [m3, m2, m1, endpoint_fuel, passengers].any?(&:nonzero?)
118
270
  m3 = m3 / passengers
119
271
  m2 = m2 / passengers
120
272
  m1 = m1 / passengers
121
273
  endpoint_fuel = endpoint_fuel / passengers
122
-
274
+
123
275
  FuelUseEquation.new m3, m2, m1, endpoint_fuel
124
276
  end
125
277
  end
126
278
 
127
- quorum 'default' do
279
+ ##### Default
280
+ # 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.
281
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
128
282
  fallback = Aircraft.fallback
129
283
  if fallback
130
284
  FuelUseEquation.new fallback.m3, fallback.m2, fallback.m1, fallback.endpoint_fuel
@@ -132,29 +286,47 @@ module BrighterPlanet
132
286
  end
133
287
  end
134
288
 
289
+ ### Fuel type
290
+ # This calculation returns the `fuel type`.
291
+ committee :fuel_type do
292
+ ##### From client input
293
+ # This implied first-tier method uses the client-input [fuel type](http://data.brighterplanet.com/fuel_types).
294
+
295
+ ##### Default
296
+ # This method assumes the flight uses **Jet Fuel**.
297
+ quorum 'default' do
298
+ FuelType.find_by_name 'Jet Fuel'
299
+ end
300
+ end
301
+
302
+ ### Passengers
303
+ # This calculation returns the number of `passengers`.
135
304
  committee :passengers do
136
- quorum 'from seats and load factor', :needs => [:seats, :load_factor] do |characteristics|
305
+ ##### From seats and load factor
306
+ # This method multiplies the number of `seats` by the `load factor`.
307
+ quorum 'from seats and load factor', :needs => [:seats, :load_factor], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
137
308
  (characteristics[:seats] * characteristics[:load_factor]).round
138
309
  end
139
310
  end
140
311
 
312
+ ### Seats
313
+ # This calculation returns the number of `seats`.
141
314
  committee :seats do
142
- # leaving this here to explain how someday we might lookup seat count based on both airline AND aircraft
143
- #SE quorum 'from_airline_and_aircraft', :needs => [:airline, :aircraft] do |characteristics, timeframe|
144
- #SE if aircraft = AirlineAircraft.memoized_find_by_airline_id_and_aircraft_id(characteristics[:airline].id, characteristics[:aircraft].id)
145
- #SE aircraft.seats
146
- #SE end
147
- #SE end
148
-
149
- quorum 'from aircraft', :needs => :aircraft do |characteristics|
315
+ ##### From aircraft
316
+ # This first-tier method looks up the [aircraft](http://data.brighterplanet.com/aircraft)'s average number of `seats`.
317
+ quorum 'from aircraft', :needs => :aircraft, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
150
318
  characteristics[:aircraft].seats
151
319
  end
152
320
 
153
- quorum 'from seats estimate', :needs => :seats_estimate do |characteristics|
321
+ ##### From seats estimate
322
+ # This second-tier method uses the input estimate of the number of `seats`.
323
+ quorum 'from seats estimate', :needs => :seats_estimate, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
154
324
  characteristics[:seats_estimate]
155
325
  end
156
326
 
157
- quorum 'from cohort', :needs => :cohort do |characteristics|
327
+ ##### From cohort
328
+ # This third-tier method calculates the average number of `seats` of the `cohort` segments, weighted by their passengers.
329
+ quorum 'from cohort', :needs => :cohort, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
158
330
  seats = characteristics[:cohort].weighted_average :seats, :weighted_by => :passengers
159
331
  if seats.nil? or seats.zero?
160
332
  nil
@@ -163,160 +335,131 @@ module BrighterPlanet
163
335
  end
164
336
  end
165
337
 
166
- quorum 'from aircraft class', :needs => :aircraft_class do |characteristics|
167
- characteristics[:aircraft_class].seats
338
+ ##### From aircraft class
339
+ # This fourth-tier method looks up the [aircraft class](http://data.brighterplanet.com/aircraft_classes)'s average number of `seats`.
340
+ quorum 'from aircraft class', :needs => :aircraft_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
341
+ characteristics[:aircraft_class].seats_before_type_cast
168
342
  end
169
343
 
344
+ ##### Default
345
+ # 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.
170
346
  quorum 'default' do
171
- FlightSegment.fallback.andand.seats
172
- end
173
- end
174
-
175
- committee :load_factor do
176
- quorum 'from cohort', :needs => :cohort do |characteristics|
177
- characteristics[:cohort].weighted_average(:load_factor, :weighted_by => :passengers)
178
- end
179
-
180
- quorum 'default' do
181
- base.fallback.andand.load_factor
347
+ FlightSegment.fallback.seats_before_type_cast # need before_type_cast b/c seats is an integer but the fallback value is a float
182
348
  end
183
349
  end
184
350
 
185
- committee :adjusted_distance do # returns nautical miles
186
- quorum 'from distance', :needs => [:distance, :emplanements_per_trip] do |characteristics|
187
- route_inefficiency_factor = base.research(:route_inefficiency_factor)
188
- dogleg_factor = base.research(:dogleg_factor)
189
- characteristics[:distance] * route_inefficiency_factor * ( dogleg_factor ** (characteristics[:emplanements_per_trip] - 1) )
190
- end
191
- end
351
+ ### Seats estimate
352
+ # This implied calculation returns the client-input `seats estimate`.
192
353
 
193
- committee :distance do # returns nautical miles
194
- quorum 'from airports', :needs => [:origin_airport, :destination_airport] do |characteristics|
195
- if characteristics[:origin_airport].latitude and
196
- characteristics[:origin_airport].longitude and
197
- characteristics[:destination_airport].latitude and
198
- characteristics[:destination_airport].longitude
199
- characteristics[:origin_airport].distance_to(characteristics[:destination_airport], :units => :kms).kilometres.to :nautical_miles
200
- end
201
- end
202
-
203
- quorum 'from distance estimate', :needs => :distance_estimate do |characteristics|
204
- characteristics[:distance_estimate].kilometres.to :nautical_miles
205
- end
206
-
207
- quorum 'from distance class', :needs => :distance_class do |characteristics|
208
- characteristics[:distance_class].distance.kilometres.to :nautical_miles
209
- end
354
+ ### Load factor
355
+ # This calculation returns the `load factor`.
356
+ # The `load factor` is the portion of available seats that are occupied.
357
+ committee :load_factor do
358
+ ##### From client input
359
+ # This implied first-tier method uses the client-input `load factor`.
210
360
 
211
- quorum 'from cohort', :needs => :cohort do |characteristics|
212
- distance = characteristics[:cohort].weighted_average(:distance, :weighted_by => :passengers).to_f.kilometres.to(:nautical_miles)
213
- distance > 0 ? distance : nil
361
+ ##### From cohort
362
+ # This second-tier method calculates the average `load factor` of the `cohort` segments, weighted by their passengers.
363
+ quorum 'from cohort', :needs => :cohort, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
364
+ characteristics[:cohort].weighted_average(:load_factor, :weighted_by => :passengers)
214
365
  end
215
366
 
216
- quorum 'default' do
217
- base.fallback.distance_estimate.kilometres.to :nautical_miles
218
- end
219
- end
220
-
221
- committee :emplanements_per_trip do # per trip
222
- quorum 'default' do
223
- base.fallback.emplanements_per_trip_before_type_cast
224
- end
225
- end
226
-
227
- committee :radiative_forcing_index do
228
- quorum 'from fuel type', :needs => :fuel_type do |characteristics|
229
- characteristics[:fuel_type].radiative_forcing_index
230
- end
231
- end
232
-
233
- committee :emission_factor do # returns kg CO2 / kg fuel
234
- quorum 'from fuel type', :needs => :fuel_type do |characteristics|
235
- #( kg CO2 / litres fuel ) * ( litres fuel / kg fuel )
236
- characteristics[:fuel_type].emission_factor * ( 1 / characteristics[:fuel_type].density).gallons.to(:litres)
237
- end
238
- end
239
-
240
- committee :fuel_type do
241
- quorum 'default' do
242
- FlightFuelType.fallback
367
+ ##### Default
368
+ # 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.
369
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
370
+ FlightSegment.fallback.load_factor
243
371
  end
244
372
  end
245
373
 
374
+ ### Freight share
375
+ # This calculation returns the `freight share`.
376
+ # The `freight share` is the percent of the total aircraft weight that is freight cargo and mail (as opposed to passengers and their baggage).
246
377
  committee :freight_share do
247
- quorum 'from cohort', :needs => :cohort do |characteristics|
378
+ ##### From cohort
379
+ # This first-tier method calculates the average `freight share` of the `cohort` segments, weighted by their passengers
380
+ quorum 'from cohort', :needs => :cohort, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
248
381
  characteristics[:cohort].weighted_average(:freight_share, :weighted_by => :passengers)
249
382
  end
250
383
 
251
- quorum 'default' do
252
- FlightSegment.fallback.andand.freight_share
384
+ ##### Default
385
+ # 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.
386
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
387
+ FlightSegment.fallback.freight_share
253
388
  end
254
389
  end
255
390
 
391
+ ### Trips
392
+ # This calculation returns the number of `trips`.
393
+ # A one-way flight has one trip; a round-trip flight has two trips.
256
394
  committee :trips do
257
- quorum 'default' do
258
- base.fallback.andand.trips_before_type_cast
395
+ ##### From client input
396
+ # This implied first-tier method uses the client-input number of `trips`.
397
+
398
+ ##### Default
399
+ # 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).
400
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
401
+ base.fallback.trips_before_type_cast # need before_type_cast b/c trips is an integer but fallback value is a float
259
402
  end
260
403
  end
261
404
 
262
- # Disabling this for now because domesticity is pretty useless
263
- # FIXME TODO make domesticity non-us-centric and check that data's ok
264
- # committee :domesticity do
265
- # quorum 'from airports', :needs => [:origin_airport, :destination_airport] do |characteristics|
266
- # if [characteristics[:origin_airport], characteristics[:destination_airport]].all?(&:united_states?)
267
- # FlightDomesticity.find_by_name('domestic')
268
- # elsif [characteristics[:origin_airport], characteristics[:destination_airport]].any?(&:united_states?)
269
- # FlightDomesticity.find_by_name('international')
270
- # end
271
- # end
272
- #
273
- # quorum 'from origin', :needs => :origin_airport do |characteristics|
274
- # if characteristics[:origin_airport].all_flights_from_here_domestic?
275
- # FlightDomesticity.find_by_name('domestic')
276
- # end
277
- # end
278
- #
279
- # quorum 'from destination', :needs => :destination_airport do |characteristics|
280
- # if characteristics[:destination_airport].all_flights_to_here_domestic?
281
- # FlightDomesticity.find_by_name('domestic')
282
- # end
283
- # end
284
- #
285
- # quorum 'from airline', :needs => :airline do |characteristics|
286
- # if characteristics[:airline].all_flights_domestic?
287
- # FlightDomesticity.find_by_name('domestic')
288
- # end
289
- # end
290
- # end
291
-
405
+ ### Seat class multiplier
406
+ # This calculation returns the `seat class multiplier`, which reflects the amount of cabin space occupied by the passenger's seat.
292
407
  committee :seat_class_multiplier do
293
- quorum 'from seat class', :needs => :seat_class do |characteristics|
408
+ ##### From seat class
409
+ # This first-tier method looks up the [seat class](http://data.brighterplanet.com/flight_seat_classes)'s `seat class multiplier`.
410
+ quorum 'from seat class', :needs => :seat_class, :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
294
411
  characteristics[:seat_class].multiplier
295
412
  end
296
413
 
297
- quorum 'default' do
298
- FlightSeatClass.fallback.andand.multiplier
414
+ ##### Default
415
+ # This default method uses a `seat class multiplier` of **1**.
416
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
417
+ FlightSeatClass.fallback.multiplier
299
418
  end
300
419
  end
301
420
 
302
- committee :date do
303
- quorum 'from creation date', :needs => :creation_date do |characteristics|
304
- characteristics[:creation_date]
421
+ ### Seat class
422
+ # This implied calculation returns the client-input [seat class](http://data.brighterplanet.com/seat_classes).
423
+
424
+ ### Country
425
+ # This calculation returns the [country](http://data.brighterplanet.com/countries) in which a flight occurs.
426
+ committee :country do
427
+ ##### From origin airport and destination airport
428
+ # This method checks that the flight's `origin airport` and `destination airport` are within the same country.
429
+ # If so, that country is the `country`.
430
+ quorum 'from origin airport and destination airport', :needs => [:origin_airport, :destination_airport], :complies => [:ghg_protocol, :iso, :tcr] do |characteristics|
431
+ if characteristics[:origin_airport].country == characteristics[:destination_airport].country
432
+ characteristics[:origin_airport].country
433
+ end
305
434
  end
306
435
 
307
- quorum 'from timeframe' do |characteristics, timeframe|
308
- timeframe.present? ? timeframe.from : nil
309
- end
436
+ ##### From client input
437
+ # This implied method uses the client-input [country](http://data.brighterplanet.com/countries).
310
438
  end
311
439
 
440
+ ### Cohort
441
+ # 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.
312
442
  committee :cohort do
313
- quorum 'from t100', :appreciates => [:origin_airport, :destination_airport, :aircraft, :airline] do |characteristics|
314
- provided_characteristics = [:origin_airport, :destination_airport, :aircraft, :airline].
315
- inject(ActiveSupport::OrderedHash.new) do |memo, characteristic_name|
316
- memo[characteristic_name] = characteristics[characteristic_name]
317
- memo
318
- end
319
- cohort = FlightSegment.strict_cohort provided_characteristics
443
+ ##### From segments per trip and input
444
+ # This method:
445
+ #
446
+ # 1. Checks that the flight is direct
447
+ # 2. Takes the input values for `origin airport`, `destination airport`, `aircraft`, and `airline`
448
+ # 3. Selects all the records in the T-100 database that match the available input values
449
+ # 4. Drops the last input value (initially `airline`, then `aircraft`, etc.) if no records match all of the available input values
450
+ # 5. Repeats steps 3 and 4 until some records match or no input values remain
451
+ #
452
+ # If no records match any of the input values, or if the flight is indirect, then `cohort` is undefined.
453
+ 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|
454
+ cohort = {}
455
+ if characteristics[:segments_per_trip] == 1
456
+ provided_characteristics = [:origin_airport, :destination_airport, :aircraft, :airline].
457
+ inject(ActiveSupport::OrderedHash.new) do |memo, characteristic_name|
458
+ memo[characteristic_name] = characteristics[characteristic_name]
459
+ memo
460
+ end
461
+ cohort = FlightSegment.strict_cohort provided_characteristics
462
+ end
320
463
  if cohort.any?
321
464
  cohort
322
465
  else
@@ -324,10 +467,56 @@ module BrighterPlanet
324
467
  end
325
468
  end
326
469
  end
470
+
471
+ ### Origin airport
472
+ # This implied calculation returns the client-input [origin airport](http://data.brighterplanet.com/airports).
473
+
474
+ ### Destination airport
475
+ # This implied calculation returns the client-input [destination airport](http://data.brighterplanet.com/airports).
476
+
477
+ ### Aircraft
478
+ # This implied calculation returns the client-input type of [aircraft](http://data.brighterplanet.com/aircraft).
479
+
480
+ ### Aircraft class
481
+ # This implied calculation returns the client-input [aircraft_class](http://data.brighterplanet.com/aircraft_classes).
482
+
483
+ ### Airline
484
+ # This implied calculation returns the client-input [airline](http://data.brighterplanet.com/airlines) operating the flight.
485
+
486
+ ### Segments per trip
487
+ # This calculation returns the `segments per trip`.
488
+ # Direct flights have a single segment per trip. Indirect flights with one or more layovers have two or more segments per trip.
489
+ committee :segments_per_trip do
490
+ ##### From client input
491
+ # This implied first-tier method uses the client-input `segments per trip`.
492
+
493
+ ##### Default
494
+ # This default method calculates the average `segments per trip` from the [U.S. National Household Travel Survey](http://nhts.ornl.gov/).
495
+ quorum 'default', :complies => [:ghg_protocol, :iso, :tcr] do
496
+ 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
497
+ end
498
+ end
499
+
500
+ ### Date
501
+ # This calculation returns the `date` on which the flight occured.
502
+ committee :date do
503
+ ##### From client input
504
+ # This implied first-tier method uses the client-input value for `date`.
505
+
506
+ ##### From timeframe
507
+ # This second-tier method assumes the flight occured on the first day of the `timeframe`.
508
+ quorum 'from timeframe', :complies => [:ghg_protocol, :iso, :tcr] do |characteristics, timeframe|
509
+ timeframe.from
510
+ end
511
+ end
327
512
  end
328
513
  end
329
514
 
330
- class FuelUseEquation < Struct.new(:m3, :m2, :m1, :endpoint_fuel); end
515
+ class FuelUseEquation < Struct.new(:m3, :m2, :m1, :endpoint_fuel)
516
+ def empty?
517
+ m3.nil? and m2.nil? and m1.nil? and endpoint_fuel.nil?
518
+ end
519
+ end
331
520
  end
332
521
  end
333
522
  end