Merge pull request #349 from Hestia-Homes/renewables-recommendations

Renewables recommendations
This commit is contained in:
KhalimCK 2024-10-08 18:45:55 +01:00 committed by GitHub
commit eadd015308
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 337 additions and 272 deletions

View file

@ -69,7 +69,7 @@ class GoogleSolarApi:
self.floor_area = None self.floor_area = None
self.roof_area = None self.roof_area = None
self.roof_segment_indexes = None self.roof_segment_indexes = None
self.panel_area = None self.panel_area = assumptions.RDSAP_AREA_PER_PANEL
self.panel_wattage = None self.panel_wattage = None
self.panel_performance = None self.panel_performance = None
@ -166,10 +166,6 @@ class GoogleSolarApi:
self.roof_area = self.insights_data["solarPotential"]["wholeRoofStats"]['areaMeters2'] self.roof_area = self.insights_data["solarPotential"]["wholeRoofStats"]['areaMeters2']
self.floor_area = self.insights_data["solarPotential"]["wholeRoofStats"]['groundAreaMeters2'] self.floor_area = self.insights_data["solarPotential"]["wholeRoofStats"]['groundAreaMeters2']
self.panel_area = (
self.insights_data["solarPotential"]["panelHeightMeters"] *
self.insights_data["solarPotential"]["panelWidthMeters"]
)
self.panel_wattage = self.insights_data["solarPotential"]["panelCapacityWatts"] self.panel_wattage = self.insights_data["solarPotential"]["panelCapacityWatts"]
if self.panel_wattage != 400: if self.panel_wattage != 400:
# In the API documentation, it claims that the default output is 250W, however we've only seen 400W, so if # In the API documentation, it claims that the default output is 250W, however we've only seen 400W, so if
@ -450,8 +446,8 @@ class GoogleSolarApi:
# We want max roi, minimal generation deficit, and max generation value - we create a ranking score # We want max roi, minimal generation deficit, and max generation value - we create a ranking score
# Assign equal weights to each metric # Assign equal weights to each metric
weights = {'roi': 0.6, 'generation_value': 0.2, 'generation_deficit': 0.2} weights = {'roi': 0.8, 'generation_value': 0.2}
metrics = panel_performance[['roi', 'generation_value', 'generation_deficit']] metrics = panel_performance[['roi', 'generation_value']].copy()
# Normalize the columns (0 to 1 scale) # Normalize the columns (0 to 1 scale)
scaler = MinMaxScaler() scaler = MinMaxScaler()
@ -459,12 +455,11 @@ class GoogleSolarApi:
# Convert normalized metrics back to a dataframe # Convert normalized metrics back to a dataframe
normalized_metrics_df = pd.DataFrame( normalized_metrics_df = pd.DataFrame(
normalized_metrics, columns=['roi', 'generation_value', 'generation_deficit'] normalized_metrics, columns=['roi', 'generation_value']
) )
normalized_metrics_df['combined_score'] = ( normalized_metrics_df['combined_score'] = (
normalized_metrics_df['roi'] * weights['roi'] + normalized_metrics_df['roi'] * weights['roi'] +
normalized_metrics_df['generation_value'] * weights['generation_value'] + normalized_metrics_df['generation_value'] * weights['generation_value']
(1 - normalized_metrics_df['generation_deficit']) * weights['generation_deficit']
) )
panel_performance['combined_score'] = normalized_metrics_df['combined_score'].values panel_performance['combined_score'] = normalized_metrics_df['combined_score'].values
@ -799,7 +794,6 @@ class GoogleSolarApi:
"panel_performance": solar_api_client.panel_performance, "panel_performance": solar_api_client.panel_performance,
"unit_share_of_energy": 1 "unit_share_of_energy": 1
}, },
roof_area=solar_api_client.roof_area
) )
return input_properties return input_properties
@ -825,7 +819,7 @@ class GoogleSolarApi:
n_panels=10, has_battery=False, n_floors=property_instance.number_of_floors n_panels=10, has_battery=False, n_floors=property_instance.number_of_floors
)["total"], )["total"],
'weighted_ratio': None, 'weighted_ratio': None,
'panneled_roof_area': 10 * 1.8, 'panneled_roof_area': 10 * assumptions.RDSAP_AREA_PER_PANEL,
'array_wattage': 4000, 'array_wattage': 4000,
'initial_ac_kwh_per_year': 4000 * 0.95, # Assumed 95% efficient wattage -> ac 'initial_ac_kwh_per_year': 4000 * 0.95, # Assumed 95% efficient wattage -> ac
'lifetime_ac_kwh': None, 'lifetime_ac_kwh': None,
@ -845,7 +839,7 @@ class GoogleSolarApi:
n_panels=6, has_battery=False, n_floors=property_instance.number_of_floors n_panels=6, has_battery=False, n_floors=property_instance.number_of_floors
)["total"], )["total"],
'weighted_ratio': None, 'weighted_ratio': None,
'panneled_roof_area': 6 * 1.8, 'panneled_roof_area': 6 * assumptions.RDSAP_AREA_PER_PANEL,
'array_wattage': 2400, 'array_wattage': 2400,
'initial_ac_kwh_per_year': 2400 * 0.95, # Assumed 95% efficient wattage -> ac 'initial_ac_kwh_per_year': 2400 * 0.95, # Assumed 95% efficient wattage -> ac
'lifetime_ac_kwh': None, 'lifetime_ac_kwh': None,

View file

@ -7,6 +7,11 @@ AVERAGE_ASHP_EFFICIENCY = 250
# be exported # be exported
SOLAR_CONSUMPTION_PROPORTION = 0.5 SOLAR_CONSUMPTION_PROPORTION = 0.5
# Typically, each solar panel takes up around 3.4 m2 of roof space under RdSAP. This was been verified in Elmhurst
RDSAP_AREA_PER_PANEL = 3.4
SOCIAL_TENURES = ["Rented (social)", "rental (social)"]
DESCRIPTIONS_TO_FUEL_TYPES = { DESCRIPTIONS_TO_FUEL_TYPES = {
"Air source heat pump, radiators, electric": { "Air source heat pump, radiators, electric": {
"fuel": "Electricity", "cop": AVERAGE_ASHP_EFFICIENCY / 100 "fuel": "Electricity", "cop": AVERAGE_ASHP_EFFICIENCY / 100

View file

@ -631,27 +631,38 @@ async def trigger_plan(body: PlanTriggerRequest):
for p in input_properties: for p in input_properties:
if not recommendations.get(p.id): if not recommendations.get(p.id):
continue continue
input_measures = prepare_input_measures(recommendations[p.id], body.goal) input_measures = prepare_input_measures(recommendations[p.id], body.goal)
current_sap_points = int(p.data["current-energy-efficiency"]) current_sap_points = int(p.data["current-energy-efficiency"])
target_sap_points = epc_to_sap_lower_bound(body.goal_value) target_sap_points = epc_to_sap_lower_bound(body.goal_value)
sap_gain = CostOptimiser.calculate_sap_gain_with_slack(target_sap_points - current_sap_points) sap_gain = CostOptimiser.calculate_sap_gain_with_slack(target_sap_points - current_sap_points)
if body.budget: if not body.optimise:
optimiser = GainOptimiser( if body.goal != "Increasing EPC":
input_measures, max_cost=body.budget, max_gain=sap_gain if sap_gain > 0 else 0 raise NotImplementedError("Only EPC optimisation is currently supported")
) solution = []
for sub_list in input_measures:
# Select the entry with the highest gain, and if tied, choose the one with the lowest cost
best_measure = max(sub_list, key=lambda x: (x['gain'], -x['cost']))
solution.append(best_measure)
else: else:
# The minimum gain is the minimum number of SAP points required to get to the target SAP band
# If the gain is negative, the optimiser will return an empty solution
optimiser = CostOptimiser(
input_measures,
min_gain=sap_gain
)
optimiser.setup() if body.budget:
optimiser.solve() optimiser = GainOptimiser(
solution = optimiser.solution input_measures, max_cost=body.budget, max_gain=sap_gain if sap_gain > 0 else 0
)
else:
# The minimum gain is the minimum number of SAP points required to get to the target SAP band
# If the gain is negative, the optimiser will return an empty solution
optimiser = CostOptimiser(
input_measures,
min_gain=sap_gain
)
optimiser.setup()
optimiser.solve()
solution = optimiser.solution
selected_recommendations = {r["id"] for r in solution} selected_recommendations = {r["id"] for r in solution}

View file

@ -86,6 +86,9 @@ class PlanTriggerRequest(BaseModel):
# exists in the portfolio, it will be ignored # exists in the portfolio, it will be ignored
multi_plan: Optional[bool] = False multi_plan: Optional[bool] = False
# if False, allows optimisation to be switched off
optimise: Optional[bool] = False
_allowed_goals = {"Increasing EPC"} _allowed_goals = {"Increasing EPC"}
_allowed_housing_types = {"Social", "Private"} _allowed_housing_types = {"Social", "Private"}

View file

@ -841,7 +841,7 @@ class Costs:
"labour_days": labour_days, "labour_days": labour_days,
} }
def high_heat_electric_storage_heaters(self, number_heated_rooms): def high_heat_electric_storage_heaters(self, number_heated_rooms, needs_cylinder):
""" """
We base the estimates for the cost of electric storage heaters on the cost per room as estimated by the We base the estimates for the cost of electric storage heaters on the cost per room as estimated by the
@ -852,7 +852,12 @@ class Costs:
:param number_heated_rooms: int, number of rooms to be heated :param number_heated_rooms: int, number of rooms to be heated
""" """
total_cost = 1500 * number_heated_rooms if needs_cylinder:
# 1000 is the cost of a new hot water cylinder
total_cost = 1200 * number_heated_rooms + 1000
else:
# 500 is the cost of a dual immersion heater - a rough estimate
total_cost = 1200 * number_heated_rooms + 500
subtotal_before_vat = total_cost / (1 + self.VAT_RATE) subtotal_before_vat = total_cost / (1 + self.VAT_RATE)
vat = total_cost - subtotal_before_vat vat = total_cost - subtotal_before_vat
@ -1143,7 +1148,7 @@ class Costs:
"labour_days": labour_days, "labour_days": labour_days,
} }
def air_source_heat_pump(self): def air_source_heat_pump(self, ashp_size):
""" """
Based on the region and type of property, this function will produce a cost estimation for an air source heat Based on the region and type of property, this function will produce a cost estimation for an air source heat
pump. This cost will include the boiler upgrade scheme grant pump. This cost will include the boiler upgrade scheme grant
@ -1151,14 +1156,19 @@ class Costs:
""" """
# This is the average cost of a project, we'll add some additional contingency # This is the average cost of a project, we'll add some additional contingency
regional_cost = MCS_AIR_SOURCE_HEAT_PUMP_COST_DATA[self.region]
total_cost = regional_cost * (1 + self.CONTINGENCY) - BOILER_UPGRADE_SCHEME_ASHP_VALUE if ashp_size is None:
cost = [x for x in INSTALLER_ASHP_COSTS if x["capacity_kw"] is None][0]["cost"]
else:
cost = [x for x in INSTALLER_ASHP_COSTS if x][0]["cost"]
# We add some contingency since there are additional costs such as resizing radiators, that could be required
total_cost = cost * (1 + self.CONTINGENCY)
subtotal_before_vat = total_cost / (1 + self.VAT_RATE) subtotal_before_vat = total_cost / (1 + self.VAT_RATE)
vat = total_cost - subtotal_before_vat vat = total_cost - subtotal_before_vat
# We assume 3 days installation # We assume 5 days installation
labour_days = 3 labour_days = 5
labour_hours = labour_days * 8 labour_hours = labour_days * 8
return { return {

View file

@ -1,3 +1,5 @@
import re
import backend.app.assumptions as assumptions
from recommendations.Costs import Costs, BOILER_UPGRADE_SCHEME_ASHP_VALUE from recommendations.Costs import Costs, BOILER_UPGRADE_SCHEME_ASHP_VALUE
from recommendations.recommendation_utils import ( from recommendations.recommendation_utils import (
check_simulation_difference, override_costs, combine_recommendation_configs check_simulation_difference, override_costs, combine_recommendation_configs
@ -368,6 +370,75 @@ class HeatingRecommender:
description = ("Replace the existing boiler and cylinder without a thermostat with a new electric combi " description = ("Replace the existing boiler and cylinder without a thermostat with a new electric combi "
"boiler") "boiler")
def size_heat_pump(self):
"""
Given the methodology by installers (SCIS) this function will perform a basic heat loss calculation and
produce a recommendation for the size of the heat pump
:return:
"""
floor_area = self.property.floor_area
# We use the default heat loss W/m2 values are specified by the insaller, depending on the property type
def remap_to_heat_loss(construction_age_band):
if "before 1900" in construction_age_band:
return "Pre 1900 (solid stone)"
elif "1900-1929" in construction_age_band:
return "Early 1900s (solid brick)"
elif re.search(r'1930|1949|1950|1966|1967|1975', construction_age_band):
return "1950-1980 (cavity void)"
elif re.search(r'1976|1982|1983|1990', construction_age_band):
return "Post 1980 (cavity wall construction)"
elif re.search(r'1991|1995|1996|2002|2003|2011', construction_age_band):
return "2000-2018"
elif "2012 onwards" in construction_age_band:
return "New build (2018+)"
else:
return None
def select_heatpump_size(heat_loss_calculation):
"""
This function calculates the size of the heat pump based on the heat loss calculation, mapping
the heat loss calculation to the size of the heat pump in KW
:param heat_loss_calculation: This is calcualted as the floor area multipled by the heat loss constant,
divided by 1000
"""
if heat_loss_calculation < 5:
return 5
elif 5 <= heat_loss_calculation < 6:
return 6
elif 6 <= heat_loss_calculation < 8.5:
return 8.5
elif 8.5 <= heat_loss_calculation < 11.2:
return 11.2
elif 11.2 <= heat_loss_calculation < 14:
return 14
elif 14 <= heat_loss_calculation < 17:
return 17
elif 17 <= heat_loss_calculation < 20:
return 20
else:
return None
heat_loss_constants = {
"New build (2018+)": 35,
"2000-2018": 50,
"Post 1980 (cavity wall construction)": 60,
"1950-1980 (cavity void)": 70,
"Early 1900s (solid brick)": 80,
"Pre 1900 (solid stone)": 90
}
heat_loss_group = remap_to_heat_loss(self.property.construction_age_band)
heat_loss_constant = heat_loss_constants[heat_loss_group]
heat_loss_calculation = floor_area * heat_loss_constant / 1000
heat_pump_size = select_heatpump_size(heat_loss_calculation)
return heat_pump_size
def recommend_air_source_heat_pump(self, phase, has_cavity_or_loft_recommendations, _return=False): def recommend_air_source_heat_pump(self, phase, has_cavity_or_loft_recommendations, _return=False):
""" """
This method will implement the recommendation for an air source heat pump This method will implement the recommendation for an air source heat pump
@ -383,8 +454,9 @@ class HeatingRecommender:
controls_recommender = HeatingControlRecommender(self.property) controls_recommender = HeatingControlRecommender(self.property)
controls_recommender.recommend(heating_description="Air source heat pump, radiators, electric") controls_recommender.recommend(heating_description="Air source heat pump, radiators, electric")
ashp_size = self.size_heat_pump()
ashp_costs = self.costs.air_source_heat_pump() ashp_costs = self.costs.air_source_heat_pump(ashp_size)
if non_intrusive_recommendation: if non_intrusive_recommendation:
# Update with non-intrusive recommendation # Update with non-intrusive recommendation
if non_intrusive_recommendation.get("cost"): if non_intrusive_recommendation.get("cost"):
@ -413,11 +485,13 @@ class HeatingRecommender:
# This is a map from the heating controls description to the description of the air source heat pump set up # This is a map from the heating controls description to the description of the air source heat pump set up
ashp_descriptions = { ashp_descriptions = {
"Time and temperature zone control": ( "Time and temperature zone control": (
"Install an air source heat pump, and upgrade heating controls to Smart Thermostats, " f"Install a {ashp_size}KW air source heat pump, and upgrade heating controls to Smart Thermostats, "
"room sensors and smart radiator valves (time & temperature zone control)." "room sensors and smart radiator valves (time & temperature zone control). Ensure you have an 18 or "
"24 hour tariff"
), ),
"Programmer, TRVs and bypass": ( "Programmer, TRVs and bypass": (
"Install an air source heat pump, with programmer, TRVs and a Bypass valve." f"Install a {ashp_size}KW air source heat pump, with programmer, TRVs and a Bypass valve. Ensure you "
"have an 18 or 24 hour tariff"
), ),
} }
@ -434,7 +508,7 @@ class HeatingRecommender:
ashp_costs_with_controls[key] += controls_rec[key] ashp_costs_with_controls[key] += controls_rec[key]
if controls_rec is None: if controls_rec is None:
description = "Install a Mitsubish air source heat pump." description = f"Install a {ashp_size}KW Air source heat pump. Ensure you have an 18 or 24 hour tariff"
elif already_installed: elif already_installed:
description = "The property already has an air source heat pump, no further action needed." description = "The property already has an air source heat pump, no further action needed."
else: else:
@ -443,17 +517,16 @@ class HeatingRecommender:
# If the property does not have existing cavity and loft insulation, we include a note that the cost # 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 # includes the boiler upgrade scheme and that the cavity and loft need to be treated, to ensure access
# to the funding # to the funding
if not non_intrusive_recommendation: if not non_intrusive_recommendation and self.property.data["tenure"] not in assumptions.SOCIAL_TENURES:
if has_cavity_or_loft_recommendations: if has_cavity_or_loft_recommendations:
description = description + ( description = description + (
f" The cost includes the £" f" You must ensure that the property has an insulated cavity and "
f"{BOILER_UPGRADE_SCHEME_ASHP_VALUE} boiler upgrade scheme grant. " f"270mm+ loft insulation to qualify for the grant, to claim £"
f"You must ensure that the property has an insulated cavity and " f"{BOILER_UPGRADE_SCHEME_ASHP_VALUE} of funding from the boiler upgrade scheme grant. "
f"270mm+ loft insulation to qualify for the grant"
) )
else: else:
description = description + ( description = description + (
f" The cost includes the £{BOILER_UPGRADE_SCHEME_ASHP_VALUE} boiler upgrade scheme grant" f" £{BOILER_UPGRADE_SCHEME_ASHP_VALUE} of funding can be claimed from the boiler upgrade scheme"
) )
simulation_config = { simulation_config = {
@ -739,20 +812,37 @@ class HeatingRecommender:
new_heating_description = self.DUAL_HEATING_DESCRIPTIONS[ new_heating_description = self.DUAL_HEATING_DESCRIPTIONS[
self.property.main_heating["clean_description"] self.property.main_heating["clean_description"]
]["hhr"]["mainheating_description"] ]["hhr"]["mainheating_description"]
new_hot_water_description = self.property.hotwater["clean_description"] # We keep the hot water system
else: else:
new_heating_description = "Electric storage heaters" new_heating_description = "Electric storage heaters"
new_hot_water_description = "Electric immersion, off-peak"
# Set up artefacts, suitable for the simulation and regardless of controls # Set up artefacts, suitable for the simulation and regardless of controls
heating_ending_config = MainHeatAttributes(new_heating_description).process() heating_ending_config = MainHeatAttributes(new_heating_description).process()
heating_simulation_config = check_simulation_difference( heating_simulation_config = check_simulation_difference(
new_config=heating_ending_config, old_config=self.property.main_heating new_config=heating_ending_config, old_config=self.property.main_heating
) )
hot_water_end_config = HotWaterAttributes(new_hot_water_description).process()
hot_water_simulation_config = check_simulation_difference(
new_config=hot_water_end_config, old_config=self.property.hotwater
)
heating_simulation_config = {
**heating_simulation_config,
**hot_water_simulation_config
}
# This upgrade will only take the heating system to average energy efficiency # This upgrade will only take the heating system to average energy efficiency
if self.property.data["mainheat-energy-eff"] in ["Very Poor", "Poor"]: if self.property.data["mainheat-energy-eff"] in ["Very Poor", "Poor"] and not self.dual_heating:
heating_simulation_config["mainheat_energy_eff_ending"] = "Average" heating_simulation_config["mainheat_energy_eff_ending"] = "Average"
else: else:
heating_simulation_config["mainheat_energy_eff_ending"] = self.property.data["mainheat-energy-eff"] heating_simulation_config["mainheat_energy_eff_ending"] = self.property.data["mainheat-energy-eff"]
if self.property.data["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"]
# If the property is off-gas and has no heating system in place, the number of heated rooms will actually # 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 # be 0, so we use the number of rooms as the figure
number_heated_rooms = ( number_heated_rooms = (
@ -768,7 +858,8 @@ class HeatingRecommender:
# Upgrade to electric storage heaters # Upgrade to electric storage heaters
costs = self.costs.high_heat_electric_storage_heaters( costs = self.costs.high_heat_electric_storage_heaters(
number_heated_rooms=number_heated_rooms number_heated_rooms=number_heated_rooms,
needs_cylinder=self.property.hotwater["system_type"] == "from main system"
) )
if self.dual_heating: if self.dual_heating:
description = self.DUAL_HEATING_DESCRIPTIONS[ description = self.DUAL_HEATING_DESCRIPTIONS[
@ -776,7 +867,7 @@ class HeatingRecommender:
]["hhr"]["recommendation_description"] ]["hhr"]["recommendation_description"]
else: else:
description = "Install high heat retention electric storage heaters." description = "Install high heat retention electric storage heaters with an appropriate off-peak tariff."
# We check the existing heating system and controls # We check the existing heating system and controls
if ( if (
@ -790,6 +881,8 @@ class HeatingRecommender:
heating_description_simulation = { heating_description_simulation = {
"mainheat-description": new_heating_description, "mainheat-description": new_heating_description,
"mainheat-energy-eff": heating_simulation_config["mainheat_energy_eff_ending"], "mainheat-energy-eff": heating_simulation_config["mainheat_energy_eff_ending"],
"hotwater-description": new_hot_water_description,
"hot-water-energy-eff": heating_simulation_config["hot_water_energy_eff_ending"]
} }
recommendations = self.combine_heating_and_controls( recommendations = self.combine_heating_and_controls(

View file

@ -337,7 +337,7 @@ class RoofRecommendations:
if proposed_depth >= 300: if proposed_depth >= 300:
new_efficiency = "Very Good" new_efficiency = "Very Good"
else: else:
if self.property.data["walls-energy-eff"] not in ["Good", "Very Good"]: if self.property.data["roof-energy-eff"] not in ["Good", "Very Good"]:
new_efficiency = "Good" new_efficiency = "Good"
else: else:
new_efficiency = "Very Good" new_efficiency = "Very Good"
@ -416,7 +416,7 @@ class RoofRecommendations:
self.recommendations = recommendations self.recommendations = recommendations
def recommend_room_roof_insulation(self, u_value, phase): def recommend_room_roof_insulation(self, u_value, phase, default_u_values):
""" """
This method recommends room in roof insulation for properties that have been identified This method recommends room in roof insulation for properties that have been identified
to possess a room in roof. to possess a room in roof.
@ -455,6 +455,8 @@ class RoofRecommendations:
- Flat ceilings can be insulated like a standard loft. - Flat ceilings can be insulated like a standard loft.
:param u_value: Current u-value of the roof :param u_value: Current u-value of the roof
:param phase: Phase of the recommendation
:param default_u_values: Use default u-values
:return: :return:
""" """
@ -495,7 +497,7 @@ class RoofRecommendations:
sap_points = rir_non_invasive_recommendation.get("sap_points", None) sap_points = rir_non_invasive_recommendation.get("sap_points", None)
# Could also be Roof room(s), ceiling insulated # Could also be Roof room(s), ceiling insulated
new_descriptin = "Pitched, insulated at rafters" new_descriptin = "Roof room(s), insulated"
roof_ending_config = RoofAttributes(new_descriptin).process() roof_ending_config = RoofAttributes(new_descriptin).process()
roof_simulation_config = check_simulation_difference( roof_simulation_config = check_simulation_difference(
new_config=roof_ending_config, old_config=self.property.roof, prefix="roof_" new_config=roof_ending_config, old_config=self.property.roof, prefix="roof_"
@ -505,6 +507,19 @@ class RoofRecommendations:
else: else:
new_efficiency = self.property.data["roof-energy-eff"] new_efficiency = self.property.data["roof-energy-eff"]
if default_u_values:
new_u_value = get_roof_u_value(
insulation_thickness="average",
has_dwelling_above=self.property.roof["has_dwelling_above"],
is_loft=self.property.roof["is_loft"],
is_roof_room=self.property.roof["is_roof_room"],
is_thatched=self.property.roof["is_thatched"],
age_band=self.property.age_band,
is_flat=self.property.roof["is_flat"],
is_pitched=self.property.roof["is_pitched"],
is_at_rafters=self.property.roof["is_at_rafters"],
)
simulation_config = { simulation_config = {
**roof_simulation_config, **roof_simulation_config,
"roof_thermal_transmittance_ending": new_u_value, "roof_thermal_transmittance_ending": new_u_value,

View file

@ -17,6 +17,8 @@ class SolarPvRecommendations:
MAX_SYSTEM_WATTAGE = 6000 MAX_SYSTEM_WATTAGE = 6000
MIN_SYSTEM_WATTAGE = 1000 MIN_SYSTEM_WATTAGE = 1000
MAX_ROOF_AREA_PERCENTAGE = 0.7
def __init__(self, property_instance): def __init__(self, property_instance):
""" """
:param property_instance: Instance of the Property class, for the home associated to property_id :param property_instance: Instance of the Property class, for the home associated to property_id
@ -187,15 +189,20 @@ class SolarPvRecommendations:
) )
else: else:
# TODO: There may be some instances where we don't want to use the solar API so we should cover for them # TODO: There may be some instances where we don't want to use the solar API so we should cover for them
panel_performance = self.property.solar_panel_configuration["panel_performance"] panel_performance = self.property.solar_panel_configuration["panel_performance"].copy()
# We don't allow for more than 70% of the roof to be covered
panel_performance = panel_performance[
panel_performance["panneled_roof_area"] / self.property.roof_area <= self.MAX_ROOF_AREA_PERCENTAGE
]
roof_area = self.property.roof_area roof_area = self.property.roof_area
solar_configurations = panel_performance.head(3).reset_index(drop=True) solar_configurations = panel_performance.head(3).reset_index(drop=True)
# We combine each of these configurations with estimates with and without a battery # We combine each of these configurations with estimates with and without a battery
for rank, recommendation_config in solar_configurations.iterrows(): for rank, recommendation_config in solar_configurations.iterrows():
roof_coverage_percent = round(recommendation_config["panneled_roof_area"] / roof_area * 100) roof_coverage_percent = round(recommendation_config["panneled_roof_area"] / roof_area * 100)
# We round up to the nearest 10 # We round up to the nearest 5
roof_coverage_percent = np.ceil(roof_coverage_percent / 10) * 10 roof_coverage_percent = np.ceil(roof_coverage_percent / 5) * 5
for has_battery in [False, True]: for has_battery in [False, True]:
cost_result = self.costs.solar_pv( cost_result = self.costs.solar_pv(
has_battery=has_battery, has_battery=has_battery,

View file

@ -282,11 +282,14 @@ def get_u_value_from_s9(
): ):
"""Get the U-value from table S9 based on the insulation thickness.""" """Get the U-value from table S9 based on the insulation thickness."""
if thickness in ["below average", "average", "above average", "none", None, "0", 0] or ( if thickness in ["below average", "average", "above average", "none", None] or (
not is_loft and not is_roof_room and not is_at_rafters not is_loft and not is_roof_room and not is_at_rafters
): ):
return None return None
if thickness in [0, "0"] and is_loft:
return None
# Determine the column to refer based on the roof type # Determine the column to refer based on the roof type
column = ( column = (
"Thatched_roof_U_value_W_m2K" "Thatched_roof_U_value_W_m2K"
@ -294,8 +297,11 @@ def get_u_value_from_s9(
else "Slates_or_tiles_U_value_W_m2K" else "Slates_or_tiles_U_value_W_m2K"
) )
# Get the correct U-value based on the insulation thickness if thickness in [0, "0"] and is_roof_room:
return s9[s9["Insulation_thickness_mm"] >= thickness][column].iloc[0] return s9[pd.isnull(s9["Insulation_thickness_mm"])][column].iloc[0]
else:
# Get the correct U-value based on the insulation thickness
return s9[s9["Insulation_thickness_mm"] >= thickness][column].iloc[0]
def get_roof_u_value( def get_roof_u_value(

View file

@ -39,15 +39,8 @@ testing_examples = [
'fixed-lighting-outlets-count': 10.0, 'low-energy-fixed-light-count': 7.0, 'uprn': 100110195416.0, 'fixed-lighting-outlets-count': 10.0, 'low-energy-fixed-light-count': 7.0, 'uprn': 100110195416.0,
'uprn-source': 'Address Matched' 'uprn-source': 'Address Matched'
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": ["air_source_heat_pump"],
"Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and " "heating_controls_measure_types": ["time_temperature_zone_control"],
"smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade "
"scheme grant",
],
"heating_controls_recommendation_descriptions": [
"Upgrade heating controls to Smart Thermostats, room sensors and smart radiator valves (time & "
"temperature zone control)"
],
"notes": "This property has a boiler, radiators & mains gas with good efficiency so the only recommendation" "notes": "This property has a boiler, radiators & mains gas with good efficiency so the only recommendation"
"we expect here is for an air source heat pump. The heating controls are a programmer, room thermostat" "we expect here is for an air source heat pump. The heating controls are a programmer, room thermostat"
"and TRVs and so we should expect a TTZC recommendation" "and TRVs and so we should expect a TTZC recommendation"
@ -93,11 +86,10 @@ testing_examples = [
'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 10012342725.0, 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 10012342725.0,
'uprn-source': 'Address Matched', 'uprn-source': 'Address Matched',
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
"Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention " "high_heat_retention_storage_heater",
"Storage Heater Controls"
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has electric room heaters and is off gas so a boiler recommendation is not appropriate." "notes": "This property has electric room heaters and is off gas so a boiler recommendation is not appropriate."
"We would expect a high heat retention storage recommendation. The property is a flat and therefore" "We would expect a high heat retention storage recommendation. The property is a flat and therefore"
"we don't expect an air source heat pump recommendation. We also wouldn't expect a specific heating" "we don't expect an air source heat pump recommendation. We also wouldn't expect a specific heating"
@ -144,16 +136,8 @@ testing_examples = [
'tenure': 'owner-occupied', 'fixed-lighting-outlets-count': 6.0, 'low-energy-fixed-light-count': 4.0, 'tenure': 'owner-occupied', 'fixed-lighting-outlets-count': 6.0, 'low-energy-fixed-light-count': 4.0,
'uprn': 100090311351.0, 'uprn-source': 'Address Matched', 'property-type_y': None, 'built-form_y': None, 'uprn': 100090311351.0, 'uprn-source': 'Address Matched', 'property-type_y': None, 'built-form_y': None,
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": ['high_heat_retention_storage_heater', 'air_source_heat_pump'],
'Install high heat retention electric storage heaters. The current electric heaters may be retrofit with ' "heating_controls_measure_types": [],
'high heat retention storage controls however this is dependent on the existing system and may not be '
'possible. Upgrade heating controls to High Heat Retention Storage Heater Controls',
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and '
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade '
'scheme grant'
],
"heating_controls_recommendation_descriptions": [],
"notes": "This test has electric storage heaters with automatic charge control - we recommend hhr storage" "notes": "This test has electric storage heaters with automatic charge control - we recommend hhr storage"
"heaters in this case, but because there are already electic storage heaters in place, we " "heaters in this case, but because there are already electic storage heaters in place, we "
"note, in the description of the recommendation, that this upgrade may be possible by retrofitting" "note, in the description of the recommendation, that this upgrade may be possible by retrofitting"
@ -197,13 +181,10 @@ testing_examples = [
'tenure': 'owner-occupied', 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'tenure': 'owner-occupied', 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None,
'uprn': 100021560521.0, 'uprn-source': 'Address Matched', 'uprn': 100021560521.0, 'uprn-source': 'Address Matched',
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": ['boiler_upgrade'],
'Upgrade to a new condensing boiler.' "heating_controls_measure_types": [
], 'roomstat_programmer_trvs',
"heating_controls_recommendation_descriptions": [ 'time_temperature_zone_control',
'Upgrade heating controls to Room thermostat, programmer and TRVs',
'Upgrade heating controls to Smart Thermostats, room sensors and smart radiator valves (time & '
'temperature zone control)'
], ],
"notes": "Because of this property is a maisonette, which already has a boiler (but an inefficient one due to " "notes": "Because of this property is a maisonette, which already has a boiler (but an inefficient one due to "
"the current water heating efficiency) the only recommendation we expect is for " "the current water heating efficiency) the only recommendation we expect is for "
@ -250,12 +231,10 @@ testing_examples = [
'tenure': 'owner-occupied', 'fixed-lighting-outlets-count': 9.0, 'low-energy-fixed-light-count': 5.0, 'tenure': 'owner-occupied', 'fixed-lighting-outlets-count': 9.0, 'low-energy-fixed-light-count': 5.0,
'uprn': 100021936225.0, 'uprn-source': 'Address Matched', 'uprn': 100021936225.0, 'uprn-source': 'Address Matched',
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [],
], "heating_controls_measure_types": [
"heating_controls_recommendation_descriptions": [ 'roomstat_programmer_trvs',
'Upgrade heating controls to Room thermostat, programmer and TRVs', 'time_temperature_zone_control',
'Upgrade heating controls to Smart Thermostats, room sensors and smart radiator valves (time & '
'temperature zone control)'
], ],
"notes": "Because this property already has a boiler, we don't recommend HHR. We don't recommend an ashp " "notes": "Because this property already has a boiler, we don't recommend HHR. We don't recommend an ashp "
"because the home is mid-terraced. Because the heating controls are " "because the home is mid-terraced. Because the heating controls are "
@ -302,11 +281,10 @@ testing_examples = [
'tenure': 'rental (private)', 'fixed-lighting-outlets-count': 7.0, 'low-energy-fixed-light-count': 6.0, 'tenure': 'rental (private)', 'fixed-lighting-outlets-count': 7.0, 'low-energy-fixed-light-count': 6.0,
'uprn': 43088770.0, 'uprn-source': 'Address Matched', 'uprn': 43088770.0, 'uprn-source': 'Address Matched',
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention ' 'high_heat_retention_storage_heater',
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property is a flat so we don't have an ASHP recommendation. It also doesn't have access to the " "notes": "This property is a flat so we don't have an ASHP recommendation. It also doesn't have access to the "
"mains and so it can't have a gas boiler. We don't expect any controls recommendations" "mains and so it can't have a gas boiler. We don't expect any controls recommendations"
}, },
@ -348,16 +326,10 @@ testing_examples = [
'tenure': 'owner-occupied', 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'tenure': 'owner-occupied', 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None,
'uprn': 100080513604.0, 'uprn-source': 'Address Matched' 'uprn': 100080513604.0, 'uprn-source': 'Address Matched'
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": ['air_source_heat_pump'],
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' "heating_controls_measure_types": [
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'roomstat_programmer_trvs',
'scheme grant' 'time_temperature_zone_control',
],
"heating_controls_recommendation_descriptions": [
'Upgrade heating controls to Room thermostat, programmer and TRVs',
'Upgrade heating controls to Smart Thermostats, room sensors and smart radiator valves (time & '
'temperature zone control)'
], ],
"notes": "This has a very efficient boiler and is a detached bungalow, but only has " "notes": "This has a very efficient boiler and is a detached bungalow, but only has "
"Programmer and room thermostat for heating controls so we'd expect an ASHP heating recommendation" "Programmer and room thermostat for heating controls so we'd expect an ASHP heating recommendation"
@ -404,14 +376,12 @@ testing_examples = [
'fixed-lighting-outlets-count': 10.0, 'low-energy-fixed-light-count': 9.0, 'uprn': 100070358594, 'fixed-lighting-outlets-count': 10.0, 'low-energy-fixed-light-count': 9.0, 'uprn': 100070358594,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Upgrade to a new condensing boiler. Upgrade heating controls to Room thermostat, programmer and TRVs', 'boiler_upgrade',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention ' 'high_heat_retention_storage_heater',
'Storage Heater Controls', 'boiler_upgrade'
'Upgrade to a new condensing boiler. Upgrade heating controls to Smart Thermostats, room sensors and smart '
'radiator valves (time & temperature zone control)'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has assumed electric heating and is mid-terrace house. It has a mains gas connection." "notes": "This property has assumed electric heating and is mid-terrace house. It has a mains gas connection."
"We can recommend a boiler upgrade and high heat retention storage heaters" "We can recommend a boiler upgrade and high heat retention storage heaters"
}, },
@ -453,15 +423,11 @@ testing_examples = [
'fixed-lighting-outlets-count': 42.0, 'low-energy-fixed-light-count': 13.0, 'uprn': 100070985545, 'fixed-lighting-outlets-count': 42.0, 'low-energy-fixed-light-count': 13.0, 'uprn': 100070985545,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'high_heat_retention_storage_heater',
'scheme grant',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has an oil boiler and doesn't have a mains gas connection so we can only recommend" "notes": "This property has an oil boiler and doesn't have a mains gas connection so we can only recommend"
"an air source heat pump and HHR (since if the home has a non-gas boiler, we recommend HHR)" "an air source heat pump and HHR (since if the home has a non-gas boiler, we recommend HHR)"
}, },
@ -505,17 +471,13 @@ testing_examples = [
'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 2465031849, 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 2465031849,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Upgrade to a new condensing boiler. Upgrade heating controls to Room thermostat, programmer and TRVs', 'boiler_upgrade',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention ' 'high_heat_retention_storage_heater',
'Storage Heater Controls', 'air_source_heat_pump',
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'boiler_upgrade' # TTZs
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade '
'scheme grant',
'Upgrade to a new condensing boiler. Upgrade heating controls to Smart Thermostats, room sensors and '
'smart radiator valves (time & temperature zone control)'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has room heaters, from the mains gas supply. We recommend a boiler upgrade as" "notes": "This property has room heaters, from the mains gas supply. We recommend a boiler upgrade as"
"well as an air source heat pump and HHR (since the home has a room heater set up)" "well as an air source heat pump and HHR (since the home has a room heater set up)"
}, },
@ -558,14 +520,12 @@ testing_examples = [
'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Upgrade to a new condensing boiler. Upgrade heating controls to Room thermostat, programmer and TRVs', 'boiler_upgrade',
'Upgrade to a new condensing boiler. Upgrade heating controls to Smart Thermostats, room sensors and smart ' 'boiler_upgrade',
'radiator valves (time & temperature zone control)', 'high_heat_retention_storage_heater',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls',
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has assumed electric heaters. Boiler upgrade, HHR are recommended. We don't recommend" "notes": "This property has assumed electric heaters. Boiler upgrade, HHR are recommended. We don't recommend"
"an ASHP off of the bat because it's mid-terrace." "an ASHP off of the bat because it's mid-terrace."
}, },
@ -607,14 +567,12 @@ testing_examples = [
'fixed-lighting-outlets-count': 10.0, 'low-energy-fixed-light-count': None, 'uprn': 100071089116, 'fixed-lighting-outlets-count': 10.0, 'low-energy-fixed-light-count': None, 'uprn': 100071089116,
'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Upgrade to a new condensing boiler. Upgrade heating controls to Room thermostat, programmer and TRVs', 'boiler_upgrade',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention ' 'high_heat_retention_storage_heater',
'Storage Heater Controls', 'boiler_upgrade'
'Upgrade to a new condensing boiler. Upgrade heating controls to Smart Thermostats, room sensors and '
'smart radiator valves (time & temperature zone control)'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This has a form of assumed electric heating and has a mains connection so we recommend HHR, boiler" "notes": "This has a form of assumed electric heating and has a mains connection so we recommend HHR, boiler"
"upgrade and ASHP" "upgrade and ASHP"
}, },
@ -657,15 +615,12 @@ testing_examples = [
'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 100030352255, 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 100030352255,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Upgrade to a new condensing boiler. Upgrade heating controls to Room thermostat, programmer and TRVs', 'boiler_upgrade',
'Upgrade to a new condensing boiler. Upgrade heating controls to Smart Thermostats, room sensors and smart ' 'boiler_upgrade',
'radiator valves (time & temperature zone control)', 'high_heat_retention_storage_heater',
'Install high heat retention electric storage heaters. The current electric heaters may be retrofit with '
'high heat retention storage controls however this is dependent on the existing system and may not be '
'possible. Upgrade heating controls to High Heat Retention Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property already has storage heaters with manual charge control. The home is mid terrace so" "notes": "This property already has storage heaters with manual charge control. The home is mid terrace so"
"the ashp is not suitable" "the ashp is not suitable"
}, },
@ -709,14 +664,11 @@ testing_examples = [
'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 10009573249, 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 10009573249,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention ' 'high_heat_retention_storage_heater',
'Storage Heater Controls', 'air_source_heat_pump',
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and '
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade '
'scheme grant'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has an LFG boiler but it doesn't have a mains gas connection so we can only recommend" "notes": "This property has an LFG boiler but it doesn't have a mains gas connection so we can only recommend"
"an air source heat pump and hhr storage" "an air source heat pump and hhr storage"
}, },
@ -758,14 +710,11 @@ testing_examples = [
'fixed-lighting-outlets-count': 8.0, 'low-energy-fixed-light-count': None, 'uprn': 10013181470, 'fixed-lighting-outlets-count': 8.0, 'low-energy-fixed-light-count': None, 'uprn': 10013181470,
'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention ' 'high_heat_retention_storage_heater',
'Storage Heater Controls', 'air_source_heat_pump',
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and '
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade '
'scheme grant'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has electric boilers in place, but does not have a mains connection so we don't " "notes": "This property has electric boilers in place, but does not have a mains connection so we don't "
"recommend a boiler upgrade. We recommend HHR and ASHP" "recommend a boiler upgrade. We recommend HHR and ASHP"
}, },
@ -809,14 +758,11 @@ testing_examples = [
'fixed-lighting-outlets-count': 11.0, 'low-energy-fixed-light-count': 7.0, 'uprn': 452047507, 'fixed-lighting-outlets-count': 11.0, 'low-energy-fixed-light-count': 7.0, 'uprn': 452047507,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'high_heat_retention_storage_heater'
'scheme grant',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has a dual fuel boiler and no mains gas connection. We recommend ASHP and HHR, but" "notes": "This property has a dual fuel boiler and no mains gas connection. We recommend ASHP and HHR, but"
"no gas condensing boiler" "no gas condensing boiler"
}, },
@ -857,14 +803,11 @@ testing_examples = [
'fixed-lighting-outlets-count': 16.0, 'low-energy-fixed-light-count': 4.0, 'uprn': 100030309413, 'fixed-lighting-outlets-count': 16.0, 'low-energy-fixed-light-count': 4.0, 'uprn': 100030309413,
'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'high_heat_retention_storage_heater',
'scheme grant',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has a coal boiler and no mains gas connection. We recommend ASHP and HHR, but" "notes": "This property has a coal boiler and no mains gas connection. We recommend ASHP and HHR, but"
"no gas condensing boiler" "no gas condensing boiler"
}, },
@ -908,15 +851,11 @@ testing_examples = [
'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 10007366417, 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 10007366417,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'high_heat_retention_storage_heater',
'scheme grant',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has a smokeless fuel boiler and no mains gas connection. We recommend ASHP and HHR, but" "notes": "This property has a smokeless fuel boiler and no mains gas connection. We recommend ASHP and HHR, but"
"no gas condensing boiler" "no gas condensing boiler"
}, },
@ -958,15 +897,11 @@ testing_examples = [
'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 100030256931, 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 100030256931,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'high_heat_retention_storage_heater',
'scheme grant',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has a wood pellets boiler and no mains gas connection. We recommend ASHP and HHR, but" "notes": "This property has a wood pellets boiler and no mains gas connection. We recommend ASHP and HHR, but"
"no gas condensing boiler" "no gas condensing boiler"
}, },
@ -1009,11 +944,10 @@ testing_examples = [
'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 10000460605, 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 10000460605,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention ' 'high_heat_retention_storage_heater',
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This is an end-terrace house, without mains gas connection, so all we recommend is HHR" "notes": "This is an end-terrace house, without mains gas connection, so all we recommend is HHR"
}, },
{ {
@ -1054,8 +988,8 @@ testing_examples = [
'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 100031045596, 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'uprn': 100031045596,
'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [], "heating_measure_types": [],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property already has an ashp. We don't recommend any heating upgrades" "notes": "This property already has an ashp. We don't recommend any heating upgrades"
}, },
{ {
@ -1095,17 +1029,12 @@ testing_examples = [
'tenure': 'Rented (social)', 'fixed-lighting-outlets-count': 11.0, 'low-energy-fixed-light-count': None, 'tenure': 'Rented (social)', 'fixed-lighting-outlets-count': 11.0, 'low-energy-fixed-light-count': None,
'uprn': 90041166, 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn': 90041166, 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'high_heat_retention_storage_heater'
'scheme grant',
'Install high heat retention electric storage heaters alongside the boiler. The current electric heaters '
'may be retrofit with high heat retention storage controls however this is dependent on the existing '
'system and may not be possible. Upgrade heating controls to High Heat Retention Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [ "heating_controls_measure_types": [
'Upgrade heating controls to Smart Thermostats, room sensors and smart radiator valves (time & ' 'time_temperature_zone_control',
'temperature zone control)'
], ],
"notes": "This property has dual heating. A boiler and electric storage heaters. The heating is efficient so" "notes": "This property has dual heating. A boiler and electric storage heaters. The heating is efficient so"
"we recommend ASHP and HHR. We also recommend upgrading the heating controls for the boiler" "we recommend ASHP and HHR. We also recommend upgrading the heating controls for the boiler"
@ -1147,20 +1076,14 @@ testing_examples = [
'tenure': 'Rented (social)', 'fixed-lighting-outlets-count': 11.0, 'low-energy-fixed-light-count': None, 'tenure': 'Rented (social)', 'fixed-lighting-outlets-count': 11.0, 'low-energy-fixed-light-count': None,
'uprn': 90041166, 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn': 90041166, 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'boiler_upgrade',
'scheme grant', 'boiler_upgrade+high_heat_retention_storage_heater',
'Upgrade the existing boiler to a new, more efficient condensing boiler. ', 'high_heat_retention_storage_heater'
'Upgrade both the existing boiler to a new condensing boiler and upgrade storage heaters to high heat '
'retention storage heaters.',
'Install high heat retention electric storage heaters alongside the boiler. The current electric heaters '
'may be retrofit with high heat retention storage controls however this is dependent on the existing '
'system and may not be possible. Upgrade heating controls to High Heat Retention Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [ "heating_controls_measure_types": [
'Upgrade heating controls to Smart Thermostats, room sensors and smart radiator valves (time & ' 'time_temperature_zone_control'
'temperature zone control)'
], ],
"notes": "This property is a modified version of the previous dual heating property, where we lower the" "notes": "This property is a modified version of the previous dual heating property, where we lower the"
"starting heating efficiency so that we a combined heating upgrade to both the boiler and the electric" "starting heating efficiency so that we a combined heating upgrade to both the boiler and the electric"
@ -1204,14 +1127,11 @@ testing_examples = [
'tenure': 'rental (social)', 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None, 'tenure': 'rental (social)', 'fixed-lighting-outlets-count': None, 'low-energy-fixed-light-count': None,
'uprn': 10009574286, 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn': 10009574286, 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'high_heat_retention_storage_heater'
'scheme grant',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has anthracite heating without mains. " "notes": "This property has anthracite heating without mains. "
"We recommend ASHP and HHR, but no gas condensing boiler" "We recommend ASHP and HHR, but no gas condensing boiler"
}, },
@ -1254,14 +1174,12 @@ testing_examples = [
'fixed-lighting-outlets-count': 8.0, 'low-energy-fixed-light-count': None, 'uprn': 100031556691, 'fixed-lighting-outlets-count': 8.0, 'low-energy-fixed-light-count': None, 'uprn': 100031556691,
'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None 'uprn-source': 'Energy Assessor', 'sheating-energy-eff': None, 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Upgrade to a new condensing boiler. Upgrade heating controls to Room thermostat, programmer and TRVs', 'boiler_upgrade',
'Upgrade to a new condensing boiler. Upgrade heating controls to Smart Thermostats, room sensors and smart ' 'boiler_upgrade',
'radiator valves (time & temperature zone control)', 'high_heat_retention_storage_heater'
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has room heaters with two different fuel sources, so we recommend HHR, ASHP, and a " "notes": "This property has room heaters with two different fuel sources, so we recommend HHR, ASHP, and a "
"boiler upgrade" "boiler upgrade"
}, },
@ -1302,11 +1220,10 @@ testing_examples = [
'uprn': 100070685908, 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'uprn': 100070685908, 'uprn-source': 'Address Matched', 'sheating-energy-eff': None,
'sheating-env-eff': None 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention ' 'high_heat_retention_storage_heater'
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property is a flag, without mains gas connection. Currently has underfloor electric heating" "notes": "This property is a flag, without mains gas connection. Currently has underfloor electric heating"
"so we recommend HHR" "so we recommend HHR"
}, },
@ -1348,17 +1265,13 @@ testing_examples = [
'uprn': 100071209105, 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'uprn': 100071209105, 'uprn-source': 'Address Matched', 'sheating-energy-eff': None,
'sheating-env-eff': None 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Install an air source heat pump, and upgrade heating controls to Smart Thermostats, room sensors and ' 'air_source_heat_pump',
'smart radiator valves (time & temperature zone control). The cost includes the £7500 boiler upgrade ' 'boiler_upgrade',
'scheme grant', 'boiler_upgrade',
'Upgrade to a new condensing boiler. Upgrade heating controls to Room thermostat, programmer and TRVs', 'high_heat_retention_storage_heater'
'Upgrade to a new condensing boiler. Upgrade heating controls to Smart Thermostats, room sensors and smart '
'radiator valves (time & temperature zone control)',
'Install high heat retention electric storage heaters. Upgrade heating controls to High Heat Retention '
'Storage Heater Controls'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "The property has warm air electricaire heating, so we recommend ASHP and HHR. It also has a mains" "notes": "The property has warm air electricaire heating, so we recommend ASHP and HHR. It also has a mains"
"connection so we recommend a gas condensing boiler" "connection so we recommend a gas condensing boiler"
}, },
@ -1402,12 +1315,11 @@ testing_examples = [
'uprn': 100070955137, 'uprn-source': 'Address Matched', 'sheating-energy-eff': None, 'uprn': 100070955137, 'uprn-source': 'Address Matched', 'sheating-energy-eff': None,
'sheating-env-eff': None 'sheating-env-eff': None
}, },
"heating_recommendation_descriptions": [ "heating_measure_types": [
'Upgrade to a new condensing boiler. Upgrade heating controls to Room thermostat, programmer and TRVs', 'boiler_upgrade',
'Upgrade to a new condensing boiler. Upgrade heating controls to Smart Thermostats, room sensors and smart ' 'boiler_upgrade',
'radiator valves (time & temperature zone control)'
], ],
"heating_controls_recommendation_descriptions": [], "heating_controls_measure_types": [],
"notes": "This property has warm air mains gas heating, so we recommend a gas condensing boiler" "notes": "This property has warm air mains gas heating, so we recommend a gas condensing boiler"
} }
] ]

View file

@ -96,19 +96,19 @@ class TestHeatingRecommendations:
recommender.recommend(has_cavity_or_loft_recommendations=False) recommender.recommend(has_cavity_or_loft_recommendations=False)
assert len(recommender.heating_recommendations) == len(test_case["heating_recommendation_descriptions"]) assert len(recommender.heating_recommendations) == len(test_case["heating_measure_types"])
assert ( assert (
len(recommender.heating_control_recommendations) == len(recommender.heating_control_recommendations) ==
len(test_case["heating_controls_recommendation_descriptions"]) len(test_case["heating_controls_measure_types"])
) )
# Check the exact descriptions # Check the exact measure types
assert ( assert (
{x["description"] for x in recommender.heating_recommendations} == {x["measure_type"] for x in recommender.heating_recommendations} ==
set(test_case["heating_recommendation_descriptions"]) set(test_case["heating_measure_types"])
) )
assert ( assert (
{x["description"] for x in recommender.heating_control_recommendations} == {x["measure_type"] for x in recommender.heating_control_recommendations} ==
set(test_case["heating_controls_recommendation_descriptions"]) set(test_case["heating_controls_measure_types"])
) )

View file

@ -231,6 +231,15 @@ class TestRecommendationUtils:
expected_uvalue = test_case["uvalue"] expected_uvalue = test_case["uvalue"]
inputs = test_case.copy() inputs = test_case.copy()
del inputs["uvalue"] del inputs["uvalue"]
# insulation_thickness = inputs["insulation_thickness"]
# has_dwelling_above = inputs["has_dwelling_above"]
# is_loft = inputs["is_loft"]
# is_roof_room = inputs["is_roof_room"]
# is_thatched = inputs["is_thatched"]
# age_band = inputs["age_band"]
# is_flat = inputs["is_flat"]
# is_pitched = inputs["is_pitched"]
# is_at_rafters = inputs["is_at_rafters"]
uvalue = recommendation_utils.get_roof_u_value(**inputs) uvalue = recommendation_utils.get_roof_u_value(**inputs)
assert expected_uvalue == uvalue, f"Expected u value {expected_uvalue}, recieved {uvalue}" assert expected_uvalue == uvalue, f"Expected u value {expected_uvalue}, recieved {uvalue}"

View file

@ -35,7 +35,7 @@ class TestRoofRecommendations:
def test_loft_insulation_recommendation_50mm_insulation(self): def test_loft_insulation_recommendation_50mm_insulation(self):
epc_record = EPCRecord() epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Kent"} epc_record.prepared_epc = {"county": "Kent", "roof-energy-eff": "Very Poor"}
property_instance2 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record) property_instance2 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
property_instance2.age_band = "F" property_instance2.age_band = "F"
property_instance2.insulation_floor_area = 100 property_instance2.insulation_floor_area = 100
@ -63,7 +63,7 @@ class TestRoofRecommendations:
assert roof_recommender2.recommendations[0]["parts"][0]["depth"] == 270 assert roof_recommender2.recommendations[0]["parts"][0]["depth"] == 270
epc_record = EPCRecord() epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Greater London Authority"} epc_record.prepared_epc = {"county": "Greater London Authority", "roof-energy-eff": "Very Poor"}
property_instance3 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record) property_instance3 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
property_instance3.age_band = "F" property_instance3.age_band = "F"
property_instance3.insulation_floor_area = 100 property_instance3.insulation_floor_area = 100
@ -89,7 +89,7 @@ class TestRoofRecommendations:
def test_loft_insulation_recommendation_150mm_insulation(self): def test_loft_insulation_recommendation_150mm_insulation(self):
epc_record = EPCRecord() epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "North East Lincolnshire"} epc_record.prepared_epc = {"county": "North East Lincolnshire", "roof-energy-eff": "Good"}
property_instance4 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record) property_instance4 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
property_instance4.age_band = "F" property_instance4.age_band = "F"
property_instance4.insulation_floor_area = 100 property_instance4.insulation_floor_area = 100
@ -117,7 +117,7 @@ class TestRoofRecommendations:
assert roof_recommender4.recommendations[0]["parts"][0]["depth"] == 200 assert roof_recommender4.recommendations[0]["parts"][0]["depth"] == 200
epc_record = EPCRecord() epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Somerset"} epc_record.prepared_epc = {"county": "Somerset", "roof-energy-eff": "Good"}
property_instance5 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record) property_instance5 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
property_instance5.age_band = "F" property_instance5.age_band = "F"
property_instance5.insulation_floor_area = 100 property_instance5.insulation_floor_area = 100
@ -189,8 +189,8 @@ class TestRoofRecommendations:
roof_recommender7.recommend(phase=0) roof_recommender7.recommend(phase=0)
assert len(roof_recommender7.recommendations) == 1 assert len(roof_recommender7.recommendations) == 1
assert roof_recommender7.recommendations[0]["new_u_value"] == 0.23 assert roof_recommender7.recommendations[0]["new_u_value"] == 0.24
assert roof_recommender7.recommendations[0]["starting_u_value"] == 1.5 assert roof_recommender7.recommendations[0]["starting_u_value"] == 2.3
assert roof_recommender7.recommendations[0]["description"] == "Insulate room in roof at rafters and re-decorate" assert roof_recommender7.recommendations[0]["description"] == "Insulate room in roof at rafters and re-decorate"
def test_ceiling_insulated_room_in_roof(self): def test_ceiling_insulated_room_in_roof(self):