Skip to content

Commit

Permalink
Merge pull request #172 from pledge4future/parametervalidation
Browse files Browse the repository at this point in the history
Validation of user parameters for all emission types
  • Loading branch information
redfrexx authored May 5, 2024
2 parents 56bf95f + d7cb1f5 commit 6769cef
Show file tree
Hide file tree
Showing 10 changed files with 479 additions and 386 deletions.
467 changes: 129 additions & 338 deletions co2calculator/calculate.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion co2calculator/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class CarFuel(str, enum.Enum):

ELECTRIC = "electric"
HYBRID = "hybrid"
PLUGIN_HYBRID = "plug-in hybrid"
PLUGIN_HYBRID = "plug-in_hybrid"
CNG = "cng"
GASOLINE = "gasoline"
DIESEL = "diesel"
Expand Down
89 changes: 89 additions & 0 deletions co2calculator/data_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Data handler class to handle and validate emission factors from csv file"""

from pathlib import Path
import pandas as pd
from .exceptions import EmissionFactorNotFound, ConversionFactorNotFound

script_path = str(Path(__file__).parent)


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

def get(self, parameters: dict):
"""
Returns factors from the database
:param parameters:
:type parameters:
:return:
:rtype:
"""
selected_factors = self.emission_factors

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

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:
raise EmissionFactorNotFound(
f"{len(selected_factors)} emission factors found. Please provide more specific selection criteria."
)
else:
return selected_factors["co2e"].values[0]


class Airports:
def __init__(self):
"""Init"""
self.airports = pd.read_csv(
"https://davidmegginson.github.io/ourairports-data/airports.csv"
)


class DetourFactors:
def __init__(self):
"""Init"""
self.detour_factors = pd.read_csv(f"{script_path}/../data/detour.csv")


class ConversionFactors:
def __init__(self):
"""Init"""
self.conversion_factors = pd.read_csv(
f"{script_path}/../data/conversion_factors_heating.csv"
)

def get(self, fuel_type, unit):
"""
Returns factors from the database
:param parameters:
:type parameters:
:return:
:rtype:
"""
selected_factors = self.conversion_factors.query(
f'fuel_type=="{fuel_type}" & unit=="{unit}"'
)
if selected_factors.empty:
raise ConversionFactorNotFound(
"No suitable conversion factor found in database. Please adapt your query."
)
else:
return selected_factors["conversion_value"].values[0]
15 changes: 15 additions & 0 deletions co2calculator/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Exceptions for the co2calculator package"""


class EmissionFactorNotFound(Exception):
def __init__(self, message):
"""Init"""
self.message = message


class ConversionFactorNotFound(Exception):
def __init__(self, message):
"""Init"""
self.message = message
186 changes: 186 additions & 0 deletions co2calculator/parameters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Base classes to handle and validate parameters for emission calculations"""

from pydantic import BaseModel, validator
from .constants import (
TransportationMode,
Size,
CarFuel,
BusFuel,
TrainFuel,
BusTrainRange,
FlightRange,
FlightClass,
FerryClass,
ElectricityFuel,
HeatingFuel,
Unit,
)
from typing import Union


class TrainEmissionParameters(BaseModel):

subcategory: TransportationMode = TransportationMode.TRAIN
fuel_type: Union[TrainFuel, str] = TrainFuel.AVERAGE
range: Union[BusTrainRange, str] = BusTrainRange.LONG_DISTANCE
size: Union[Size, str] = Size.AVERAGE

@validator("fuel_type", allow_reuse=True)
def check_fueltype(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in TrainFuel)
v = v.lower()
return TrainFuel(v)

@validator("range", allow_reuse=True)
def check_range(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in BusTrainRange)
v = v.lower()
return BusTrainRange(v)

@validator("size", allow_reuse=True)
def check_size(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in Size)
v = v.lower()
return Size(v)


class TramEmissionParameters(BaseModel):

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

@validator("size", allow_reuse=True)
def check_size(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in Size)
v = v.lower()
return Size(v)


class CarEmissionParameters(BaseModel):

subcategory: TransportationMode = TransportationMode.CAR
fuel_type: Union[CarFuel, str] = CarFuel.AVERAGE
size: Union[Size, str] = Size.AVERAGE
passengers: int = 1

@validator("fuel_type", allow_reuse=True)
def check_fueltype(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in CarFuel)
v = v.lower()
return CarFuel(v)

@validator("size", allow_reuse=True)
def check_size(cls, v, values):
if isinstance(v, str):
assert v.lower() in (item.value for item in Size)
v = v.lower()
return Size(v)


class PlaneEmissionParameters(BaseModel):

subcategory: TransportationMode = TransportationMode.PLANE
seating: Union[FlightClass, str] = FlightClass.AVERAGE
range: Union[FlightRange, str]

@validator("range")
def check_range(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in FlightRange)
v = v.lower()
return FlightRange(v)

@validator("seating")
def check_seating(cls, v):
if isinstance(v, str):
# Check if v is a valid value of enum FlightClass
assert v.lower() in (item.value for item in FlightClass)
v = v.lower()
return FlightClass(v)


class FerryEmissionParameters(BaseModel):

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

@validator("seating")
def check_seating(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in FerryClass)
v = v.lower()
return FerryClass(v)


class BusEmissionParameters(BaseModel):

subcategory: TransportationMode = TransportationMode.BUS
fuel_type: Union[BusFuel, str] = BusFuel.DIESEL
size: Union[Size, str] = Size.AVERAGE
occupancy: int = 50
range: Union[BusTrainRange, str] = BusTrainRange.LONG_DISTANCE

@validator("fuel_type", allow_reuse=True)
def check_fueltype(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in BusFuel)
v = v.lower()
return BusFuel(v)

@validator("size", allow_reuse=True)
def check_size(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in Size)
v = v.lower()
return Size(v)

@validator("range", allow_reuse=True)
def check_range(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in BusTrainRange)
v = v.lower()
return BusTrainRange(v)


class MotorbikeEmissionParameters(BaseModel):

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

@validator("size", allow_reuse=True)
def check_size(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in Size)
v = v.lower()
return Size(v)


class ElectricityEmissionParameters(BaseModel):

fuel_type: Union[Size, str] = ElectricityFuel.GERMAN_ENERGY_MIX

@validator("fuel_type", allow_reuse=True)
def check_fueltype(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in ElectricityFuel)
v = v.lower()
return ElectricityFuel(v)


class HeatingEmissionParameters(BaseModel):

fuel_type: Union[Size, str] = HeatingFuel.GAS

@validator("fuel_type", allow_reuse=True)
def check_fueltype(cls, v):
if isinstance(v, str):
assert v.lower() in (item.value for item in HeatingFuel)
v = v.lower()
return HeatingFuel(v)
2 changes: 1 addition & 1 deletion data/conversion_factors_heating.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
,fuel,unit,conversion_value
,fuel_type,unit,conversion_value
0,oil,l,10.6
1,liquid_gas,kg,14.1
2,coal,kg,6.0
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 @@
,category,subcategory,source,model,name,unit,size_class,occupancy,capacity,range,fuel_type,co2e_unit,co2e,seating,comment
,category,subcategory,source,model,name,unit,size,occupancy,capacity,range,fuel_type,co2e_unit,co2e,seating,comment
0,transport,bus,Öko-Institut,gemis,Bus-Linie-BZ-DE-2020-Basis,P.km,average,"",,local,hydrogen,kg/P.km,0.0251,,
1,transport,bus,Öko-Institut-adapted,gemis,Bus-Linie-BZ-DE-2020-Basis,P.km,average,"",,long-distance,hydrogen,kg/P.km,0.0251,,
2,transport,bus,Öko-Institut,gemis,Bus-Linie-CNG-DE-2020-Basis,P.km,average,"",,local,cng,kg/P.km,0.0617,,
Expand Down
4 changes: 2 additions & 2 deletions tests/functional/test_calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ class TestCalculateCommuting:
"transportation_mode,expected_emissions",
[
pytest.param("car", 9.03, id="transportation_mode: 'car'"),
pytest.param("bus", 1.63, id="transportation_mode: 'bus'"),
pytest.param("train", 2.54, id="transportation_mode: 'train'"),
pytest.param("bus", 1.65, id="transportation_mode: 'bus'"),
pytest.param("train", 1.38, id="transportation_mode: 'train'"),
pytest.param("bicycle", 0.38, id="transportation_mode: 'bicycle'"),
pytest.param("pedelec", 0.63, id="transportation_mode: 'pedelec'"),
pytest.param("motorbike", 4.77, id="transportation_mode: 'motorbike'"),
Expand Down
Loading

0 comments on commit 6769cef

Please sign in to comment.