mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
added re-baselining to the property model
This commit is contained in:
parent
b156513524
commit
808a5122ee
6 changed files with 118 additions and 32 deletions
|
|
@ -800,13 +800,19 @@ class Property:
|
|||
to_update[k] = None
|
||||
return to_update
|
||||
|
||||
def get_full_property_data(self, current_valuation=None):
|
||||
def get_full_property_data(self, current_valuation=None, needs_rebaselining=False, rebaselining_sap=0):
|
||||
"""
|
||||
This method extracts the data which is pushed to the database, containing core information, from the EPC
|
||||
about a property
|
||||
:return:
|
||||
"""
|
||||
|
||||
current_sap_rating = self.data["current-energy-efficiency"]
|
||||
if needs_rebaselining:
|
||||
current_sap_rating += rebaselining_sap
|
||||
|
||||
current_epc_rating = sap_to_epc(current_sap_rating)
|
||||
|
||||
property_data = {
|
||||
"creation_status": "READY",
|
||||
"uprn": int(self.data["uprn"]),
|
||||
|
|
@ -823,9 +829,12 @@ class Property:
|
|||
"number_of_rooms": self.number_of_rooms,
|
||||
"year_built": self.year_built,
|
||||
"tenure": self.data["tenure"],
|
||||
"current_epc_rating": self.data["current-energy-rating"],
|
||||
"current_sap_points": self.data["current-energy-efficiency"],
|
||||
"current_epc_rating": current_epc_rating,
|
||||
"current_sap_points": current_sap_rating,
|
||||
"current_valuation": current_valuation,
|
||||
"original_sap_points": self.data["current-energy-efficiency"],
|
||||
"is_sap_points_adjusted_for_installed_measures": needs_rebaselining,
|
||||
"installed_measures_sap_point_adjustment": rebaselining_sap,
|
||||
}
|
||||
|
||||
property_data = self._clean_upload_data(property_data)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ def prepare_plan_data(
|
|||
"""
|
||||
# Plan carbon savings
|
||||
co2_savings = sum([r["co2_equivalent_savings"] for r in default_recommendations])
|
||||
raise Exception("CHECK ME")
|
||||
post_co2_emissions = p.energy["co2_emissions"] - co2_savings
|
||||
|
||||
# Plan bill savings
|
||||
|
|
|
|||
|
|
@ -929,9 +929,7 @@ async def model_engine(body: PlanTriggerRequest):
|
|||
# any panel performance, we ensure that we have a 3kWp and 4kWp option for the property
|
||||
|
||||
logger.info("Identifying property recommendations")
|
||||
recommendations = {}
|
||||
recommendations_scoring_data = []
|
||||
representative_recommendations = {}
|
||||
recommendations, recommendations_scoring_data, representative_recommendations = {}, [], {}
|
||||
for p in tqdm(input_properties):
|
||||
# We set the ECO package data, if we have it
|
||||
property_eco_package = eco_packages.get(p.id, (None, None, None))
|
||||
|
|
@ -965,17 +963,15 @@ async def model_engine(body: PlanTriggerRequest):
|
|||
recommendations_scoring_data.extend(p.recommendations_scoring_data)
|
||||
|
||||
logger.info("Preparing data for scoring in sap change api")
|
||||
recommendations_scoring_data = pd.DataFrame(recommendations_scoring_data)
|
||||
recommendations_scoring_data = pd.DataFrame(recommendations_scoring_data).drop(
|
||||
columns=[
|
||||
"rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
|
||||
"carbon_ending"
|
||||
]
|
||||
)
|
||||
# Temp putting this here
|
||||
recommendations_scoring_data["is_post_sap10_ending"] = True
|
||||
|
||||
recommendations_scoring_data["sap_starting"] = 77
|
||||
|
||||
recommendations_scoring_data = recommendations_scoring_data.drop(
|
||||
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
|
||||
"carbon_ending"]
|
||||
)
|
||||
|
||||
all_predictions = await model_api.async_paginated_predictions(
|
||||
data=recommendations_scoring_data,
|
||||
bucket=get_settings().DATA_BUCKET,
|
||||
|
|
@ -1015,19 +1011,19 @@ async def model_engine(body: PlanTriggerRequest):
|
|||
|
||||
# We now insert kwh estimates and costs into the recommendations
|
||||
logger.info("Calculating tenant savings - kwh and bills")
|
||||
for property_id in tqdm([p.id for p in input_properties]):
|
||||
for p in tqdm(input_properties):
|
||||
property_id = p.id
|
||||
property_recommendations = recommendations.get(property_id, [])
|
||||
property_instance = [p for p in input_properties if p.id == property_id][0]
|
||||
|
||||
property_current_energy_bill = (
|
||||
Recommendations.calculate_recommendation_tenant_savings(
|
||||
property_instance=property_instance,
|
||||
property_instance=p,
|
||||
kwh_simulation_predictions=kwh_simulation_predictions,
|
||||
property_recommendations=property_recommendations,
|
||||
ashp_cop=body.ashp_cop
|
||||
)
|
||||
)
|
||||
property_instance.current_energy_bill = property_current_energy_bill
|
||||
p.current_energy_bill = property_current_energy_bill
|
||||
|
||||
# Insert the predictions into the recommendations and run the optimiser
|
||||
logger.info("Optimising measures")
|
||||
|
|
@ -1195,23 +1191,40 @@ async def model_engine(body: PlanTriggerRequest):
|
|||
|
||||
property_updates, property_epc_details, property_spatial_updates = [], [], []
|
||||
plans_to_create, recommendations_to_create = [], []
|
||||
# TODO: Check the update to carbon
|
||||
print("NEED TO CHECK THE UPDATE TO CARBON")
|
||||
# Prepare the data that will need to be uploaded in bulk
|
||||
for p in input_properties:
|
||||
recommendations_for_property = recommendations.get(p.id, [])
|
||||
default_recommendations = [r for r in recommendations_for_property if r["default"]]
|
||||
|
||||
# We need to:
|
||||
# Get already installed measures
|
||||
already_installed_default = [r for r in default_recommendations if r["already_installed"]]
|
||||
# Property should be have increased SAP
|
||||
needs_rebaselining = bool(len(already_installed_default))
|
||||
rebaselining_sap = float(sum([r["sap_points"] for r in already_installed_default]))
|
||||
rebaselining_carbon = float(sum([r["co2_equivalent_savings"] for r in already_installed_default]))
|
||||
rebaselining_heat_demand = float(sum([r["heat_demand"] for r in already_installed_default]))
|
||||
rebaselining_kwh = float(sum([r["kwh_savings"] for r in already_installed_default]))
|
||||
rebaselining_bills = float(sum([r["energy_cost_savings"] for r in already_installed_default]))
|
||||
# TODO - gotta apply the adjustments to the property table, and the property_details_epc table
|
||||
|
||||
# This will include everything, including already installed
|
||||
total_sap_points = sum([r["sap_points"] for r in default_recommendations])
|
||||
new_sap_points = float(p.data["current-energy-efficiency"]) + total_sap_points
|
||||
new_epc = sap_to_epc(new_sap_points)
|
||||
total_cost = sum([r["total"] for r in default_recommendations])
|
||||
# Already installed measures do not have a cost but we remove anyway
|
||||
total_cost = sum([r["total"] for r in default_recommendations if not r["already_installed"]])
|
||||
valuations = PropertyValuation.estimate(property_instance=p, target_epc=new_epc, total_cost=total_cost)
|
||||
|
||||
# --- property-level updates (always) ---
|
||||
property_updates.append({
|
||||
"property_id": p.id,
|
||||
"portfolio_id": body.portfolio_id,
|
||||
"data": p.get_full_property_data(current_valuation=valuations["current_value"])
|
||||
"data": p.get_full_property_data(
|
||||
current_valuation=valuations["current_value"],
|
||||
needs_rebaselining=needs_rebaselining,
|
||||
rebaselining_sap=rebaselining_sap,
|
||||
)
|
||||
})
|
||||
|
||||
property_epc_details.append(p.get_property_details_epc(portfolio_id=body.portfolio_id))
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@ class ModelApi:
|
|||
@staticmethod
|
||||
def extract_phase(recommendation_id):
|
||||
if 'phase=' in recommendation_id:
|
||||
return int(recommendation_id.split('phase=')[1][0])
|
||||
extracted = recommendation_id.split('phase=')[1]
|
||||
return int(extracted.strip())
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import re
|
||||
import backend.app.assumptions as assumptions
|
||||
from etl.customers.immo.pilot.asset_list import already_installed
|
||||
from recommendations.recommendation_utils import (
|
||||
check_simulation_difference, override_costs, combine_recommendation_configs
|
||||
)
|
||||
|
|
@ -320,12 +321,6 @@ class HeatingRecommender:
|
|||
|
||||
measures = MEASURE_MAP["heating"] if measures is None else measures
|
||||
|
||||
# TODO: We could have a system flush recommendation for an existing boiler, where there is no need to replace
|
||||
# the boiler, but instead flushing the system will make it run more efficiently. There is a cost for this
|
||||
# in the Costs class, stored as SYSTEM_FLUSH_COST
|
||||
|
||||
# TODO: Right now, we don't have recommendations for electric boilers - we should probably have one
|
||||
|
||||
# if we have a non-invasive ashp recommendation, we get the configuration directly from the property instance
|
||||
non_invasive_ashp_recommendation = next(
|
||||
(r for r in self.property.non_invasive_recommendations if r["type"] == "air_source_heat_pump"),
|
||||
|
|
@ -1115,6 +1110,7 @@ class HeatingRecommender:
|
|||
"hot-water-energy-eff": heating_simulation_config["hot_water_energy_eff_ending"]
|
||||
}
|
||||
|
||||
# TODO: Probably don't need to use this for HHRSH - simplify
|
||||
recommendations = self.combine_heating_and_controls(
|
||||
controls_recommendations=controls_recommender.recommendation,
|
||||
heating_simulation_config=heating_simulation_config,
|
||||
|
|
@ -1128,6 +1124,12 @@ class HeatingRecommender:
|
|||
non_intrusive_recommendation=non_intrusive_recommendation,
|
||||
heating_product=hhrsh_product
|
||||
)
|
||||
|
||||
# Check if HHRSH are already installed
|
||||
already_installed = "high_heat_retention_storage_heaters" in self.property.already_installed
|
||||
for rec in recommendations:
|
||||
rec["already_installed"] = already_installed
|
||||
|
||||
if _return:
|
||||
return recommendations
|
||||
|
||||
|
|
@ -1347,7 +1349,7 @@ class HeatingRecommender:
|
|||
n_rooms=self.property.number_of_rooms
|
||||
)
|
||||
|
||||
already_installed = "heating" in self.property.already_installed
|
||||
already_installed = "boiler_upgrade" in self.property.already_installed
|
||||
if already_installed:
|
||||
boiler_costs = override_costs(boiler_costs)
|
||||
description = "Heating system has already been upgraded, no further action needed."
|
||||
|
|
|
|||
|
|
@ -272,6 +272,36 @@ class Recommendations:
|
|||
property_recommendations.append(self.solar_recommender.recommendation)
|
||||
phase += 1
|
||||
|
||||
if self.property_instance.already_installed:
|
||||
# We need to re-shuffle our measures
|
||||
property_recommendations_removed_installed = []
|
||||
already_installed_recs = []
|
||||
for recs in property_recommendations:
|
||||
phase_recs = []
|
||||
phase_already_installed_recs = []
|
||||
for rec in recs:
|
||||
if rec["already_installed"]:
|
||||
phase_already_installed_recs.append(rec)
|
||||
else:
|
||||
phase_recs.append(rec)
|
||||
if phase_recs:
|
||||
property_recommendations_removed_installed.append(phase_recs)
|
||||
if phase_already_installed_recs:
|
||||
already_installed_recs.append(phase_already_installed_recs)
|
||||
|
||||
# We re-set the phases
|
||||
for i, recs in enumerate(property_recommendations_removed_installed):
|
||||
for rec in recs:
|
||||
rec["phase"] = i
|
||||
# already installed recs get negative phasing
|
||||
already_installed_phase = -len(already_installed_recs)
|
||||
for recs in already_installed_recs:
|
||||
for rec in recs:
|
||||
rec["phase"] = already_installed_phase
|
||||
already_installed_phase += 1
|
||||
|
||||
property_recommendations = already_installed_recs + property_recommendations_removed_installed
|
||||
|
||||
# We insert temporary ids into the recommendations which is important for the optimiser later
|
||||
property_recommendations = self.insert_temp_recommendation_id(property_recommendations)
|
||||
|
||||
|
|
@ -486,6 +516,11 @@ class Recommendations:
|
|||
mv_increasing_variables = ["carbon", "heat_demand"]
|
||||
mv_decreasing_variables = ["sap"]
|
||||
|
||||
# We allow for negative phase
|
||||
starting_phase = min(
|
||||
rec["phase"] for recs in property_recommendations for rec in recs
|
||||
)
|
||||
|
||||
impact_summary = []
|
||||
for recommendations_by_type in property_recommendations:
|
||||
for rec in recommendations_by_type:
|
||||
|
|
@ -526,7 +561,7 @@ class Recommendations:
|
|||
|
||||
# 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:
|
||||
if rec["phase"] == starting_phase:
|
||||
# These are just the starting values, from the EPC. When we score the ML models,
|
||||
# heating_cost_starting and heating_cost_ending are just the values in the EPC. However, with
|
||||
# heating_cost_ending, we expect that the EPC will predict a heating cost based on what would happen
|
||||
|
|
@ -954,6 +989,33 @@ class Recommendations:
|
|||
pd.isnull(kwh_impact_table["hotwater_fuel_type"]).sum()):
|
||||
raise Exception("Fuel type is missing")
|
||||
|
||||
# As one final adjustment, if we
|
||||
# 1) have a boiler upgrade recommendation
|
||||
# 2) Have an average efficiency boiler, we adjust the COP of the existing boiler down to 75%
|
||||
heating_upgrades = [x for x in property_recommendations if x[0]["type"] == "heating"]
|
||||
boiler_upgrade = [r for recs in heating_upgrades for r in recs if r["measure_type"] == "boiler_upgrade"]
|
||||
existing_heating_efficiency = property_instance.data["mainheat-energy-eff"]
|
||||
|
||||
if len(boiler_upgrade) and existing_heating_efficiency in ["Very Poor", "Poor", "Average"]:
|
||||
efficiency_map = {"Very Poor": 0.6, "Poor": 0.65, "Average": 0.7}
|
||||
adjusted_cop = efficiency_map[existing_heating_efficiency]
|
||||
boiler_phase = boiler_upgrade[0]["phase"]
|
||||
heating_measure_types_to_id = [
|
||||
{"recommendation_id": r["recommendation_id"], "measure_type": r["measure_type"]}
|
||||
for r in heating_upgrades[0]
|
||||
]
|
||||
kwh_impact_table = kwh_impact_table.merge(
|
||||
pd.DataFrame(heating_measure_types_to_id), how="left", on="recommendation_id"
|
||||
)
|
||||
for col in ["heating_cop", "hotwater_cop"]:
|
||||
kwh_impact_table[col] = np.where(
|
||||
(kwh_impact_table["phase"] <= boiler_phase) &
|
||||
(kwh_impact_table["heating_fuel_type"] == "Natural Gas") &
|
||||
(kwh_impact_table["measure_type"] != "boiler_upgrade"),
|
||||
adjusted_cop, kwh_impact_table[col]
|
||||
)
|
||||
kwh_impact_table = kwh_impact_table.drop(columns=["measure_type"])
|
||||
|
||||
# We now calculate the fuel cost
|
||||
for k in ["heating", "hotwater"]:
|
||||
kwh_impact_table[f"{k}_cost"] = kwh_impact_table.apply(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue