Skip to content

Commit

Permalink
adapt EmissionFators so that it reades splitted csv files incl. tests
Browse files Browse the repository at this point in the history
  • Loading branch information
redfrexx committed May 5, 2024
1 parent b9fb8d4 commit 07d13f5
Show file tree
Hide file tree
Showing 10 changed files with 469 additions and 32 deletions.
18 changes: 6 additions & 12 deletions co2calculator/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,20 @@
class HeatingFuel(enum.Enum):
"""Enum for heating fuel types"""

HEAT_PUMP_AIR = "heat_pump_air"
HEAT_PUMP_GROUND = "heat_pump_ground"
HEAT_PUMP_WATER = "heat_pump_water"
LIQUID_GAS = "liquid_gas"
OIL = "oil"
PELLETS = "pellets"
SOLAR = "solar"
WOODCHIPS = "woodchips"
ELECTRICITY = "electricity"
GAS = "gas"
COAL = "coal"
DISTRICT_HEATING = "district_heating"
GAS = "gas"
WOOD_PELLETS = "wood pellets"
WOOD_CHIPS = "wood chips"
LPG = "liquid_gas"


@enum.unique
class ElectricityFuel(str, enum.Enum):
"""Enum for electricity fuel types"""

GERMAN_ENERGY_MIX = "german_energy_mix"
SOLAR = "solar"
PRODUCTION_FUEL_MIX = "production fuel mix"
RESIDUAL_FUEL_MIX = "residual fuel mix"


@enum.unique
Expand Down
244 changes: 244 additions & 0 deletions co2calculator/data/emission_factors_electricity.csv

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions co2calculator/data/emission_factors_heating.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
,country_code,category,source,name,unit,fuel_type,co2e_unit,co2e
0,global,heating,"UK, Department for Business, Energy & Industrial Strategy",coal (domestic),kWh,coal,kg/kWh,0.35
1,global,heating,"UK, Department for Business, Energy & Industrial Strategy",natural gas,kWh,gas,kg/kWh,0.18
2,global,heating,"UK, Department for Business, Energy & Industrial Strategy",LPG,kWh,liquid_gas,kg/kWh,0.21
3,global,heating,"UK, Department for Business, Energy & Industrial Strategy",fuel oil,kWh,oil,kg/kWh,0.27
4,global,heating,"UK, Department for Business, Energy & Industrial Strategy",wood pellets,kWh,wood pellets,kg/kWh,0.01074
5,global,heating,"UK, Department for Business, Energy & Industrial Strategy",wood chips,kWh,wood chips,kg/kWh,0.01074
61 changes: 61 additions & 0 deletions co2calculator/data/emission_factors_transport.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
,country_code,category,subcategory,source,name,unit,size_class,range,fuel_type,co2e_unit,co2e,seating
1,global,transport,bus,mobitool,Stadtbus (9m),P.km,small,local,cng,kg/P.km,0.1464,
2,global,transport,bus,mobitool,Stadtbus (13m),P.km,medium,local,cng,kg/P.km,0.1034,
3,global,transport,bus,mobitool,Stadtbus (18m),P.km,large,local,cng,kg/P.km,0.0709,
4,global,transport,bus,mobitool,Stadtbus (13m),P.km,average,local,cng,kg/P.km,0.1034,
5,global,transport,bus,mobitool,Stadtbus (13m),P.km,average,local,diesel,kg/P.km,0.1338,
6,global,transport,bus,mobitool,Stadtbus (13m),P.km,medium,local,diesel,kg/P.km,0.1338,
7,global,transport,bus,mobitool,Stadtbus (18m),P.km,large,local,diesel,kg/P.km,0.0905,
8,global,transport,bus,mobitool,Stadtbus (9m),P.km,small,local,diesel,kg/P.km,0.1716,
9,global,transport,bus,mobitool,Reisebus,P.km,small,long-distance,diesel,kg/P.km,0.0465,
10,global,transport,bus,mobitool,Trolleybus,P.km,average,local,electric,kg/P.km,0.0296,
11,global,transport,car,mobitool,Personenkraftwagen,P.km,large,,cng,kg/P.km,0.1528,
12,global,transport,car,mobitool,Personenkraftwagen,P.km,small,,cng,kg/P.km,0.1033,
13,global,transport,car,mobitool,Personenkraftwagen,P.km,medium,,cng,kg/P.km,0.1233,
14,global,transport,car,mobitool,Personenkraftwagen,P.km,average,,average,kg/P.km,0.1864,
15,global,transport,car,mobitool,Personenkraftwagen,P.km,average,,gasoline,kg/P.km,0.186,
16,global,transport,car,mobitool,Personenkraftwagen,P.km,small,,gasoline,kg/P.km,0.1297,
17,global,transport,car,mobitool,Personenkraftwagen,P.km,medium,,gasoline,kg/P.km,0.1506,
18,global,transport,car,mobitool,Personenkraftwagen,P.km,large,,gasoline,kg/P.km,0.2004,
19,global,transport,car,mobitool,Personenkraftwagen,P.km,average,,diesel,kg/P.km,0.189,
20,global,transport,car,mobitool,Personenkraftwagen,P.km,small,,diesel,kg/P.km,0.1109,
21,global,transport,car,mobitool,Personenkraftwagen,P.km,medium,,diesel,kg/P.km,0.1322,
22,global,transport,car,mobitool,Personenkraftwagen,P.km,large,,diesel,kg/P.km,0.1757,
23,global,transport,train,mobitool,Eisenbahn,P.km,,long-distance,,kg/P.km,0.0068,
24,global,transport,train,mobitool,Eisenbahn,P.km,,local,,kg/P.km,0.008,
25,global,transport,train,mobitool,Eisenbahn,P.km,,average,,kg/P.km,0.007,
26,global,transport,tram,mobitool,Strassenbahn,P.km,,local,electric,kg/P.km,0.0428,
27,DE,transport,train,mobitool,Bahn Deutschland,P.km,,average,,kg/P.km,0.0408,
28,FR,transport,train,mobitool,Bahn Frankreich,P.km,,average,,kg/P.km,0.0125,
29,IT,transport,train,mobitool,Bahn Italien,P.km,,average,,kg/P.km,0.0747,
30,AT,transport,train,mobitool,Bahn �sterreich,P.km,,average,,kg/P.km,0.0151,
31,global,transport,car,mobitool,Personenkraftwagen,P.km,large,,hybrid,kg/P.km,0.1785,
32,global,transport,car,mobitool,Personenkraftwagen,P.km,small,,hybrid,kg/P.km,0.1182,
33,global,transport,car,mobitool,Personenkraftwagen,P.km,medium,,hybrid,kg/P.km,0.1397,
34,global,transport,car,mobitool,Personenkraftwagen,P.km,average,,hybrid,kg/P.km,0.1397,
35,global,transport,car,mobitool,Personenkraftwagen,P.km,average,,plug-in_hybrid,kg/P.km,0.1278,
36,global,transport,car,mobitool,Personenkraftwagen,P.km,small,,plug-in_hybrid,kg/P.km,0.0799,
37,global,transport,car,mobitool,Personenkraftwagen,P.km,medium,,plug-in_hybrid,kg/P.km,0.1278,
38,global,transport,car,mobitool,Personenkraftwagen,P.km,large,,plug-in_hybrid,kg/P.km,0.1718,
39,global,transport,car,mobitool,Personenkraftwagen,P.km,average,,electric,kg/P.km,0.0898,
40,global,transport,car,mobitool,Personenkraftwagen,P.km,small,,electric,kg/P.km,0.0599,
41,global,transport,car,mobitool,Personenkraftwagen,P.km,medium,,electric,kg/P.km,0.0697,
42,global,transport,car,mobitool,Personenkraftwagen,P.km,large,,electric,kg/P.km,0.091,
43,global,transport,plane,mobitool,Flugzeug,P.km,,average,kerosine,kg/P.km,0.263,average
44,global,transport,plane,mobitool,Flugzeug,P.km,,short-haul,kerosine,kg/P.km,0.3192,average
45,global,transport,plane,mobitool,Flugzeug,P.km,,short-haul,kerosine,kg/P.km,0.2918,economy_class
46,global,transport,plane,mobitool,Flugzeug,P.km,,short-haul,kerosine,kg/P.km,0.4488,business_class
47,global,transport,plane,mobitool,Flugzeug,P.km,,long-haul,kerosine,kg/P.km,0.2372,average
48,global,transport,plane,mobitool,Flugzeug,P.km,,long-haul,kerosine,kg/P.km,0.1895,economy_class
49,global,transport,plane,mobitool,Flugzeug,P.km,,long-haul,kerosine,kg/P.km,0.3914,business_class
50,global,transport,plane,mobitool,Flugzeug,P.km,,long-haul,kerosine,kg/P.km,0.6031,first_class
51,global,transport,ferry,"UK, Department for Business, Energy & Industrial Strategy","Ferry, Average",P.km,,,,kg/P.km,0.11286,average
52,global,transport,motorbike,mobitool,Motorrad,P.km,small,,,kg/P.km,0.121,
53,global,transport,motorbike,mobitool,Motorrad,P.km,medium,,,kg/P.km,0.1548,
54,global,transport,motorbike,mobitool,Motorrad,P.km,large,,,kg/P.km,0.2036,
55,global,transport,motorbike,mobitool,Motorrad,P.km,average,,,kg/P.km,0.1636,
56,global,transport,bicycle,mobitool,Fahrrad,P.km,,,,kg/P.km,0.0056,
57,global,transport,pedelec,mobitool,E-Bike,P.km,,,,kg/P.km,0.0113,
58,global,transport,ferry,"UK, Department for Business, Energy & Industrial Strategy","Ferry, Foot passenger",P.km,,,,kg/P.km,0.01874,foot_passenger
59,global,transport,ferry,"UK, Department for Business, Energy & Industrial Strategy","Ferry, Car passenger",P.km,,,,kg/P.km,0.12952,car_passenger
60,global,transport,bus,mobitool,Reisebus,P.km,large,long-distance,diesel,kg/P.km,0.0474,
70 changes: 52 additions & 18 deletions co2calculator/data_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,74 @@
class EmissionFactors:
def __init__(self):
"""Init"""
self.emission_factors = pd.read_csv(f"{script_path}/data/emission_factors.csv")
self.column_names = self.emission_factors.columns
self.electricity = pd.read_csv(
f"{script_path}/data/emission_factors_electricity.csv"
)
self.heating = pd.read_csv(f"{script_path}/data/emission_factors_heating.csv")
self.transport = pd.read_csv(
f"{script_path}/data/emission_factors_transport.csv"
)

self.databases = {
"electricity": self.electricity,
"heating": self.heating,
"transport": self.transport,
}

def get(self, parameters: dict):
"""
Returns factors from the database
Returns factor from the database
:param parameters:
:type parameters:
:return:
:rtype:
"""
selected_factors = self.emission_factors
assert (
"category" in parameters
), "Please provide a category for the emission factor."
assert parameters["category"] in [
"electricity",
"heating",
"transport",
], "Please provide a valid emission factor category."

for k, v in parameters.items():
# TODO: shortterm hack to make it work until occupancy is removed from emission factors
if not isinstance(v, int):
v = str(v.value)
if v is None or k not in self.column_names:
continue
# Search suitable emission factors
selected_factors = self._search_factors(parameters, parameters["category"])

selected_factors_new = selected_factors[selected_factors[k] == v]
selected_factors = selected_factors_new
if selected_factors_new.empty:
raise EmissionFactorNotFound(
"No suitable emission factor found in database. Please adapt your query."
)

if len(selected_factors) > 1:
if len(selected_factors) == 0:
raise EmissionFactorNotFound(
"No suitable emission factor found in database. Please adapt your query."
)
elif len(selected_factors) > 1:
raise EmissionFactorNotFound(
f"{len(selected_factors)} emission factors found. Please provide more specific selection criteria."
)
else:
return selected_factors["co2e"].values[0]

def _search_factors(self, parameters, emission_category):
"""
Searches for factors in the database
:param parameters: Search parameters
:type parameters: dict
:param emission_category: Category of emission factors
:type emission_category: str
"""
# Select table for emission category
candidates = self.databases[emission_category]
for k, v in parameters.items():
# TODO: shortterm hack to make it work until occupancy is removed from emission factors
if not isinstance(v, int):
v = str(v.value)
if v is None or k not in candidates.columns:
continue
new_candidates = candidates[candidates[k] == v]
if new_candidates.empty:
return new_candidates
candidates = new_candidates

return candidates


class Airports:
def __init__(self):
Expand Down
12 changes: 11 additions & 1 deletion co2calculator/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
ElectricityFuel,
HeatingFuel,
Unit,
EmissionCategory,
)
from typing import Union


class TrainEmissionParameters(BaseModel):

category: TransportationMode = EmissionCategory.TRANSPORT
subcategory: TransportationMode = TransportationMode.TRAIN
fuel_type: Union[TrainFuel, str] = TrainFuel.AVERAGE
range: Union[BusTrainRange, str] = BusTrainRange.LONG_DISTANCE
Expand Down Expand Up @@ -51,6 +53,7 @@ def check_size(cls, v):

class TramEmissionParameters(BaseModel):

category: TransportationMode = EmissionCategory.TRANSPORT
subcategory: TransportationMode = TransportationMode.TRAM
size: Union[Size, str] = Size.AVERAGE

Expand All @@ -64,6 +67,7 @@ def check_size(cls, v):

class CarEmissionParameters(BaseModel):

category: TransportationMode = EmissionCategory.TRANSPORT
subcategory: TransportationMode = TransportationMode.CAR
fuel_type: Union[CarFuel, str] = CarFuel.AVERAGE
size: Union[Size, str] = Size.AVERAGE
Expand All @@ -86,6 +90,7 @@ def check_size(cls, v, values):

class PlaneEmissionParameters(BaseModel):

category: TransportationMode = EmissionCategory.TRANSPORT
subcategory: TransportationMode = TransportationMode.PLANE
seating: Union[FlightClass, str] = FlightClass.AVERAGE
range: Union[FlightRange, str]
Expand All @@ -108,6 +113,7 @@ def check_seating(cls, v):

class FerryEmissionParameters(BaseModel):

category: TransportationMode = EmissionCategory.TRANSPORT
subcategory: TransportationMode = TransportationMode.FERRY
seating: Union[FerryClass, str] = FerryClass.AVERAGE

Expand All @@ -121,6 +127,7 @@ def check_seating(cls, v):

class BusEmissionParameters(BaseModel):

category: TransportationMode = EmissionCategory.TRANSPORT
subcategory: TransportationMode = TransportationMode.BUS
fuel_type: Union[BusFuel, str] = BusFuel.DIESEL
size: Union[Size, str] = Size.AVERAGE
Expand Down Expand Up @@ -151,6 +158,7 @@ def check_range(cls, v):

class MotorbikeEmissionParameters(BaseModel):

category: TransportationMode = EmissionCategory.TRANSPORT
subcategory: TransportationMode = TransportationMode.MOTORBIKE
size: Union[Size, str] = Size.AVERAGE

Expand All @@ -164,7 +172,8 @@ def check_size(cls, v):

class ElectricityEmissionParameters(BaseModel):

fuel_type: Union[Size, str] = ElectricityFuel.GERMAN_ENERGY_MIX
category: TransportationMode = EmissionCategory.ELECTRICITY
fuel_type: Union[Size, str] = ElectricityFuel.PRODUCTION_FUEL_MIX

@validator("fuel_type", allow_reuse=True)
def check_fueltype(cls, v):
Expand All @@ -176,6 +185,7 @@ def check_fueltype(cls, v):

class HeatingEmissionParameters(BaseModel):

category: TransportationMode = EmissionCategory.HEATING
fuel_type: Union[Size, str] = HeatingFuel.GAS

@validator("fuel_type", allow_reuse=True)
Expand Down
2 changes: 1 addition & 1 deletion data/emission_factors.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
,country_code,category,subcategory,source,name,unit,size_class,range,fuel_type,co2e_unit,co2e,seating
,country_code,category,subcategory,source,name,unit,size,range,fuel_type,co2e_unit,co2e,seating
0,global,heating,,"UK, Department for Business, Energy & Industrial Strategy",coal (domestic),kWh,,,coal,kg/kWh,0.35,
1,global,heating,,"UK, Department for Business, Energy & Industrial Strategy",natural gas,kWh,,,gas,kg/kWh,0.18,
2,global,heating,,"UK, Department for Business, Energy & Industrial Strategy",LPG,kWh,,,liquid_gas,kg/kWh,0.21,
Expand Down
30 changes: 30 additions & 0 deletions docs/businesstrips.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
==============
Business trips
==============

.. toctree::
:maxdepth: 2
:caption: See also:
:titlesonly:
:includehidden:

calculate/distances
calculate/transport_modes

The calculation of emissions from business trips is currently supported for the following modes of transport:

* car
* bus
* train
* plane
* ferry

The user must specify either the distance of a trip or the location of departure and destination.
The distance (in km) may be used, when the user has the direct information of the distance travelled, e.g., from the speedometer of a car.
In other cases, the distance can be calculated from the given locations, see :ref:`Distance calculations <Distance calculations>`.
With the boolean paramter ``roundtrip``, users can indicate whether a trip was a roundtrip, in which case the distance wil be doubled.

Aside of the mode of transport, the user should provide the specifica of the trip, depending on the mode of transport
(see :doc'Transportation modes <calculate/transport_modes>' :doc:`Emission factors <calculate/emission_factors>`).

.. autofunction:: co2calculator.calculate.calc_co2_businesstrip
43 changes: 43 additions & 0 deletions tests/functional/test_data_code_compliance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Check if values in the csv files are compliant with enums"""

import pytest

from co2calculator import HeatingFuel, emission_factors, ElectricityFuel


@pytest.mark.parametrize(
"column_name,enum,emission_category",
[
pytest.param(
"fuel_type", HeatingFuel, "heating", id="fuel_type: 'HeatingFuel'"
),
pytest.param(
"fuel_type",
ElectricityFuel,
"electricity",
id="fuel_type: 'ElectricityFuel'",
),
],
)
def test_enums_heating(column_name, enum, emission_category):
"""Test whether all values in the csv files are present in the enums"""

# Get unique values of the size column
column_values = emission_factors.databases[emission_category][column_name].unique()

# Check if all column values are present in the enum
for v in column_values:
# skip is v is nan
if str(v) == "nan":
continue
assert v in (
item.value for item in enum
), f"'{v}' in column '{column_name}' of csv file is not contained in enum '{enum}'."

# Check if all values in the enum are present in the emission factor csv file
for item in enum:
assert (
item.value in column_values
), f"Column '{enum}' in emission_factors.csv does not contain value '{item.value}' of enum 'Size'"
14 changes: 14 additions & 0 deletions tests/unit/test_data_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Test data handlers"""

from co2calculator.data_handlers import EmissionFactors
import pandas as pd


def test_load_emission_factors():
"""Test if the emission factors are loaded correctly"""
emission_factors = EmissionFactors()
assert isinstance(emission_factors.heating, pd.DataFrame)
assert isinstance(emission_factors.electricity, pd.DataFrame)
assert isinstance(emission_factors.transport, pd.DataFrame)

0 comments on commit 07d13f5

Please sign in to comment.