mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
halfway to adding the individual recommendation impact
This commit is contained in:
parent
6c07a290e7
commit
4c57b74d96
3 changed files with 204 additions and 111 deletions
|
|
@ -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"],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue