mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Added in the sequential scoring code
This commit is contained in:
parent
925a6c1887
commit
0f60082ba1
4 changed files with 207 additions and 114 deletions
|
|
@ -150,158 +150,195 @@ class Property:
|
|||
|
||||
# self.base_difference_record.df
|
||||
|
||||
def adjust_difference_record_with_recommendations(self, property_recommendations):
|
||||
def adjust_difference_record_with_recommendations(
|
||||
self, property_recommendations,
|
||||
property_representative_recommendations
|
||||
):
|
||||
"""
|
||||
This method will adjust the difference record, based on the recommendations made for the property
|
||||
|
||||
In order to score the measures, we need to consider the phase of the retrofit.
|
||||
|
||||
:param property_recommendations: dictionary of recommendations for the property
|
||||
:param property_representative_recommendations: dictionary of representative recommendations for the property
|
||||
"""
|
||||
|
||||
self.recommendations_scoring_data = []
|
||||
phases = sorted([r[0]["phase"] for r in property_recommendations if r[0]["phase"] is not None])
|
||||
|
||||
for phase in phases:
|
||||
property_recommendations_by_phase = [r for r in property_recommendations if r[0]["phase"] == phase][0]
|
||||
previous_phases = [p for p in phases if p < phase]
|
||||
previous_phase_representatives = [
|
||||
r for r in property_representative_recommendations if r["phase"] in previous_phases
|
||||
]
|
||||
recommendation_record = self.base_difference_record.df.to_dict("records")[0].copy()
|
||||
|
||||
for rec in property_recommendations_by_phase:
|
||||
# We simulate the impact of the recommendation at this current phase, and all of the prior phases
|
||||
|
||||
for recommendations_by_type in property_recommendations:
|
||||
for i, rec in enumerate(recommendations_by_type):
|
||||
recommendation_record = self.base_difference_record.df.to_dict("records")[0].copy()
|
||||
scoring_dict = self.create_recommendation_scoring_data(
|
||||
property_id=self.id, recommendation_record=recommendation_record, recommendation=rec,
|
||||
property_id=self.id,
|
||||
recommendation_record=recommendation_record,
|
||||
recommendations=previous_phase_representatives + [rec],
|
||||
primary_recommendation_id=rec["recommendation_id"]
|
||||
)
|
||||
|
||||
self.recommendations_scoring_data.append(scoring_dict)
|
||||
|
||||
@staticmethod
|
||||
def create_recommendation_scoring_data(property_id, recommendation_record, recommendation: dict):
|
||||
def create_recommendation_scoring_data(
|
||||
property_id, recommendation_record, recommendations: list, primary_recommendation_id: int
|
||||
):
|
||||
"""
|
||||
This function will iterate through a list of recommendations and apply a simulation for each recommendation
|
||||
This allows us to later multiple measures and see the impact of the measures on the property
|
||||
:param property_id: The id of the property
|
||||
:param recommendation_record: The record of the property, which will be updated
|
||||
:param recommendations: The list of recommendations to apply
|
||||
:param primary_recommendation_id: The id of the primary recommendation, which is used to identify the record
|
||||
:return: The updated recommendation record
|
||||
"""
|
||||
|
||||
output = recommendation_record.copy()
|
||||
|
||||
for col in [
|
||||
"walls_insulation_thickness", "floor_insulation_thickness", "roof_insulation_thickness"
|
||||
]:
|
||||
if recommendation_record[col] is None:
|
||||
recommendation_record[col] = "none"
|
||||
if output[col] is None:
|
||||
output[col] = "none"
|
||||
|
||||
# We update the description to indicate it's insulated
|
||||
if recommendation["type"] in ["internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation"]:
|
||||
# The upgrade made here is to the u-value of the walls and the description of the
|
||||
# insulation thickness
|
||||
recommendation_record["walls_thermal_transmittance_ending"] = recommendation["new_u_value"]
|
||||
recommendation_record["walls_insulation_thickness_ending"] = "above average"
|
||||
recommendation_record["walls_energy_eff_ending"] = "Good"
|
||||
for recommendation in recommendations:
|
||||
# For the list of recommendations we have, we iteratively update the output
|
||||
|
||||
# Note: often when the wall is insulatied, the internal/external insulation is not noted so we should
|
||||
# test the impact of using these booleans
|
||||
if recommendation["type"] == "external_wall_insulation":
|
||||
recommendation_record["external_insulation"] = True
|
||||
recommendation_record["internal_insulation"] = False
|
||||
# We update the description to indicate it's insulated
|
||||
if recommendation["type"] in [
|
||||
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation"
|
||||
]:
|
||||
# The upgrade made here is to the u-value of the walls and the description of the
|
||||
# insulation thickness
|
||||
output["walls_thermal_transmittance_ending"] = recommendation["new_u_value"]
|
||||
output["walls_insulation_thickness_ending"] = "above average"
|
||||
output["walls_energy_eff_ending"] = "Good"
|
||||
|
||||
if recommendation["type"] == "internal_wall_insulation":
|
||||
recommendation_record["external_insulation"] = False
|
||||
recommendation_record["internal_insulation"] = True
|
||||
# Note: often when the wall is insulatied, the internal/external insulation is not noted so we should
|
||||
# test the impact of using these booleans
|
||||
if recommendation["type"] == "external_wall_insulation":
|
||||
output["external_insulation"] = True
|
||||
output["internal_insulation"] = False
|
||||
|
||||
else:
|
||||
if recommendation_record["walls_thermal_transmittance_ending"] is None:
|
||||
raise ValueError("We should not have a None value for the u value")
|
||||
if recommendation["type"] == "internal_wall_insulation":
|
||||
output["external_insulation"] = False
|
||||
output["internal_insulation"] = True
|
||||
|
||||
if recommendation_record["walls_insulation_thickness_ending"] is None:
|
||||
recommendation_record["walls_insulation_thickness_ending"] = "none"
|
||||
# When making a recommendation for the wall, we will also update the ventilation
|
||||
if output["mechanical_ventilation_ending"] == 'natural':
|
||||
output["mechanical_ventilation_ending"] = 'mechanical, extract only'
|
||||
|
||||
# Update description to indicate it's insulate
|
||||
if recommendation["type"] in [
|
||||
"solid_floor_insulation", "suspended_floor_insulation", "exposed_floor_insulation"
|
||||
]:
|
||||
if len(recommendation["parts"]) > 1:
|
||||
raise NotImplementedError("Have more than 1 floor insulation part - handle this case")
|
||||
else:
|
||||
if output["walls_thermal_transmittance_ending"] is None:
|
||||
raise ValueError("We should not have a None value for the u value")
|
||||
|
||||
# recommendation_record["floor_thermal_transmittance_ending"] = recommendation["new_u_value"]
|
||||
# We don't really see above average for this in the training data
|
||||
recommendation_record["floor_insulation_thickness_ending"] = "average"
|
||||
# This is rarely ever populated in the training data
|
||||
# recommendation_record["floor_energy_eff_ending"] = "Good"
|
||||
else:
|
||||
if recommendation_record["floor_thermal_transmittance_ending"] is None:
|
||||
raise ValueError("We should not have a None value for the u value")
|
||||
if output["walls_insulation_thickness_ending"] is None:
|
||||
output["walls_insulation_thickness_ending"] = "none"
|
||||
|
||||
if recommendation_record["floor_insulation_thickness_ending"] is None:
|
||||
recommendation_record["floor_insulation_thickness_ending"] = "none"
|
||||
# Update description to indicate it's insulate
|
||||
if recommendation["type"] in [
|
||||
"solid_floor_insulation", "suspended_floor_insulation", "exposed_floor_insulation"
|
||||
]:
|
||||
if len(recommendation["parts"]) > 1:
|
||||
raise NotImplementedError("Have more than 1 floor insulation part - handle this case")
|
||||
|
||||
if recommendation["type"] in ["loft_insulation", "room_roof_insulation", "flat_roof_insulation"]:
|
||||
recommendation_record["roof_thermal_transmittance_ending"] = recommendation["new_u_value"]
|
||||
# output["floor_thermal_transmittance_ending"] = recommendation["new_u_value"]
|
||||
# We don't really see above average for this in the training data
|
||||
output["floor_insulation_thickness_ending"] = "average"
|
||||
# This is rarely ever populated in the training data
|
||||
# output["floor_energy_eff_ending"] = "Good"
|
||||
else:
|
||||
if output["floor_thermal_transmittance_ending"] is None:
|
||||
raise ValueError("We should not have a None value for the u value")
|
||||
|
||||
parts = recommendation["parts"]
|
||||
if len(parts) != 1:
|
||||
raise ValueError("More than one part for roof insulation - investiage me")
|
||||
if output["floor_insulation_thickness_ending"] is None:
|
||||
output["floor_insulation_thickness_ending"] = "none"
|
||||
|
||||
# This is based on the values we have in the training data
|
||||
valid_numeric_values = [
|
||||
12, 25, 50, 75, 100, 150, 200, 250, 270, 300, 350, 400
|
||||
]
|
||||
if recommendation["type"] in ["loft_insulation", "room_roof_insulation", "flat_roof_insulation"]:
|
||||
output["roof_thermal_transmittance_ending"] = recommendation["new_u_value"]
|
||||
|
||||
proposed_depth = int(parts[0]["depth"])
|
||||
if proposed_depth not in valid_numeric_values:
|
||||
# Take the nearest value for scoring
|
||||
proposed_depth = min(valid_numeric_values, key=lambda x: abs(x - proposed_depth))
|
||||
parts = recommendation["parts"]
|
||||
if len(parts) != 1:
|
||||
raise ValueError("More than one part for roof insulation - investiage me")
|
||||
|
||||
recommendation_record["roof_insulation_thickness_ending"] = str(proposed_depth)
|
||||
if recommendation["type"] == "loft_insulation":
|
||||
if proposed_depth >= 270:
|
||||
recommendation_record["roof_energy_eff_ending"] = "Very Good"
|
||||
# This is based on the values we have in the training data
|
||||
valid_numeric_values = [
|
||||
12, 25, 50, 75, 100, 150, 200, 250, 270, 300, 350, 400
|
||||
]
|
||||
|
||||
proposed_depth = int(parts[0]["depth"])
|
||||
if proposed_depth not in valid_numeric_values:
|
||||
# Take the nearest value for scoring
|
||||
proposed_depth = min(valid_numeric_values, key=lambda x: abs(x - proposed_depth))
|
||||
|
||||
output["roof_insulation_thickness_ending"] = str(proposed_depth)
|
||||
if recommendation["type"] == "loft_insulation":
|
||||
if proposed_depth >= 270:
|
||||
output["roof_energy_eff_ending"] = "Very Good"
|
||||
else:
|
||||
output["roof_energy_eff_ending"] = "Good"
|
||||
else:
|
||||
recommendation_record["roof_energy_eff_ending"] = "Good"
|
||||
output["roof_energy_eff_ending"] = "Very Good"
|
||||
else:
|
||||
recommendation_record["roof_energy_eff_ending"] = "Very Good"
|
||||
else:
|
||||
# Fill missing roof u-values - this fill is not based on recommended upgrades
|
||||
if recommendation_record["roof_thermal_transmittance_ending"] is None:
|
||||
raise ValueError("We should not have a None value for the u value")
|
||||
# Fill missing roof u-values - this fill is not based on recommended upgrades
|
||||
if output["roof_thermal_transmittance_ending"] is None:
|
||||
raise ValueError("We should not have a None value for the u value")
|
||||
|
||||
if recommendation_record["roof_insulation_thickness_ending"] is None:
|
||||
recommendation_record["roof_insulation_thickness_ending"] = "none"
|
||||
if output["roof_insulation_thickness_ending"] is None:
|
||||
output["roof_insulation_thickness_ending"] = "none"
|
||||
|
||||
if recommendation["type"] == "mechanical_ventilation":
|
||||
recommendation_record["mechanical_ventilation_ending"] = 'mechanical, extract only'
|
||||
if recommendation["type"] == "sealing_open_fireplace":
|
||||
output["number_open_fireplaces_ending"] = 0
|
||||
|
||||
if recommendation["type"] == "sealing_open_fireplace":
|
||||
recommendation_record["number_open_fireplaces_ending"] = 0
|
||||
if recommendation["type"] == "low_energy_lighting":
|
||||
output["low_energy_lighting_ending"] = 100
|
||||
output["lighting_energy_eff_starting"] = "Very Good"
|
||||
|
||||
if recommendation["type"] == "low_energy_lighting":
|
||||
recommendation_record["low_energy_lighting_ending"] = 100
|
||||
recommendation_record["lighting_energy_eff_starting"] = "Very Good"
|
||||
if recommendation["type"] == "windows_glazing":
|
||||
output["multi_glaze_proportion_ending"] = 100
|
||||
output["windows_energy_eff_ending"] = "Average"
|
||||
|
||||
if recommendation["type"] == "windows_glazing":
|
||||
recommendation_record["multi_glaze_proportion_ending"] = 100
|
||||
recommendation_record["windows_energy_eff_ending"] = "Average"
|
||||
is_secondary_glazing = recommendation["is_secondary_glazing"]
|
||||
|
||||
is_secondary_glazing = recommendation["is_secondary_glazing"]
|
||||
if output["glazing_type_ending"] == "multiple":
|
||||
pass
|
||||
elif output["glazing_type_ending"] == "single":
|
||||
output["glazing_type_ending"] = "secondary" if is_secondary_glazing else "double"
|
||||
elif output["glazing_type_ending"] == "double":
|
||||
output["glazing_type_ending"] = "multiple" if is_secondary_glazing else "double"
|
||||
elif output["glazing_type_ending"] == "secondary":
|
||||
output["glazing_type_ending"] = "secondary" if is_secondary_glazing else "multiple"
|
||||
elif output["glazing_type_ending"] in ["triple", "high performance"]:
|
||||
output["glazing_type_ending"] = "multiple"
|
||||
else:
|
||||
raise ValueError("Invalid glazing type - implement me")
|
||||
|
||||
if recommendation_record["glazing_type_ending"] == "multiple":
|
||||
pass
|
||||
elif recommendation_record["glazing_type_ending"] == "single":
|
||||
recommendation_record["glazing_type_ending"] = "secondary" if is_secondary_glazing else "double"
|
||||
elif recommendation_record["glazing_type_ending"] == "double":
|
||||
recommendation_record["glazing_type_ending"] = "multiple" if is_secondary_glazing else "double"
|
||||
elif recommendation_record["glazing_type_ending"] == "secondary":
|
||||
recommendation_record["glazing_type_ending"] = "secondary" if is_secondary_glazing else "multiple"
|
||||
elif recommendation_record["glazing_type_ending"] in ["triple", "high performance"]:
|
||||
recommendation_record["glazing_type_ending"] = "multiple"
|
||||
else:
|
||||
raise ValueError("Invalid glazing type - implement me")
|
||||
if is_secondary_glazing:
|
||||
output["glazed_type_ending"] = "secondary glazing"
|
||||
else:
|
||||
output["glazed_type_ending"] = "double glazing installed during or after 2002 "
|
||||
|
||||
if is_secondary_glazing:
|
||||
recommendation_record["glazed_type_ending"] = "secondary glazing"
|
||||
else:
|
||||
recommendation_record["glazed_type_ending"] = "double glazing installed during or after 2002 "
|
||||
if recommendation["type"] == "solar_pv":
|
||||
output["photo_supply_ending"] = recommendation["photo_supply"]
|
||||
|
||||
if recommendation["type"] == "solar_pv":
|
||||
recommendation_record["photo_supply_ending"] = recommendation["photo_supply"]
|
||||
if recommendation["type"] not in [
|
||||
"mechanical_ventilation", "sealing_open_fireplace", "low_energy_lighting",
|
||||
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation",
|
||||
"loft_insulation", "room_roof_insulation", "flat_roof_insulation",
|
||||
"solid_floor_insulation", "suspended_floor_insulation", "exposed_floor_insulation",
|
||||
"windows_glazing", "solar_pv"
|
||||
]:
|
||||
raise NotImplementedError("Implement me")
|
||||
|
||||
if recommendation["type"] not in [
|
||||
"mechanical_ventilation", "sealing_open_fireplace", "low_energy_lighting",
|
||||
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation",
|
||||
"loft_insulation", "room_roof_insulation", "flat_roof_insulation",
|
||||
"solid_floor_insulation", "suspended_floor_insulation", "exposed_floor_insulation",
|
||||
"windows_glazing", "solar_pv"
|
||||
]:
|
||||
raise NotImplementedError("Implement me")
|
||||
output['id'] = "+".join([str(property_id), str(primary_recommendation_id)])
|
||||
|
||||
recommendation_record['id'] = "+".join([str(property_id), str(recommendation["recommendation_id"])])
|
||||
|
||||
return recommendation_record
|
||||
return output
|
||||
|
||||
def get_components(self, cleaned, photo_supply_lookup, floor_area_decile_thresholds):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -136,22 +136,32 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
|
||||
recommendations = {}
|
||||
recommendations_scoring_data = []
|
||||
|
||||
representive_recommendations = {}
|
||||
for p in input_properties:
|
||||
|
||||
# Property recommendations
|
||||
p.get_components(cleaned, photo_supply_lookup, floor_area_decile_thresholds)
|
||||
|
||||
# TODO: For the private customer, we should probably NOT allow floor insulation, because it often requires
|
||||
# decanting the tenant
|
||||
recommender = Recommendations(property_instance=p, materials=materials)
|
||||
property_recommendations = recommender.recommend()
|
||||
property_recommendations, property_representative_recommendations = recommender.recommend()
|
||||
|
||||
if not property_recommendations:
|
||||
continue
|
||||
|
||||
recommendations[p.id] = property_recommendations
|
||||
representive_recommendations[p.id] = property_representative_recommendations
|
||||
|
||||
p.create_base_difference_epc_record(cleaned_lookup=cleaned)
|
||||
p.adjust_difference_record_with_recommendations(property_recommendations)
|
||||
p.adjust_difference_record_with_recommendations(
|
||||
property_recommendations, property_representative_recommendations
|
||||
)
|
||||
|
||||
p.recommendations_scoring_data[0]["id"]
|
||||
p.recommendations_scoring_data[0]["walls_thermal_transmittance"]
|
||||
p.recommendations_scoring_data[0]["walls_thermal_transmittance_ending"]
|
||||
p.recommendations_scoring_data[0]["walls_thermal_transmittance_ending"]
|
||||
|
||||
recommendations_scoring_data.extend(p.recommendations_scoring_data)
|
||||
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ class FloorRecommendations(Definitions):
|
|||
if self.property.floor["is_suspended"]:
|
||||
# Given the U-value, we recommend underfloor insulation
|
||||
self.recommend_floor_insulation(
|
||||
phase=phase,
|
||||
u_value=u_value,
|
||||
insulation_materials=self.suspended_floor_insulation_materials,
|
||||
non_insulation_materials=self.suspended_floor_non_insulation_materials
|
||||
|
|
|
|||
|
|
@ -107,7 +107,52 @@ class Recommendations:
|
|||
# We insert temporary ids into the recommendations which is important for the optimiser later
|
||||
property_recommendations = self.insert_temp_recommendation_id(property_recommendations)
|
||||
|
||||
return property_recommendations
|
||||
# We also need to create the representative recommendations for each recommendation type
|
||||
property_representative_recommendations = self.create_representative_recommendations(property_recommendations)
|
||||
|
||||
return property_recommendations, property_representative_recommendations
|
||||
|
||||
@staticmethod
|
||||
def create_representative_recommendations(property_recommendations):
|
||||
"""
|
||||
This method will create a representative recommendation for each recommendation type
|
||||
In order to create a representative recommendation, we choose the recommendation that has:
|
||||
1) Where a U-value is available, has the best U-value to cost ratio
|
||||
2) Where SAP points are available, has the best SAP points to cost ratio
|
||||
|
||||
We don't include mechanical ventilation in the representative recommendations, since we don't attribute a
|
||||
SAP impact to this recommendation
|
||||
:return:
|
||||
"""
|
||||
property_representative_recommendations = []
|
||||
|
||||
for recommendations_by_type in property_recommendations:
|
||||
|
||||
if recommendations_by_type[0].get("type") == "mechanical_ventilation":
|
||||
continue
|
||||
|
||||
has_u_value = recommendations_by_type[0].get("new_u_value") is not None
|
||||
has_sap_points = recommendations_by_type[0].get("sap_points") is not None
|
||||
|
||||
if has_u_value:
|
||||
# We sort by the cost per U-value improvement - the lower the better
|
||||
recommendations_by_type.sort(
|
||||
key=lambda x: x["total"] / x["starting_u_value"] - x["new_u_value"]
|
||||
)
|
||||
elif not has_u_value and has_sap_points:
|
||||
# Sort the options by the cost per SAP point improvement - the lower the better
|
||||
recommendations_by_type.sort(
|
||||
key=lambda x: x["total"] / x["sap_points"]
|
||||
)
|
||||
else:
|
||||
# Sort the options by cost - the lower the better
|
||||
recommendations_by_type.sort(
|
||||
key=lambda x: x["total"]
|
||||
)
|
||||
|
||||
property_representative_recommendations.append(recommendations_by_type[0])
|
||||
|
||||
return property_representative_recommendations
|
||||
|
||||
@staticmethod
|
||||
def insert_temp_recommendation_id(property_recommendations):
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue