From 30ce7df6c161c94959789d760a577d074fa6a11c Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Mon, 8 Jul 2024 14:12:37 +0100 Subject: [PATCH] completed the current energy bill estimate --- backend/Property.py | 55 +++++++++++++++++--- backend/ml_models/AnnualBillSavings.py | 71 ++++++++++++++++++++++---- 2 files changed, 107 insertions(+), 19 deletions(-) diff --git a/backend/Property.py b/backend/Property.py index 35c19034..5a9d3fe8 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -112,6 +112,8 @@ class Property: self.wall_type = None self.floor_type = None + self.energy_cost_estimates = {} + self.energy = { "primary_energy_consumption": epc_record.get("energy_consumption_current"), "co2_emissions": epc_record.get("co2_emissions_current"), @@ -612,26 +614,63 @@ class Property: for col in ["heating_kwh", "hot_water_kwh"]: scoring_df[col] = None + # We should adjust the costs first and then calculate the energy consumption + lighting_cost = float(self.data["lighting-cost-current"]) + heating_cost = float(self.data["heating-cost-current"]) + hot_water_cost = float(self.data["hot-water-cost-current"]) + total_cost = lighting_cost + heating_cost + hot_water_cost + + adjusted_heating_cost = AnnualBillSavings.adjust_energy_cost_to_metered( + epc_energy_cost=heating_cost, + current_epc_rating=self.data["current-energy-rating"], + ) + + adjusted_hot_water_cost = AnnualBillSavings.adjust_energy_cost_to_metered( + epc_energy_cost=hot_water_cost, + current_epc_rating=self.data["current-energy-rating"], + ) + + adjusted_lighting_cost = AnnualBillSavings.adjust_energy_cost_to_metered( + epc_energy_cost=lighting_cost, + current_epc_rating=self.data["current-energy-rating"], + ) + + scoring_df["heating-cost-current"] = [adjusted_heating_cost] + scoring_df["hot-water-cost-current"] = [adjusted_hot_water_cost] + scoring_df["lighting-cost-current"] = [adjusted_lighting_cost] + energy_consumption_client.data = None heating_prediction = energy_consumption_client.score_new_data( new_data=scoring_df, target="heating_kwh" - ) + )[0] hot_water_prediction = energy_consumption_client.score_new_data( new_data=scoring_df, target="hot_water_kwh" - ) + )[0] - starting_heat_demand = ( - float(self.data["energy-consumption-current"]) * self.floor_area - ) + # We convert the lighting cost into kwh, just using the price cap + lighting_kwh = float(adjusted_lighting_cost) / AnnualBillSavings.ELECTRICITY_PRICE_CAP + + appliances_energy_use = AnnualBillSavings.estimate_appliances_energy_use(total_floor_area=self.floor_area) + appliances_energy_cost = appliances_energy_use * AnnualBillSavings.ELECTRICITY_PRICE_CAP + + total_energy_consumption = heating_prediction + hot_water_prediction + lighting_kwh + appliances_energy_use self.current_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered( - epc_energy_consumption=starting_heat_demand, + epc_energy_consumption=total_energy_consumption, current_epc_rating=self.data["current-energy-rating"], - total_floor_area=self.floor_area ) - self.current_energy_bill = AnnualBillSavings.calculate_annual_bill(self.current_adjusted_energy) + self.energy_cost_estimates = { + "heating": adjusted_heating_cost, + "hot_water": adjusted_hot_water_cost, + "lighting": adjusted_lighting_cost, + "appliances": appliances_energy_cost + } + + self.expected_energy_bill = ( + adjusted_heating_cost + adjusted_hot_water_cost + adjusted_lighting_cost + appliances_energy_cost + ) def set_spatial(self, spatial: pd.DataFrame): """ diff --git a/backend/ml_models/AnnualBillSavings.py b/backend/ml_models/AnnualBillSavings.py index e6494bcd..4747e587 100644 --- a/backend/ml_models/AnnualBillSavings.py +++ b/backend/ml_models/AnnualBillSavings.py @@ -25,9 +25,9 @@ class AnnualBillSavings: AVERAGE_GAS_CONSUMPTION = 11500 # Latest price cap figures from Ofgem are for April 2024 - # https://www.ofgem.gov.uk/publications/new-energy-price-cap-level-april-june-2024-starts-today - ELECTRICITY_PRICE_CAP = 0.245 - GAS_PRICE_CAP = 0.0604 + # https://www.ofgem.gov.uk/energy-price-cap + ELECTRICITY_PRICE_CAP = 0.2236 + GAS_PRICE_CAP = 0.0548 # This is the most recent export payment figure, at 12p per kwh ELECTRICITY_EXPORT_PAYMENT = 0.12 @@ -125,7 +125,17 @@ class AnnualBillSavings: return eam @classmethod - def adjust_energy_to_metered(cls, epc_energy_consumption, current_epc_rating, total_floor_area): + def estimate_appliances_energy_use(cls, total_floor_area): + # The EPC energy consumption does not factor in cooking and applicance use, so this is estimated using the + # methodology outlined in SAP, and is discussed in the UCL paper in section 3.1.1 + estimated_occupants = cls.calculate_occupants(total_floor_area=total_floor_area) + appliances_energy_use = cls.estimate_electrical_appliances(estimated_occupants, total_floor_area) + return appliances_energy_use + + @classmethod + def adjust_energy_to_metered( + cls, epc_energy_consumption, current_epc_rating + ): """ The over-prediction of energy use by EPCs in Great Britain: A comparison of EPC-modelled and metered primary energy use intensity @@ -136,13 +146,6 @@ class AnnualBillSavings: :return: """ - # The EPC energy consumption does not factor in cooking and applicance use, so this is estimated using the - # methodology outlined in SAP, and is discussed in the UCL paper in section 3.1.1 - estimated_occupants = cls.calculate_occupants(total_floor_area=total_floor_area) - appliances_energy_use = cls.estimate_electrical_appliances(estimated_occupants, total_floor_area) - - epc_energy_consumption += appliances_energy_use - gradients = { "A": -0.1, "B": -0.1, @@ -175,6 +178,52 @@ class AnnualBillSavings: return adjusted_consumption + @classmethod + def adjust_energy_cost_to_metered(cls, epc_energy_cost, current_epc_rating): + """ + The over-prediction of energy use by EPCs in Great Britain: A comparison + of EPC-modelled and metered primary energy use intensity + + Which can be found here: https://www.sciencedirect.com/science/article/pii/S0378778823002542 + We implement the results on page 10 + + This is used to just re-map the cost from the EPC to the metered cost + :return: + """ + + gradients = { + "A": -0.1, + "B": -0.1, + "C": -0.43, + "D": -0.52, + "E": -0.7, + "F": -0.76, + "G": -0.76 + } + + intercepts = { + "A": 28, + "B": 28, + "C": 97, + "D": 119, + "E": 160, + "F": 157, + "G": 157 + } + + gradient = gradients[current_epc_rating] + intercept = intercepts[current_epc_rating] + + # This should be negative + consumption_difference = gradient * epc_energy_cost + intercept + consumption_difference = 0 if consumption_difference > 0 else consumption_difference + + adjusted_consumption = (epc_energy_cost + consumption_difference) + if adjusted_consumption < 0: + raise ValueError("consumption_difference should be negative") + + return adjusted_consumption + @classmethod def adjust_expected_band(cls, expected_epc_rating, current_epc_rating): """