mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
refactoring calculate_recommendation_impact
This commit is contained in:
parent
891545804e
commit
db3ab9bb4a
4 changed files with 285 additions and 447 deletions
|
|
@ -1025,7 +1025,7 @@ class Property:
|
|||
built_form=self.data["built-form"],
|
||||
)
|
||||
|
||||
if self.insulation_floor_area is not None:
|
||||
if self.insulation_floor_area is None:
|
||||
self.insulation_floor_area = float(
|
||||
self.energy_assessment_condition_data["main_dwelling_ground_floor_area"]
|
||||
) if (condition_data.get("main_dwelling_ground_floor_area") is not None) else (
|
||||
|
|
|
|||
|
|
@ -438,7 +438,120 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
|
||||
# prepare the data
|
||||
|
||||
# TODO: Some junk is being returned by the heating kwh model!
|
||||
# TODO - this needs to be moved to the etl process
|
||||
import numpy as np
|
||||
def add_features_from_code(df):
|
||||
|
||||
FEATURES = {
|
||||
"heating_kwh": [
|
||||
"lodgement-year", "lodgement-month", "current-energy-efficiency", "energy-consumption-current",
|
||||
"heating-cost-current", "heating-cost-potential", "total-floor-area", "number-heated-rooms",
|
||||
"mainheat-description", "mainheat-energy-eff", "main-fuel", "secondheat-description",
|
||||
"property-type",
|
||||
"built-form", "mainheatcont-description", "hotwater-description", "hot-water-energy-eff",
|
||||
"walls-energy-eff",
|
||||
"roof-energy-eff", "windows-description", "windows-energy-eff", "floor-description",
|
||||
"flat-top-storey",
|
||||
"flat-storey-count", "unheated-corridor-length", "solar-water-heating-flag",
|
||||
"mechanical-ventilation",
|
||||
"low-energy-lighting", "environment-impact-current", "energy-tariff",
|
||||
"county", "construction-age-band", "co2-emissions-current",
|
||||
],
|
||||
"hot_water_kwh": [
|
||||
"lodgement-year", "lodgement-month",
|
||||
"current-energy-efficiency",
|
||||
"energy-consumption-current",
|
||||
"hot-water-cost-current",
|
||||
"total-floor-area", "number-heated-rooms",
|
||||
"hotwater-description", "hot-water-energy-eff", "main-fuel", "property-type", "built-form",
|
||||
"co2-emissions-current",
|
||||
]
|
||||
}
|
||||
CATEGORICAL_COLUMNS = [
|
||||
"lodgement-year", "lodgement-month", "main-fuel", "mainheat-description", "number-heated-rooms",
|
||||
"number-habitable-rooms", "mainheat-energy-eff", "mainheatcont-description", "property-type",
|
||||
"built-form",
|
||||
"construction-age-band", "secondheat-description", "hotwater-description", "hot-water-energy-eff",
|
||||
"walls-description", "walls-energy-eff", "roof-description", "roof-energy-eff", "floor-description",
|
||||
"county",
|
||||
"windows-description", "windows-energy-eff", "flat-top-storey",
|
||||
"flat-storey-count", "unheated-corridor-length", "solar-water-heating-flag", "mechanical-ventilation",
|
||||
"low-energy-lighting", "environment-impact-current", "energy-tariff", "current-energy-rating"
|
||||
]
|
||||
|
||||
NUMERICAL_COLUMNS = list({
|
||||
x for x in FEATURES["heating_kwh"] + FEATURES["hot_water_kwh"]
|
||||
if x not in CATEGORICAL_COLUMNS
|
||||
})
|
||||
|
||||
"""Performs feature engineering on the dataset."""
|
||||
df["lodgement-date"] = pd.to_datetime(df["lodgement-date"])
|
||||
df["lodgement-year"] = df["lodgement-date"].dt.year
|
||||
df["lodgement-month"] = df["lodgement-date"].dt.month
|
||||
|
||||
# For walls, roof, floor description where we have average thermal transmittance, to avoid too many
|
||||
# categories
|
||||
# we group them
|
||||
ranges = {
|
||||
"lessthan 0.1": (0, 0.1),
|
||||
"0.1 - 0.3": (0.1, 0.3),
|
||||
"0.3 - 0.5": (0.3, 0.5),
|
||||
"morethan 0.5": (0.5, 2.5),
|
||||
}
|
||||
|
||||
# Generate the lookup table
|
||||
thermal_transmittance_lookup_table = []
|
||||
for i in range(1, 251):
|
||||
value = i / 100
|
||||
for label, (low, high) in ranges.items():
|
||||
if low < value <= high:
|
||||
thermal_transmittance_lookup_table.append({"from": value, "to": label})
|
||||
break
|
||||
|
||||
# Convert to DataFrame for display
|
||||
thermal_transmittance_lookup_table = pd.DataFrame(thermal_transmittance_lookup_table)
|
||||
thermal_transmittance_lookup_table["from"] = thermal_transmittance_lookup_table["from"].astype(str)
|
||||
|
||||
# Apply the lookup table to the data
|
||||
for feature in ["walls-description", "roof-description", "floor-description"]:
|
||||
cleaned_df = pd.DataFrame(cleaned[feature])[["original_description", "thermal_transmittance"]]
|
||||
# Round to 2 decimal places and convert to string
|
||||
cleaned_df["thermal_transmittance"] = cleaned_df["thermal_transmittance"].round(2).astype(str)
|
||||
|
||||
df = df.merge(
|
||||
cleaned_df,
|
||||
how="left",
|
||||
left_on=feature,
|
||||
right_on="original_description",
|
||||
)
|
||||
# We now have the thermal transmittance in the data, which we can use to group with the lookup table
|
||||
df = df.merge(
|
||||
thermal_transmittance_lookup_table,
|
||||
how="left",
|
||||
left_on="thermal_transmittance",
|
||||
right_on="from",
|
||||
)
|
||||
# Where "to" is populated, replace feature with to
|
||||
df[feature] = np.where(
|
||||
~pd.isnull(df["to"]),
|
||||
df["to"],
|
||||
df[feature]
|
||||
)
|
||||
df = df.drop(columns=["original_description", "thermal_transmittance", "from", "to"])
|
||||
|
||||
# Convert data types
|
||||
df[NUMERICAL_COLUMNS] = df[NUMERICAL_COLUMNS].apply(pd.to_numeric)
|
||||
df[CATEGORICAL_COLUMNS] = df[CATEGORICAL_COLUMNS].astype(str)
|
||||
|
||||
return df
|
||||
|
||||
def add_estimate_annual_kwh(df):
|
||||
df['estimate_annual_kwh'] = df['energy-consumption-current'] * df['total-floor-area']
|
||||
return df
|
||||
|
||||
epcs_for_scoring = add_features_from_code(epcs_for_scoring)
|
||||
epcs_for_scoring = add_estimate_annual_kwh(epcs_for_scoring)
|
||||
|
||||
kwh_predictions = model_api.predict_all(
|
||||
df=epcs_for_scoring,
|
||||
bucket=get_settings().DATA_BUCKET,
|
||||
|
|
@ -476,7 +589,7 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
raise Exception("Missed setting of spatial data for a property")
|
||||
p.get_components(
|
||||
cleaned=cleaned,
|
||||
# energy_consumption_client=energy_consumption_client # TODO: Full remove me
|
||||
energy_consumption_client=energy_consumption_client, # TODO: Full remove me
|
||||
kwh_predictions=kwh_predictions
|
||||
)
|
||||
|
||||
|
|
@ -676,6 +789,12 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
for key, scored in predictions_dict.items():
|
||||
all_predictions[key] = pd.concat([all_predictions[key], scored])
|
||||
|
||||
# We now produce predictions for the kwh models
|
||||
|
||||
# TODO!!!!! In order to score the kwh models, we need to insert the new SAP, heat demand, carbon, cost
|
||||
# etc values, into the simulated EPC, otherwise it won't work. We might also want to drop all potential
|
||||
# columns and env-efficiency columns (POTENTIAL COLUMNS ALREADY GONE, JUST NEED TO DROP ENV EFFICIENCY)
|
||||
|
||||
# Insert the predictions into the recommendations and run the optimiser
|
||||
# TODO: If a recommendation has a negative impact on SAP, we should remove it - this seems to have become a
|
||||
# possibility with heating system
|
||||
|
|
@ -686,26 +805,14 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
|
||||
property_instance = [p for p in input_properties if p.id == property_id][0]
|
||||
|
||||
(
|
||||
recommendations_with_impact,
|
||||
expected_adjusted_energy,
|
||||
expected_energy_bill
|
||||
) = (
|
||||
recommendations_with_impact, impact_summary = (
|
||||
Recommendations.calculate_recommendation_impact(
|
||||
property_instance=property_instance,
|
||||
all_predictions=all_predictions,
|
||||
recommendations=recommendations,
|
||||
representative_recommendations=representative_recommendations,
|
||||
energy_consumption_client=energy_consumption_client
|
||||
)
|
||||
)
|
||||
|
||||
# Store the resulting adjusted energy in the property instance
|
||||
property_instance.set_adjusted_energy(
|
||||
expected_adjusted_energy=expected_adjusted_energy,
|
||||
expected_energy_bill=expected_energy_bill
|
||||
)
|
||||
|
||||
input_measures = prepare_input_measures(recommendations_with_impact, body.goal)
|
||||
|
||||
current_sap_points = int(property_instance.data["current-energy-efficiency"])
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ class ModelApi:
|
|||
"lighting_cost_predictions",
|
||||
"heating_cost_predictions",
|
||||
"hot_water_cost_predictions",
|
||||
"hotwater_kwh_predictions",
|
||||
"heating_kwh_predictions",
|
||||
]
|
||||
|
||||
MODEL_URLS = {
|
||||
|
|
@ -72,8 +70,8 @@ class ModelApi:
|
|||
:return:
|
||||
"""
|
||||
|
||||
if model_prefix not in self.MODEL_PREFIXES:
|
||||
raise ValueError(f"Model prefix specified is not in {self.MODEL_PREFIXES}")
|
||||
# if model_prefix not in self.MODEL_PREFIXES:
|
||||
# raise ValueError(f"Model prefix specified is not in {self.MODEL_PREFIXES}")
|
||||
|
||||
# Store parquet file in s3 for scoring
|
||||
file_location = f"{model_prefix}/{self.portfolio_id}/{self.timestamp}.parquet"
|
||||
|
|
|
|||
|
|
@ -359,477 +359,210 @@ class Recommendations:
|
|||
property_instance,
|
||||
all_predictions,
|
||||
recommendations,
|
||||
representative_recommendations,
|
||||
energy_consumption_client
|
||||
):
|
||||
|
||||
"""
|
||||
Given predictions from the model apis, with method will update the recommendations with the predicted
|
||||
impact of the recommendation on the property
|
||||
|
||||
This function will return two objects:
|
||||
1) Updated recommendations with the predicted impact of the recommendation
|
||||
2) A list of impacts by phase, which will be used for the kwh model scoring
|
||||
|
||||
: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 representative_recommendations: dictionary of representative recommendations for the property
|
||||
:param energy_consumption_client: Instance of the EnergyConsumptionClient class
|
||||
:return:
|
||||
"""
|
||||
|
||||
property_sap_predictions = all_predictions["sap_change_predictions"][
|
||||
all_predictions["sap_change_predictions"]["property_id"] == str(property_instance.id)
|
||||
].copy()
|
||||
property_heat_predictions = all_predictions["heat_demand_predictions"][
|
||||
all_predictions["heat_demand_predictions"]["property_id"] == str(property_instance.id)
|
||||
].copy()
|
||||
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()
|
||||
property_predictions = {
|
||||
prefix + "_predictions": all_predictions[prefix + "_predictions"][
|
||||
all_predictions[prefix + "_predictions"]["property_id"] == str(property_instance.id)
|
||||
].copy() for prefix in [
|
||||
"sap_change", "heat_demand", "carbon_change", "lighting_cost", "heating_cost", "hot_water_cost"
|
||||
]
|
||||
}
|
||||
|
||||
# We apply adjustments to each of the heating costs
|
||||
property_lighting_cost_predictions["adjusted_cost"] = property_lighting_cost_predictions["predictions"].apply(
|
||||
for prefix in ["lighting_cost", "heating_cost", "hot_water_cost"]:
|
||||
property_predictions[f"{prefix}_predictions"]["adjusted_cost"] = (
|
||||
property_predictions[f"{prefix}_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()
|
||||
|
||||
# We calculate the impact by phase
|
||||
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()
|
||||
)
|
||||
|
||||
representative_rec_ids = [
|
||||
rec["recommendation_id"] for rec in representative_recommendations[property_instance.id]
|
||||
phase_impact = {
|
||||
prefix: property_predictions[prefix + "_predictions"].groupby("phase")["predictions"].median().reset_index()
|
||||
for prefix in [
|
||||
"sap_change", "heat_demand", "carbon_change", "lighting_cost", "heating_cost", "hot_water_cost"
|
||||
]
|
||||
}
|
||||
|
||||
phase_lighting_costs = {}
|
||||
phase_kwh_figures = {}
|
||||
bill_savings_list = []
|
||||
kwh_savings_list = []
|
||||
# TODO: should fabric upgrades have an impact on hot water costs/kwh?
|
||||
# TODO: Generally, the costing models are just increasing. Maybe they're including something in the model
|
||||
# that they shouldn't e.g. SAP, carbon, heat demand etc?
|
||||
|
||||
impact_summary = []
|
||||
for recommendations_by_type in property_recommendations:
|
||||
for rec in recommendations_by_type:
|
||||
|
||||
if rec["type"] == "mechanical_ventilation":
|
||||
# We don't have a percieved sap impact of mechanical ventilation
|
||||
continue
|
||||
|
||||
new_heat_demand = property_heat_predictions[property_heat_predictions["recommendation_id"] == str(
|
||||
phase_energy_efficiency_metrics = {
|
||||
prefix: property_predictions[prefix + "_predictions"][
|
||||
property_predictions[prefix + "_predictions"]["recommendation_id"] == str(
|
||||
rec["recommendation_id"]
|
||||
)]["predictions"].values[0]
|
||||
)]["predictions"].values[0] for prefix in ["sap_change", "heat_demand", "carbon_change"]
|
||||
}
|
||||
|
||||
new_carbon = property_carbon_predictions[property_carbon_predictions["recommendation_id"] == str(
|
||||
rec["recommendation_id"]
|
||||
)]["predictions"].values[0]
|
||||
|
||||
new_sap = property_sap_predictions[property_sap_predictions["recommendation_id"] == str(
|
||||
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]
|
||||
# For phase costs, we need adusted and unadjusted values
|
||||
phase_cost = {
|
||||
prefix: property_predictions[prefix + "_predictions"][
|
||||
property_predictions[prefix + "_predictions"]["recommendation_id"] ==
|
||||
str(rec["recommendation_id"])
|
||||
] for prefix in ["lighting_cost", "heating_cost", "hot_water_cost"]
|
||||
}
|
||||
|
||||
# We structure this so that depending on the phase, we capture the previous phase impacts and
|
||||
# then just have one piece of code to calculate the difference
|
||||
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
|
||||
)
|
||||
previous_phase_values = {
|
||||
"sap": float(property_instance.data["current-energy-efficiency"]),
|
||||
"carbon": float(property_instance.data["co2-emissions-current"]),
|
||||
"heat_demand": float(property_instance.data["energy-consumption-current"]),
|
||||
}
|
||||
|
||||
if rec["type"] == "low_energy_lighting":
|
||||
new_heating_cost = property_instance.energy_cost_estimates["adjusted"]["heating"]
|
||||
new_hot_water_cost = property_instance.energy_cost_estimates["adjusted"]["hot_water"]
|
||||
new_lighting_cost = min(
|
||||
new_lighting_cost, property_instance.energy_cost_estimates["adjusted"]["lighting"]
|
||||
)
|
||||
scoring_heating_cost = property_instance.energy_cost_estimates["unadjusted"]["heating"]
|
||||
scoring_hot_water_cost = property_instance.energy_cost_estimates["unadjusted"]["hot_water"]
|
||||
scoring_lighting_cost = min(
|
||||
property_instance.energy_cost_estimates["unadjusted"]["lighting"],
|
||||
new_lighting_cost_unadjusted
|
||||
# In this instance, heating cost and hot water cost should not change so we set the previous
|
||||
# value to the new one, so the difference is zero
|
||||
previous_phase_unadjusted_costs = {
|
||||
"unadjusted_heating_cost": phase_cost["heating_cost"]["predictions"].values[0],
|
||||
"unadjusted_hot_water_cost": phase_cost["hot_water_cost"]["predictions"].values[0],
|
||||
"unadjusted_lighting_cost": (
|
||||
property_instance.energy_cost_estimates["unadjusted"]["lighting"]
|
||||
)
|
||||
}
|
||||
else:
|
||||
new_heating_cost = min(
|
||||
new_heating_cost, property_instance.energy_cost_estimates["adjusted"]["heating"]
|
||||
)
|
||||
new_hot_water_cost = min(
|
||||
new_hot_water_cost, property_instance.energy_cost_estimates["adjusted"]["hot_water"]
|
||||
)
|
||||
new_lighting_cost = property_instance.energy_cost_estimates["adjusted"]["lighting"]
|
||||
|
||||
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 = property_instance.energy_cost_estimates["unadjusted"]["lighting"]
|
||||
|
||||
predicted_heating_cost_reduction = (
|
||||
property_instance.energy_cost_estimates["adjusted"]["heating"] - new_heating_cost
|
||||
)
|
||||
predicted_hot_water_cost_reduction = (
|
||||
property_instance.energy_cost_estimates["adjusted"]["hot_water"] - new_hot_water_cost
|
||||
)
|
||||
|
||||
predicted_lighting_cost_reduction = 0 if rec["type"] != "lighting" else (
|
||||
property_instance.energy_cost_estimates["adjusted"]["lighting"] - new_lighting_cost
|
||||
)
|
||||
# We store this value for later
|
||||
phase_lighting_costs[rec["phase"]] = {
|
||||
"adjusted": new_lighting_cost,
|
||||
"unadjusted": scoring_lighting_cost
|
||||
# If the recommendaiton is not for low energy lighting, we expect the heating/hot water
|
||||
# costs to change but not te lighting
|
||||
previous_phase_unadjusted_costs = {
|
||||
"unadjusted_heating_cost": property_instance.energy_cost_estimates["adjusted"]["heating"],
|
||||
"unadjusted_hot_water_cost": (
|
||||
property_instance.energy_cost_estimates["adjusted"]["hot_water"]
|
||||
),
|
||||
"unadjusted_lighting_cost": phase_cost["lighting_cost"]["predictions"].values[0]
|
||||
}
|
||||
|
||||
# We now predict the kwh savings using the xgb model
|
||||
|
||||
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_heating_kwh = 0 if new_heating_kwh < 0 else new_heating_kwh
|
||||
|
||||
new_hot_water_kwh = energy_consumption_client.score_new_data(
|
||||
new_data=scoring_df, target="hot_water_kwh"
|
||||
)[0]
|
||||
new_hot_water_kwh = 0 if new_hot_water_kwh < 0 else new_hot_water_kwh
|
||||
|
||||
# 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
|
||||
|
||||
(
|
||||
predicted_appliances_cost_reduction,
|
||||
predicted_appliances_kwh_reduction
|
||||
) = cls._calculate_appliance_solar_savings(
|
||||
rec=rec,
|
||||
property_instance=property_instance,
|
||||
heating_kwh_reduction=heating_kwh_reduction,
|
||||
hot_water_kwh_reduction=hot_water_kwh_reduction,
|
||||
lighting_kwh_reduction=lighting_kwh_reduction
|
||||
)
|
||||
|
||||
kwh_reduction = (
|
||||
heating_kwh_reduction +
|
||||
hot_water_kwh_reduction +
|
||||
lighting_kwh_reduction +
|
||||
predicted_appliances_kwh_reduction
|
||||
)
|
||||
|
||||
predicted_bill_savings = (
|
||||
predicted_heating_cost_reduction +
|
||||
predicted_hot_water_cost_reduction +
|
||||
predicted_lighting_cost_reduction +
|
||||
predicted_appliances_cost_reduction
|
||||
)
|
||||
|
||||
phase_kwh_figures[rec["phase"]] = {
|
||||
"adjusted": {
|
||||
"heating": new_heating_kwh_adjusted,
|
||||
"hot_water": new_hot_water_kwh_adjusted
|
||||
},
|
||||
"unadjusted": {
|
||||
"heating": new_heating_kwh,
|
||||
"hot_water": new_hot_water_kwh
|
||||
}
|
||||
}
|
||||
|
||||
else:
|
||||
previous_phase = rec["phase"] - 1
|
||||
predicted_sap_points = (
|
||||
new_sap - sap_phase_impact[sap_phase_impact["phase"] == previous_phase]["predictions"].values[0]
|
||||
)
|
||||
predicted_co2_savings = (
|
||||
carbon_phase_impact[carbon_phase_impact["phase"] == previous_phase]["predictions"].values[0] -
|
||||
new_carbon
|
||||
)
|
||||
predicted_heat_demand = property_instance.floor_area * (
|
||||
heat_phase_impact[heat_phase_impact["phase"] == previous_phase]["predictions"].values[0] -
|
||||
new_heat_demand
|
||||
)
|
||||
previous_phase_values = {
|
||||
"sap": (
|
||||
phase_impact["sap_change"][phase_impact["sap_change"]["phase"] == (rec["phase"] - 1)]
|
||||
["predictions"].values[0]
|
||||
),
|
||||
"carbon": (
|
||||
phase_impact["carbon_change"][phase_impact["carbon_change"]["phase"] == (rec["phase"] - 1)]
|
||||
["predictions"].values[0]
|
||||
),
|
||||
"heat_demand": (
|
||||
phase_impact["heat_demand"][phase_impact["heat_demand"]["phase"] == (rec["phase"] - 1)]
|
||||
["predictions"].values[0]
|
||||
),
|
||||
}
|
||||
|
||||
if rec["type"] == "lighting":
|
||||
# If we have a lighting recommendation, the heating, hot water and lighting costs will
|
||||
# be from the previous phase - nothing will change
|
||||
new_heating_cost = heating_cost_phase_impact[
|
||||
heating_cost_phase_impact["phase"] == previous_phase
|
||||
]["adjusted_cost"].values[0]
|
||||
new_hot_water_cost = hot_water_cost_phase_impact[
|
||||
hot_water_cost_phase_impact["phase"] == previous_phase
|
||||
]["adjusted_cost"].values[0]
|
||||
|
||||
new_lighting_cost = min(
|
||||
new_lighting_cost, phase_lighting_costs[previous_phase]["adjusted"]
|
||||
)
|
||||
# We also use the unadjusted costs for the scoring from the previous phase
|
||||
scoring_heating_cost = heating_cost_phase_impact[
|
||||
heating_cost_phase_impact["phase"] == previous_phase
|
||||
if rec["type"] == "low_energy_lighting":
|
||||
# Heating and hot water costs shouldn't change
|
||||
# {'unadjusted_heating_cost': 501.8528134938132, 'unadjusted_hot_water_cost':
|
||||
# 171.22534405283452, 'unadjusted_lighting_cost': 127.2}
|
||||
previous_phase_unadjusted_costs = {
|
||||
"unadjusted_heating_cost": phase_cost["heating_cost"]["predictions"].values[0],
|
||||
"unadjusted_hot_water_cost": phase_cost["hot_water_cost"]["predictions"].values[0],
|
||||
"unadjusted_lighting_cost": phase_impact["lighting_cost"][
|
||||
phase_impact["lighting_cost"]["phase"] == (rec["phase"] - 1)
|
||||
]["predictions"].values[0]
|
||||
scoring_hot_water_cost = hot_water_cost_phase_impact[
|
||||
hot_water_cost_phase_impact["phase"] == previous_phase
|
||||
]["predictions"].values[0]
|
||||
scoring_lighting_cost = min(
|
||||
new_lighting_cost_unadjusted,
|
||||
phase_lighting_costs[previous_phase]["unadjusted"]
|
||||
)
|
||||
}
|
||||
else:
|
||||
# Whereas for other recommendations, we use the new costs
|
||||
new_heating_cost = min(
|
||||
new_heating_cost,
|
||||
heating_cost_phase_impact[
|
||||
heating_cost_phase_impact["phase"] == previous_phase
|
||||
]["adjusted_cost"].values[0]
|
||||
)
|
||||
new_hot_water_cost = min(
|
||||
new_hot_water_cost,
|
||||
hot_water_cost_phase_impact[
|
||||
hot_water_cost_phase_impact["phase"] == previous_phase
|
||||
]["adjusted_cost"].values[0]
|
||||
)
|
||||
new_lighting_cost = phase_lighting_costs[previous_phase]["adjusted"]
|
||||
|
||||
scoring_heating_cost = min(
|
||||
new_heating_cost_unadjusted,
|
||||
heating_cost_phase_impact[
|
||||
heating_cost_phase_impact["phase"] == previous_phase
|
||||
]["predictions"].values[0]
|
||||
)
|
||||
scoring_hot_water_cost = min(
|
||||
new_hot_water_cost_unadjusted,
|
||||
hot_water_cost_phase_impact[
|
||||
hot_water_cost_phase_impact["phase"] == previous_phase
|
||||
]["predictions"].values[0]
|
||||
)
|
||||
scoring_lighting_cost = phase_lighting_costs[previous_phase]["unadjusted"]
|
||||
|
||||
# We now estimate the adjusted cost savings for the recommendation
|
||||
predicted_heating_cost_reduction = (
|
||||
heating_cost_phase_impact[heating_cost_phase_impact["phase"] == previous_phase][
|
||||
"adjusted_cost"
|
||||
].values[0] - new_heating_cost
|
||||
)
|
||||
|
||||
predicted_hot_water_cost_reduction = (
|
||||
hot_water_cost_phase_impact[hot_water_cost_phase_impact["phase"] == previous_phase][
|
||||
"adjusted_cost"
|
||||
].values[0] - new_hot_water_cost
|
||||
)
|
||||
|
||||
# Only lighting recommendations can have an impact here
|
||||
predicted_lighting_cost_reduction = (
|
||||
phase_lighting_costs[previous_phase]["adjusted"] - new_lighting_cost
|
||||
)
|
||||
|
||||
# We now predict the kwh savings using the xgb model - this is based on
|
||||
# the new costs at this phase
|
||||
|
||||
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 (
|
||||
phase_kwh_figures[previous_phase]["adjusted"]["heating"] - new_heating_kwh_adjusted
|
||||
)
|
||||
if heating_kwh_reduction < 0:
|
||||
heating_kwh_reduction = 0
|
||||
|
||||
hot_water_kwh_reduction = 0 if predicted_hot_water_cost_reduction == 0 else (
|
||||
phase_kwh_figures[previous_phase]["adjusted"]["hot_water"] - new_hot_water_kwh_adjusted
|
||||
)
|
||||
if hot_water_kwh_reduction < 0:
|
||||
hot_water_kwh_reduction = 0
|
||||
|
||||
lighting_kwh_reduction = predicted_lighting_cost_reduction / AnnualBillSavings.ELECTRICITY_PRICE_CAP
|
||||
|
||||
(
|
||||
predicted_appliances_cost_reduction,
|
||||
predicted_appliances_kwh_reduction
|
||||
) = cls._calculate_appliance_solar_savings(
|
||||
rec=rec,
|
||||
property_instance=property_instance,
|
||||
heating_kwh_reduction=heating_kwh_reduction,
|
||||
hot_water_kwh_reduction=hot_water_kwh_reduction,
|
||||
lighting_kwh_reduction=lighting_kwh_reduction
|
||||
)
|
||||
|
||||
# We now calculate the predicted_bill_savings
|
||||
predicted_bill_savings = (
|
||||
predicted_heating_cost_reduction + predicted_hot_water_cost_reduction +
|
||||
predicted_lighting_cost_reduction + predicted_appliances_cost_reduction
|
||||
)
|
||||
|
||||
kwh_reduction = (
|
||||
heating_kwh_reduction +
|
||||
hot_water_kwh_reduction +
|
||||
lighting_kwh_reduction +
|
||||
predicted_appliances_kwh_reduction
|
||||
)
|
||||
|
||||
# We store this value for later
|
||||
phase_lighting_costs[rec["phase"]] = {
|
||||
"adjusted": new_lighting_cost,
|
||||
"unadjusted": scoring_lighting_cost
|
||||
# update heating and hot water costs
|
||||
previous_phase_unadjusted_costs = {
|
||||
"unadjusted_heating_cost": phase_impact["heating_cost"][
|
||||
phase_impact["heating_cost"]["phase"] == (rec["phase"] - 1)
|
||||
]["predictions"].values[0],
|
||||
"unadjusted_hot_water_cost": phase_impact["hot_water_cost"][
|
||||
phase_impact["hot_water_cost"]["phase"] == (rec["phase"] - 1)
|
||||
]["predictions"].values[0],
|
||||
"unadjusted_lighting_cost": phase_cost["lighting_cost"]["predictions"].values[0]
|
||||
}
|
||||
|
||||
phase_kwh_figures[rec["phase"]] = {
|
||||
"adjusted": {
|
||||
"heating": new_heating_kwh_adjusted,
|
||||
"hot_water": new_hot_water_kwh_adjusted
|
||||
},
|
||||
"unadjusted": {
|
||||
"heating": new_heating_kwh,
|
||||
"hot_water": new_hot_water_kwh
|
||||
previous_phase_values.update(previous_phase_unadjusted_costs)
|
||||
|
||||
# We extract the values for the current phase
|
||||
current_phase_values = {
|
||||
"sap": phase_energy_efficiency_metrics["sap_change"],
|
||||
"carbon": phase_energy_efficiency_metrics["carbon_change"],
|
||||
"heat_demand": phase_energy_efficiency_metrics["heat_demand"],
|
||||
"unadjusted_heating_cost": phase_cost["heating_cost"]["predictions"].values[0],
|
||||
"unadjusted_hot_water_cost": phase_cost["hot_water_cost"]["predictions"].values[0],
|
||||
"unadjusted_lighting_cost": phase_cost["lighting_cost"]["predictions"].values[0]
|
||||
}
|
||||
|
||||
property_phase_impact = {
|
||||
# Increasing
|
||||
"sap": current_phase_values["sap"] - previous_phase_values["sap"],
|
||||
# Decreasing
|
||||
"carbon": previous_phase_values["carbon"] - current_phase_values["carbon"],
|
||||
# Decreasing
|
||||
"heat_demand": previous_phase_values["heat_demand"] - current_phase_values["heat_demand"],
|
||||
# Decreasing
|
||||
"unadjusted_heating_cost": (
|
||||
previous_phase_values["unadjusted_heating_cost"] -
|
||||
current_phase_values["unadjusted_heating_cost"]
|
||||
),
|
||||
# Decreasing
|
||||
"unadjusted_hot_water_cost": (
|
||||
previous_phase_values["unadjusted_hot_water_cost"] -
|
||||
current_phase_values["unadjusted_hot_water_cost"]
|
||||
),
|
||||
# Decreasing
|
||||
"unadjusted_lighting_cost": (
|
||||
previous_phase_values["unadjusted_lighting_cost"] -
|
||||
current_phase_values["unadjusted_lighting_cost"]
|
||||
)
|
||||
}
|
||||
|
||||
# Prevent from being negative
|
||||
predicted_sap_points = 0 if predicted_sap_points < 0 else predicted_sap_points
|
||||
predicted_co2_savings = 0 if predicted_co2_savings < 0 else predicted_co2_savings
|
||||
predicted_heat_demand = 0 if predicted_heat_demand < 0 else predicted_heat_demand
|
||||
for metric in ["sap", "carbon", "heat_demand"]:
|
||||
property_phase_impact[metric] = (
|
||||
0 if property_phase_impact[metric] < 0 else property_phase_impact[metric]
|
||||
)
|
||||
if metric == "sap":
|
||||
property_phase_impact[metric] = round(property_phase_impact[metric], 2)
|
||||
|
||||
# For the moment, we cap the number of SAP points that can be achieved by LEDs at 2
|
||||
if rec["type"] == "low_energy_lighting":
|
||||
# For the moment, we cap the number of SAP points that can be achieved by ventilation at 2
|
||||
rec["sap_points"] = min(predicted_sap_points, LightingRecommendations.SAP_LIMIT)
|
||||
rec["co2_equivalent_savings"] = min(predicted_co2_savings, rec["co2_equivalent_savings"])
|
||||
rec["heat_demand"] = predicted_heat_demand
|
||||
else:
|
||||
rec["sap_points"] = predicted_sap_points
|
||||
rec["co2_equivalent_savings"] = predicted_co2_savings
|
||||
rec["heat_demand"] = predicted_heat_demand
|
||||
property_phase_impact["sap"] = min(property_phase_impact["sap"], LightingRecommendations.SAP_LIMIT)
|
||||
property_phase_impact["carbon"] = min(
|
||||
property_phase_impact["carbon"], rec["co2_equivalent_savings"]
|
||||
)
|
||||
|
||||
# Round to 2 decimal places
|
||||
rec["sap_points"] = round(rec["sap_points"], 2)
|
||||
|
||||
rec["kwh_savings"] = kwh_reduction
|
||||
rec["energy_cost_savings"] = predicted_bill_savings
|
||||
|
||||
if rec["recommendation_id"] in representative_rec_ids:
|
||||
bill_savings_list.append(predicted_bill_savings)
|
||||
kwh_savings_list.append(kwh_reduction)
|
||||
# Insert this information into the recommendation
|
||||
rec["sap_points"] = property_phase_impact["sap"]
|
||||
rec["co2_equivalent_savings"] = property_phase_impact["carbon"]
|
||||
rec["heat_demand"] = property_phase_impact["heat_demand"]
|
||||
|
||||
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):
|
||||
rec["heat_demand"] is None):
|
||||
raise ValueError("sap points, co2 or heat demand is missing")
|
||||
|
||||
# We sum up the total savings for the property and that is our expected energy bill
|
||||
|
||||
expected_energy_bill = property_instance.current_energy_bill - sum(bill_savings_list)
|
||||
expected_adjusted_energy = property_instance.current_adjusted_energy - sum(kwh_savings_list)
|
||||
|
||||
return (
|
||||
property_recommendations,
|
||||
expected_adjusted_energy,
|
||||
expected_energy_bill
|
||||
impact_summary.append(
|
||||
{
|
||||
"phase": rec["phase"],
|
||||
"recommendation_id": rec["recommendation_id"],
|
||||
**current_phase_values
|
||||
}
|
||||
)
|
||||
|
||||
return property_recommendations, impact_summary
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue