halfway to adding the individual recommendation impact

This commit is contained in:
Khalim Conn-Kowlessar 2024-07-09 17:07:05 +01:00
parent 6c07a290e7
commit 4c57b74d96
3 changed files with 204 additions and 111 deletions

View file

@ -364,10 +364,6 @@ class Property:
simulation_epc = self.epc_record.prepared_epc.copy()
# Insert static values
simulation_epc["lodgement_date"] = simulation_lodgment_date
# Insert today's costs, unadjusted (i.e. in line with what we expect the EPC would say today)
simulation_epc["heating-cost-current"] = round(self.energy_cost_estimates["unadjusted"]["heating"])
simulation_epc["lighting-cost-current"] = round(self.energy_cost_estimates["unadjusted"]["lighting"])
simulation_epc["hot-water-cost-current"] = round(self.energy_cost_estimates["unadjusted"]["hot_water"])
# Replace the understores with hyphens
simulation_epc = {k.replace("_", "-"): v for k, v in simulation_epc.items()}
@ -698,44 +694,44 @@ class Property:
appliances_kwh = AnnualBillSavings.estimate_appliances_energy_use(total_floor_area=self.floor_area)
adjusted_heating_kwh = AnnualBillSavings.adjust_energy_cost_to_metered(
epc_energy_cost=heating_prediction,
adjusted_heating_kwh = AnnualBillSavings.adjust_energy_to_metered(
epc_energy=heating_prediction,
current_epc_rating=self.data["current-energy-rating"],
)
adjusted_hot_water_kwh = AnnualBillSavings.adjust_energy_cost_to_metered(
epc_energy_cost=hot_water_prediction,
adjusted_hot_water_kwh = AnnualBillSavings.adjust_energy_to_metered(
epc_energy=hot_water_prediction,
current_epc_rating=self.data["current-energy-rating"],
)
adjusted_lighting_kwh = AnnualBillSavings.adjust_energy_cost_to_metered(
epc_energy_cost=lighting_kwh,
adjusted_lighting_kwh = AnnualBillSavings.adjust_energy_to_metered(
epc_energy=lighting_kwh,
current_epc_rating=self.data["current-energy-rating"],
)
adjusted_applicances_kwh = AnnualBillSavings.adjust_energy_cost_to_metered(
epc_energy_cost=appliances_kwh,
adjusted_applicances_kwh = AnnualBillSavings.adjust_energy_to_metered(
epc_energy=appliances_kwh,
current_epc_rating=self.data["current-energy-rating"],
)
# Adjust today's cost figures with the UCL model
adjusted_heating_cost = AnnualBillSavings.adjust_energy_cost_to_metered(
epc_energy_cost=todays_heating_cost,
adjusted_heating_cost = AnnualBillSavings.adjust_energy_to_metered(
epc_energy=todays_heating_cost,
current_epc_rating=self.data["current-energy-rating"],
)
adjusted_hot_water_cost = AnnualBillSavings.adjust_energy_cost_to_metered(
epc_energy_cost=todays_hot_water_cost,
adjusted_hot_water_cost = AnnualBillSavings.adjust_energy_to_metered(
epc_energy=todays_hot_water_cost,
current_epc_rating=self.data["current-energy-rating"],
)
adjusted_lighting_cost = AnnualBillSavings.adjust_energy_cost_to_metered(
epc_energy_cost=todays_lighting_cost,
adjusted_lighting_cost = AnnualBillSavings.adjust_energy_to_metered(
epc_energy=todays_lighting_cost,
current_epc_rating=self.data["current-energy-rating"],
)
adjusted_appliances_cost = AnnualBillSavings.adjust_energy_cost_to_metered(
epc_energy_cost=appliances_kwh * AnnualBillSavings.ELECTRICITY_PRICE_CAP,
adjusted_appliances_cost = AnnualBillSavings.adjust_energy_to_metered(
epc_energy=appliances_kwh * AnnualBillSavings.ELECTRICITY_PRICE_CAP,
current_epc_rating=self.data["current-energy-rating"],
)

View file

@ -133,53 +133,7 @@ class AnnualBillSavings:
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
Which can be found here: https://www.sciencedirect.com/science/article/pii/S0378778823002542
We implement the results on page 10
: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_consumption + intercept
adjusted_consumption = (epc_energy_consumption + consumption_difference)
if adjusted_consumption < 0:
raise ValueError("consumption_difference should be negative")
return adjusted_consumption
@classmethod
def adjust_energy_cost_to_metered(cls, epc_energy_cost, current_epc_rating):
def adjust_energy_to_metered(cls, epc_energy, 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
@ -188,6 +142,7 @@ class AnnualBillSavings:
We implement the results on page 10
This is used to just re-map the cost from the EPC to the metered cost
epc_energy could be cost or kwh
:return:
"""
@ -215,10 +170,10 @@ class AnnualBillSavings:
intercept = intercepts[current_epc_rating]
# This should be negative
consumption_difference = gradient * epc_energy_cost + intercept
consumption_difference = gradient * epc_energy + intercept
consumption_difference = 0 if consumption_difference > 0 else consumption_difference
adjusted_consumption = (epc_energy_cost + consumption_difference)
adjusted_consumption = (epc_energy + consumption_difference)
if adjusted_consumption < 0:
raise ValueError("consumption_difference should be negative")

View file

@ -1,3 +1,4 @@
import pandas as pd
from backend.Property import Property
from typing import List
from itertools import groupby
@ -276,7 +277,9 @@ class Recommendations:
return property_recommendations
@classmethod
def calculate_recommendation_impact(cls, property_instance, all_predictions, recommendations):
def calculate_recommendation_impact(
cls, property_instance, all_predictions, recommendations, energy_consumption_client
):
"""
Given predictions from the model apis, with method will update the recommendations with the predicted
@ -285,6 +288,7 @@ class Recommendations:
:param property_instance: Instance of the Property class, for the home associated to property_id
:param all_predictions: dictionary of predictions from the model apis
:param recommendations: dictionary of recommendations for the property
:param energy_consumption_client: Instance of the EnergyConsumptionClient class
:return:
"""
@ -297,6 +301,34 @@ class Recommendations:
property_carbon_predictions = all_predictions["carbon_change_predictions"][
all_predictions["carbon_change_predictions"]["property_id"] == str(property_instance.id)
].copy()
property_lighting_cost_predictions = all_predictions["lighting_cost_predictions"][
all_predictions["lighting_cost_predictions"]["property_id"] == str(property_instance.id)
].copy()
property_heating_cost_predictions = all_predictions["heating_cost_predictions"][
all_predictions["heating_cost_predictions"]["property_id"] == str(property_instance.id)
].copy()
property_hot_water_cost_predictions = all_predictions["hot_water_cost_predictions"][
all_predictions["hot_water_cost_predictions"]["property_id"] == str(property_instance.id)
].copy()
# We apply adjustments to each of the heating costs
property_lighting_cost_predictions["adjusted_cost"] = property_lighting_cost_predictions["predictions"].apply(
lambda x: AnnualBillSavings.adjust_energy_to_metered(
x, current_epc_rating=property_instance.data["current-energy-rating"]
)
)
property_heating_cost_predictions["adjusted_cost"] = property_heating_cost_predictions["predictions"].apply(
lambda x: AnnualBillSavings.adjust_energy_to_metered(
x, current_epc_rating=property_instance.data["current-energy-rating"]
)
)
property_hot_water_cost_predictions["adjusted_cost"] = property_hot_water_cost_predictions["predictions"].apply(
lambda x: AnnualBillSavings.adjust_energy_to_metered(
x, current_epc_rating=property_instance.data["current-energy-rating"]
)
)
property_recommendations = recommendations[property_instance.id].copy()
@ -304,32 +336,43 @@ class Recommendations:
sap_phase_impact = property_sap_predictions.groupby("phase")["predictions"].median().reset_index()
heat_phase_impact = property_heat_predictions.groupby("phase")["predictions"].median().reset_index()
carbon_phase_impact = property_carbon_predictions.groupby("phase")["predictions"].median().reset_index()
lighting_cost_phase_impact = (
property_lighting_cost_predictions.groupby("phase")[["adjusted_cost", "predictions"]].median().reset_index()
)
heating_cost_phase_impact = (
property_heating_cost_predictions.groupby("phase")[["adjusted_cost", "predictions"]].median().reset_index()
)
hot_water_cost_phase_impact = (
property_hot_water_cost_predictions.groupby("phase")[
["adjusted_cost", "predictions"]
].median().reset_index()
)
# The heat demand change is the difference between the starting heat demand and the value at the final phase
expected_heat_demand = property_instance.floor_area * (
heat_phase_impact[heat_phase_impact["phase"] == max(heat_phase_impact["phase"])]["predictions"].values[0]
)
starting_heat_demand = (
float(property_instance.data["energy-consumption-current"]) * property_instance.floor_area
)
# This is the unadjusted resulting heat demand
predicted_heat_demand_change = starting_heat_demand - expected_heat_demand
# TODO: This isn't quite right as this is based on EVERY possible measure, not just the ones that are
# actually implemented
expected_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered(
epc_energy_consumption=expected_heat_demand,
current_epc_rating=property_instance.data["current-energy-rating"],
total_floor_area=property_instance.floor_area
)
adjusted_heat_demand_change = (
property_instance.current_adjusted_energy - expected_adjusted_energy
)
# TODO: We should determine if the home is gas & electricity or just electricity
expected_energy_bill = AnnualBillSavings.calculate_annual_bill(expected_adjusted_energy)
# expected_heat_demand = property_instance.floor_area * (
# heat_phase_impact[heat_phase_impact["phase"] == max(heat_phase_impact["phase"])]["predictions"].values[0]
# )
# starting_heat_demand = (
# float(property_instance.data["energy-consumption-current"]) * property_instance.floor_area
# )
#
# # This is the unadjusted resulting heat demand
# predicted_heat_demand_change = starting_heat_demand - expected_heat_demand
#
# # TODO: This isn't quite right as this is based on EVERY possible measure, not just the ones that are
# # actually implemented
# expected_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered(
# epc_energy_consumption=expected_heat_demand,
# current_epc_rating=property_instance.data["current-energy-rating"],
# total_floor_area=property_instance.floor_area
# )
#
# adjusted_heat_demand_change = (
# property_instance.current_adjusted_energy - expected_adjusted_energy
# )
#
# # TODO: We should determine if the home is gas & electricity or just electricity
# expected_energy_bill = AnnualBillSavings.calculate_annual_bill(expected_adjusted_energy)
for recommendations_by_type in property_recommendations:
for rec in recommendations_by_type:
@ -350,12 +393,126 @@ class Recommendations:
rec["recommendation_id"]
)]["predictions"].values[0]
# Lighting costs won't change unless we have a lighting recommendation
new_lighting_cost_data = property_lighting_cost_predictions[
property_lighting_cost_predictions["recommendation_id"] == str(rec["recommendation_id"])
]
new_lighting_cost = new_lighting_cost_data["adjusted_cost"].values[0]
new_lighting_cost_unadjusted = new_lighting_cost_data["predictions"].values[0]
new_heating_cost_data = property_heating_cost_predictions[
property_heating_cost_predictions["recommendation_id"] == str(rec["recommendation_id"])
]
new_heating_cost = new_heating_cost_data["adjusted_cost"].values[0]
new_heating_cost_unadjusted = new_heating_cost_data["predictions"].values[0]
new_hot_water_cost_data = property_hot_water_cost_predictions[
property_hot_water_cost_predictions["recommendation_id"] == str(rec["recommendation_id"])
]
new_hot_water_cost = new_hot_water_cost_data["adjusted_cost"].values[0]
new_hot_water_cost_unadjusted = new_hot_water_cost_data["predictions"].values[0]
if rec["phase"] == 0:
predicted_sap_points = new_sap - float(property_instance.data["current-energy-efficiency"])
predicted_co2_savings = float(property_instance.data["co2-emissions-current"]) - new_carbon
predicted_heat_demand = property_instance.floor_area * (
float(property_instance.data["energy-consumption-current"]) - new_heat_demand
)
predicted_heating_cost_reduction = (
float(property_instance.energy_cost_estimates["adjusted"]["heating"]) - new_heating_cost
)
predicted_hot_water_cost_reduction = (
float(property_instance.energy_cost_estimates["adjusted"]["hot_water"]) - new_hot_water_cost
)
predicted_heating_cost_reduction = (
0 if predicted_heating_cost_reduction < 0 else predicted_heating_cost_reduction
)
predicted_hot_water_cost_reduction = (
0 if predicted_hot_water_cost_reduction < 0 else predicted_hot_water_cost_reduction
)
# Only lighting recommendations can have an impact here
predicted_lighting_cost_reduction = 0 if rec["type"] != "lighting" else (
float(property_instance.energy_cost_estimates["adjusted"]["lighting"]) - new_lighting_cost
)
# This is the total bill savings for the recommendation
if rec["type"] == "solar_pv":
# We need to calculate the predicted bill savings for the solar pv recommendation
# where we will get some savings from the cost of appliances but it depends on the amount
# of energy generated by the solar panels
# We can assume that 50% of the energy generated will be used by the property without
# a battery, to be conservative.
# SIMILARLY: We need to handle kwh savings
raise Exception("Handle me")
else:
predicted_bill_savings = (
predicted_heating_cost_reduction + predicted_hot_water_cost_reduction +
predicted_lighting_cost_reduction
)
# We now predict the kwh savings using the xgb model
scoring_heating_cost = min(
property_instance.energy_cost_estimates["unadjusted"]["heating"], new_heating_cost_unadjusted
)
scoring_hot_water_cost = min(
property_instance.energy_cost_estimates["unadjusted"]["hot_water"],
new_hot_water_cost_unadjusted
)
scoring_lighting_cost = min(
property_instance.energy_cost_estimates["unadjusted"]["lighting"], new_lighting_cost_unadjusted
) if rec["type"] == "lighting" \
else property_instance.energy_cost_estimates["unadjusted"]["lighting"]
simulation_epc = property_instance.simulation_epcs[rec["phase"]].copy()
# The current heating, hot water and energy kwh should be based on the new, unadjusted
# costs for lighting, heating, hot water
simulation_epc["heating-cost-current"] = int(scoring_heating_cost)
simulation_epc["hot-water-cost-current"] = int(scoring_hot_water_cost)
simulation_epc["lighting-cost-current"] = int(scoring_lighting_cost)
# We predict with the energy consumption model
scoring_df = pd.DataFrame([simulation_epc])
# Change columns from underscores to hyphens
scoring_df.columns = [
x.lower().replace("_", "-") for x in scoring_df.columns
]
for col in ["heating_kwh", "hot_water_kwh"]:
scoring_df[col] = None
energy_consumption_client.data = None
new_heating_kwh = energy_consumption_client.score_new_data(
new_data=scoring_df, target="heating_kwh"
)[0]
new_hot_water_kwh = energy_consumption_client.score_new_data(
new_data=scoring_df, target="hot_water_kwh"
)[0]
# Adjust these figures
new_heating_kwh_adjusted = AnnualBillSavings.adjust_energy_to_metered(
new_heating_kwh, current_epc_rating=property_instance.data["current-energy-rating"]
)
new_hot_water_kwh_adjusted = AnnualBillSavings.adjust_energy_to_metered(
new_hot_water_kwh, current_epc_rating=property_instance.data["current-energy-rating"]
)
heating_kwh_reduction = 0 if predicted_heating_cost_reduction == 0 else (
property_instance.energy_consumption_estimates["adjusted"]["heating"] - new_heating_kwh_adjusted
)
hot_water_kwh_reduction = 0 if predicted_hot_water_cost_reduction == 0 else (
property_instance.energy_consumption_estimates["adjusted"]["hot_water"] -
new_hot_water_kwh_adjusted
)
lighting_kwh_reduction = predicted_lighting_cost_reduction / AnnualBillSavings.ELECTRICITY_PRICE_CAP
kwh_reduction = heating_kwh_reduction + hot_water_kwh_reduction + lighting_kwh_reduction
else:
previous_phase = rec["phase"] - 1
predicted_sap_points = (
@ -383,23 +540,8 @@ class Recommendations:
# Round to 2 decimal places
rec["sap_points"] = round(rec["sap_points"], 2)
# We now calculate the adjusted heat demand for this recommendation, which is simply the percentage
# of the total adjusted heat demand change. The percentage we use is this recommendation's percentage
# of the total heat demand per square meter change
rec["adjusted_heat_demand"] = adjusted_heat_demand_change * (
rec["heat_demand"] / predicted_heat_demand_change
)
# We make sure this is NOT below 0
rec["adjusted_heat_demand"] = max(0, rec["adjusted_heat_demand"])
# Depending on the property's tarriff, we calculate the amount of energy savings this measure will bring
if property_instance.energy_source == "electricity":
rec["energy_cost_savings"] = AnnualBillSavings.estimate_electric(rec["adjusted_heat_demand"])
elif property_instance.energy_source == "electricity_and_gas":
rec["energy_cost_savings"] = AnnualBillSavings.estimate(rec["adjusted_heat_demand"])
else:
raise ValueError("Invalid value for energy source")
rec["kwh_savings"] = kwh_reduction
rec["energy_cost_savings"] = predicted_bill_savings
if (rec["sap_points"] is None) and (rec["co2_equivalent_savings"] is None) or (
rec["heat_demand"] is None) or (rec["energy_cost_savings"] is None):