# Copyright © 2010 Brighter Planet. # See LICENSE for details. # Contact Brighter Planet for dual-license arrangements. ## Lodging impact model # This model is used by the [Brighter Planet](http://brighterplanet.com) [CM1 web service](http://carbon.brighterplanet.com) to calculate the impacts of a lodging (e.g. a hotel stay), such as energy use, greenhouse gas emissions, and water use. ##### Timeframe # The model calculates impacts that occured during a particular time period (`timeframe`). # For example if the `timeframe` is February 2010, a lodging that occurred (`date`) on February 15, 2010 will have impacts, but a lodging 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](https://github.com/brighterplanet/sniff#readme), our emitter testing framework, for more information. require 'lodging/cbecs' module BrighterPlanet module Lodging module ImpactModel def self.included(base) base.decide :impact, :with => :characteristics do # * * * #### Carbon (*kg CO2e*) # *The lodging's total anthropogenic greenhouse gas emissions during `timeframe`.* committee :carbon do # Multiply `natural gas use` (*m3*) by natural gas' emission factor (*kg CO2 / m3*) to give *kg CO2.* # Multiply `fuel oil use` (*l*) by fuel oil's emission factor (*kg CO2 / l*) to give *kg CO2.* # Multiply `electricity use` (*kWh*) by `electricity emission factor` (*kg CO2 / kWh*) to give *kg CO2.* # Multiply `district heat use` (*MJ*) by district heat's emission factor` (*kg CO2 / MJ*) to give *kg CO2.* # Sum to give *kg CO2e.* quorum 'from natural gas use, fuel oil use, electricity use, district heat use, and electricity emission factor', :needs => [:natural_gas_use, :fuel_oil_use, :electricity_use, :district_heat_use, :electricity_emission_factor], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:natural_gas_use] * Fuel.find_by_name('Pipeline Natural Gas').co2_emission_factor + characteristics[:fuel_oil_use] * Fuel.find_by_name('Residual Fuel Oil No. 6').co2_emission_factor + characteristics[:electricity_use] * characteristics[:electricity_emission_factor] + characteristics[:district_heat_use] * Fuel.find_by_name('District Heat').co2_emission_factor end end #### Electricity emission factor (*kg CO2e / kWh*) # *A greenhouse gas emission factor for electricity used by the lodging.* committee :electricity_emission_factor do # Multiply the `eGRID subregion` electricity emission factors by 1 minus the the subregion's eGRID region loss factor (to account for transmission and distribution losses) to give *kg CO2e / kWh*. quorum 'from eGRID subregion', :needs => :egrid_subregion, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:egrid_subregion].electricity_emission_factor / (1 - characteristics[:egrid_subregion].egrid_region.loss_factor) end # Otherwise look up the `country` electricity emission factor (*kg CO2e / kWh*). quorum 'from country', :needs => :country, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| if (emission_factor = characteristics[:country].electricity_emission_factor).present? and (loss_factor = characteristics[:country].electricity_loss_factor).present? emission_factor / (1 - loss_factor) end end # Otherwise use a global average electricity emission factor (*kg CO2e / kWh*). quorum 'default', :complies => [:ghg_protocol_scope_3, :iso, :tcr] do Country.fallback.electricity_emission_factor / (1 - Country.fallback.electricity_loss_factor) end end #### Natural gas use (*m3*) # *The lodging's natural gas use during `timeframe`.* committee :natural_gas_use do # Multiply `room nights` (*room-nights*) by `natural gas intensity` (*m3 / occupied room-night*) to give *m3*. quorum 'from adjusted fuel intensities and room nights', :needs => [:adjusted_fuel_intensities, :room_nights], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:room_nights] * characteristics[:adjusted_fuel_intensities][:natural_gas] end end #### Fuel oil use (*l*) # *The lodging's fuel oil use during `timeframe`.* committee :fuel_oil_use do # Multiply `room nights` (*room-nights*) by `fuel oil intensity` (*l / occupied room-night*) to give *l*. quorum 'from adjusted fuel intensities and room nights', :needs => [:adjusted_fuel_intensities, :room_nights], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:room_nights] * characteristics[:adjusted_fuel_intensities][:fuel_oil] end end #### Electricity use (*kWh*) # *The lodging's electricity use during `timeframe`.* committee :electricity_use do # Multiply `room nights` (*room-nights*) by `electricity intensity` (*kWh / occupied room-night*) to give *kWh*. quorum 'from adjusted fuel intensities and room nights', :needs => [:adjusted_fuel_intensities, :room_nights], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:room_nights] * characteristics[:adjusted_fuel_intensities][:electricity] end end #### District heat use (*MJ*) # *The lodging's district heat use during `timeframe`.* committee :district_heat_use do # Multiply `room nights` (*room-nights*) by `district heat intensity` (*MJ / occupied room-night*) to give *MJ*. quorum 'from adjusted fuel intensities and room nights', :needs => [:adjusted_fuel_intensities, :room_nights], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:room_nights] * characteristics[:adjusted_fuel_intensities][:district_heat] end end #### Adjusted fuel intensities (*various*) # *The lodging's use per occupied room night of a variety of fuels, adjusted by number of pools, mini-fridges, etc.* # # - Natural gas intensity: *m3 / occupied room-night* # - Fuel oil intensity: *l / occupied room-night* # - Electricity intensity: *kWh / occupied room-night* # - District heat intensity: *MJ / occupied room-night* committee :adjusted_fuel_intensities do # Adjust `fuel intensities` based on any amenity adjustments: quorum 'from fuel intensities and amenity adjustments', :needs => :fuel_intensities, :appreciates => [:indoor_pool_adjustment, :outdoor_pool_adjustment, :refrigerator_adjustment, :hot_tub_adjustment], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| gas = Fuel.find_by_name('Pipeline Natural Gas') oil = Fuel.find_by_name('Residual Fuel Oil No. 6') # Duplicate the current intensities so we don't overwrite them. intensities = characteristics[:fuel_intensities].dup # TODO combine pool adjustments - so if we're below average in indoor but above in outdoor we don't accidentally subtract, get less than zero, discard some energy use, and then add energy back for outdoor pools # Cycle through each adjustment... characteristics.each do |characteristic, value| unless characteristic == :fuel_intensities value.each do |fuel, adjustment| if fuel == :pool_energy # If the adjustment relates to pool energy, convert natural gas and fuel oil inensities from physical units to energy units. gas_energy = intensities[:natural_gas] * gas.energy_content oil_energy = intensities[:fuel_oil] * oil.energy_content # Apply the adjustment to natural gas intensity. # If adjusted natural gas intensity reaches zero, apply any remaining adjustment to fuel oil intensity. # If adjusted fuel oil intensity also reaches zero, discard any remaining adjustment. if (gas_energy += adjustment) < 0.0 if (oil_energy += gas_energy) < 0.0 oil_energy = 0.0 end gas_energy = 0 end # Convert adjusted natural gas and fuel oil intensities from energy units back to physical units. intensities[:natural_gas] = gas_energy / gas.energy_content intensities[:fuel_oil] = oil_energy / oil.energy_content # Otherwise apply the adjustment to the appropriate fuel intensity. If adjusted fuel intensity reaches zero, discard any remaining adjustment. elsif (intensities[fuel] += adjustment) < 0.0 intensities[fuel] = 0.0 end end end end intensities end end #### Outdoor pools adjustment (*MJ / occupied room-night*) # *Adjusts the natural gas intensity based on the number of outdoor pools.* committee :outdoor_pool_adjustment do # Assume outdoor pool energy intensity of 329,917 *BTU / night* per [Energy Star](http://www.energystar.gov/ia/business/evaluate_performance/swimming_pool_tech_desc.pdf). # Calculate the difference between `outdoor pools` and average outdoor pools. # Multiply the difference by outdoor pool energy intensity (*MJ / night*) and divide by `property rooms` and `occupancy rate` to give *MJ / occupied room-night*. quorum 'from outdoor pools, property rooms, and occupancy rate', :needs => [:outdoor_pools, :property_rooms, :occupancy_rate], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| difference = characteristics[:outdoor_pools] - LodgingProperty.fallback.pools_outdoor { :pool_energy => difference * 329_917.btus.to(:megajoules) / characteristics[:property_rooms] / characteristics[:occupancy_rate] } end end #### Indoor pools adjustment (*MJ / occupied room-night*) # *Adjusts the natural gas intensity based on the number of indoor pools.* committee :indoor_pool_adjustment do # Assume indoor pool energy intensity of 2,770,942 *BTU / night* per [Energy Star](http://www.energystar.gov/ia/business/evaluate_performance/swimming_pool_tech_desc.pdf). # Calculate the difference between `indoor pools` and average indoor pools. # Multiply the difference by indoor pool energy intensity (*MJ / night*) and divide by `property rooms` and `occupancy rate` to give *MJ / occupied room-night*. quorum 'from indoor pools, property rooms, and occupancy rate', :needs => [:indoor_pools, :property_rooms, :occupancy_rate], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| difference = characteristics[:indoor_pools] - LodgingProperty.fallback.pools_indoor { :pool_energy => difference * 2_770_942.btus.to(:megajoules) / characteristics[:property_rooms] / characteristics[:occupancy_rate] } end end #### Hot tub adjustment (*kWh / occupied room-night*) # *Adjusts the electricity intensity based on the number of hot tubs.* committee :hot_tub_adjustment do # Calculate the difference between `hot tubs` and average hot tubs. # Assume hot tub electricity intensity of 6.3 *kWh / night* per [LBL residential energy data sourcebook, p128](http://enduse.lbl.gov/info/LBNL-40297.pdf). # Multiply the difference by the hot tub electricity intensity (*kWh / night*) and divide by `property rooms` and `occupancy rate` to give *kWh / occupied room-night*. quorum 'from hot tubs, property rooms, and occupancy rate', :needs => [:hot_tubs, :property_rooms, :occupancy_rate], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| difference = characteristics[:hot_tubs] - LodgingProperty.fallback.hot_tubs { :electricity => difference * 6.3 / characteristics[:property_rooms] / characteristics[:occupancy_rate] } end end #### Refrigerator adjustment (*kWh / occupied room-night*) # *Adjusts the electricity intensity based on refrigerator coverage.* committee :refrigerator_adjustment do # Calculate the difference between `refrigerator coverage` (*refrigerators / room*) and average refrigerator coverage (*refrigerators / room*). # Assume an auto-defrost compact fridge electricity intensity of 1.18 *kWh / refrigerator night* per [Energy Star](http://www.energystar.gov/ia/business/bulk_purchasing/bpsavings_calc/Bulk_Purchasing_CompactRefrig_Sav_Calc.xls). # Multiply the difference (*refrigerators / room*) by the refrigerator electricity intensity (*kWh / refrigerator night*) and divide by `occupancy rate` to give *kWh / occupied room-night*. quorum 'from refrigerator coverage and occupancy rate', :needs => [:refrigerator_coverage, :occupancy_rate], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| difference = characteristics[:refrigerator_coverage] - LodgingProperty.fallback.fridge_coverage { :electricity => (difference * 1.18 / characteristics[:occupancy_rate]) } end end #### Fuel intensities (*various*) # *The lodging's use per occupied room night of a variety of fuels.* # # - Natural gas intensity: *m3 / occupied room-night* # - Fuel oil intensity: *l / occupied room-night* # - Electricity intensity: *kWh / occupied room-night* # - District heat intensity: *MJ / occupied room-night* committee :fuel_intensities do # If we know `heating degree days` and `cooling degree days`, calculate fuel intensities from [CBECS 2003](http://data.brighterplanet.com/commercial_building_energy_consumption_survey_responses) data using fuzzy inference and adjust the inferred values based on `occupancy rate`. quorum 'from degree days, occupancy rate, and user inputs', :needs => [:heating_degree_days, :cooling_degree_days, :occupancy_rate], :appreciates => [:property_rooms, :floors, :construction_year, :ac_coverage], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| inputs = characteristics.to_hash.inject({}) do |memo, (characteristic, value)| case characteristic when :property_rooms memo[:lodging_rooms] = value when :ac_coverage memo[:percent_cooled] = value when :occupancy_rate # Don't include `occupancy rate` in inputs to fuzzy inference else memo[characteristic] = value end memo end kernel = CommercialBuildingEnergyConsumptionSurveyResponse.new(inputs) n, f, e, d = kernel.fuzzy_infer(:natural_gas_per_room_night, :fuel_oil_per_room_night, :electricity_per_room_night, :district_heat_per_room_night) { :natural_gas => n / characteristics[:occupancy_rate], :fuel_oil => f / characteristics[:occupancy_rate], :electricity => e / characteristics[:occupancy_rate], :district_heat => d / characteristics[:occupancy_rate] } end # Otherwise use global averages. quorum 'default', :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| intensities = { :natural_gas => Country.fallback.lodging_natural_gas_intensity, :fuel_oil => Country.fallback.lodging_fuel_oil_intensity, :electricity => Country.fallback.lodging_electricity_intensity, :district_heat => Country.fallback.lodging_district_heat_intensity, } end end #### Occupancy rate # *The percent of the proprety's rooms that are occupied on an average night.* committee :occupancy_rate do # Look up the `country` average lodging occupancy rate. quorum 'from country', :needs => :country, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:country].lodging_occupancy_rate end # Otherwise use a global average. quorum 'default', :complies => [:ghg_protocol_scope_3, :iso, :tcr] do Country.fallback.lodging_occupancy_rate end end #### Outdoor pools # *The number of outdoor pools.* committee :outdoor_pools do # Use client input, if available. # Otherwise look up the `property` number of outdoor pools, but set a ceiling of 5 outdoor pools. quorum 'from property', :needs => :property, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| unless characteristics[:property].pools_outdoor.nil? [characteristics[:property].pools_outdoor.to_f, 5].min end end end #### Indoor pools # *The number of indoor pools.* committee :indoor_pools do # Use client input, if available. # Otherwise look up the `property` number of indoor pools, but set a ceiling of 5 indoor pools. quorum 'from property', :needs => :property, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| unless characteristics[:property].pools_indoor.nil? [characteristics[:property].pools_indoor.to_f, 5].min end end end #### Hot tubs # *The number of hot tubs.* committee :hot_tubs do # Use client input, if available. # Otherwise look up the `property` number of hot tubs. quorum 'from property', :needs => :property, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:property].hot_tubs end end #### Refrigerator coverage # *The percentage of property rooms that have refrigerators.* committee :refrigerator_coverage do # Use client input, if available. # Otherwise take whichever is greater of the `property` refrigerator coverage and mini bar coverage. quorum 'from property', :needs => :property, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| if characteristics[:property].mini_bar_coverage || characteristics[:property].fridge_coverage [characteristics[:property].mini_bar_coverage.to_f, characteristics[:property].fridge_coverage.to_f].max end end end #### A/C coverage # *The percentage of property rooms that are air conditioned.* committee :ac_coverage do # Use client input, if available. # Otherwise look up the `property` A/C coverage. quorum 'from property', :needs => :property, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:property].ac_coverage end end #### Construction year # *The year the property was built.* committee :construction_year do # Use client input, if available. # Otherwise look up the year the `property` was built. quorum 'from property', :needs => :property, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:property].construction_year end end #### Floors # *The number of floors.* committee :floors do # Use client input, if available. # Otherwise look up the `property` number of floors. quorum 'from property', :needs => :property, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:property].floors end end #### Property rooms # *The number of guest rooms in the property.* committee :property_rooms do # Use client input, if available. # Otherwise look up the `property` number of rooms. quorum 'from property', :needs => :property, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:property].lodging_rooms end end #### Property # *The property where the stay occurred.* # # Use client input, if available #### Heating degree days # *The average number of annual heating degree days (base 18°C) at the lodging's location.* committee :heating_degree_days do # Use client input, if available # Otherwise look up the `climate division` heating degree days. quorum 'from climate division', :needs => :climate_division, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:climate_division].heating_degree_days end # Otherwise look up the `country` heating degree days. quorum 'from country', :needs => :country, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:country].heating_degree_days end end #### Cooling degree days # *The average number of annual cooling degree days (base 18°C) at the lodging's location.* committee :cooling_degree_days do # Use client input, if available # Otherwise look up the `climate division` cooling degree days. quorum 'from climate division', :needs => :climate_division, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:climate_division].cooling_degree_days end # Otherwise look up the `country` cooling degree days. quorum 'from country', :needs => :country, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:country].cooling_degree_days end end #### eGRID subregion # *The [eGRID subregion](http://data.brighterplanet.com/egrid_subregions).* committee :egrid_subregion do # Look up the `zip code` eGRID subregion. quorum 'from zip code', :needs => :zip_code, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:zip_code].egrid_subregion end end #### Country # *The [country](http://data.brighterplanet.com/countries).* committee :country do # Use client input, if available. # Otherwise if state is defined then the country is the United States. quorum 'from state', :needs => :state, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| Country.united_states end end #### State # *The [US state](http://data.brighterplanet.com/states).* committee :state do # Use client input, if available. # Otherwise look up the `zip code` state. quorum 'from zip code', :needs => :zip_code, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:zip_code].state end end #### City # *The city.* committee :city do # Use client input, if available. # Otherwise look up the `zip code` description. quorum 'from zip code', :needs => :zip_code, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:zip_code].description end end #### Climate division # *The [US climate division](http://data.brighterplanet.com/climate_divisions).* committee :climate_division do # Look up the `zip code` climate division. quorum 'from zip code', :needs => :zip_code, :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics| characteristics[:zip_code].climate_division end end #### Zip code # *The [US zip code](http://data.brighterplanet.com/zip_codes).* # # Use client input, if available. #### Room nights (*room-nights*) # *The stay's room-nights that occurred during `timeframe`.* committee :room_nights do # If `date` falls within `timeframe`, divide `duration` (*seconds*) by 86,400 (*seconds / night*) and multiply by `rooms` to give *room-nights*. # Otherwise `room nights` is zero. quorum 'from rooms, duration, date, and timeframe', :needs => [:rooms, :duration, :date], :complies => [:ghg_protocol_scope_3, :iso, :tcr] do |characteristics, timeframe| date = characteristics[:date].is_a?(Date) ? characteristics[:date] : Date.parse(characteristics[:date].to_s) timeframe.include?(date) ? characteristics[:duration] / 86400.0 * characteristics[:rooms] : 0 end end #### Duration (*seconds*) # *The stay's duration.* committee :duration do # Use client input, if available. # Otherwise use 86400 *seconds* (1 night). quorum 'default' do 86400.0 end end #### Rooms # *The number of rooms used.* committee :rooms do # Use client input, if available. # Otherwise use 1 *room*. quorum 'default' do 1 end end #### Date (*date*) # *The day the stay occurred.* committee :date do # Use client input, if available. # Otherwise use the first day of `timeframe`. quorum 'from timeframe', :complies => [:ghg_protocol_scope_3, :iso] do |characteristics, timeframe| timeframe.from end end end end end end end