from copy import deepcopy from backend.Property import Property from statistics import mean import random def r_value_per_mm_to_u_value(depth_mm: int, r_value_per_mm: float): """ Converts R-value per mm to U-value in W/m²K. Parameters ---------- depth_mm : int Depth of the material in mm. r_value_per_mm : float R-value per mm. Returns ------- float U-value in W/m²K. """ return 1 / (depth_mm * r_value_per_mm) def calculate_u_value_uplift(u_value, insulation_u_value): """ Calculates the U-value uplift (improvement) when applying internal wall insulation to a wall. :param u_value: Float, Starting U-value of the wall (without insulation) in W/m²K. :param insulation_u_value: Float, U-value of the internal wall insulation in W/m²K. Returns: float: U-value uplift (improvement) achieved by applying internal wall insulation in W/m²K. Raises: ZeroDivisionError: If either u_value or iwi_u_value is zero. Notes: This function assumes 100% coverage of the internal wall insulation and does not account for other factors such as thermal bridging or the specific configuration of the wall. """ inverse_u_value = 1 / u_value inverse_insulation_u_value = 1 / insulation_u_value inverse_u_total = inverse_u_value + inverse_insulation_u_value new_u_value = 1 / inverse_u_total u_value_uplift = u_value - new_u_value return u_value_uplift, new_u_value def is_diminishing_returns(recommendations, new_u_value, lowest_selected_u_value, diminishing_returns_u_value): """ What are defines diminishing returns? 1) The new u value is lower than the lowest selected u value 2) The new u value is below the diminishing returns threshold 3) We already have some recommendations so there is no need to insert another recommendation in """ # if we don't have anything selected, lowest_selected_u_value will be missing if lowest_selected_u_value is None: if recommendations: raise ValueError("Recommendations should be empty - investigate") # This means that nothing has been selected yet # the new u value is less than the threshold, however this MIGHT be the only # solution and so we consider it return False # We should already have recommendations if not recommendations: raise ValueError("Recommendations should not be empty - investigate") # We already have a solution that is suitable so we want to make sure that # any new solutin actually has a higher u-value as it will either be # 1) cheaper # 2) thinner with a more efficient material is_diminishing = (new_u_value < diminishing_returns_u_value) and ( new_u_value < lowest_selected_u_value ) return is_diminishing def update_lowest_selected_u_value(lowest_selected_u_value, new_u_value): """ Utility funciton which holds the logic for how we update the lowest selected u value :param lowest_selected_u_value: current lowest selected u value, initialised as None :param new_u_value: new u value to compare against :return: """ if lowest_selected_u_value is None: lowest_selected_u_value = new_u_value if new_u_value <= lowest_selected_u_value: lowest_selected_u_value = new_u_value return lowest_selected_u_value def get_recommended_part(part, selected_depth, selected_total_cost, quantity, quantity_unit): """ Utility function to return a recommended part with the selected depth. :param part: part to be recommended :param selected_depth: depth of the selected part :param selected_total_cost: Total cost of the selected part :param quantity: Quantity of the selected part :param quantity_unit: Unit of the quantity :return: """ recommended_part = deepcopy(part) recommended_part["depths"] = [selected_depth] recommended_part["estimated_cost"] = selected_total_cost recommended_part["quantity"] = quantity recommended_part["quantity_unit"] = quantity_unit return recommended_part def get_uvalue_estimate(uvalue_estimates, property: Property, total_floor_area_group_decile): """ Wrapper function which contains the methodology to extract a property's walls u-value estimate when we don't have a true value and if we can't base our assumption off of the material :return: """ if not uvalue_estimates: raise ValueError("No U-value estimate found for the given property - investigate") # We try and filter on total_floor_area_group_decile floor_area_filter = [ x for x in uvalue_estimates if x["total-floor-area_group"] == total_floor_area_group_decile ] if not floor_area_filter: # Take a mean of all the u-value estimates return mean( [x["median_thermal_transmittance"] for x in uvalue_estimates if x["median_thermal_transmittance"]] ) # Because of how spuriously populated the data is for number-habitable-rooms and number-heated-rooms, # we will try and filter on these to see if we get a result habitable_rooms_filer = [ x for x in floor_area_filter if x["number-habitable-rooms"] == property.data["number-habitable-rooms"] ] if not habitable_rooms_filer: # Take a mean of all the u-value estimates return mean( [x["median_thermal_transmittance"] for x in floor_area_filter if x["median_thermal_transmittance"]] ) # Try perform a filter on heated rooms heated_rooms_filter = [ x for x in habitable_rooms_filer if x["number-heated-rooms"] == property.data["number-heated-rooms"] ] if not heated_rooms_filter: # Take a mean of all the u-value estimates return mean( [x["median_thermal_transmittance"] for x in habitable_rooms_filer if x["median_thermal_transmittance"]] ) return mean( [x["median_thermal_transmittance"] for x in heated_rooms_filter if x["median_thermal_transmittance"]] )