def test__EcoDeclaration__constructor__not_all_emissions_have_same_keys__should_set_default_None_where_missing( ): declaration = EcoDeclaration( emissions={ begin1: EmissionValues(CO2=100, NOx=100), begin2: EmissionValues(CO2=100, CO=100), }, consumed_amount={ begin1: 100, begin2: 100, }, retired_amount={}, technologies={ begin1: EmissionValues(Solar=10, Wind=90), begin2: EmissionValues(Solar=20, Wind=80), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, ) assert sorted(declaration.emissions[begin1].keys()) == sorted( ['CO2', 'CO', 'NOx']) assert sorted(declaration.emissions[begin2].keys()) == sorted( ['CO2', 'CO', 'NOx']) assert declaration.emissions[begin1]['CO'] is None assert declaration.emissions[begin2]['NOx'] is None
def build_general_declaration(self, measurements, general_mix_emissions): """ :param list[Measurement] measurements: :param dict[datetime, dict[str, EmissionData]] general_mix_emissions: :rtype: EcoDeclaration """ # Emission in gram (mapped by begin) # {begin: {key: value}} emissions: Dict[datetime, EmissionValues[str, float]] = {} # Consumption in Wh (mapped by begin) # {begin: amount} consumed_amount: Dict[datetime, float] = {} # Consumed amount in Wh per technology (mapped by begin) # {begin: {technology: amount}} technologies: Dict[datetime, EmissionValues[str, float]] = {} # Group measurements by their begin measurements_sorted_and_grouped = groupby( iterable=sorted(measurements, key=lambda m: m.begin), key=lambda m: m.begin, ) for begin, measurements in measurements_sorted_and_grouped: # unique_sectors_this_begin = set(m.sector for m in measurements) unique_sectors = ('DK1', 'DK2') for sector in unique_sectors: if sector not in general_mix_emissions[begin]: continue mix = general_mix_emissions[begin][sector] emissions.setdefault(begin, EmissionValues()) emissions[begin] += mix.emissions consumed_amount.setdefault(begin, 0) consumed_amount[begin] += mix.amount technologies.setdefault(begin, EmissionValues()) technologies[begin] += mix.technologies return EcoDeclaration( emissions=emissions, consumed_amount=consumed_amount, retired_amount={}, technologies=technologies, resolution=EcoDeclarationResolution.hour, utc_offset=0, )
def emissions(self): """ Returns Emissions in gram :rtype: EmissionValues[str, float] """ return sum((part.emissions for part in self.parts), EmissionValues())
def mix_emissions_to_emission_data(mix_emissions): mix_emissions_grouped = groupby( iterable=mix_emissions, key=lambda row: (parse_mix_emissions_timestamp(row['timestamp_utc']), row['sector']), ) for (timestamp_utc, sector), rows in mix_emissions_grouped: parts = [] for row in rows: emissions = EmissionValues( **{ k: v for k, v in row.items() if k not in ('timestamp_utc', 'sector', 'technology', 'amount') }) parts.append( EmissionPart( technology=row['technology'], amount=row['amount'], emissions=emissions, )) yield EmissionData( timestamp_utc=timestamp_utc, sector=sector, parts=parts, )
def test__EcoDeclaration__constructor__consumed_amount_is_not_of_type_dict__should_raise_ValueError( ): with pytest.raises(ValueError): EcoDeclaration( emissions={ begin1: EmissionValues(), begin2: EmissionValues(), }, consumed_amount=123, # Should be dict retired_amount={}, technologies={ begin1: EmissionValues(Solar=110, Wind=90), begin2: EmissionValues(Solar=120, Wind=80), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, )
def technologies(self): """ Returns consumed amount per technology :rtype: EmissionValues[str, int] """ return EmissionValues( **{part.technology: part.amount for part in self.parts})
def test__EmissionData__amount__has_parts___should_return_sum_of_parts(): uut = EmissionData( sector='DK1', timestamp_utc=datetime(2020, 1, 1, 1, 0, tzinfo=timezone.utc), parts=[ EmissionPart(technology='A', amount=111, emissions=EmissionValues()), EmissionPart(technology='B', amount=222, emissions=EmissionValues()), EmissionPart(technology='C', amount=333, emissions=EmissionValues()), ], ) assert uut.amount == 111 + 222 + 333
def test__EmissionData__emissions_per_wh__has_parts_but_amount_is_zero___should_return_empty_EmissionValues( ): uut = EmissionData( sector='DK1', timestamp_utc=datetime(2020, 1, 1, 1, 0, tzinfo=timezone.utc), parts=[ EmissionPart(technology='A', amount=0, emissions=EmissionValues(CO2=100, CO=200)), EmissionPart(technology='B', amount=0, emissions=EmissionValues(CO2=300, CO=400, NOx=500)), EmissionPart(technology='C', amount=0, emissions=EmissionValues(CO=600, NOx=700)), ], ) assert isinstance(uut.emissions_per_wh, EmissionValues) assert uut.emissions_per_wh == {}
def test__EcoDeclaration__constructor__technologies_values_are_not_all_of_type_EmissionValues__should_raise_ValueError( ): with pytest.raises(ValueError): EcoDeclaration( emissions={ begin1: EmissionValues(), begin2: EmissionValues(), }, consumed_amount={ begin1: 100, begin2: 100, }, retired_amount={}, technologies={ begin1: 123, # Should be EmissionValues begin2: EmissionValues(Solar=20, Wind=80), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, )
def test__EcoDeclaration__constructor__emissions_and_consumed_amount_does_not_have_the_same_keys__should_raise_ValueError( ): with pytest.raises(ValueError): EcoDeclaration( emissions={ begin1: EmissionValues(), begin2: EmissionValues(), }, consumed_amount={ begin2: 100, # Should have begin1 and begin2 as keys, like emissions begin3: 100, }, retired_amount={}, technologies={ begin1: EmissionValues(Solar=110, Wind=90), begin2: EmissionValues(Solar=120, Wind=80), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, )
def technologies_share(self): """ Returns consumed amount per technology :rtype: EmissionValues[str, int] """ amount = self.amount if amount > 0: return self.technologies / amount else: return EmissionValues()
def emissions_per_wh(self): """ Returns Emissions per Wh (gram/Wh) :rtype: EmissionValues[str, float] """ amount = self.amount if amount > 0: return self.emissions / amount else: return EmissionValues()
def test__EcoDeclaration__constructor__sum_of_consumed_amount_is_not_equal_to_sum_of_technologies__should_raise_ValueError( ): with pytest.raises(ValueError): EcoDeclaration( emissions={ begin1: EmissionValues(), begin2: EmissionValues(), }, consumed_amount={ begin1: 100, begin2: 100, }, retired_amount={}, technologies={ begin1: EmissionValues(Solar=110, Wind=90), # Should be 10 + 90 = 100 begin2: EmissionValues(Solar=20, Wind=80), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, )
def test__EcoDeclaration__technologies_percentage__technologies_exists__should_return_EmissionValues_with_correct_values( ): # Arrange uut = EcoDeclaration( emissions={ begin1: EmissionValues(CO2=100, CH4=200), begin2: EmissionValues(CO2=300, CH4=400), begin3: EmissionValues(CO2=500, CH4=600, NOx=700), }, consumed_amount={ begin1: 10, begin2: 20, begin3: 30, }, retired_amount={}, technologies={ begin1: EmissionValues(Solar=5, Wind=5), begin2: EmissionValues(Solar=15, Wind=5), begin3: EmissionValues(Solar=20, Wind=10), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, ) # Assert assert isinstance(uut.technologies_percentage, EmissionValues) assert uut.technologies_percentage == { 'Wind': 20 / (10 + 20 + 30) * 100, 'Solar': 40 / (10 + 20 + 30) * 100, }
def test__EcoDeclaration__total_consumed_amount__consumed_amount_exists__should_return_correct_number( ): # Arrange uut = EcoDeclaration( emissions={ begin1: EmissionValues(CO2=100, CH4=200), begin2: EmissionValues(CO2=300, CH4=400), begin3: EmissionValues(CO2=500, CH4=600, NOx=700), }, consumed_amount={ begin1: 10, begin2: 20, begin3: 30, }, retired_amount={}, technologies={ begin1: EmissionValues(Solar=5, Wind=5), begin2: EmissionValues(Solar=15, Wind=5), begin3: EmissionValues(Solar=20, Wind=10), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, ) # Assert assert uut.total_consumed_amount == 10 + 20 + 30
def test__EcoDeclaration__total_emissions_per_wh__emissions_exists__should_return_EmissionValues_with_correct_values( ): # Arrange uut = EcoDeclaration( emissions={ begin1: EmissionValues(CO2=100, CH4=200), begin2: EmissionValues(CO2=300, CH4=400), begin3: EmissionValues(CO2=500, CH4=600, NOx=700), }, consumed_amount={ begin1: 0, begin2: 20, begin3: 30, }, retired_amount={}, technologies={ begin1: EmissionValues(), begin2: EmissionValues(Solar=15, Wind=5), begin3: EmissionValues(Solar=20, Wind=10), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, ) # Assert assert isinstance(uut.total_emissions_per_wh, EmissionValues) assert uut.total_emissions_per_wh == { 'CO2': (100 + 300 + 500) / (20 + 30), 'CH4': (200 + 400 + 600) / (20 + 30), 'NOx': 700 / (20 + 30), }
def test__EcoDeclaration__total_consumed_amount__NO_consumed_amount_exists__should_return_zero( ): # Arrange uut = EcoDeclaration( emissions={}, consumed_amount={}, retired_amount={}, technologies=EmissionValues(), resolution=EcoDeclarationResolution.hour, utc_offset=0, ) # Assert assert uut.total_consumed_amount == 0
def test__EmissionData__emissions_per_wh__has_parts___should_return_emissions_per_wh_as_EmissionValues( ): uut = EmissionData( sector='DK1', timestamp_utc=datetime(2020, 1, 1, 1, 0, tzinfo=timezone.utc), parts=[ EmissionPart(technology='A', amount=111, emissions=EmissionValues(CO2=100, CO=200)), EmissionPart(technology='B', amount=222, emissions=EmissionValues(CO2=300, CO=400, NOx=500)), EmissionPart(technology='C', amount=333, emissions=EmissionValues(CO=600, NOx=700)), ], ) assert isinstance(uut.emissions_per_wh, EmissionValues) assert uut.emissions_per_wh == { 'CO2': (100 + 300) / (111 + 222 + 333), 'CO': (200 + 400 + 600) / (111 + 222 + 333), 'NOx': (500 + 700) / (111 + 222 + 333), }
def test__EmissionData__technologies_share__has_parts___should_return_technologies_share_as_EmissionValues( ): uut = EmissionData( sector='DK1', timestamp_utc=datetime(2020, 1, 1, 1, 0, tzinfo=timezone.utc), parts=[ EmissionPart(technology='Solar', amount=111, emissions=EmissionValues(CO2=100, CO=200)), EmissionPart(technology='Wind', amount=222, emissions=EmissionValues(CO2=300, CO=400, NOx=500)), EmissionPart(technology='Coal', amount=333, emissions=EmissionValues(CO=600, NOx=700)), ], ) assert isinstance(uut.technologies_share, EmissionValues) assert uut.technologies_share == { 'Solar': 111 / (111 + 222 + 333), 'Wind': 222 / (111 + 222 + 333), 'Coal': 333 / (111 + 222 + 333), }
def test__EcoDeclaration__as_resolution__resolution_is_higher_than_current__should_raise_ValueError( current_resolution, new_resolution): # Arrange uut = EcoDeclaration( emissions={}, consumed_amount={}, retired_amount={}, technologies=EmissionValues(), resolution=current_resolution, utc_offset=0, ) # Assert with pytest.raises(ValueError): uut.as_resolution(new_resolution, 0)
def test__EcoDeclaration__total_emissions_per_wh__NO_emissions_exists__should_return_empty_EmissionValues( ): # Arrange uut = EcoDeclaration( emissions={}, consumed_amount={}, retired_amount={}, technologies=EmissionValues(), resolution=EcoDeclarationResolution.hour, utc_offset=0, ) # Assert assert isinstance(uut.total_emissions_per_wh, EmissionValues) assert uut.total_emissions_per_wh == {}
def test__EcoDeclaration__emissions_per_wh__consumed_amount_exists__should_return_EmissionValues_with_correct_values( ): # Arrange uut = EcoDeclaration( emissions={ begin1: EmissionValues(CO2=100, CH4=200), begin2: EmissionValues(CO2=300, CH4=400), begin3: EmissionValues(CO2=500, CH4=600, NOx=700), }, consumed_amount={ begin1: 0, begin2: 20, begin3: 40, }, retired_amount={}, technologies={ begin1: EmissionValues(), begin2: EmissionValues(Solar=15, Wind=5), begin3: EmissionValues(Solar=20, Wind=20), }, resolution=EcoDeclarationResolution.hour, utc_offset=0, ) # Assert assert isinstance(uut.emissions_per_wh, dict) assert all( isinstance(v, EmissionValues) for v in uut.emissions_per_wh.values()) assert uut.emissions_per_wh == { begin1: { 'CO2': 0, 'CH4': 0, 'NOx': 0 }, begin2: { 'CO2': 300 / 20, 'CH4': 400 / 20, 'NOx': 0 }, begin3: { 'CO2': 500 / 40, 'CH4': 600 / 40, 'NOx': 700 / 40 }, }
import pytest from origin.common import EmissionValues ev1 = EmissionValues( carbon_dioxide=1, methane=2, nitrous_oxide=3, greenhouse_gasses=4, sulfur_dioxide=5, nitrogen_dioxide=6, carbon_monoxide=7, hydrocarbons=8, particles=9, coal_fly_ash=10, coal_slag=11, desulfuriazion_products=12, slag=13, rga=14, bioash=15, radioactive_waste=16, ) ev2 = EmissionValues( carbon_dioxide=100, methane=200, nitrous_oxide=300, greenhouse_gasses=400, sulfur_dioxide=500, nitrogen_dioxide=600, carbon_monoxide=700,
import marshmallow from typing import List from itertools import groupby from datetime import datetime, timezone from dataclasses import dataclass from marshmallow_dataclass import NewType from origin.common import EmissionValues EmissionValuesType = NewType( name='EmissionValuesType', typ=dict, field=marshmallow.fields.Function, deserialize=lambda emissions: EmissionValues(**emissions), ) @dataclass class EmissionPart: technology: str # Consumed amount in Wh amount: int # Emissions in gram emissions: EmissionValuesType @dataclass class EmissionData: sector: str
def test__EcoDeclarationBuilder__integration(energytype_service_mock, datahub_service_mock): # -- Arrange ------------------------------------------------------------- uut = EcoDeclarationBuilder() user = Mock() gsrn1 = 'GSRN1' gsrn2 = 'GSRN2' gsrn3 = 'GSRN3' meteringpoints = [ Mock(gsrn=gsrn1, sector='DK1'), Mock(gsrn=gsrn2, sector='DK2'), Mock(gsrn=gsrn3, sector='DK2'), ] begin0 = datetime(2020, 1, 1, 1, 0, tzinfo=timezone.utc) begin1 = datetime(2020, 1, 1, 1, 0, tzinfo=timezone.utc) begin2 = datetime(2020, 1, 1, 2, 0, tzinfo=timezone.utc) begin3 = datetime(2020, 1, 1, 3, 0, tzinfo=timezone.utc) begin4 = datetime(2020, 1, 1, 4, 0, tzinfo=timezone.utc) # datahub_service.get_measurements() datahub_service_mock.get_measurements.return_value = GetMeasurementListResponse( success=True, total=9, measurements=[ Measurement(gsrn=gsrn1, sector='DK1', begin=begin1, amount=100000, end=None, address=None, type=None), Measurement(gsrn=gsrn1, sector='DK1', begin=begin2, amount=200000, end=None, address=None, type=None), Measurement(gsrn=gsrn1, sector='DK1', begin=begin3, amount=300000, end=None, address=None, type=None), Measurement(gsrn=gsrn1, sector='DK1', begin=begin4, amount=400000, end=None, address=None, type=None), Measurement(gsrn=gsrn2, sector='DK2', begin=begin3, amount=700000, end=None, address=None, type=None), Measurement(gsrn=gsrn2, sector='DK2', begin=begin4, amount=800000, end=None, address=None, type=None), # No Retired GGOs exists for GSRN3 # Two measurements in DK2 at begin6, only one of them should count Measurement(gsrn=gsrn3, sector='DK2', begin=begin4, amount=1000000, end=None, address=None, type=None), ]) # energytype_service.get_residual_mix() energytype_service_mock.get_residual_mix.return_value = GetMixEmissionsResponse( success=True, mix_emissions=[ # DK1 EmissionData(sector='DK1', timestamp_utc=begin1, parts=[ EmissionPart(technology='Solar', amount=111, emissions=EmissionValues(CO2=50, CH4=51)), EmissionPart(technology='Wind', amount=222, emissions=EmissionValues(CO2=52, CH4=53)), ]), EmissionData(sector='DK1', timestamp_utc=begin2, parts=[ EmissionPart(technology='Solar', amount=333, emissions=EmissionValues(CO2=54, CH4=55)), EmissionPart(technology='Wind', amount=444, emissions=EmissionValues(CO2=56, CH4=57)), ]), EmissionData(sector='DK1', timestamp_utc=begin3, parts=[ EmissionPart(technology='Solar', amount=555, emissions=EmissionValues(CO2=58, CH4=59)), EmissionPart(technology='Wind', amount=666, emissions=EmissionValues(CO2=60, CH4=61)), ]), EmissionData(sector='DK1', timestamp_utc=begin4, parts=[ EmissionPart(technology='Solar', amount=777, emissions=EmissionValues(CO2=62, CH4=63)), EmissionPart(technology='Wind', amount=888, emissions=EmissionValues(CO2=64, CH4=65)), ]), # DK2 EmissionData(sector='DK2', timestamp_utc=begin3, parts=[ EmissionPart(technology='Solar', amount=131313, emissions=EmissionValues(CO2=74, CH4=75)), EmissionPart(technology='Wind', amount=141414, emissions=EmissionValues(CO2=76, CH4=77)), ]), EmissionData(sector='DK2', timestamp_utc=begin4, parts=[ EmissionPart(technology='Solar', amount=151515, emissions=EmissionValues(CO2=78, CH4=79)), EmissionPart(technology='Wind', amount=161616, emissions=EmissionValues(CO2=80, CH4=81)), ]), ]) # uut.fetch_retired_ggos_from_db() uut.fetch_retired_ggos_from_db = Mock(return_value=[ # GSRN1 Ggo(retire_gsrn=gsrn1, begin=begin1, amount=100000, emissions={ 'CO2': 1, 'CH4': 2 }, technology=Mock(technology='Nuclear')), Ggo(retire_gsrn=gsrn1, begin=begin2, amount=2222, emissions={ 'CO2': 3, 'CH4': 4 }, technology=Mock(technology='Oil')), Ggo(retire_gsrn=gsrn1, begin=begin2, amount=3333, emissions=None, technology=Mock(technology='Waste')), # No emissions - is ignored Ggo(retire_gsrn=gsrn1, begin=begin3, amount=4444, emissions={ 'CO2': 5, 'CH4': 6 }, technology=Mock(technology='Hydro')), Ggo(retire_gsrn=gsrn1, begin=begin4, amount=5555, emissions={ 'CO2': 7, 'CH4': 8 }, technology=Mock(technology='Coal')), # GSRN2 Ggo(retire_gsrn=gsrn2, begin=begin3, amount=6666, emissions={ 'CO2': 9, 'CH4': 10 }, technology=Mock(technology='Biomass')), Ggo(retire_gsrn=gsrn2, begin=begin4, amount=7777, emissions={ 'CO2': 11, 'CH4': 12 }, technology=Mock(technology='Naturalgas')), Ggo(retire_gsrn=gsrn2, begin=begin4, amount=8888, emissions=None, technology=Mock(technology='Biogas')), # No emissions - is ignored ]) # -- Act ----------------------------------------------------------------- individual, general = uut.build_eco_declaration( user=user, meteringpoints=meteringpoints, begin_range=DateTimeRange(begin=begin0, end=begin4), session=Mock(), ) # -- Assert -------------------------------------------------------------- # Individual declaration assert individual.consumed_amount == { begin1: 100000, begin2: 200000, begin3: 300000 + 700000, begin4: 400000 + 800000 + 1000000, } # TODO assert technologies assert individual.emissions[begin1] == { 'CO2': 100000 * 1, 'CH4': 100000 * 2, } assert individual.emissions[begin2] == { 'CO2': 2222 * 3 + (200000 - 3333 - 2222) * ((54 + 56) / (333 + 444)), 'CH4': 2222 * 4 + (200000 - 3333 - 2222) * ((55 + 57) / (333 + 444)), } assert individual.emissions[begin3] == { 'CO2': 4444 * 5 + (300000 - 4444) * ((58 + 60) / (555 + 666)) + 6666 * 9 + (700000 - 6666) * ((74 + 76) / (131313 + 141414)), 'CH4': 4444 * 6 + (300000 - 4444) * ((59 + 61) / (555 + 666)) + 6666 * 10 + (700000 - 6666) * ((75 + 77) / (131313 + 141414)), } assert individual.emissions[begin4] == { 'CO2': 5555 * 7 + (400000 - 5555) * ((62 + 64) / (777 + 888)) + 7777 * 11 + (800000 - 8888 - 7777) * ((78 + 80) / (151515 + 161616)) + 1000000 * ((78 + 80) / (151515 + 161616)), 'CH4': 5555 * 8 + (400000 - 5555) * ((63 + 65) / (777 + 888)) + 7777 * 12 + (800000 - 8888 - 7777) * ((79 + 81) / (151515 + 161616)) + 1000000 * ((79 + 81) / (151515 + 161616)), } # General declaration assert general.consumed_amount == { begin1: 111 + 222, begin2: 333 + 444, begin3: 555 + 666 + 131313 + 141414, begin4: 777 + 888 + 151515 + 161616, } # TODO assert technologies assert general.emissions[begin1] == { 'CO2': 50 + 52, 'CH4': 51 + 53, } assert general.emissions[begin2] == { 'CO2': 54 + 56, 'CH4': 55 + 57, } assert general.emissions[begin3] == { 'CO2': 58 + 60 + 74 + 76, 'CH4': 59 + 61 + 75 + 77, } assert general.emissions[begin4] == { 'CO2': 62 + 64 + 78 + 80, 'CH4': 63 + 65 + 79 + 81, } # Correct call parameters to datahub_service_mock.get_measurements() datahub_service_mock.get_measurements.assert_called_once() get_measurements_call_args = datahub_service_mock.get_measurements.call_args[ 1] assert get_measurements_call_args['token'] is user.access_token assert get_measurements_call_args[ 'request'].filters.type is MeasurementType.CONSUMPTION assert get_measurements_call_args['request'].filters.gsrn == [ gsrn1, gsrn2, gsrn3 ] assert get_measurements_call_args[ 'request'].filters.begin_range.begin == begin1 assert get_measurements_call_args[ 'request'].filters.begin_range.end == begin4 # Correct call parameters to energytype_service.get_residual_mix() energytype_service_mock.get_residual_mix.assert_called_once() get_residual_mix_call_args = energytype_service_mock.get_residual_mix.call_args[ 1] assert sorted(get_residual_mix_call_args['sector']) == ['DK1', 'DK2'] assert get_residual_mix_call_args['begin_from'] == begin1 assert get_residual_mix_call_args['begin_to'] == begin4
def test__EcoDeclarationBuilder__build_general_declaration(): # -- Arrange ------------------------------------------------------------- uut = EcoDeclarationBuilder() begin1 = datetime(2020, 1, 1, 1, 0) begin2 = datetime(2020, 1, 1, 2, 0) begin3 = datetime(2020, 1, 1, 3, 0) measurements = [ Mock(sector='DK1', begin=begin1), Mock(sector='DK1', begin=begin2), Mock(sector='DK1', begin=begin3), # Two measurements in DK2 at begin2, only one of them should count Mock(sector='DK2', begin=begin1), Mock(sector='DK2', begin=begin2), Mock(sector='DK2', begin=begin2), ] general_mix_emissions = { begin1: { 'DK1': Mock( amount=110, emissions=EmissionValues(CO2=111, CH4=222), technologies=EmissionValues(Solar=35, Wind=75), ), 'DK2': Mock( amount=220, emissions=EmissionValues(CO2=333, CH4=444), technologies=EmissionValues(Solar=130, Wind=90), ), }, begin2: { 'DK1': Mock( amount=330, emissions=EmissionValues(CO2=555, CH4=666), technologies=EmissionValues(Solar=91, Wind=239), ), 'DK2': Mock( amount=440, emissions=EmissionValues(CO2=777, CH4=888), technologies=EmissionValues(Solar=250, Wind=190), ), }, begin3: { 'DK1': Mock( amount=500, emissions=EmissionValues(CO2=999, CH4=101010), technologies=EmissionValues(Solar=490, Wind=10), ), }, } # -- Act ----------------------------------------------------------------- declaration = uut.build_general_declaration( measurements=measurements, general_mix_emissions=general_mix_emissions, ) # -- Assert -------------------------------------------------------------- assert declaration.consumed_amount == { begin1: 110 + 220, begin2: 330 + 440, begin3: 500, } assert declaration.technologies == { begin1: { 'Solar': 35 + 130, 'Wind': 75 + 90, }, begin2: { 'Solar': 91 + 250, 'Wind': 239 + 190, }, begin3: { 'Solar': 490, 'Wind': 10, }, } assert declaration.emissions[begin1] == { 'CO2': 111 + 333, 'CH4': 222 + 444, } assert declaration.emissions[begin2] == { 'CO2': 555 + 777, 'CH4': 666 + 888, } assert declaration.emissions[begin3] == { 'CO2': 999, 'CH4': 101010, }
def test__EcoDeclaration__as_resolution__group_by_year(): # Arrange month1_day1_begin1 = datetime(2020, 1, 1, 0, 0) month1_day1_begin2 = datetime(2020, 1, 1, 1, 0) month1_day2_begin1 = datetime(2020, 1, 2, 0, 0) month1_day2_begin2 = datetime(2020, 1, 2, 1, 0) month2_day1_begin1 = datetime(2020, 2, 1, 0, 0) month2_day1_begin2 = datetime(2020, 2, 1, 1, 0) month2_day2_begin1 = datetime(2020, 2, 2, 0, 0) month2_day2_begin2 = datetime(2020, 2, 2, 1, 0) uut = EcoDeclaration( resolution=EcoDeclarationResolution.hour, utc_offset=0, emissions={ month1_day1_begin1: EmissionValues(CO2=1, NO2=2), month1_day1_begin2: EmissionValues(CO2=3, NO2=4), month1_day2_begin1: EmissionValues(CO2=5, NO2=6), month1_day2_begin2: EmissionValues(CO2=7, NO2=8), month2_day1_begin1: EmissionValues(CO2=9, NO2=10), month2_day1_begin2: EmissionValues(CO2=11, NO2=12), month2_day2_begin1: EmissionValues(CO2=13, NO2=14), month2_day2_begin2: EmissionValues(CO2=15, NO2=16), }, consumed_amount={ month1_day1_begin1: 10, month1_day1_begin2: 20, month1_day2_begin1: 30, month1_day2_begin2: 40, month2_day1_begin1: 50, month2_day1_begin2: 60, month2_day2_begin1: 70, month2_day2_begin2: 80, }, retired_amount={}, technologies={ month1_day1_begin1: EmissionValues(Solar=5, Wind=5), month1_day1_begin2: EmissionValues(Solar=15, Wind=5), month1_day2_begin1: EmissionValues(Solar=15, Wind=15), month1_day2_begin2: EmissionValues(Solar=35, Wind=5), month2_day1_begin1: EmissionValues(Solar=25, Wind=25), month2_day1_begin2: EmissionValues(Solar=55, Wind=5), month2_day2_begin1: EmissionValues(Solar=65, Wind=5), month2_day2_begin2: EmissionValues(Solar=75, Wind=5), }, ) # Act new_declaration = uut.as_resolution(EcoDeclarationResolution.year, 0) # Assert assert new_declaration.emissions == { datetime(2020, 1, 1, 0, 0): { 'CO2': 1 + 3 + 5 + 7 + 9 + 11 + 13 + 15, 'NO2': 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16, }, } assert new_declaration.consumed_amount == { datetime(2020, 1, 1, 0, 0): 10 + 20 + 30 + 40 + 50 + 60 + 70 + 80, } assert new_declaration.technologies == { datetime(2020, 1, 1, 0, 0): { 'Solar': 5 + 15 + 15 + 35 + 25 + 55 + 65 + 75, 'Wind': 5 + 5 + 15 + 5 + 25 + 5 + 5 + 5, }, }
def build_individual_declaration(self, measurements, retired_ggos, general_mix_emissions): """ :param list[Measurement] measurements: :param dict[str, dict[datetime, list[Ggo]]] retired_ggos: :param dict[datetime, dict[str, EmissionData]] general_mix_emissions: :rtype: EcoDeclaration """ # Emission in gram (mapped by begin) # {begin: {key: value}} emissions: Dict[datetime, EmissionValues[str, float]] = {} # Consumption in Wh (mapped by begin) # {begin: amount} consumed_amount: Dict[datetime, float] = {} # TODO retired_amount: Dict[datetime, float] = {} # Consumed amount in Wh per technology (mapped by begin) # {begin: {technology: amount}} technologies: Dict[datetime, EmissionValues[str, float]] = {} for m in measurements: ggos = retired_ggos.get(m.gsrn, {}).get(m.begin, []) ggos_total_amount = sum(ggo.amount for ggo in ggos) remaining_amount = m.amount - ggos_total_amount assert 0 <= ggos_total_amount <= m.amount assert 0 <= remaining_amount <= m.amount assert ggos_total_amount + remaining_amount == m.amount # Consumed amount consumed_amount.setdefault(m.begin, 0) consumed_amount[m.begin] += m.amount # Consumed amount retired_amount.setdefault(m.begin, 0) retired_amount[m.begin] += ggos_total_amount # Set default (empty) emission values for this begin emissions.setdefault(m.begin, EmissionValues()) # Set default (empty) emission values for this begin technologies.setdefault(m.begin, EmissionValues()) # Emission from retired GGOs for ggo in ggos: if ggo.emissions: emissions[m.begin] += \ EmissionValues(**ggo.emissions) * ggo.amount else: emissions[m.begin] += EmissionValues() technologies[m.begin].setdefault(ggo.technology_label, 0) technologies[m.begin][ggo.technology_label] += ggo.amount # Remaining emission from General mix # Assume there exists mix emissions for each # begin in the period, otherwise fail hard if remaining_amount: mix = general_mix_emissions[m.begin][m.sector] emissions[m.begin] += \ mix.emissions_per_wh * remaining_amount technologies[m.begin] += \ mix.technologies_share * remaining_amount return EcoDeclaration( emissions=emissions, consumed_amount=consumed_amount, retired_amount=retired_amount, technologies=technologies, resolution=EcoDeclarationResolution.hour, utc_offset=0, )
def test__EcoDeclarationBuilder__build_individual_declaration(): """ begin1: One measurement with 100% of emission from retired GGO begin2: One measurement with 50% of emission from retired GGO and 50% emission from general mix emissions begin3: Two measurements with 100% of emission from retired GGOs begin4: Two measurements with 50% of emission from retired GGOs and 50% emission from general mix emissions begin5: One measurement with 100% of emission from general mix emissions begin6: Two measurements with 100% of emission from general mix emissions """ # -- Arrange ------------------------------------------------------------- uut = EcoDeclarationBuilder() gsrn1 = 'GSRN1' gsrn2 = 'GSRN2' gsrn3 = 'GSRN3' begin1 = datetime(2020, 1, 1, 1, 0) begin2 = datetime(2020, 1, 1, 2, 0) begin3 = datetime(2020, 1, 1, 3, 0) begin4 = datetime(2020, 1, 1, 4, 0) begin5 = datetime(2020, 1, 1, 5, 0) begin6 = datetime(2020, 1, 1, 6, 0) measurements = [ Mock(gsrn=gsrn1, sector='DK1', begin=begin1, amount=100), Mock(gsrn=gsrn1, sector='DK1', begin=begin2, amount=200), Mock(gsrn=gsrn1, sector='DK1', begin=begin3, amount=300), Mock(gsrn=gsrn1, sector='DK1', begin=begin4, amount=400), Mock(gsrn=gsrn1, sector='DK1', begin=begin5, amount=500), Mock(gsrn=gsrn1, sector='DK1', begin=begin6, amount=600), Mock(gsrn=gsrn2, sector='DK2', begin=begin3, amount=700), Mock(gsrn=gsrn2, sector='DK2', begin=begin4, amount=800), Mock(gsrn=gsrn2, sector='DK2', begin=begin6, amount=900), # No Retired GGOs exists for GSRN3 Mock(gsrn=gsrn3, sector='DK2', begin=begin6, amount=1000), ] retired_ggos = { gsrn1: { begin1: [ Mock(amount=100, technology_label='Coal', emissions={ 'CO2': 1, 'CH4': 2 }) ], begin2: [ Mock(amount=25, technology_label='Coal', emissions={ 'CO2': 3, 'CH4': 4 }), Mock(amount=50, technology_label='Wind', emissions=None) ], begin3: [ Mock(amount=300, technology_label='Coal', emissions={ 'CO2': 5, 'CH4': 6 }) ], begin4: [ Mock(amount=60, technology_label='Coal', emissions={ 'CO2': 7, 'CH4': 8 }) ], }, gsrn2: { begin3: [ Mock(amount=700, technology_label='Coal', emissions={ 'CO2': 9, 'CH4': 10 }) ], begin4: [ Mock(amount=50, technology_label='Coal', emissions={ 'CO2': 11, 'CH4': 12 }), Mock(amount=50, technology_label='Wind', emissions=None) ], }, } general_mix_emissions = { begin1: { 'DK1': Mock( emissions_per_wh=EmissionValues(CO2=13, CH4=14), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), }, begin2: { 'DK1': Mock( emissions_per_wh=EmissionValues(CO2=15, CH4=16), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), }, begin3: { 'DK1': Mock( emissions_per_wh=EmissionValues(CO2=17, CH4=18), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), 'DK2': Mock( emissions_per_wh=EmissionValues(CO2=25, CH4=26), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), }, begin4: { 'DK1': Mock( emissions_per_wh=EmissionValues(CO2=19, CH4=20), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), 'DK2': Mock( emissions_per_wh=EmissionValues(CO2=27, CH4=28), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), }, begin5: { 'DK1': Mock( emissions_per_wh=EmissionValues(CO2=21, CH4=22), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), }, begin6: { 'DK1': Mock( emissions_per_wh=EmissionValues(CO2=23, CH4=24), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), 'DK2': Mock( emissions_per_wh=EmissionValues(CO2=29, CH4=30), technologies_share=EmissionValues(Solar=0.5, Wind=0.5), ), }, } # -- Act ----------------------------------------------------------------- declaration = uut.build_individual_declaration( measurements=measurements, retired_ggos=retired_ggos, general_mix_emissions=general_mix_emissions, ) # -- Assert -------------------------------------------------------------- assert declaration.consumed_amount == { begin1: 100, begin2: 200, begin3: 300 + 700, begin4: 400 + 800, begin5: 500, begin6: 600 + 900 + 1000, } # TODO Assert declaration.technologies assert declaration.emissions[begin1] == { 'CO2': 100 * 1, 'CH4': 100 * 2, } assert declaration.emissions[begin2] == { 'CO2': 25 * 3 + (200 - 50 - 25) * 15, 'CH4': 25 * 4 + (200 - 50 - 25) * 16, } assert declaration.emissions[begin3] == { 'CO2': 300 * 5 + 700 * 9, 'CH4': 300 * 6 + 700 * 10, } assert declaration.emissions[begin4] == { 'CO2': 60 * 7 + (400 - 60) * 19 + 50 * 11 + (800 - 50 - 50) * 27, 'CH4': 60 * 8 + (400 - 60) * 20 + 50 * 12 + (800 - 50 - 50) * 28, } assert declaration.emissions[begin5] == { 'CO2': 500 * 21, 'CH4': 500 * 22, } assert declaration.emissions[begin6] == { 'CO2': 600 * 23 + 900 * 29 + 1000 * 29, 'CH4': 600 * 24 + 900 * 30 + 1000 * 30, }