Compare commits

..

3 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
e92b70f9ec modifying recommendation classes 2026-03-18 22:11:16 +00:00
Khalim Conn-Kowlessar
84d4263d9a removing data 2026-03-18 19:17:22 +00:00
Khalim Conn-Kowlessar
f45260706e fixed bug in epc record cleaning" 2026-03-18 18:16:57 +00:00
14 changed files with 177 additions and 120 deletions

1
.idea/Model.iml generated
View file

@ -6,6 +6,7 @@
<sourceFolder url="file://$MODULE_DIR$/model_data" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/infrastructure/terraform/.terraform" />
</content>
<orderEntry type="jdk" jdkName="Fastapi-backend" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />

View file

@ -73,25 +73,24 @@ def app():
Property UPRN
"""
data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Lifespace Rentals/Missed"
# data_filename = "For Modelling - Final - reviewed.xlsx"
data_filename = "Missed Properties - with address.xlsx"
data_folder = "/Users/khalimconn-kowlessar/Documents/hestia/Customers/Livewest/March 2026 SAL"
data_filename = "Domna System Review - Livewest.xlsx"
sheet_name = "Sheet1"
postcode_column = "Postcode"
address1_column = "address1"
address1_method = None
fulladdress_column = "address1"
address1_column = None
address1_method = "house_number_extraction"
fulladdress_column = "Address"
address_cols_to_concat = []
missing_postcodes_method = None
landlord_year_built = None
landlord_os_uprn = "UPRN"
landlord_property_type = "Type"
landlord_built_form = None
landlord_os_uprn = "gov UPRN"
landlord_property_type = "AssetType"
landlord_built_form = "AssetType"
landlord_wall_construction = None
landlord_roof_construction = None
landlord_heating_system = None
landlord_existing_pv = None
landlord_property_id = "Reference"
landlord_property_id = "landlord_uprn"
landlord_sap = None
outcomes_filename = None
outcomes_sheetname = None

View file

@ -173,6 +173,7 @@ def get_data(
errors = []
no_epc = []
for _, home in tqdm(df.iterrows(), total=len(df)):
try:
# If we have a block of flats, we cannot retrieve this data

View file

@ -182,8 +182,8 @@ class GoogleSolarApi:
self.exclude_north_facing_segments(property_instance=property_instance)
# If a property is semi-detached, it's possible for us to include segments from an attached unit
if property_instance is not None:
if (property_instance.data["built-form"] == "Semi-Detached") and (
property_instance.data["extension-count"] == 0
if (property_instance.epc_record.built_form == "Semi-Detached") and (
property_instance.epc_record.extension_count == 0
):
self.exclude_likely_duplicate_surfaces()
@ -708,7 +708,7 @@ class GoogleSolarApi:
# We set the target rating to EPC C, which is the typical EPC rating we would expect the
# property to achieve post retrofit of just the fabric
"energy_consumption": cls.estimate_new_consumption(
current_energy_efficiency=min(p.data["current-energy-efficiency"], 100),
current_energy_efficiency=min(p.epc_record.current_energy_efficiency, 100),
target_efficiency="69",
current_consumption=p.estimate_electrical_consumption(
assumed_ashp_efficiency=assumptions.AVERAGE_ASHP_EFFICIENCY, exclusions=body.exclusions
@ -727,7 +727,7 @@ class GoogleSolarApi:
# We set the target rating to EPC C, which is the typical EPC rating we would expect the
# property to achieve post retrofit of just the fabric
"energy_consumption": cls.estimate_new_consumption(
current_energy_efficiency=min(int(p.data["current-energy-efficiency"]), 100),
current_energy_efficiency=min(p.epc_record.current_energy_efficiency, 100),
target_efficiency="69",
current_consumption=p.estimate_electrical_consumption(
assumed_ashp_efficiency=assumptions.AVERAGE_ASHP_EFFICIENCY, exclusions=body.exclusions

View file

@ -1,5 +1,5 @@
from typing import Optional
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import func
from backend.app.db.models.addresses import PostcodeSearch
from utils.logger import setup_logger
@ -20,7 +20,7 @@ def _get_associated_records(results, uprn, uprn_key="UPRN"):
return matched_record
def get_associated_uprns(postcode_search: PostcodeSearch, uprn: str | int):
def get_associated_uprns(postcode_search: Optional[PostcodeSearch], uprn: str | int):
"""
Given a postcode and UPRN, for a remote assessment, fetch all associated UPRNs, based
on parent UPRN. This will be properties in the same building

View file

@ -147,6 +147,10 @@ class PropertyModel(Base):
is_sap_points_adjusted_for_installed_measures = Column(Boolean, default=False)
original_sap_points = Column(Float)
# New for re-scoring - we will need to delete some of the redundant fields but there is a ticket for this
lodged_sap_points = Column(Float)
lodged_epc_rating = Column(Enum(Epc))
class FeatureRating(enum.Enum):
VERY_GOOD = 5
@ -253,6 +257,12 @@ class PropertyDetailsEpcModel(Base):
installed_measures_heat_demand_adjustment = Column(Float)
is_epc_adjusted_for_installed_measures = Column(Boolean, default=False)
# New columns - we'll need to delete some of the redundant fields, associated to "already installed" but
# we have a ticket for this piece of work
lodged_co2_emissions = Column(Float)
lodged_heat_demand = Column(Float)
has_been_remodelled = Column(Boolean, default=False)
class PropertyDetailsSpatial(Base):
__tablename__ = "property_details_spatial"

View file

@ -837,41 +837,41 @@ async def model_engine(body: PlanTriggerRequest):
extract_uprn=True
)
for idx, rebaselined_prediction in rebaselining_response["retrofit-sap-baseline-predictions"].iterrows():
property_instance = next(p for p in input_properties if p.uprn == int(rebaselined_prediction["uprn"]))
new_rating = rebaselined_prediction["predictions"]
new_epc_rating = sap_to_epc(new_rating)
# Insert
# TODO: TEMP: Compare values
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
compare_scores.append({
"uprn": x,
"original_sap": original_sap,
"new_sap": new_sap,
"lodgement_date": lodgement_date
})
compare_scores = pd.DataFrame(compare_scores)
# property_instance.data["current-energy-efficiency"] = sap_to_epc(new_rating)
for uprn in rebaselining_scoring_data["uprn"].unique():
# Get the predictions
sap_prediction = rebaselining_response["retrofit-sap-baseline-predictions"][
rebaselining_response["retrofit-sap-baseline-predictions"]["uprn"] == uprn
]["predictions"].values[0]
addr = [a for a in addresses if a.uprn == property_instance.uprn][0]
landlord_remapping = {
"total-floor-area": addr.landlord_total_floor_area_m2, # 1m tolerance on floor area to perform remap
"property-type": addr.landlord_property_type,
"built-form": addr.landlord_built_form,
# Components
"walls-description": addr.landlord_wall_construction,
"roof-description": addr.landlord_roof_construction,
"floor-description": addr.landlord_floor_construction,
"windows-description": addr.landlord_windows_type,
"main-fuel": addr.landlord_fuel_type,
"mainheatcont-description": addr.landlord_heating_controls,
"hotwater-description": addr.landlord_hot_water_system,
# Efficiency
"walls-energy-eff": addr.landlord_wall_efficiency,
"roof-energy-eff": addr.landlord_roof_efficiency,
"windows-energy-eff": addr.landlord_windows_efficiency,
"mainheat-energy-eff": addr.landlord_heating_efficiency,
"mainheatc-energy-eff": addr.landlord_heating_controls_efficiency,
"hot-water-energy-eff": addr.landlord_hot_water_efficiency,
"multi-glaze-proportion": addr.landlord_multi_glaze_proportion * 100, # TODO: Fix this!
"construction-age-band": addr.landlord_construction_age_band,
}
carbon_prediction = 1337
heat_demand_prediction = 1337
# Insert the re-baselined scores into the property data
for p in input_properties:
property_rebaselined_sap = rebaselining_response["retrofit-sap-baseline-predictions"]
epc_prediction = sap_to_epc(sap_prediction)
# We now need to insert the new values into the epc_record
property_instance = next(p for p in input_properties if p.uprn == int(uprn))
property_instance.epc_record.insert_new_performance_values(
new_sap=sap_prediction,
new_epc=epc_prediction,
new_carbon=carbon_prediction,
new_heat_demand=heat_demand_prediction,
)
kwh_client = KwhData(bucket=get_settings().DATA_BUCKET, read_consumption_data=True)
@ -924,26 +924,6 @@ async def model_engine(body: PlanTriggerRequest):
# We also make a tweak - if the property has been flagged for solar but doesn't contain
# any panel performance, we ensure that we have a 3kWp and 4kWp option for the property
# TODO: Temp - test re-baselining
p = input_properties[0]
p.create_base_difference_epc_record(cleaned_lookup=cleaned)
scoring_data = p.base_difference_record.df
# We just need a recent date to trigger the right models,
# as we are only interested in the deltas
scoring_data["is_post_sap10_starting"] = True
# Score model - SAP re-baselining model
model_api.MODEL_URLS["retrofit-sap-baseline-predictions"] = "sapbaselinemodel"
model_api.prediction_buckets["retrofit-sap-baseline-predictions"] = "retrofit-sap-baseline-predictions-dev"
example_response = model_api.predict_all(
df=scoring_data,
bucket=get_settings().DATA_BUCKET,
model_prefixes=["retrofit-sap-baseline-predictions"],
extract_ids=False
)
input_properties[0].data["current-energy-efficiency"] = 58.8
input_properties[0].data["current-energy-rating"] = "D"
logger.info("Identifying property recommendations")
recommendations, recommendations_scoring_data, representative_recommendations = {}, [], {}
for p in tqdm(input_properties):

View file

@ -287,7 +287,7 @@ class KwhData:
:return:
"""
epc = p.data.copy()
epc = p.epc_record.to_dict(case="kebab", source="prepared")
numeric_cols = [
'current-energy-efficiency',
'potential-energy-efficiency', 'environment-impact-current',

View file

@ -309,6 +309,7 @@ class EPCRecord:
# Indicates if the EPC record has been predicted. By default, false
estimated: Optional[bool] = False
sap_05_overwritten: Optional[bool] = False
has_been_remodelled: Optional[bool] = False
# ------------------------------------------------------------------
# MODEL FLAGS
@ -386,6 +387,35 @@ class EPCRecord:
return
def insert_new_performance_values(
self, new_sap: float, new_epc: float, new_carbon: float, new_heat_demand: float,
):
"""
Given re-modelling for this property, is used to insert the new values and also keep a record of the
fact that re-modelling has taken place
:param new_sap:
:param new_epc:
:param new_carbon:
:param new_heat_demand:
:return:
"""
self.has_been_remodelled = True
# Update prepared epc
update_data = {
"current_energy_efficiency": new_sap,
"current_energy_rating": new_epc,
"co2_emissions_current": new_carbon,
"energy_consumption_current": new_heat_demand,
}
# Validate we're updating correct fields
for k in update_data:
if k not in self._prepared_epc:
raise ValueError(f"Attempting to update unknown field '{k}' in prepared EPC")
self._prepared_epc.update(update_data)
# Update dataclass attributes
self._expand_prepared_epc_to_attributes()
def _apply_averages_cleaning(self) -> None:
"""
Fills missing property dimension values using medians from cleaning_data.
@ -626,6 +656,10 @@ class EPCRecord:
# Ignore keys that are not part of the dataclass schema
continue
if value is None:
setattr(self, key, None)
continue
try:
cast_value = self._cast_value(value, field_map[key].type)
setattr(self, key, cast_value)
@ -812,14 +846,17 @@ class EPCRecord:
(property_dimensions["PROPERTY_TYPE"] == self._prepared_epc["property-type"])
]
if self.construction_age_band not in DATA_ANOMALY_MATCHES:
if (
(self.construction_age_band not in DATA_ANOMALY_MATCHES) and
(self.construction_age_band in result["CONSTRUCTION_AGE_BAND"].values)
):
result = result[
(result["CONSTRUCTION_AGE_BAND"] == self.construction_age_band)
]
if (
self._prepared_epc["built-form"] not in DATA_ANOMALY_MATCHES
and self._prepared_epc["built-form"] in result["BUILT_FORM"]
and self._prepared_epc["built-form"] in result["BUILT_FORM"].values
):
result = result[(result["BUILT_FORM"] == self._prepared_epc["built-form"])]
@ -935,7 +972,7 @@ class EPCRecord:
self._prepared_epc["unheated-corridor-length"] = (
float(self._prepared_epc["unheated-corridor-length"])
if self._prepared_epc["unheated-corridor-length"] not in ["", None]
if self._prepared_epc["unheated-corridor-length"] not in DATA_ANOMALY_MATCHES
else None
)
@ -1208,6 +1245,34 @@ class EPCRecord:
return self.__dict__[RDSAP_RESPONSE] <= other.__dict__[RDSAP_RESPONSE]
def to_dict(
self,
case: Literal["snake", "kebab"] = "kebab",
source: Literal["prepared", "attributes"] = "prepared",
) -> dict[str, Any]:
if source == "prepared":
if self._prepared_epc is None:
raise ValueError("Prepared EPC not available")
data = self._prepared_epc.copy()
elif source == "attributes":
data = {
k: v for k, v in vars(self).items()
if not k.startswith("_")
}
else:
raise ValueError(f"Unknown source: {source}")
if case == "snake":
return {k.replace("-", "_"): v for k, v in data.items()}
if case == "kebab":
return {k.replace("_", "-"): v for k, v in data.items()}
return data
def get(
self,
key: str | list[str],

View file

@ -200,20 +200,20 @@ class Costs:
self.property = property_instance
self.regional_labour_variations = regional_labour_variations
self.region = county_to_region_map.get(self.property.data["county"], None)
self.region = county_to_region_map.get(self.property.epc_record.county, None)
if self.region is None:
# Try and grab using the local-authority-label
self.region = county_to_region_map.get(self.property.data["local-authority-label"], None)
self.region = county_to_region_map.get(self.property.epc_record.local_authority_label, None)
if self.region is None:
# Try and get the region after converting the keys to lower
self.region = {
k.lower(): v for k, v in county_to_region_map.items()
}.get(self.property.data["local-authority-label"].lower(), None)
}.get(self.property.property.epc_record.local_authority_label.lower(), None)
if self.region is None:
logger.warning("No region found for county %s, defaulting to South East England",
self.property.data["county"])
self.property.epc_record.county)
self.region = "South East England"
self.labour_adjustment_factor = [
@ -858,8 +858,8 @@ class Costs:
n_radiators = self._estimate_n_radiators(
number_habitable_rooms=n_rooms,
total_floor_area=self.property.floor_area,
property_type=self.property.data["property-type"],
built_form=self.property.data["built-form"]
property_type=self.property.epc_record.property - type,
built_form=self.property.epc_record.built_form
)
additionals_labour_cost = labour_rate * self.labour_adjustment_factor

View file

@ -28,7 +28,7 @@ class FireplaceRecommendations(Definitions):
:return:
"""
number_open_fireplaces = int(self.property.data["number-open-fireplaces"])
number_open_fireplaces = self.property.epc_record.number_open_fireplaces
if number_open_fireplaces == 0:
return

View file

@ -76,7 +76,7 @@ class FloorRecommendations(Definitions):
return
u_value = self.property.floor["thermal_transmittance"]
property_type = self.property.data["property-type"]
property_type = self.property.epc_record.property_type
floor_area = self.property.insulation_floor_area
if self.property.floor["another_property_below"] | (self.property.floor["insulation_thickness"] in [

View file

@ -56,7 +56,7 @@ class HeatingControlRecommender:
We can then consider the heating system itself
:return:
"""
if (self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor", "Average"]) or (
if (self.property.epc_record.mainheatc_energy_eff in ["Poor", "Very Poor", "Average"]) or (
self.property.main_heating_controls["clean_description"] in ["Programmer and room thermostat"]
):
# We recommend Programmer and appliance thermostats as the heating control. This has an average energy
@ -125,10 +125,10 @@ class HeatingControlRecommender:
new_config=ending_config, old_config=self.property.main_heating_controls
)
# This upgrade will only take the heating system to average energy efficiency
if self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor", "Average"]:
if self.property.epc_record.mainheatc_energy_eff in ["Poor", "Very Poor", "Average"]:
simulation_config["mainheatc_energy_eff_ending"] = "Good"
else:
simulation_config["mainheatc_energy_eff_ending"] = self.property.data["mainheatc-energy-eff"]
simulation_config["mainheatc_energy_eff_ending"] = self.property.epc_record.mainheatc_energy_eff
description_simulation = {
"mainheatcont-description": new_description,
@ -193,10 +193,10 @@ class HeatingControlRecommender:
)
# This upgrade will only take the heating system to average energy efficiency
# If the current system is below good, we make it good
if self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor", "Average"]:
if self.property.epc_record.mainheatc_energy_eff in ["Poor", "Very Poor", "Average"]:
simulation_config["mainheatc_energy_eff_ending"] = "Good"
else:
simulation_config["mainheatc_energy_eff_ending"] = self.property.data["mainheatc-energy-eff"]
simulation_config["mainheatc_energy_eff_ending"] = self.property.epc_record.mainheatc_energy_eff
description_simulation = {
"mainheatcont-description": new_controls_description,
@ -208,7 +208,7 @@ class HeatingControlRecommender:
has_trvs = not needs_trvs
cost_result = self.costs.roomstat_programmer_trvs(
number_heated_rooms=int(self.property.data["number-heated-rooms"]),
number_heated_rooms=self.property.epc_record.number_heated_rooms,
has_programmer=has_programmer,
has_room_thermostat=has_room_thermostat,
has_trvs=has_trvs
@ -257,7 +257,7 @@ class HeatingControlRecommender:
if (
(self.property.main_heating_controls["thermostatic_control"] == "time and temperature zone control") or
(self.property.data["mainheatc-energy-eff"] in ["Very Good"])
(self.property.epc_record.mainheatc_energy_eff in ["Very Good"])
):
# No recommendation needed
return
@ -274,17 +274,17 @@ class HeatingControlRecommender:
)
# If the current system is below very good, we make it very good
if self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor", "Average", "Good"]:
if self.property.epc_record.mainheatc_energy_eff in ["Poor", "Very Poor", "Average", "Good"]:
simulation_config["mainheatc_energy_eff_ending"] = "Very Good"
else:
simulation_config["mainheatc_energy_eff_ending"] = self.property.data["mainheatc-energy-eff"]
simulation_config["mainheatc_energy_eff_ending"] = self.property.epc_record.mainheatc_energy_eff
description_simulation = {
"mainheatcont-description": new_controls_description,
"mainheatc-energy-eff": simulation_config["mainheatc_energy_eff_ending"]
}
cost_result = self.costs.time_and_temperature_zone_control(
number_heated_rooms=int(self.property.data["number-heated-rooms"])
number_heated_rooms=self.property.epc_record.number_heated_rooms
)
description = (
@ -324,10 +324,10 @@ class HeatingControlRecommender:
new_config=ending_config, old_config=self.property.main_heating_controls
)
# Only adjust if the current system is below good
if self.property.data["mainheatc-energy-eff"] in ["Poor", "Very Poor"]:
if self.property.epc_record.mainheatc_energy_eff in ["Poor", "Very Poor"]:
simulation_config["mainheatc_energy_eff_ending"] = "Average"
else:
simulation_config["mainheatc_energy_eff_ending"] = self.property.data["mainheatc-energy-eff"]
simulation_config["mainheatc_energy_eff_ending"] = self.property.epc_record.mainheatc_energy_eff
description_simulation = {
"mainheatcont-description": new_controls_description,
@ -339,7 +339,7 @@ class HeatingControlRecommender:
has_bypass = self.property.main_heating_controls["auxiliary_systems"] == "bypass"
cost_result = self.costs.programmer_trvs_bypass(
number_heated_rooms=int(self.property.data["number-heated-rooms"]),
number_heated_rooms=self.property.epc_record.number_heated_rooms,
has_trvs=has_trvs,
has_programmer=has_programmer,
has_bypass=has_bypass

View file

@ -160,7 +160,7 @@ class HeatingRecommender:
"""
# We can also recommend hhr if the property doesn't have a mains has connection
no_mains = not self.property.data["mains-gas-flag"]
no_mains = not self.property.epc_record.mains_gas_flag
# If the property already has room heaters then we recommend HHR as an option since the home already has
# a variation of room heaters
@ -199,28 +199,28 @@ class HeatingRecommender:
# 2) If the property doesn't have a heating system, but it has access to the mains gas
no_heating_has_mains = self.property.main_heating["clean_description"] in [
'No system present, electric heaters assumed'
] and self.property.data["mains-gas-flag"]
] and self.property.epc_record.mains_gas_flag
# The property is using portable heaters and has access to gas mains
has_room_heaters = self.has_room_heaters and self.property.data["mains-gas-flag"]
has_room_heaters = self.has_room_heaters and self.property.epc_record.mains_gas_flag
# We also check if the property has electric heating, but it has access to the mains gas
electic_heating_has_mains = self.has_electric_heating_description and self.property.data["mains-gas-flag"]
electic_heating_has_mains = self.has_electric_heating_description and self.property.epc_record.mains_gas_flag
portable_heaters_has_mains = (
self.property.main_heating["has_portable_electric_heaters"] and self.property.data["mains-gas-flag"]
self.property.main_heating["has_portable_electric_heaters"] and self.property.epc_record.mains_gas_flag
)
# The next condition is if the home has a non-gas boiler, such as an oil boiler, with a mains gas connection
non_gas_boiler = (
self.property.main_heating["has_boiler"] and
not self.property.main_heating["has_mains_gas"] and
self.property.data["mains-gas-flag"]
self.property.epc_record.mains_gas_flag
)
# Additionally, if the property has a gas connection, is using gas heating but doesn't have a boiler,
# we recommend a boiler
non_boiler_gas_heating = (
self.property.data["mains-gas-flag"] and
self.property.epc_record.mains_gas_flag and
self.property.main_heating["has_mains_gas"] and
not self.property.main_heating["has_boiler"]
)
@ -386,7 +386,7 @@ class HeatingRecommender:
recommendation_phase = phase
if self.property.data["mainheat-energy-eff"] not in ["Poor", "Very Poor"]:
if self.property.epc_record.mainheat_energy_eff not in ["Poor", "Very Poor"]:
return
hotwater_from_mains = self.property.hotwater["clean_description"] in ["From main system"]
@ -407,7 +407,7 @@ class HeatingRecommender:
size=None,
exising_room_heaters=False,
system_change=False,
n_heated_rooms=self.property.data["number-heated-rooms"],
n_heated_rooms=self.property.epc_record.number_heated_rooms,
n_rooms=self.property.number_of_rooms,
is_electric=True
)
@ -581,7 +581,7 @@ class HeatingRecommender:
# New functions to estimate size of ASHP
estimated_load = self.estimate_peak_kw(
floor_area_m2=self.property.floor_area,
epc_primary_kwh_per_m2_yr=self.property.data["energy-consumption-current"],
epc_primary_kwh_per_m2_yr=self.property.epc_record.energy_consumption_current,
primary_to_delivered_factor=1.55, # use 1.13 if heating fuel is gas
space_heat_fraction_range=(0.35, 0.60),
hdd_base_dd=2000.0, # set from location
@ -670,7 +670,7 @@ class HeatingRecommender:
# If the property does not have existing cavity and loft insulation, we include a note that the cost
# includes the boiler upgrade scheme and that the cavity and loft need to be treated, to ensure access
# to the funding
if not non_intrusive_recommendation and self.property.data["tenure"] not in assumptions.SOCIAL_TENURES:
if not non_intrusive_recommendation and self.property.epc_record.tenure not in assumptions.SOCIAL_TENURES:
if has_cavity_or_loft_recommendations:
description = description + (
f" You must ensure that the property has an insulated cavity and "
@ -923,7 +923,7 @@ class HeatingRecommender:
# If the property is off-gas and has no heating system in place, the number of heated rooms will actually
# be 0, so we use the number of rooms as the figure
number_heated_rooms = (
self.property.data["number-heated-rooms"] if self.property.data["number-heated-rooms"] > 0
self.property.epc_record.number_heated_rooms if self.property.epc_record.number_heated_rooms > 0
else (
self.property.number_of_rooms - 1 if self.property.number_of_rooms > 1 else
self.property.number_of_rooms
@ -949,20 +949,20 @@ class HeatingRecommender:
}
# Fallback if property type unknown
base = base_by_type.get(self.property.data["property-type"], 1)
base = base_by_type.get(self.property.epc_record.property_type, 1)
# Area-based adjustments
if self.property.data["property-type"] in ("Flat", "Maisonette"):
if self.property.epc_record.property_type in ("Flat", "Maisonette"):
if self.property.floor_area > 90:
return base + 1 # duplex or very large flat
return base
if self.property.data["property-type"] == "Bungalow":
if self.property.epc_record.property_type == "Bungalow":
if self.property.floor_area > 100:
return base + 1 # secondary corridor
return base
if self.property.data["property-type"] == "House":
if self.property.epc_record.property_type == "House":
if self.property.floor_area > 140:
return base + 1 # extra landing / circulation
return base
@ -1060,17 +1060,17 @@ class HeatingRecommender:
**hot_water_simulation_config
}
# This upgrade will only take the heating system to average energy efficiency
if self.property.data["mainheat-energy-eff"] in ["Very Poor", "Poor"] and not self.dual_heating:
if self.property.epc_record.mainheat_energy_eff in ["Very Poor", "Poor"] and not self.dual_heating:
heating_simulation_config["mainheat_energy_eff_ending"] = "Average"
else:
heating_simulation_config["mainheat_energy_eff_ending"] = self.property.data["mainheat-energy-eff"]
heating_simulation_config["mainheat_energy_eff_ending"] = self.property.epc_record.mainheat_energy_eff
# TODO:We possibly shouldn't touch the hot water energy efficiency if we aren't recommending dual immersion
# we'll keep this for the moment though
if self.property.data["hot-water-energy-eff"] in ["Very Poor", "Poor"]:
if self.property.epc_record.hot_water_energy_eff in ["Very Poor", "Poor"]:
heating_simulation_config["hot_water_energy_eff_ending"] = "Average"
else:
heating_simulation_config["hot_water_energy_eff_ending"] = self.property.data["hot-water-energy-eff"]
heating_simulation_config["hot_water_energy_eff_ending"] = self.property.epc_record.hot_water_energy_eff
number_heated_rooms = self._estimate_n_heated_rooms()
@ -1261,11 +1261,12 @@ class HeatingRecommender:
boiler_recommendation = {}
description_simulation = {}
has_inefficient_space_heating = self.property.data["mainheat-energy-eff"] in ["Very Poor", "Poor", "Average"]
has_inefficient_space_heating = self.property.epc_record.mainheat_energy_eff in ["Very Poor", "Poor", "Average"]
# We check if there's a mains connection and the hot water is inefficient, as this will improve with a boiler
has_inefficient_water = (
self.property.data["mains-gas-flag"] and self.property.data["hot-water-energy-eff"] in ["Very Poor", "Poor"]
self.property.epc_record.mains_gas_flag and self.property.epc_record.hot_water_energy_eff in ["Very Poor",
"Poor"]
)
non_invasive_recommendation = next((
@ -1281,13 +1282,13 @@ class HeatingRecommender:
)
new_heating_eff = (
"Good" if self.property.data["mainheat-energy-eff"] in ["Very Poor", "Poor", "Average"]
else self.property.data["mainheat-energy-eff"]
"Good" if self.property.epc_record.mainheat_energy_eff in ["Very Poor", "Poor", "Average"]
else self.property.epc_record.mainheat_energy_eff
)
new_hotwater_eff = (
"Good" if self.property.data["hot-water-energy-eff"] in ["Very Poor", "Poor", "Average"]
else self.property.data["hot-water-energy-eff"]
"Good" if self.property.epc_record.hot_water_energy_eff in ["Very Poor", "Poor", "Average"]
else self.property.epc_record.hot_water_energy_eff
)
simulation_config = {
@ -1343,7 +1344,7 @@ class HeatingRecommender:
boiler_costs = self.costs.boiler(
exising_room_heaters=exising_room_heaters,
system_change=system_change,
n_heated_rooms=self.property.data["number-heated-rooms"],
n_heated_rooms=self.property.epc_record.number_heated_rooms,
n_rooms=self.property.number_of_rooms
)