mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Added cavity extraction and re-fill recommendation and costing
This commit is contained in:
parent
0c1fb0360f
commit
4cf4d67ac9
5 changed files with 63 additions and 17 deletions
|
|
@ -61,7 +61,7 @@ class Property:
|
|||
n_bedrooms = None
|
||||
|
||||
def __init__(
|
||||
self, id, postcode, address, epc_record, already_installed=None, property_non_invasive_recommendations=None,
|
||||
self, id, postcode, address, epc_record, already_installed=None, non_invasive_recommendations=None,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
|
|
@ -82,8 +82,8 @@ class Property:
|
|||
|
||||
self.already_installed = ast.literal_eval(already_installed['already_installed']) if already_installed else []
|
||||
self.non_invasive_recommendations = (
|
||||
ast.literal_eval(property_non_invasive_recommendations['recommendations']) if
|
||||
property_non_invasive_recommendations else []
|
||||
ast.literal_eval(non_invasive_recommendations['recommendations']) if
|
||||
non_invasive_recommendations else []
|
||||
)
|
||||
|
||||
self.uprn = epc_record.get("uprn")
|
||||
|
|
@ -284,6 +284,7 @@ class Property:
|
|||
recommendation_record=recommendation_record,
|
||||
recommendations=previous_phase_representatives + [rec],
|
||||
primary_recommendation_id=rec["recommendation_id"],
|
||||
non_invasive_recommendations=self.non_invasive_recommendations,
|
||||
)
|
||||
self.recommendations_scoring_data.append(scoring_dict)
|
||||
|
||||
|
|
@ -293,6 +294,7 @@ class Property:
|
|||
recommendation_record,
|
||||
recommendations: list,
|
||||
primary_recommendation_id: int,
|
||||
non_invasive_recommendations: list = None,
|
||||
):
|
||||
"""
|
||||
This function will iterate through a list of recommendations and apply a simulation for each recommendation
|
||||
|
|
@ -301,10 +303,12 @@ class 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
|
||||
:param non_invasive_recommendations: The list of non-invasive recommendations
|
||||
:return: The updated recommendation record
|
||||
"""
|
||||
|
||||
output = recommendation_record.copy()
|
||||
non_invasive_recommendations = [] if non_invasive_recommendations is None else non_invasive_recommendations
|
||||
|
||||
for col in [
|
||||
"walls_insulation_thickness",
|
||||
|
|
@ -323,6 +327,13 @@ class Property:
|
|||
"external_wall_insulation",
|
||||
"cavity_wall_insulation",
|
||||
]:
|
||||
|
||||
# # If we have a non-incasive recommendation that the cavity wall is partially filled, we skip the
|
||||
# # cavity wall insulation recommendation (since on the EPC, the property will look like how it did
|
||||
# # before any works)
|
||||
# if "cavity_surveyed_as_filled_is_partial" in non_invasive_recommendations:
|
||||
# continue
|
||||
|
||||
# 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[
|
||||
|
|
|
|||
|
|
@ -171,11 +171,13 @@ def extract_portfolio_aggregation_data(
|
|||
def format_money(amount):
|
||||
return f"£{amount:,.0f}"
|
||||
|
||||
valuation_improvment_per_unit = format_money(
|
||||
total_valuation_increase / n_units) + (f" ({format_money(valuation_improvement_lower_bound_per_unit)} - "
|
||||
f"{format_money(valuation_improvement_upper_bound_per_unit)})")
|
||||
valuation_improvment_per_unit = str(
|
||||
format_money(
|
||||
total_valuation_increase / n_units) + (f" ({format_money(valuation_improvement_lower_bound_per_unit)} - "
|
||||
f"{format_money(valuation_improvement_upper_bound_per_unit)})")
|
||||
)
|
||||
|
||||
valuation_return_on_investment = (
|
||||
valuation_return_on_investment = str(
|
||||
str(round(total_valuation_increase / agg_data["cost"].sum(), 2)) +
|
||||
f" ("
|
||||
f"{agg_data['lower_bound_valuation_uplift'].sum() / agg_data['cost'].sum():,.2f} - "
|
||||
|
|
@ -189,8 +191,8 @@ def extract_portfolio_aggregation_data(
|
|||
"epc_breakdown_post_retrofit": json.dumps(
|
||||
reformat_epc_data(agg_data["post_retrofit_epc"].value_counts().to_dict())
|
||||
),
|
||||
"number_of_properties": n_units,
|
||||
"n_units_to_retrofit": n_units_to_retrofit,
|
||||
"number_of_properties": int(n_units),
|
||||
"n_units_to_retrofit": int(n_units_to_retrofit),
|
||||
"co2_per_unit_pre_retrofit": str(round(agg_data["pre_retrofit_co2"].mean(), 2)) + "t",
|
||||
"co2_per_unit_post_retrofit": str(round(agg_data["post_retrofit_co2"].mean(), 2)) + "t",
|
||||
"energy_bill_per_unit_pre_retrofit": format_money(agg_data["pre_retrofit_energy_bill"].mean()),
|
||||
|
|
|
|||
|
|
@ -91,6 +91,10 @@ DOUBLE_RADIATOR_COST = 300
|
|||
FLUE_COST = 600
|
||||
PIPEWORK_COST = 750 # Min cost is £500
|
||||
|
||||
# This is the cost per meter squared for cavity extraction
|
||||
# https://www.checkatrade.com/blog/cost-guides/cavity-wall-insulation-removal-cost/
|
||||
CAVITY_EXTRACTION_COST = 21.5
|
||||
|
||||
|
||||
class Costs:
|
||||
"""
|
||||
|
|
@ -173,7 +177,7 @@ class Costs:
|
|||
if not self.labour_adjustment_factor:
|
||||
raise ValueError("Labour adjustment factor not found")
|
||||
|
||||
def cavity_wall_insulation(self, wall_area, material):
|
||||
def cavity_wall_insulation(self, wall_area, material, is_extraction_and_refill=False):
|
||||
"""
|
||||
Calculates the total cost for cavity wall insulation based on material and labor costs,
|
||||
including contingency, preliminaries, profit, and VAT.
|
||||
|
|
@ -208,6 +212,13 @@ class Costs:
|
|||
# Assume a team of 2
|
||||
labour_days = (labour_hours / 8) / 2
|
||||
|
||||
if is_extraction_and_refill:
|
||||
# bump up the cost of the work
|
||||
total_cost = total_cost + CAVITY_EXTRACTION_COST * wall_area
|
||||
# Additional 2 days work
|
||||
labour_hours = labour_hours + (2 * 8)
|
||||
labour_days = labour_days + 2
|
||||
|
||||
return {
|
||||
"total": total_cost,
|
||||
"subtotal": subtotal_before_vat,
|
||||
|
|
|
|||
|
|
@ -149,12 +149,14 @@ class Recommendations:
|
|||
property_recommendations = self.insert_temp_recommendation_id(property_recommendations)
|
||||
|
||||
# We also need to create the representative recommendations for each recommendation type
|
||||
property_representative_recommendations = self.create_representative_recommendations(property_recommendations)
|
||||
property_representative_recommendations = self.create_representative_recommendations(
|
||||
property_recommendations, non_invasive_recommendations=self.property_instance.non_invasive_recommendations
|
||||
)
|
||||
|
||||
return property_recommendations, property_representative_recommendations
|
||||
|
||||
@staticmethod
|
||||
def create_representative_recommendations(property_recommendations):
|
||||
def create_representative_recommendations(property_recommendations, non_invasive_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:
|
||||
|
|
@ -169,6 +171,13 @@ class Recommendations:
|
|||
|
||||
for recommendations_by_type in property_recommendations:
|
||||
|
||||
# If the property was initially surveyed as filled, but the cavity was only partially filled, we don't
|
||||
# want to include the cavity wall insulation recommendation in the defaults
|
||||
# if (recommendations_by_type[0].get("type") == "cavity_wall_insulation") and (
|
||||
# "cavity_surveyed_as_filled_is_partial" in non_invasive_recommendations
|
||||
# ):
|
||||
# continue
|
||||
|
||||
if recommendations_by_type[0].get("type") == "mechanical_ventilation":
|
||||
continue
|
||||
|
||||
|
|
@ -238,13 +247,13 @@ class Recommendations:
|
|||
|
||||
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_recommendations = recommendations[property_instance.id].copy()
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,9 @@ class WallRecommendations(Definitions):
|
|||
insulation_thickness = self.property.walls["insulation_thickness"]
|
||||
|
||||
# We check if the wall is already insulated and if so, we exit
|
||||
if (insulation_thickness in ["average", "above average"]) or self.property.walls["is_filled_cavity"]:
|
||||
if ((insulation_thickness in ["average", "above average"]) or self.property.walls["is_filled_cavity"]) and (
|
||||
"cavity_extract_and_refill" not in self.property.non_invasive_recommendations
|
||||
):
|
||||
return
|
||||
|
||||
if u_value:
|
||||
|
|
@ -216,15 +218,26 @@ class WallRecommendations(Definitions):
|
|||
if new_u_value <= self.BUILDING_REGULATIONS_PART_L_CAVITY_WALL_MAX_U_VALUE:
|
||||
lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value)
|
||||
|
||||
is_extraction_and_refill = "cavity_extract_and_refill" in self.property.non_invasive_recommendations
|
||||
|
||||
cost_result = self.costs.cavity_wall_insulation(
|
||||
wall_area=self.property.insulation_wall_area,
|
||||
material=material.to_dict(),
|
||||
is_extraction_and_refill=is_extraction_and_refill
|
||||
)
|
||||
|
||||
already_installed = "cavity_wall_insulation" in self.property.already_installed
|
||||
if already_installed:
|
||||
cost_result = override_costs(cost_result)
|
||||
|
||||
if is_extraction_and_refill:
|
||||
description = f"Extract and refill cavity wall insulation with {material['description']}"
|
||||
else:
|
||||
description = self._make_description(material)
|
||||
|
||||
# updated the new u-value with the best possible our installers have
|
||||
new_u_value = max(0.31, new_u_value)
|
||||
|
||||
recommendations.append(
|
||||
{
|
||||
"phase": phase,
|
||||
|
|
@ -237,7 +250,7 @@ class WallRecommendations(Definitions):
|
|||
)
|
||||
],
|
||||
"type": "cavity_wall_insulation",
|
||||
"description": self._make_description(material),
|
||||
"description": description,
|
||||
"starting_u_value": u_value,
|
||||
"new_u_value": new_u_value,
|
||||
"sap_points": None,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue