diff --git a/.idea/Model.iml b/.idea/Model.iml
index 4d94187d..cedf86d9 100644
--- a/.idea/Model.iml
+++ b/.idea/Model.iml
@@ -6,6 +6,7 @@
+
diff --git a/backend/Property.py b/backend/Property.py
index 5976d8ec..5e994cae 100644
--- a/backend/Property.py
+++ b/backend/Property.py
@@ -772,7 +772,7 @@ class Property:
"current_epc_rating": current_epc_rating,
"current_sap_points": current_sap_rating,
"current_valuation": current_valuation,
- "original_sap_points": self.epc_record.current_energy_efficiency,
+ "original_sap_points": self.epc_record.original_epc["current-energy-efficiency"],
"is_sap_points_adjusted_for_installed_measures": needs_rebaselining,
"installed_measures_sap_point_adjustment": rebaselining_sap,
}
@@ -886,6 +886,10 @@ class Property:
"installed_measures_total_energy_bill_adjustment": rebaselining_bills,
"installed_measures_heat_demand_adjustment": rebaselining_heat_demand,
"is_epc_adjusted_for_installed_measures": needs_rebaselining,
+ # Re-baselining variables - to replace already installed variables entirely
+ "lodged_co2_emissions": float(self.epc_record.original_epc["co2-emissions-current"]),
+ "lodged_heat_demand": float(self.epc_record.original_epc["energy-consumption-current"]),
+ "has_been_remodelled": self.epc_record.has_been_remodelled,
}
return property_details_epc
diff --git a/backend/app/db/functions/energy_assessment_functions.py b/backend/app/db/functions/energy_assessment_functions.py
index c9e40b3f..72e05314 100644
--- a/backend/app/db/functions/energy_assessment_functions.py
+++ b/backend/app/db/functions/energy_assessment_functions.py
@@ -101,7 +101,7 @@ def get_latest_assessments_for_uprns(
found_set = set(result.keys())
missing_uprns = uprn_set - found_set
-
+
for uprn in missing_uprns:
result[uprn] = EnergyAssessment.empty_response()
diff --git a/backend/engine/engine.py b/backend/engine/engine.py
index 4454a709..043e77b7 100644
--- a/backend/engine/engine.py
+++ b/backend/engine/engine.py
@@ -719,8 +719,10 @@ async def model_engine(body: PlanTriggerRequest):
# Otherwise, we use the newest EPC
# energy_assessment_is_newer will tell us if the energy assessment is newer than the newest EPC that
# has been publically lodged
- epc_records, energy_assessment_is_newer = create_epc_records(
- epc_searcher, energy_assessment if energy_assessment is not None else {"epc": None}
+ if energy_assessment is None:
+ energy_assessment = {}
+ epc_records, energy_assessment["energy_assessment_is_newer"] = create_epc_records(
+ epc_searcher, energy_assessment
)
req_data = extract_property_request_data(
@@ -845,61 +847,7 @@ async def model_engine(body: PlanTriggerRequest):
extract_uprn=True
)
- # TODO: TEMP: Compare values - and summarise the differences
- compare_scores = []
-
- for x in rebaselining_scoring_data["uprn"].unique():
- record = [p for p in input_properties if p.uprn == x][0].epc_record
-
- original_sap = record.current_energy_efficiency
- new_sap = rebaselining_response["retrofit_sap_baseline_predictions"][
- rebaselining_response["retrofit_sap_baseline_predictions"]["uprn"] == x
- ]["predictions"].values[0]
-
- lodgement_date = record.lodgement_date
- ll_differences = record.landlord_differences
-
- # 🔑 Normalise original keys to match LL format
- original = {
- k.replace("-", "_"): v
- for k, v in record.original_epc.items()
- if k.replace("-", "_") in ll_differences
- }
-
- row = {
- "uprn": x,
- "original_sap": original_sap,
- "new_sap": new_sap,
- "differences": ll_differences,
- "lodgement_date": lodgement_date,
- }
-
- # 🔑 Add paired columns in order
- for key in ll_differences.keys():
- row[f"{key}_ori"] = original.get(key)
- row[f"{key}_ll"] = ll_differences.get(key)
-
- compare_scores.append(row)
-
- compare_scores = pd.DataFrame(compare_scores)
- df = compare_scores.copy()
-
- ori_cols = [c for c in df.columns if c.endswith("_ori")]
-
- for ori_col in ori_cols:
- ll_col = ori_col.replace("_ori", "_ll")
-
- if ll_col in df.columns:
- # Handle NaNs properly
- same = (
- df[ori_col].fillna("NULL")
- == df[ll_col].fillna("NULL")
- )
-
- df.loc[same, [ori_col, ll_col]] = None
-
- # --- Refactored: Efficiently update EPC records with new model predictions ---
- # Pre-index input_properties by UPRN for fast lookup
+ # Update EPC records with new model predictions
input_properties_by_uprn = {int(p.uprn): p for p in input_properties if p.uprn is not None}
# Pre-index predictions for each model by UPRN
@@ -913,10 +861,9 @@ async def model_engine(body: PlanTriggerRequest):
df = rebaselining_response[model]
predictions_by_model_and_uprn[model] = dict(zip(df["uprn"].astype(int), df["predictions"]))
- for uprn in rebaselining_scoring_data["uprn"].unique():
+ for uprn_int in rebaselining_scoring_data["uprn"].unique().astype(int):
try:
- uprn_int = int(uprn)
- property_instance = input_properties_by_uprn.get(uprn_int)
+ property_instance = input_properties_by_uprn[uprn_int]
if property_instance is None:
logger.warning(f"No property found for UPRN {uprn_int} during rebaselining update.")
continue
@@ -935,10 +882,8 @@ async def model_engine(body: PlanTriggerRequest):
new_carbon=new_carbon,
new_heat_demand=new_heat_demand,
)
- logger.info(f"Updated EPC record for UPRN {uprn_int} with new model predictions.")
except Exception as e:
- logger.error(f"Error updating EPC record for UPRN {uprn}: {e}")
- # --- End refactor ---
+ logger.error(f"Error updating EPC record for UPRN {uprn_int}: {e}")
kwh_client = KwhData(bucket=get_settings().DATA_BUCKET, read_consumption_data=True)
@@ -1015,6 +960,12 @@ async def model_engine(body: PlanTriggerRequest):
if not property_recommendations:
continue
+ # Perform a check for properties (temp) where we've remodelled
+ if p.epc_record.has_been_remodelled:
+ for x in property_recommendations:
+ if any(y.get("survey") for y in x):
+ raise ValueError("Should not have survey true for remodelled properties")
+
recommendations[p.id] = property_recommendations
representative_recommendations[p.id] = property_representative_recommendations
diff --git a/recommendations/FireplaceRecommendations.py b/recommendations/FireplaceRecommendations.py
index 44f57a00..d8828a5e 100644
--- a/recommendations/FireplaceRecommendations.py
+++ b/recommendations/FireplaceRecommendations.py
@@ -1,4 +1,3 @@
-import pandas as pd
from BaseUtility import Definitions
from backend.Property import Property
diff --git a/recommendations/FloorRecommendations.py b/recommendations/FloorRecommendations.py
index df86c497..53930e41 100644
--- a/recommendations/FloorRecommendations.py
+++ b/recommendations/FloorRecommendations.py
@@ -9,7 +9,7 @@ from backend.app.plan.schemas import MEASURE_MAP
from backend.Property import Property
from recommendations.recommendation_utils import (
r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, update_lowest_selected_u_value,
- get_recommended_part, get_floor_u_value, override_costs, check_simulation_difference
+ get_recommended_part, get_floor_u_value, override_costs, check_simulation_difference, check_use_survey
)
from recommendations.Costs import Costs
from etl.epc_clean.epc_attributes.FloorAttributes import FloorAttributes
@@ -226,7 +226,6 @@ class FloorRecommendations(Definitions):
raise NotImplementedError("Implement me!")
sap_points = non_invasive_recs.get("sap_points", None)
- survey = non_invasive_recs.get("survey", False)
floor_ending_config = FloorAttributes(new_description).process()
floor_simulation_config = check_simulation_difference(
@@ -257,7 +256,9 @@ class FloorRecommendations(Definitions):
"starting_u_value": u_value,
"new_u_value": new_u_value,
"sap_points": sap_points,
- "survey": survey,
+ "survey": check_use_survey(
+ non_invasive_recs, self.property.epc_record.has_been_remodelled
+ ),
"already_installed": already_installed,
"simulation_config": simulation_config,
"description_simulation": {
diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py
index a40b409f..74881730 100644
--- a/recommendations/HeatingRecommender.py
+++ b/recommendations/HeatingRecommender.py
@@ -1,7 +1,7 @@
import re
import backend.app.assumptions as assumptions
from recommendations.recommendation_utils import (
- check_simulation_difference, override_costs, combine_recommendation_configs
+ check_simulation_difference, override_costs, combine_recommendation_configs, check_use_survey
)
from backend.Property import Property
from backend.app.plan.schemas import MEASURE_MAP
@@ -865,7 +865,9 @@ class HeatingRecommender:
"description_simulation": recommendation_description_simulation,
# We insert the heating system type here
"system_type": system_type,
- "survey": non_intrusive_recommendation.get("survey", False),
+ "survey": check_use_survey(
+ non_intrusive_recommendation, self.property.epc_record.has_been_remodelled
+ ),
# In this instance, we are recommending an entire heating system so the innovation rate is becased
# on the heating system as whole
"innovation_rate": heating_product["innovation_rate"],
@@ -1367,7 +1369,7 @@ class HeatingRecommender:
"description_simulation": description_simulation,
**boiler_costs,
"system_type": "boiler_upgrade",
- "survey": non_invasive_recommendation.get("survey", None),
+ "survey": check_use_survey(non_invasive_recommendation, self.property.epc_record.has_been_remodelled),
"innovation_rate": 0,
}
diff --git a/recommendations/HotwaterRecommendations.py b/recommendations/HotwaterRecommendations.py
index 2d03e023..8b8cb579 100644
--- a/recommendations/HotwaterRecommendations.py
+++ b/recommendations/HotwaterRecommendations.py
@@ -1,6 +1,6 @@
from backend.Property import Property
from recommendations.Costs import Costs
-from recommendations.recommendation_utils import override_costs, check_simulation_difference
+from recommendations.recommendation_utils import override_costs, check_simulation_difference, check_use_survey
from etl.epc_clean.epc_attributes.HotWaterAttributes import HotWaterAttributes
@@ -39,7 +39,7 @@ class HotwaterRecommendations:
self.recommend_tank_insulation(
phase=recommendations_phase,
sap_points=non_invasive_rec["sap_points"],
- survey=non_invasive_rec["survey"],
+ survey=check_use_survey(non_invasive_rec, self.property.epc_record.has_been_remodelled),
)
recommendations_phase += 1
@@ -47,7 +47,7 @@ class HotwaterRecommendations:
self.recommend_cylinder_thermostat(
phase=recommendations_phase,
sap_points=non_invasive_rec["sap_points"],
- survey=non_invasive_rec["survey"],
+ survey=check_use_survey(non_invasive_rec, self.property.epc_record.has_been_remodelled),
)
recommendations_phase += 1
diff --git a/recommendations/LightingRecommendations.py b/recommendations/LightingRecommendations.py
index 6fa93fb8..61b1f66a 100644
--- a/recommendations/LightingRecommendations.py
+++ b/recommendations/LightingRecommendations.py
@@ -3,7 +3,7 @@ import pandas as pd
from backend.Property import Property
from typing import List
from recommendations.Costs import Costs
-from recommendations.recommendation_utils import override_costs
+from recommendations.recommendation_utils import override_costs, check_use_survey
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
@@ -169,7 +169,9 @@ class LightingRecommendations:
"low-energy-lighting": 100,
},
**cost_result,
- "survey": leds_recommendation_config.get("survey", False),
+ "survey": check_use_survey(
+ leds_recommendation_config, self.property.epc_record.has_been_remodelled
+ ),
"innovation_rate": self.material["innovation_rate"],
}
]
diff --git a/recommendations/RoofRecommendations.py b/recommendations/RoofRecommendations.py
index 3f434976..8882a015 100644
--- a/recommendations/RoofRecommendations.py
+++ b/recommendations/RoofRecommendations.py
@@ -7,7 +7,7 @@ from datatypes.enums import QuantityUnits
from recommendations.recommendation_utils import (
get_roof_u_value, r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns,
update_lowest_selected_u_value, get_recommended_part, convert_thickness_to_numeric, override_costs,
- check_simulation_difference
+ check_simulation_difference, check_use_survey
)
from recommendations.Costs import Costs
from etl.epc_clean.epc_attributes.RoofAttributes import RoofAttributes
@@ -874,7 +874,9 @@ class RoofRecommendations:
"roof-energy-eff": new_efficiency
},
**cost_result,
- "survey": non_invasive_recommendations.get("survey", False),
+ "survey": check_use_survey(
+ non_invasive_recommendations, self.property.epc_record.has_been_remodelled
+ ),
"innovation_rate": material.to_dict()["innovation_rate"]
}
)
@@ -1009,7 +1011,9 @@ class RoofRecommendations:
},
**cost_result,
"already_installed": already_installed,
- "survey": rir_non_invasive_recommendation.get("survey", None),
+ "survey": check_use_survey(
+ rir_non_invasive_recommendation, self.property.epc_record.has_been_remodelled
+ ),
"innovation_rate": material.innovation_rate
}
)
@@ -1079,7 +1083,9 @@ class RoofRecommendations:
},
**cost_result,
"already_installed": "sloping_ceiling_insulation" in self.property.already_installed,
- "survey": sloping_ceiling_recommendation.get("survey", None),
+ "survey": check_use_survey(
+ sloping_ceiling_recommendation, self.property.epc_record.has_been_remodelled
+ ),
"innovation_rate": 0
}
]
diff --git a/recommendations/WallRecommendations.py b/recommendations/WallRecommendations.py
index 2a96da28..a5192363 100644
--- a/recommendations/WallRecommendations.py
+++ b/recommendations/WallRecommendations.py
@@ -11,7 +11,8 @@ from BaseUtility import Definitions
from etl.epc_clean.epc_attributes.WallAttributes import WallAttributes
from recommendations.recommendation_utils import (
r_value_per_mm_to_u_value, calculate_u_value_uplift, is_diminishing_returns, update_lowest_selected_u_value,
- get_recommended_part, get_wall_u_value, override_costs, check_simulation_difference
+ get_recommended_part, get_wall_u_value, override_costs, check_simulation_difference,
+ check_use_survey
)
from recommendations.config import PARTIALLY_FILLED_PERCENTAGE_ASSUMPTION
from recommendations.Costs import Costs
@@ -443,7 +444,9 @@ class WallRecommendations(Definitions):
"walls-energy-eff": "Good"
},
**cost_result,
- "survey": non_invasive_recommendations.get("survey", False),
+ "survey": check_use_survey(
+ non_invasive_recommendations, self.property.epc_record.has_been_remodelled
+ ),
"innovation_rate": material.to_dict()["innovation_rate"]
}
)
@@ -573,7 +576,6 @@ class WallRecommendations(Definitions):
raise ValueError("Invalid material type")
sap_points = non_invasive_recommendations.get("sap_points", None)
- survey = non_invasive_recommendations.get("survey", False)
wall_ending_config = WallAttributes(new_description).process()
@@ -624,7 +626,9 @@ class WallRecommendations(Definitions):
"walls-energy-eff": simulation_config["walls_energy_eff_ending"]
},
**cost_result,
- "survey": survey,
+ "survey": check_use_survey(
+ non_invasive_recommendations, self.property.epc_record.has_been_remodelled
+ ),
"innovation_rate": material.to_dict()["innovation_rate"]
}
)
diff --git a/recommendations/WindowsRecommendations.py b/recommendations/WindowsRecommendations.py
index 8940148d..ff75e72d 100644
--- a/recommendations/WindowsRecommendations.py
+++ b/recommendations/WindowsRecommendations.py
@@ -6,7 +6,7 @@ from backend.Property import Property
from backend.app.plan.schemas import MEASURE_MAP
from etl.epc_clean.epc_attributes.WindowAttributes import WindowAttributes
from recommendations.Costs import Costs
-from recommendations.recommendation_utils import override_costs, check_simulation_difference
+from recommendations.recommendation_utils import override_costs, check_simulation_difference, check_use_survey
class WindowsRecommendations:
@@ -259,7 +259,9 @@ class WindowsRecommendations:
"is_secondary_glazing": is_secondary_glazing,
"description_simulation": description_simulation,
"simulation_config": simulation_config,
- "survey": non_invasive_recommendation.get("survey", None),
+ "survey": check_use_survey(
+ non_invasive_recommendation, self.property.epc_record.has_been_remodelled
+ ),
"innovation_rate": self.glazing_material["innovation_rate"],
}
]
diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py
index b1744c69..b342a479 100644
--- a/recommendations/recommendation_utils.py
+++ b/recommendations/recommendation_utils.py
@@ -1,7 +1,7 @@
import math
from datetime import datetime
from copy import deepcopy
-from typing import Union
+from typing import Union, Dict
import numpy as np
import pandas as pd
@@ -975,3 +975,16 @@ def combine_recommendation_configs(recommendation_config1, recommendation_config
combined[key] = eff_2[key]
return combined
+
+
+def check_use_survey(non_invasive_recommendations: Dict[str, bool], has_been_remodelled: bool):
+ """
+ Determines if we should use a survey SAP points or not
+ :return:
+ """
+
+ use_survey = (
+ non_invasive_recommendations.get("survey", False) if not
+ has_been_remodelled else False
+ )
+ return use_survey