From d066f2ce17e89fefef5944c6516435f7e4271765 Mon Sep 17 00:00:00 2001 From: Guille Date: Fri, 14 Jul 2023 15:37:12 -0400 Subject: [PATCH] partial refactor --- .../{life_cycle_costs.py => capital_costs.py} | 122 +++++++++++------- costs/configuration.py | 14 +- costs/cost.py | 2 +- costs/end_of_life_costs.py | 48 +++++++ costs/life_cycle_costs_old.py | 2 + costs/total_maintenance_costs.py | 75 +++++++++++ costs/total_operational_costs.py | 109 ++++++++++++++++ costs/total_operational_incomes.py | 55 ++++++++ 8 files changed, 376 insertions(+), 51 deletions(-) rename costs/{life_cycle_costs.py => capital_costs.py} (73%) create mode 100644 costs/end_of_life_costs.py create mode 100644 costs/total_maintenance_costs.py create mode 100644 costs/total_operational_costs.py create mode 100644 costs/total_operational_incomes.py diff --git a/costs/life_cycle_costs.py b/costs/capital_costs.py similarity index 73% rename from costs/life_cycle_costs.py rename to costs/capital_costs.py index be85e8c..c40d125 100644 --- a/costs/life_cycle_costs.py +++ b/costs/capital_costs.py @@ -1,5 +1,5 @@ """ -Life cycle costs module +Capital costs module """ import math @@ -11,20 +11,26 @@ from configuration import Configuration from costs import SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV, SYSTEM_RETROFIT_AND_PV -class LifeCycleCosts: +class CapitalCosts: """ - Life cycle costs class + Capital costs class """ - def __init__(self, building: Building, configuration: Configuration, retrofit_scenario): + def __init__(self, building: Building, configuration: Configuration): self._building = building self._configuration = configuration - self._retrofit_scenario = retrofit_scenario self._total_floor_area = 0 for internal_zone in building.internal_zones: for thermal_zone in internal_zone.thermal_zones: self._total_floor_area += thermal_zone.total_floor_area - self._archetype = None + for archetype in self._configuration.cost_catalog.entries('archetypes').archetype: + if str(building.function) == str(archetype.function): + self._archetype = archetype + self._capital_costs_chapter = self._archetype.capital_cost + break + if not self._archetype: + raise KeyError('archetype not found') + self._capital_costs_chapter = None rng = range(configuration.number_of_years) self._yearly_capital_costs = pd.DataFrame( @@ -66,18 +72,10 @@ class LifeCycleCosts: self._yearly_capital_incomes.loc[0, 'Subsidies HVAC'] = 0 self._yearly_capital_incomes.loc[0, 'Subsidies PV'] = 0 - for archetype in self._configuration.cost_catalog.entries('archetypes').archetype: - if str(building.function) == str(archetype.function): - self._archetype = archetype - self._capital_costs_chapter = self._archetype.capital_cost - break - if not self._archetype: - raise KeyError('archetype not found') - - def calculate_capital_costs(self): + def calculate(self) -> tuple[pd.DataFrame, pd.DataFrame]: """ Calculate capital cost - :return: pd.DataFrame + :return: pd.DataFrame, pd.DataFrame """ surface_opaque = 0 surface_transparent = 0 @@ -114,7 +112,7 @@ class LifeCycleCosts: self._yearly_capital_costs.fillna(0, inplace=True) own_capital = (1 - self._configuration.percentage_credit) - if self._retrofit_scenario in (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): + if self._configuration.retrofit_scenario in (SKIN_RETROFIT, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): chapter = self._capital_costs_chapter.chapter('B_shell') capital_cost_opaque = surface_opaque * chapter.item('B2010_opaque_walls').refurbishment[0] capital_cost_transparent = surface_transparent * chapter.item('B2020_transparent').refurbishment[0] @@ -125,7 +123,7 @@ class LifeCycleCosts: self._yearly_capital_costs.loc[0, 'B3010_opaque_roof'] = capital_cost_roof * own_capital self._yearly_capital_costs.loc[0]['B10_superstructure'] = capital_cost_ground * own_capital - if self._retrofit_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): + if self._configuration.retrofit_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): chapter = self._capital_costs_chapter.chapter('D_services') capital_cost_pv = surface_pv * chapter.item('D301010_photovoltaic_system').initial_investment[0] capital_cost_heating_equipment = peak_heating * chapter.item('D3020_heat_generating_systems').initial_investment[0] @@ -160,7 +158,8 @@ class LifeCycleCosts: self._yearly_capital_costs.loc[year, 'B3010_opaque_roof'] = ( -npf.pmt( self._configuration.interest_rate, - self._configuration.credit_years,capital_cost_roof * self._configuration.percentage_credit + self._configuration.credit_years, + capital_cost_roof * self._configuration.percentage_credit ) ) self._yearly_capital_costs.loc[year, 'B10_superstructure'] = ( @@ -170,46 +169,71 @@ class LifeCycleCosts: capital_cost_ground * self._configuration.percentage_credit ) ) - self._yearly_capital_costs.loc[year, 'D3020_heat_generating_systems'] = -npf.pmt(self._configuration.interest_rate,self._configuration.credit_years, - capital_cost_heating_equipment - * (self._configuration.percentage_credit)) - self._yearly_capital_costs.loc[year, 'D3030_cooling_generation_systems'] = -npf.pmt(self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_cooling_equipment - * (self._configuration.percentage_credit)) - self._yearly_capital_costs.loc[year, 'D3040_distribution_systems'] = -npf.pmt(self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_distribution_equipment - * (self._configuration.percentage_credit)) - self._yearly_capital_costs.loc[year, 'D3080_other_hvac_ahu'] = -npf.pmt(self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_other_hvac_ahu - * (self._configuration.percentage_credit)) - self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] = -npf.pmt(self._configuration.interest_rate, self._configuration.credit_years, - capital_cost_lighting - * (self._configuration.percentage_credit)) + self._yearly_capital_costs.loc[year, 'D3020_heat_generating_systems'] = ( + -npf.pmt( + self._configuration.interest_rate, + self._configuration.credit_years, + capital_cost_heating_equipment * self._configuration.percentage_credit + ) + ) + self._yearly_capital_costs.loc[year, 'D3030_cooling_generation_systems'] = ( + -npf.pmt( + self._configuration.interest_rate, + self._configuration.credit_years, + capital_cost_cooling_equipment * self._configuration.percentage_credit + ) + ) + self._yearly_capital_costs.loc[year, 'D3040_distribution_systems'] = ( + -npf.pmt( + self._configuration.interest_rate, + self._configuration.credit_years, + capital_cost_distribution_equipment * self._configuration.percentage_credit + ) + ) + self._yearly_capital_costs.loc[year, 'D3080_other_hvac_ahu'] = ( + -npf.pmt( + self._configuration.interest_rate, + self._configuration.credit_years, + capital_cost_other_hvac_ahu * self._configuration.percentage_credit + ) + ) + self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] = ( + -npf.pmt( + self._configuration.interest_rate, + self._configuration.credit_years, + capital_cost_lighting * self._configuration.percentage_credit + ) + ) + if (year % chapter.item('D3020_heat_generating_systems').lifetime) == 0: - reposition_cost_heating_equipment = peak_heating * chapter.item('D3020_heat_generating_systems').reposition[0] \ - * costs_increase + reposition_cost_heating_equipment = ( + peak_heating * chapter.item('D3020_heat_generating_systems').reposition[0] * costs_increase + ) self._yearly_capital_costs.loc[year, 'D3020_heat_generating_systems'] += reposition_cost_heating_equipment if (year % chapter.item('D3030_cooling_generation_systems').lifetime) == 0: - reposition_cost_cooling_equipment = peak_cooling \ - * chapter.item('D3030_cooling_generation_systems').reposition[0] \ - * costs_increase + reposition_cost_cooling_equipment = ( + peak_cooling * chapter.item('D3030_cooling_generation_systems').reposition[0] * costs_increase + ) self._yearly_capital_costs.loc[year, 'D3030_cooling_generation_systems'] += reposition_cost_cooling_equipment if (year % chapter.item('D3080_other_hvac_ahu').lifetime) == 0: - reposition_cost_hvac_ahu = peak_cooling * chapter.item('D3080_other_hvac_ahu').reposition[0] * costs_increase + reposition_cost_hvac_ahu = ( + peak_cooling * chapter.item('D3080_other_hvac_ahu').reposition[0] * costs_increase + ) self._yearly_capital_costs.loc[year, 'D3080_other_hvac_ahu'] = reposition_cost_hvac_ahu if (year % chapter.item('D5020_lighting_and_branch_wiring').lifetime) == 0: - reposition_cost_lighting = total_floor_area * chapter.item('D5020_lighting_and_branch_wiring').reposition[0] \ - * costs_increase + reposition_cost_lighting = ( + self._total_floor_area * chapter.item('D5020_lighting_and_branch_wiring').reposition[0] * costs_increase + ) self._yearly_capital_costs.loc[year, 'D5020_lighting_and_branch_wiring'] += reposition_cost_lighting - if self._retrofitting_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): + if self._configuration.retrofit_scenario in (SYSTEM_RETROFIT_AND_PV, SKIN_RETROFIT_AND_SYSTEM_RETROFIT_AND_PV): if (year % chapter.item('D301010_photovoltaic_system').lifetime) == 0: - self._yearly_capital_costs.loc[year]['D301010_photovoltaic_system'] += surface_pv \ - * chapter.item( - 'D301010_photovoltaic_system').reposition[0] * costs_increase + self._yearly_capital_costs.loc[year]['D301010_photovoltaic_system'] += ( + surface_pv * chapter.item('D301010_photovoltaic_system').reposition[0] * costs_increase + ) capital_cost_skin = capital_cost_opaque + capital_cost_ground + capital_cost_transparent + capital_cost_roof capital_cost_hvac = ( capital_cost_heating_equipment + @@ -219,9 +243,9 @@ class LifeCycleCosts: ) self._yearly_capital_incomes.loc[0, 'Subsidies construction'] = ( - capital_cost_skin * archetype.income.construction_subsidy/100 + capital_cost_skin * self._archetype.income.construction_subsidy/100 ) - self._yearly_capital_incomes.loc[0, 'Subsidies HVAC'] = capital_cost_hvac * archetype.income.hvac_subsidy/100 - self._yearly_capital_incomes.loc[0, 'Subsidies PV'] = capital_cost_pv * archetype.income.photovoltaic_subsidy/100 + self._yearly_capital_incomes.loc[0, 'Subsidies HVAC'] = capital_cost_hvac * self._archetype.income.hvac_subsidy/100 + self._yearly_capital_incomes.loc[0, 'Subsidies PV'] = capital_cost_pv * self._archetype.income.photovoltaic_subsidy/100 self._yearly_capital_incomes.fillna(0, inplace=True) return self._yearly_capital_costs, self._yearly_capital_incomes diff --git a/costs/configuration.py b/costs/configuration.py index afbdd5b..4e87da4 100644 --- a/costs/configuration.py +++ b/costs/configuration.py @@ -21,7 +21,9 @@ class Configuration: gas_price_index, discount_rate, retrofitting_year_construction, - factories_handler + factories_handler, + retrofit_scenario, + fuel_type ): self._number_of_years = number_of_years self._percentage_credit = percentage_credit @@ -35,6 +37,8 @@ class Configuration: self._retrofitting_year_construction = retrofitting_year_construction self._factories_handler = factories_handler self._cost_catalog = CostCatalogFactory(factories_handler).catalog + self._retrofit_scenario = retrofit_scenario + self._fuel_type = fuel_type @property def number_of_years(self): @@ -196,3 +200,11 @@ class Configuration: Get cost catalog """ return self._cost_catalog + + @property + def retrofit_scenario(self): + return self._retrofit_scenario + + @property + def fuel_type(self): + return self._fuel_type diff --git a/costs/cost.py b/costs/cost.py index 7adebb9..b49f4f4 100644 --- a/costs/cost.py +++ b/costs/cost.py @@ -5,7 +5,7 @@ import pandas as pd from hub.city_model_structure.city import City from configuration import Configuration -from life_cycle_costs import LifeCycleCosts +from capital_costs import LifeCycleCosts class Cost: diff --git a/costs/end_of_life_costs.py b/costs/end_of_life_costs.py new file mode 100644 index 0000000..bf45384 --- /dev/null +++ b/costs/end_of_life_costs.py @@ -0,0 +1,48 @@ +""" +End of life costs module +""" +import math +import pandas as pd +from hub.city_model_structure.building import Building + +from configuration import Configuration + + +class EndOfLifeCosts: + """ + End of life costs class + """ + def __init__(self, building: Building, configuration: Configuration): + self._building = building + self._configuration = configuration + self._total_floor_area = 0 + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + self._total_floor_area += thermal_zone.total_floor_area + self._archetype = None + for archetype in self._configuration.cost_catalog.entries('archetypes').archetype: + if str(building.function) == str(archetype.function): + self._archetype = archetype + self._capital_costs_chapter = self._archetype.capital_cost + break + if not self._archetype: + raise KeyError('archetype not found') + + rng = range(configuration.number_of_years) + self._yearly_end_of_life_costs = pd.DataFrame(index=rng, columns=['End_of_life_costs'], dtype='float') + + def calculate(self): + """ + Calculate end of life costs + :return: pd.DataFrame + """ + archetype = self._archetype + total_floor_area = self._total_floor_area + for year in range(1, self._configuration.number_of_years + 1): + price_increase = math.pow(1 + self._configuration.consumer_price_index, year) + if year == self._configuration.number_of_years: + self._yearly_end_of_life_costs.at[year, 'End_of_life_costs'] = ( + total_floor_area * archetype.end_of_life_cost * price_increase + ) + self._yearly_end_of_life_costs.fillna(0, inplace=True) + return self._yearly_end_of_life_costs diff --git a/costs/life_cycle_costs_old.py b/costs/life_cycle_costs_old.py index 7fc3974..db21264 100644 --- a/costs/life_cycle_costs_old.py +++ b/costs/life_cycle_costs_old.py @@ -334,6 +334,8 @@ class LifeCycleCosts: self._yearly_operational_incomes.fillna(0, inplace=True) return self._yearly_operational_incomes + ___________________________________________________________ + def calculate_total_maintenance_costs(self): """ Calculate total maintenance costs diff --git a/costs/total_maintenance_costs.py b/costs/total_maintenance_costs.py new file mode 100644 index 0000000..42a7121 --- /dev/null +++ b/costs/total_maintenance_costs.py @@ -0,0 +1,75 @@ +""" +Total maintenance costs module +""" +import math +import pandas as pd +from hub.city_model_structure.building import Building +import hub.helpers.constants as cte + +from configuration import Configuration + + +class TotalMaintenanceCosts: + """ + Total maintenance costs class + """ + def __init__(self, building: Building, configuration: Configuration): + self._building = building + self._configuration = configuration + self._total_floor_area = 0 + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + self._total_floor_area += thermal_zone.total_floor_area + self._archetype = None + for archetype in self._configuration.cost_catalog.entries('archetypes').archetype: + if str(building.function) == str(archetype.function): + self._archetype = archetype + self._capital_costs_chapter = self._archetype.capital_cost + break + if not self._archetype: + raise KeyError('archetype not found') + + rng = range(configuration.number_of_years) + self._yearly_maintenance_costs = pd.DataFrame( + index=rng, + columns=[ + 'Heating_maintenance', + 'Cooling_maintenance', + 'PV_maintenance' + ], + dtype='float' + ) + + def calculate(self) -> pd.DataFrame: + """ + Calculate total maintenance costs + :return: pd.DataFrame + """ + building = self._building + archetype = self._archetype + # todo: change area pv when the variable exists + roof_area = 0 + for roof in building.roofs: + roof_area += roof.solid_polygon.area + surface_pv = roof_area * 0.5 + + peak_heating = building.heating_peak_load[cte.YEAR][cte.HEATING_PEAK_LOAD][0] + peak_cooling = building.cooling_peak_load[cte.YEAR][cte.COOLING_PEAK_LOAD][0] + + maintenance_heating_0 = peak_heating * archetype.operational_cost.maintenance_heating + maintenance_cooling_0 = peak_cooling * archetype.operational_cost.maintenance_cooling + maintenance_pv_0 = surface_pv * archetype.operational_cost.maintenance_pv + + for year in range(1, self._configuration.number_of_years + 1): + costs_increase = math.pow(1 + self._configuration.consumer_price_index, year) + self._yearly_maintenance_costs.loc[year, 'Heating_maintenance'] = ( + maintenance_heating_0 * costs_increase + ) + self._yearly_maintenance_costs.loc[year, 'Cooling_maintenance'] = ( + maintenance_cooling_0 * costs_increase + ) + self._yearly_maintenance_costs.loc[year, 'PV_maintenance'] = ( + maintenance_pv_0 * costs_increase + ) + self._yearly_maintenance_costs.fillna(0, inplace=True) + return self._yearly_maintenance_costs diff --git a/costs/total_operational_costs.py b/costs/total_operational_costs.py new file mode 100644 index 0000000..c883e07 --- /dev/null +++ b/costs/total_operational_costs.py @@ -0,0 +1,109 @@ +""" +Total operational costs module +""" +import math +import pandas as pd + +from hub.city_model_structure.building import Building +import hub.helpers.constants as cte + +from configuration import Configuration + + +class TotalOperationalCosts: + """ + End of life costs class + """ + def __init__(self, building: Building, configuration: Configuration): + self._building = building + self._configuration = configuration + self._total_floor_area = 0 + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + self._total_floor_area += thermal_zone.total_floor_area + self._archetype = None + for archetype in self._configuration.cost_catalog.entries('archetypes').archetype: + if str(building.function) == str(archetype.function): + self._archetype = archetype + self._capital_costs_chapter = self._archetype.capital_cost + break + if not self._archetype: + raise KeyError('archetype not found') + + rng = range(configuration.number_of_years) + self._yearly_operational_costs = pd.DataFrame( + index=rng, + columns=[ + 'Fixed_costs_electricity_peak', + 'Fixed_costs_electricity_monthly', + 'Variable_costs_electricity', + 'Fixed_costs_gas', + 'Variable_costs_gas' + ], + dtype='float' + ) + + @property + def calculate(self) -> pd.DataFrame: + """ + Calculate total operational costs + :return: pd.DataFrame + """ + building = self._building + archetype = self._archetype + total_floor_area = self._total_floor_area + factor_residential = total_floor_area / 80 + # todo: split the heating between fuels + fixed_gas_cost_year_0 = 0 + variable_gas_cost_year_0 = 0 + electricity_heating = 0 + domestic_hot_water_electricity = 0 + if self._configuration.fuel_type == 1: + fixed_gas_cost_year_0 = archetype.operational_cost.fuels[1].fixed_monthly * 12 * factor_residential + variable_gas_cost_year_0 = ( + (building.heating_consumption[cte.YEAR][0] + building.domestic_hot_water_consumption[cte.YEAR][0]) / 1000 * + archetype.operational_cost.fuels[1].variable[0] + ) + if self._configuration.fuel_type == 0: + electricity_heating = building.heating_consumption[cte.YEAR][0] / 1000 + domestic_hot_water_electricity = building.domestic_hot_water_consumption[cte.YEAR][0] / 1000 + + electricity_cooling = building.cooling_consumption[cte.YEAR][0] / 1000 + electricity_lighting = building.lighting_electrical_demand[cte.YEAR]['insel meb'] / 1000 + electricity_plug_loads = building.appliances_electrical_demand[cte.YEAR]['insel meb'] / 1000 + electricity_distribution = 0 + total_electricity_consumption = ( + electricity_heating + electricity_cooling + electricity_lighting + domestic_hot_water_electricity + + electricity_plug_loads + electricity_distribution + ) + + # todo: change when peak electricity demand is coded. Careful with factor residential + peak_electricity_demand = 100 # self._peak_electricity_demand + variable_electricity_cost_year_0 = total_electricity_consumption * archetype.operational_cost.fuels[0].variable[0] + peak_electricity_cost_year_0 = peak_electricity_demand * archetype.operational_cost.fuels[0].fixed_power * 12 + monthly_electricity_cost_year_0 = archetype.operational_cost.fuels[0].fixed_monthly * 12 * factor_residential + + for year in range(1, self._configuration.number_of_years + 1): + price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year) + price_increase_peak_electricity = math.pow(1 + self._configuration.electricity_peak_index, year) + price_increase_gas = math.pow(1 + self._configuration.gas_price_index, year) + self._yearly_operational_costs.at[year, 'Fixed_costs_electricity_peak'] = ( + peak_electricity_cost_year_0 * price_increase_peak_electricity + ) + + self._yearly_operational_costs.at[year, 'Fixed_costs_electricity_monthly'] = ( + monthly_electricity_cost_year_0 * price_increase_peak_electricity + ) + self._yearly_operational_costs.at[year, 'Variable_costs_electricity'] = float( + variable_electricity_cost_year_0 * price_increase_electricity + ) + self._yearly_operational_costs.at[year, 'Fixed_costs_gas'] = fixed_gas_cost_year_0 * price_increase_gas + self._yearly_operational_costs.at[year, 'Variable_costs_gas'] = ( + variable_gas_cost_year_0 * price_increase_peak_electricity + ) + self._yearly_operational_costs.at[year, 'Variable_costs_gas'] = ( + variable_gas_cost_year_0 * price_increase_peak_electricity + ) + self._yearly_operational_costs.fillna(0, inplace=True) + + return self._yearly_operational_costs diff --git a/costs/total_operational_incomes.py b/costs/total_operational_incomes.py new file mode 100644 index 0000000..386c1a2 --- /dev/null +++ b/costs/total_operational_incomes.py @@ -0,0 +1,55 @@ +""" +Total operational incomes module +""" +import math +import pandas as pd +from hub.city_model_structure.building import Building +import hub.helpers.constants as cte + +from configuration import Configuration + + +class TotalOperationalIncomes: + """ + Total operational incomes class + """ + def __init__(self, building: Building, configuration: Configuration): + self._building = building + self._configuration = configuration + self._total_floor_area = 0 + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + self._total_floor_area += thermal_zone.total_floor_area + self._archetype = None + for archetype in self._configuration.cost_catalog.entries('archetypes').archetype: + if str(building.function) == str(archetype.function): + self._archetype = archetype + self._capital_costs_chapter = self._archetype.capital_cost + break + if not self._archetype: + raise KeyError('archetype not found') + + rng = range(configuration.number_of_years) + self._yearly_operational_incomes = pd.DataFrame(index=rng, columns=['Incomes electricity'], dtype='float') + + def calculate(self) -> pd.DataFrame: + """ + Calculate total operational incomes + :return: pd.DataFrame + """ + building = self._building + if cte.YEAR not in building.onsite_electrical_production: + onsite_electricity_production = 0 + else: + onsite_electricity_production = building.onsite_electrical_production[cte.YEAR][0] / 1000 + + for year in range(1, self._configuration.number_of_years + 1): + price_increase_electricity = math.pow(1 + self._configuration.electricity_price_index, year) + # todo: check the adequate assignation of price. Pilar + price_export = 0.075 # archetype.income.electricity_export + self._yearly_operational_incomes.loc[year, 'Incomes electricity'] = ( + onsite_electricity_production * price_export * price_increase_electricity + ) + + self._yearly_operational_incomes.fillna(0, inplace=True) + return self._yearly_operational_incomes