mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Compare commits
3 commits
5e8847d028
...
e92b70f9ec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e92b70f9ec | ||
|
|
84d4263d9a | ||
|
|
f45260706e |
14 changed files with 177 additions and 120 deletions
1
.idea/Model.iml
generated
1
.idea/Model.iml
generated
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue