flight 0.0.23 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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