impact_model.rb

Copyright © 2010 Brighter Planet. See LICENSE for details. Contact Brighter Planet for dual-license arrangements.

Flight impact model

This model is used by the Brighter Planet CM1 web service to calculate the per-passenger impacts of a flight, such as energy use and greenhouse gas emissions.

Timeframe

The model calculates impacts that occured during a particular time period (timeframe). For example if the timeframe is February 2010, a flight that occurred (date) on February 15, 2010 will have impacts, but a flight that occurred on January 31, 2010 will have zero impacts.

The default timeframe is the current calendar year.

Calculations

The final impacts are the result of the calculations below. These are performed in reverse order, starting with the last calculation listed and finishing with the greenhouse gas emissions calculation.

Each calculation listing shows:

  • value returned (units of measurement)
  • description of the value
  • calculation methods, listed from most to least preferred

Some methods use values returned by prior calculations. If any of these values are unknown the method is skipped. If all the methods for a calculation are skipped, the value the calculation would return is unknown.

Standard compliance

When compliance with a particular standard is requested, all methods that do not comply with that standard are ignored. Thus any values a method needs will have been calculated using a compliant method or will be unknown. To see which standards a method complies with, look at the :complies => section of the code in the right column.

Client input complies with all standards.

Collaboration

Contributions to this impact 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 should include test coverage for new functionality. Please see sniff, our emitter testing framework, for more information.

require 'flight/impact_model/fuel_use_equation'

module BrighterPlanet
  module Flight
    module ImpactModel
      def self.included(base)
        base.decide :impact, :with => :characteristics do

          

Carbon (kg CO2e)

The passenger’s share of the flight’s anthropogenic greenhouse emissions during timeframe.

          committee :carbon do

Multiply fuel use (l) by greenhouse gas emission factor (kg CO2e / l) to give kg CO2e.

            quorum 'from fuel use and greenhouse gas emission factor', :needs => [:fuel_use, :ghg_emission_factor],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:fuel_use] * characteristics[:ghg_emission_factor]
            end
          end
          

Greenhouse gas emission factor (kg CO2 / l)

An emission factor that includes the extra forcing effects of high-altitude emissions.

          committee :ghg_emission_factor do

Multiply the fuel’s co2 emission factor (kg CO2 / l) by aviation multiplier to give kg CO2 / l.

            quorum 'from fuel and aviation multiplier', :needs => [:fuel, :aviation_multiplier],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:fuel].co2_emission_factor * characteristics[:aviation_multiplier]
            end
          end
          

Aviation multiplier

A multiplier to account for the extra climate impact of greenhouse gas emissions high in the atmosphere.

          committee :aviation_multiplier do

Use 2.0 after Kollmuss and Crimmins (2009).

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                2.0
            end
          end
          

Energy (MJ)

The passenger’s share of the flight’s energy consumption during timeframe.

          committee :energy do

Multiply fuel use (l) by the fuel’s energy content (MJ / l) to give MJ.

            quorum 'from fuel use and fuel', :needs => [:fuel_use, :fuel] do |characteristics|
              characteristics[:fuel_use] * characteristics[:fuel].energy_content
            end
          end
          

Fuel use (l)

The passenger’s share of the flight’s fuel use during timeframe.

          committee :fuel_use do

Check whether date falls within timeframe – otherwise fuel use is zero. Multiply fuel per segment (kg) by segments per trip and trips to give total fuel use (kg). Multiply by (1 – freight share) to take out fuel attributed to cargo and mail, leaving fuel attributed to passengers and their baggage. Divide by passengers and multiply by seat class multiplier to account for the portion of the cabin occupied by the passenger’s seat. Divide by the fuel’s density (kg / l) to give l.

            quorum 'from fuel per segment, segments per trip, trips, freight_share, passengers, seat class multiplier, fuel, date, and timeframe',
              :needs => [:fuel_per_segment, :segments_per_trip, :trips, :freight_share, :passengers, :seat_class_multiplier, :fuel, :date],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics, timeframe|
=begin
  FIXME TODO date should already be coerced
=end
                date = characteristics[:date].is_a?(Date) ? characteristics[:date] : Date.parse(characteristics[:date].to_s)
                if timeframe.include? date
                  characteristics[:fuel_per_segment] * characteristics[:segments_per_trip] * characteristics[:trips] *
                    (1 - characteristics[:freight_share]) / characteristics[:passengers] * characteristics[:seat_class_multiplier] /
                    characteristics[:fuel].density
                else
                  0
                end
            end
          end
          

Fuel per segment (kg)

The fuel used by each nonstop segment of the flight.

          committee :fuel_per_segment do

Fuel per segment = m3d3 + m2d2 + m1d + b where d is adjusted distance per segment and m3, m2, m1, and b are the fuel use coefficients.

            quorum 'from adjusted distance per segment and fuel use coefficients', :needs => [:adjusted_distance_per_segment, :fuel_use_coefficients],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:fuel_use_coefficients].m3 * characteristics[:adjusted_distance_per_segment] ** 3 +
                  characteristics[:fuel_use_coefficients].m2 * characteristics[:adjusted_distance_per_segment] ** 2 +
                  characteristics[:fuel_use_coefficients].m1 * characteristics[:adjusted_distance_per_segment] +
                  characteristics[:fuel_use_coefficients].b
            end
          end
          

Seat class multiplier

A multiplier to account for the portion of cabin space occupied by the passenger’s seat.

          committee :seat_class_multiplier do

Use the distance class seat class multiplier.

            quorum 'from distance class seat class', :needs => :distance_class_seat_class,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:distance_class_seat_class].multiplier
            end
            

Otherwise use the average distance class seat class multiplier.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                FlightDistanceClassSeatClass.fallback.multiplier
            end
          end
          

Distance class seat class

The passenger’s distance class and seat class.

          committee :distance_class_seat_class do

Check whether the distance class and seat class combination matches any records in our database. If it doesn’t then we don’t know distance class seat class.

            quorum 'from distance class and seat class', :needs => [:distance_class, :seat_class],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                FlightDistanceClassSeatClass.find_by_distance_class_name_and_seat_class_name(characteristics[:distance_class].name, characteristics[:seat_class].name)
            end
          end
          

Distance class

The flight’s distance class.

          committee :distance_class do

Use client input, if available.

            

Otherwise look up the distance class that corresponds to adjusted distance per segment.

            quorum 'from adjusted distance per segment', :needs => :adjusted_distance_per_segment,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                FlightDistanceClass.find_by_distance(characteristics[:adjusted_distance_per_segment].nautical_miles.to :kilometres)
            end
          end
          

Seat class

The passenger’s seat class.

Use client input if available.

          

Adjusted distance per segment (nautical miles)

The distance of each nonstop segment of the flight.

          committee :adjusted_distance_per_segment do

Divide adjusted distance (nautical miles) by segments per trip to give nautical miles.

            quorum 'from adjusted distance and segments per trip', :needs => [:adjusted_distance, :segments_per_trip],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:adjusted_distance] / characteristics[:segments_per_trip]
            end
          end
          

Adjusted distance (nautical miles)

The flight’s distance accounting for factors that increase the actual distance traveled by real world flights.

          committee :adjusted_distance do

Multiply distance (nautical miles) by route inefficiency factor and dogleg factor to give nautical miles.

            quorum 'from distance, route inefficiency factor, and dogleg factor', :needs => [:distance, :route_inefficiency_factor, :dogleg_factor],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:distance] * characteristics[:route_inefficiency_factor] * characteristics[:dogleg_factor]
            end
          end
          

Distance (nautical miles)

The flight’s base distance.

          committee :distance do

Calculate the great circle distance between the origin airport and destination airport (km) and convert to nautical miles.

            quorum 'from airports', :needs => [:origin_airport, :destination_airport],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                if characteristics[:origin_airport].latitude and
                    characteristics[:origin_airport].longitude and
                    characteristics[:destination_airport].latitude and
                    characteristics[:destination_airport].longitude
                  characteristics[:origin_airport].distance_to(characteristics[:destination_airport], :units => :kms).kilometres.to :nautical_miles
                end
            end
            

Otherwise convert distance estimate(km) to nautical miles.

            quorum 'from distance estimate', :needs => :distance_estimate,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:distance_estimate].kilometres.to :nautical_miles
            end
            

Otherwise convert the distance class distance (km) to nautical miles.

            quorum 'from distance class', :needs => :distance_class,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:distance_class].distance.kilometres.to :nautical_miles
            end
            
=begin
  This should not be prioritized over distance estimate or distance class because cohort here never has both airports
=end

Otherwise calculate the average distance of the cohort segments, weighted by passengers, (km) and convert to nautical miles.

            quorum 'from cohort', :needs => :cohort do |characteristics|
              distance = characteristics[:cohort].weighted_average(:distance, :weighted_by => :passengers).kilometres.to :nautical_miles
=begin
  Need to check distance > 0 because some flight segments have 0 for distance
=end
              distance > 0 ? distance : nil
            end
            

Otherwise calculate the average distance of all flight segments in our database, weighted by passengers, (km) and convert to nautical miles.

            quorum 'default' do
              FlightSegment.fallback.distance.kilometres.to :nautical_miles
            end
          end
          

Route inefficiency factor

A multiplier to account for factors like flight path routing around controlled airspace and circling while waiting for clearance to land.

          committee :route_inefficiency_factor do

Use the country route inefficiency factor.

            quorum 'from country', :needs => :country,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:country].flight_route_inefficiency_factor
            end
            

Otherwise use the global average route inefficiency factor.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                Country.fallback.flight_route_inefficiency_factor
            end
          end
          

Dogleg factor

A multiplier that represents how ‘out of the way’ connecting airports are compared to a nonstop flight.

          committee :dogleg_factor do

Assume that each connection increases the total flight distance by 25%.

            quorum 'from segments per trip', :needs => :segments_per_trip,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                1.25 ** (characteristics[:segments_per_trip] - 1)
            end
          end
          

Distance estimate (km)

The client’s estimate of the flight’s distance.

Use client input, if available.

          

Distance class

The flight’s distance class.

Use client input, if available.

          

Fuel use coefficients

The coefficients of a third-order polynomial equation that describes aircraft fuel use.

          committee :fuel_use_coefficients do
            quorum 'from cohort', :needs => :cohort,

Calculate the average fuel use coefficients of the cohort aircraft, weighted by the passengers carried by each of those aircraft:

              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
  • Extract the cohort ‘where’ conditions to let us reproduce it in our sql queries
                cohort_conditions = characteristics[:cohort].where_values.map { |x| x.respond_to?(:to_sql) ? x.to_sql : x }.join(' AND ')
                
  • Create a temporary table to hold the values we need
                c = ActiveRecord::Base.connection
                c.execute %{
                  DROP TABLE IF EXISTS tmp_fuel_use_coefficients
                }
                c.execute %{
                  CREATE TEMPORARY TABLE tmp_fuel_use_coefficients (description VARCHAR(255), m3 FLOAT, m2 FLOAT, m1 FLOAT, b FLOAT, passengers INT)
                }
                
  • Look up the unique aircraft descriptions in cohort
                aircraft_descriptions = c.select_values %{
                  SELECT DISTINCT aircraft_description
                  FROM flight_segments
                  WHERE (#{cohort_conditions})
                }
                
  • For each unique aircraft description:
    1. look up all the aircraft it refers to
    1. average those aircraft’s fuel use coefficients
    1. store the resulting values in the temporary table along with the unique aircraft_description
=begin
  NOTE: this requires that missing data in aircraft be "NULL" rather than 0
=end
                c.execute %{
                  INSERT INTO tmp_fuel_use_coefficients (description, m3, m2, m1, b)
                    SELECT t1.b, AVG(aircraft.m3), AVG(aircraft.m2), AVG(aircraft.m1), AVG(aircraft.b)
                    FROM loose_tight_dictionary_cached_results AS t1
                      INNER JOIN aircraft
                      ON t1.a = aircraft.description
                    WHERE t1.b IN ('#{aircraft_descriptions.join("', '")}')
                    GROUP BY t1.b
                }
                
  • For each unique aircraft description:
    1. look up all the flight segments in cohort that match the aircraft description
    1. sum passengers across those flight segments
    1. store the resulting value in the temporary table
                c.execute %{
                  UPDATE tmp_fuel_use_coefficients
                  SET passengers = (
                    SELECT SUM(passengers)
                    FROM flight_segments
                    WHERE (#{cohort_conditions})
                    AND flight_segments.aircraft_description = tmp_fuel_use_coefficients.description
                  )
                }
                
  • Calculate the average of the coefficients in the temporary table, weighted by passengers
=begin
  `select_values` "Returns an array of the values of the first column in a select" so it doesn't work here
=end
                m3, m2, m1, b = (c.select_rows %{
                  SELECT
                    SUM(1.0 * m3 * passengers)/SUM(passengers),
                    SUM(1.0 * m2 * passengers)/SUM(passengers),
                    SUM(1.0 * m1 * passengers)/SUM(passengers),
                    SUM(1.0 * b * passengers)/SUM(passengers)
                  FROM tmp_fuel_use_coefficients
                  WHERE
                    m3 IS NOT NULL
                    AND m2 IS NOT NULL
                    AND m1 IS NOT NULL
                    AND b IS NOT NULL
                    AND passengers > 0
                }).flatten
                
                FuelUseEquation.new_if_valid m3, m2, m1, b
            end
            

Otherwise use the aircraft fuel use coefficients.

            quorum 'from aircraft', :needs => :aircraft,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                FuelUseEquation.new_if_valid characteristics[:aircraft].m3, characteristics[:aircraft].m2, characteristics[:aircraft].m1, characteristics[:aircraft].b
            end
            

Otherwise calculate the average fuel use coefficients of all aircraft, weighted by passengers.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                FuelUseEquation.new_if_valid Aircraft.fallback.m3, Aircraft.fallback.m2, Aircraft.fallback.m1, Aircraft.fallback.b
            end
          end
          

Fuel

The type of fuel used by the aircraft.

          committee :fuel do

Use client input, if available.

            

Otherwise assume the flight uses Jet Fuel.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                Fuel.find_by_name 'Jet Fuel'
            end
          end
          

Passengers

The number of passengers on the flight.

          committee :passengers do

Multiply seats by load factor and round to the nearest whole number.

            quorum 'from seats and load factor', :needs => [:seats, :load_factor],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                (characteristics[:seats] * characteristics[:load_factor]).round
            end
          end
          

Seats

The number of seats on the aircraft.

          committee :seats do

Uses client input, if available.

            

Otherwise calculate the average seats of the cohort segments, weighted by passengers.

            quorum 'from cohort', :needs => :cohort,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:cohort].weighted_average(:seats_per_flight, :weighted_by => :passengers)
            end
            

Otherwise use the aircraft average number of seats.

            quorum 'from aircraft', :needs => :aircraft,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:aircraft].seats
            end
            

Otherwise calculate the average seats of all flight segments in our database, weighted by passengers.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                FlightSegment.fallback.seats_per_flight
            end
          end
          

Load factor

The portion of available seats that are occupied.

          committee :load_factor do

Uses client input, if available.

            

Otherwise calculate the average load factor of the cohort segments, weighted by passengers.

            quorum 'from cohort', :needs => :cohort,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                load_factor = characteristics[:cohort].weighted_average(:load_factor, :weighted_by => :passengers)
            end
            

Otherwise calculate the average load factor of all flight segments in our database, weighted by passengers.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                FlightSegment.fallback.load_factor
            end
          end
          

Freight share

The percent of the total aircraft weight that is cargo and mail, as opposed to passengers and their baggage.

          committee :freight_share do

Calculate the average freight share of the cohort segments, weighted by passengers.

            quorum 'from cohort', :needs => :cohort,
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                characteristics[:cohort].weighted_average(:freight_share, :weighted_by => :passengers)
            end
            

Otherwise calculate the average freight share of all flight segments in our database, weighted by passengers.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                FlightSegment.fallback.freight_share
            end
          end
          

Trips

A one-way flight has one trip; a round-trip flight has two trips.

          committee :trips do

Uses client input, if available.

            

Otherwise use an average of 1.7, calculated from the BTS Origin and Destination Survey.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                1.7
            end
          end
          

Country

The country in which the flight occured.

          committee :country do

If the flight’s origin airport and destination airport are within the same country, use that country.

            quorum 'from origin airport and destination airport', :needs => [:origin_airport, :destination_airport],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|
                if characteristics[:origin_airport].country == characteristics[:destination_airport].country
                  characteristics[:origin_airport].country
                end
            end
          end
          

Cohort

A set of flight segment records in our database that match certain client-input values.

          committee :cohort do

If the client specified the id of a single flight segment in our database, use that flight segment.

            quorum 'from row_hash', :needs => [:flight_segment_row_hash] do |characteristics|
              FlightSegment.where(:row_hash => characteristics[:flight_segment_row_hash].value).to_cohort
            end
            

Otherwise assemble a cohort based on whatever client inputs are available:

            quorum 'from segments per trip and input',
              :needs => :segments_per_trip, :appreciates => [:origin_airport, :destination_airport, :aircraft, :airline, :date],
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics|

Only assemble a cohort if the flight is nonstop

                if characteristics[:segments_per_trip] == 1
                  cohort = {}
                  provided_characteristics = []
=begin
  FIXME TODO date should already be coerced
=end
                  date = characteristics[:date].is_a?(Date) ? characteristics[:date] : Date.parse(characteristics[:date].to_s)
                  

Restrict the cohort to flight segments that occurred the same year as the flight or the previous year. (We need to include the previous year because BTS flight segment data releases lag by 6 months.)

=begin
  FIXME TODO check how far back we should look on account of ICAO data
=end
                  relevant_years = [date.year - 1, date.year]
                  
=begin
  FIXME TODO refactor this
=end
                  

If we have both origin airport and destination airport:

                  if characteristics[:origin_airport].present? and characteristics[:destination_airport].present?
  • If either airport is in the US, use airport iata codes to assemble a cohort of BTS flight segments
                    if characteristics[:origin_airport].country_iso_3166_code == "US" or characteristics[:destination_airport].country_iso_3166_code == "US"
=begin
  NOTE: It's possible that the origin/destination pair won't appear in our database and we'll end up using a
  cohort based just on origin. If that happens, even if the origin is not in the US we still don't want to use
  origin airport city, because we know the flight was going to the US and ICAO segments never touch the US.
=end
                      provided_characteristics.push [:origin_airport_iata_code, characteristics[:origin_airport].iata_code]
                      provided_characteristics.push [:destination_airport_iata_code, characteristics[:destination_airport].iata_code]
                    
  • If neither airport is in the US, use airport cities to assemble a cohort of ICAO flight segments
=begin
  FIXME TODO deal with cities in multiple countries that share a name
  Tried pushing country, which works on a flight from Mexico City to Barcelona, Spain because it does
  not include flights to Barcelona, Venezuela BUT it doesn't work if we're trying to go from Montreal
  end up with flights to London, United Kingdom. Also pushing country breaks addition of cohorts - all 'AND'
  statements get changed to 'OR' so you end up with all flights to that country
  e.g. WHERE origin_airport_iata_code = 'JFK' OR origin_country_iso_3166_code = 'US'
=end
                    else
                      provided_characteristics.push [:origin_airport_city, characteristics[:origin_airport].city]
                      provided_characteristics.push [:destination_airport_city, characteristics[:destination_airport].city]
                    end
                    
  • Also use aircraft and airline if they’re available
                    if characteristics[:aircraft].present?
                      provided_characteristics.push [:aircraft_description, characteristics[:aircraft].flight_segments_foreign_keys]
                    end
                    
                    if characteristics[:airline].present?
                      provided_characteristics.push [:airline_name, characteristics[:airline].name]
                    end
                    
  • Assemble a cohort by starting with all flight segments in the relevant years. Select only the segments that match the characteristics we’ve decided to use. If no segments match all the characteristics, drop the last characteristic (initially airline) and try again. Continue until we have some segments or we’ve dropped all the characteristics.
                    cohort = FlightSegment.where(:year => relevant_years).where("passengers > 0").strict_cohort(*provided_characteristics)
                    
  • Ignore the cohort if it’s empty.
=begin
  TODO: make 'passengers > 0' a constraint once cohort_scope supports non-hash constraints
=end
                    cohort.any? ? cohort : nil
                  

If we don’t have both origin airport and destination airport:

                  else
  • Use airport iata codes to assemble a cohort of BTS flight segments
                    if characteristics[:origin_airport].present?
                      provided_characteristics.push [:origin_airport_iata_code, characteristics[:origin_airport].iata_code]
                    end
                    
                    if characteristics[:destination_airport].present?
                      provided_characteristics.push [:destination_airport_iata_code, characteristics[:destination_airport].iata_code]
                    end
                    
  • Also use aircraft and airline if they’re available.
                    if characteristics[:aircraft].present?
                      provided_characteristics.push [:aircraft_description, characteristics[:aircraft].flight_segments_foreign_keys]
                    end
                    
                    if characteristics[:airline].present?
                      provided_characteristics.push [:airline_name, characteristics[:airline].name]
                    end
                    
=begin
  Note: can't use where conditions here e.g. where(:year => relevant_years) because when we combine the cohorts
  all AND become OR so we get WHERE year IN (*relevant_years*) OR *other conditions* which returns every
  flight segment in the relevant_years
=end
  • Assemble a cohort by starting with all flight segments in the relevant years. Select only the segments that match the characteristics we’ve decided to use. If no segments match all the characteristics, drop the last characteristic (initially airline) and try again. Continue until we have some segments or we’ve dropped all the characteristics.
                    bts_cohort = FlightSegment.strict_cohort(*provided_characteristics)
                    
  • Then use airport city to assemble a cohort of ICAO flight segments
=begin
  FIXME TODO: deal with cities in multiple countries that share a name
  Tried pushing country, which works on a flight from Mexico City to Barcelona, Spain because it does
  not include flights to Barcelona, Venezuela BUT it doesn't work if we're trying to go from Montreal
  to London, Canada because there are no nonstop flights to London, Canada so country gets dropped and we
  end up with flights to London, United Kingdom. Also pushing country breaks addition of cohorts - all 'AND'
  statements get changed to 'OR' so you end up with all flights to that country
  e.g. WHERE origin_airport_iata_code = 'JFK' OR origin_country_iso_3166_code = 'US'
=end
                    provided_characteristics = []
                    if characteristics[:origin_airport].present?
                      provided_characteristics.push [:origin_airport_city, characteristics[:origin_airport].city]
                    end
                    
                    if characteristics[:destination_airport].present?
                      provided_characteristics.push [:destination_airport_city, characteristics[:destination_airport].city]
                    end
                    
  • Also use aircraft and airline if they’re available.
                    if characteristics[:aircraft].present?
                      provided_characteristics.push [:aircraft_description, characteristics[:aircraft].flight_segments_foreign_keys]
                    end
                    
                    if characteristics[:airline].present?
                      provided_characteristics.push [:airline_name, characteristics[:airline].name]
                    end
                    
  • Assemble a cohort by starting with all flight segments in the relevant years. Select only the segments that match the characteristics we’ve decided to use. If no segments match all the characteristics, drop the last characteristic (initially airline) and try again. Continue until we have some segments or we’ve dropped all the characteristics.
                    icao_cohort = FlightSegment.strict_cohort(*provided_characteristics)
                    
  • Combine the two cohorts, making sure to restrict to relevant years and segments with passengers
=begin
  Note: cohort_scope 0.2.1 provides cohort + cohort => cohort; cohort.where() => relation; relation.to_cohort => cohort
=end
                    cohort = (bts_cohort + icao_cohort).where(:year => relevant_years).where("passengers > 0").to_cohort
                    
  • Ignore the resulting cohort if it’s empty
                    cohort.any? ? cohort : nil
                  end
                end
            end
          end
          

Origin airport

The flight’s origin airport.

Use client input, if available.

          

Destination airport

The flight’s destination airport.

Use client input, if available.

          

Aircraft

The flight’s aircraft.

Use client input, if available.

          

Airline

The airline that operates the flight. For codeshare flights this may be different than the ticketing airline.

Use client input, if available.

          

Segments per trip

The number of nonstop flight segments. A nonstop flight has 1 segment per trip (e.g. JFK to SFO); a connecting flight has 2 or more segments (e.g. JFK to SFO via ORD).

          committee :segments_per_trip do

Use client input, if available.

            

Otherwise use an average of 1.68, calculated from the BTS Origin and Destination Survey.

            quorum 'default',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do
                1.68
            end
          end
          

Date (date)

The day the flight occurred.

          committee :date do

Use client input, if available.

            

Otherwise assume the flight occurred on the first day of timeframe.

            quorum 'from timeframe',
              :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics, timeframe|
                timeframe.from
            end
          end
        end
      end
    end
  end
end