diff --git a/backend/Property.py b/backend/Property.py index 411a4db0..a80c3057 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -950,7 +950,6 @@ class Property: construction_age_band=self.construction_age_band, floor_area=self.floor_area, number_habitable_rooms=self.number_of_rooms, - extension_count=float(self.data["extension-count"]), ) def set_solar_panel_area(self, photo_supply_lookup, floor_area_decile_thresholds): diff --git a/recommendations/Costs.py b/recommendations/Costs.py index b056274e..68870841 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -261,6 +261,20 @@ class Costs: :return: A dictionary containing detailed cost breakdown. """ + + labour_hours = material["labour_hours_per_unit"] * floor_area + # Assume a team of 1 person + labour_days = labour_hours / 8 + + if material["is_installer_quote"]: + total_cost = material["total_cost"] * floor_area + + return { + "total": total_cost, + "labour_hours": labour_hours, + "labour_days": labour_days, + } + material_cost_per_m2 = material["material_cost"] # We inflate material costs due to recent price increases @@ -282,11 +296,6 @@ class Costs: total_cost = subtotal_before_vat + vat_cost - labour_hours = material["labour_hours_per_unit"] * floor_area - - # Assume a team of 1 person - labour_days = labour_hours / 8 - return { "total": total_cost, "subtotal": subtotal_before_vat, @@ -423,6 +432,21 @@ class Costs: :return: """ + # if the material is based on an installer cost, we return the flat price + if material["is_installer_quote"]: + total_cost = material["total_cost"] * insulation_floor_area + + labour_hours = material["labour_hours_per_unit"] * insulation_floor_area + # To install suspended floor insulation, a small to medium size project might be conducted by a team of 3 + # people + labour_days = (labour_hours / 8) / 3 + + return { + "total": total_cost, + "labour_hours": labour_hours, + "labour_days": labour_days, + } + demolition_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_demolition"] vapour_barrier_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_vapour_barrier"] redecoration_data = [x for x in non_insulation_materials if x["type"] == "suspended_floor_redecoration"] @@ -525,6 +549,21 @@ class Costs: :return: """ + # if the material is based on an installer cost, we return the flat price + if material["is_installer_quote"]: + total_cost = material["total_cost"] * insulation_floor_area + + labour_hours = material["labour_hours_per_unit"] * insulation_floor_area + # To install suspended floor insulation, a small to medium size project might be conducted by a team of 3 + # people + labour_days = (labour_hours / 8) / 3 + + return { + "total": total_cost, + "labour_hours": labour_hours, + "labour_days": labour_days, + } + demolition_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_demolition"] preparation_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_preparation"] vapour_barrier_data = [x for x in non_insulation_materials if x["type"] == "solid_floor_vapour_barrier"] @@ -915,6 +954,19 @@ class Costs: """ + if material["is_installer_quote"]: + total_cost = material["total_cost"] * number_of_windows + + labour_hours = material["labour_hours_per_unit"] * number_of_windows + # To install windows, a small to medium size project might be conducted by a team of 2-3 people + labour_days = (labour_hours / 8) / 2 + + return { + "total": total_cost, + "labour_hours": labour_hours, + "labour_days": labour_days, + } + material_cost = material["material_cost"] * number_of_windows labour_cost = ( diff --git a/recommendations/optimiser/optimiser_functions.py b/recommendations/optimiser/optimiser_functions.py index d6353eea..083a7c25 100644 --- a/recommendations/optimiser/optimiser_functions.py +++ b/recommendations/optimiser/optimiser_functions.py @@ -18,11 +18,10 @@ def prepare_input_measures(property_recommendations, goal): input_measures = [] for recs in property_recommendations: + if recs[0]["type"] == "solar_pv": - # if the recommendation is a solar recommendation without a battery, we exclude it from the optimisation. - # That will ensure that the optimiser only considers solar recommendations with batteries, so we don't - # under-report the potential cost - recs = [r for r in recs if r["has_battery"]] + # if the recommendation is a solar recommendation with a battery, we exclude it from the optimisation. + recs = [r for r in recs if ~r["has_battery"]] input_measures.append( [ diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py index 07a861dc..9b5e22d1 100644 --- a/recommendations/recommendation_utils.py +++ b/recommendations/recommendation_utils.py @@ -673,8 +673,10 @@ def esimtate_pitched_roof_area(floor_area: float, floor_height: float) -> float: def estimate_windows( - property_type, built_form, construction_age_band, floor_area, number_habitable_rooms, extension_count + property_type, built_form, construction_age_band, floor_area, number_habitable_rooms ): + # If there is an extension, that will boost the number of habitable rooms + # Base window count based on habitable rooms window_count = number_habitable_rooms @@ -717,9 +719,6 @@ def estimate_windows( # Older houses with smaller, more numerous windows window_count += 1 - # Adjust for extensions (each extension might add windows) - window_count += extension_count - # Adjustments for specific property types if property_type in ["Flat", "Maisontte"]: # Flats might have fewer windows due to shared walls