diff --git a/etl/customers/peabody/Nov 2025 Consulting Project/f_diagnostics.py b/etl/customers/peabody/Nov 2025 Consulting Project/f_diagnostics.py index 4c2a49ca..fa7383a2 100644 --- a/etl/customers/peabody/Nov 2025 Consulting Project/f_diagnostics.py +++ b/etl/customers/peabody/Nov 2025 Consulting Project/f_diagnostics.py @@ -23,9 +23,10 @@ scenario_sap_targets = { 859: 69, } +problems = [] for scenario_id, scenario_name in scenario_names.items(): # Read in the recommended measures - + print("Reading") df = pd.read_excel( f"/Users/khalimconn-kowlessar/Documents/hestia/Customers/Peabody/Nov 2025 Consulting Project/" f"{scenario_name}.xlsx" @@ -34,30 +35,98 @@ for scenario_id, scenario_name in scenario_names.items(): # find properties that are below the scenario sap target, but have no recommended measures df["below_scenario_target"] = df["current_sap_points"] < scenario_sap_targets[scenario_id] df["no_recommended_measures"] = df["sap_points"] == 0 + df["zero_cost"] = df["total_retrofit_cost"] == 0 + df["sap_points_above_zero"] = df["sap_points"] > 0 + + # Also look for zero cost and SAP points > 0 problematic_properties = df[ - df["below_scenario_target"] & df["no_recommended_measures"] - ] + (df["below_scenario_target"] & df["no_recommended_measures"]) + ].copy() + + if scenario_sap_targets[scenario_id] == 81: + problematic_properties = problematic_properties[problematic_properties["property_type"] != "Flat"] + + zero_cost_above_zero_sap = df[ + (df["sap_points_above_zero"] & df["zero_cost"]) + ].copy() # show all columns # Source - https://stackoverflow.com/a # Posted by YOLO, modified by community. See post 'Timeline' for change history # Retrieved 2026-01-06, License - CC BY-SA 4.0 - pd.set_option('display.max_rows', 500) - pd.set_option('display.max_columns', 500) - pd.set_option('display.width', 1000) - problematic_properties.head(len(problematic_properties)) + # pd.set_option('display.max_rows', 500) + # pd.set_option('display.max_columns', 500) + # pd.set_option('display.width', 1000) + # problematic_properties.head(len(problematic_properties)) - # + print(f"We have {len(problematic_properties)} problematic properties for scenario {scenario_name} ({scenario_id})") + print(f"We have {len(zero_cost_above_zero_sap)} zero cost properties for scenario {scenario_name} ({scenario_id})") + + problems.append(problematic_properties) + problems.append(zero_cost_above_zero_sap) + + # plan_input = [ + # { + # "uprn": 100022725126, + # "address": "FLAT 5 Daveys Court", + # "postcode": "WC2N 4BW" + # } + # ] + + # plan_input = [ + # { + # "uprn": 100120966352, + # "address": "FLAT 11 Kingsgate", + # "postcode": "OX18 2BP" + # } + # ] plan_input = [ { - "uprn": 100022725126, - "address": "FLAT 5 Daveys Court", - "postcode": "WC2N 4BW" + "uprn": 200003371857, + "postcode": "SE1 5SJ", + "address": "39 BUTTERMERE CLOSE", } ] +all_problems = pd.concat(problems) +all_problems = all_problems.drop_duplicates(subset=["uprn"]) + +sal = pd.read_excel( + "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Peabody/Nov 2025 Consulting Project/20251213 Model " + "data.xlsx", + sheet_name="Standardised Asset List" +) +sal2 = pd.read_excel( + "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Peabody/Nov 2025 Consulting Project/20260105 - additional " + "UPRNS.xlsx", + sheet_name="Standardised Asset List" +) + +sal = pd.concat([sal, sal2]) + +retry = sal[sal["epc_os_uprn"].isin(all_problems["uprn"])] + +# Store +retry.to_excel( + "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Peabody/Nov 2025 Consulting Project/" + "d_problematic_properties_to_review_20260106.xlsx", + sheet_name="Standardised Asset List", + index=False +) + +# Delete associated plans +# 1) Get the property IDs for these UPRNS, for this portfolio +portfolio_id = 419 +uprns = retry + +# TODO: Delete all plans for these properties and re-build # Plan notes: # UPRN: 5870109770, property ID: 281244 - need to delete and re-build all scenarios +# UPRN: 100022725126, property ID: 283781 - need to delete and re-build all scenarios + + +# Bugs: +12156800 diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index c7c5895d..fdc25bf9 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -170,9 +170,14 @@ class HeatingRecommender: # If the property has community heating heaters in place, we don't recommend HHRSH has_community_heating = self.property.main_fuel["is_community"] - hhr_suitable = hhr_suitable and ( - "underfloor heating" not in self.property.main_heating["clean_description"] - ) and not has_community_heating + # If the property currently has electric underfloor heating, we allow this if there is elecric immersion + # hot water heating + underfloor_not_an_issue = True + if self.property.main_heating["has_electric_underfloor_heating"]: + if self.property.hotwater["heater_type"] != "electric immersion": + underfloor_not_an_issue = False + + hhr_suitable = hhr_suitable and not has_community_heating and underfloor_not_an_issue # If the property has a ground source heat pump, or air source heat pump, we don't recommend HHRSH diff --git a/recommendations/WindowsRecommendations.py b/recommendations/WindowsRecommendations.py index 8bdab5d1..7b85ac49 100644 --- a/recommendations/WindowsRecommendations.py +++ b/recommendations/WindowsRecommendations.py @@ -86,9 +86,17 @@ class WindowsRecommendations: # 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 - ) + + if (self.property.windows["clean_description"] == "Some double glazing") and ( + self.property.data["windows-energy-eff"] == "Very Poor") and ( + self.property.data["multi-glaze-proportion"] == 100 + ): + # In this case, we assume all of the dinwos need replacing + n_windows_scalar = 1 + else: + 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 @@ -97,6 +105,9 @@ class WindowsRecommendations: number_of_windows *= n_windows_scalar number_of_windows = np.ceil(number_of_windows) + # Handle edge case - prevent number of windows 0 + number_of_windows = max(1, 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,