from typing import List import numpy as np from backend.Property import Property from recommendations.Costs import Costs class WindowsRecommendations: # If the property has existing glazing, we scale down the number of windows that need to be glazed COVERAGE_MAP = { # If most of the windows have already been glazed, we assume that 2/3 are glazed and 1/2 are remaining to be # glazed "most": 0.33, # If glazing is partial, we assume 50/50 split between glazed and unglazed "partial": 0.5 } def __init__(self, property_instance: Property, materials: List): self.property = property_instance self.costs = Costs(self.property) self.recommendation = [] self.glazing_material = [ material for material in materials if material["type"] == "windows_glazing" ] if len(self.glazing_material) != 1: raise ValueError("There should only be one window glazing material") self.glazing_material = self.glazing_material[0] def recommend(self): """ This method will recommend the best possible glazing options for a property. In order to do this, we need to estimate the number of windows that the home has. This information will be stored in the property object, under property.number_of_windows :return: """ # If the property is in a conservation area or is a listed building, it becomes more difficult to install # double glazing. Therefore, we don't recommend it. It is still possible but is not practical as it # requires planning permission and might require a more expensive window type, such as timber. number_of_windows = self.property.number_of_windows is_secondary_glazing = self.property.restricted_measures or ( self.property.windows["glazing_type"] == "secondary" ) if not number_of_windows: raise ValueError("Number of windows not specified") if self.property.windows["has_glazing"] & (self.property.windows["glazing_coverage"] == "full"): return # We scale the number of windows based on the proportion of existing glazing if self.property.data["multi-glaze-proportion"] != "": n_windows_scalar = 1 - (int(self.property.data["multi-glaze-proportion"]) / 100) else: n_windows_scalar = self.COVERAGE_MAP.get(self.property.windows["glazing_coverage"], 1) number_of_windows *= n_windows_scalar number_of_windows = np.ceil(number_of_windows) # We then price the job based on the number of windows that there are cost_result = self.costs.window_glazing( number_of_windows=number_of_windows, material=self.glazing_material, is_secondary_glazing=is_secondary_glazing ) glazing_type = "secondary glazing" if is_secondary_glazing else "double glazing" if self.property.windows["glazing_coverage"] in ["partial", "most"]: description = f"Install {glazing_type} to the remaining windows" else: description = f"Install {glazing_type} to all windows" if self.property.is_listed: description += ". Secondary glazing recommended due to listed building status" elif self.property.is_heritage: description += ". Secondary glazing recommended due to herigate building status" elif self.property.in_conservation_area: description += ". Secondary glazing recommended due to conservation area status" self.recommendation = [ { "parts": [], "type": "windows_glazing", "description": description, "starting_u_value": None, "new_u_value": None, "sap_points": None, **cost_result, "is_secondary_glazing": is_secondary_glazing } ]