implemented further scenarios into solar recommendations

This commit is contained in:
Khalim Conn-Kowlessar 2024-02-14 11:06:10 +00:00
parent 7a219285fc
commit ec80473f3e
6 changed files with 1586 additions and 30 deletions

View file

@ -118,7 +118,6 @@ class Property:
self.number_lighting_outlets = epc_record.prepared_epc.get("fixed_lighting_outlets_count") self.number_lighting_outlets = epc_record.prepared_epc.get("fixed_lighting_outlets_count")
self.floor_level = None self.floor_level = None
self.number_of_windows = None self.number_of_windows = None
self.solar_pv_roof_area = None
self.solar_pv_percentage = None self.solar_pv_percentage = None
self.current_adjusted_energy = None self.current_adjusted_energy = None
@ -185,6 +184,8 @@ class Property:
recommendation_record["walls_insulation_thickness_ending"] = "above average" recommendation_record["walls_insulation_thickness_ending"] = "above average"
recommendation_record["walls_energy_eff_ending"] = "Good" recommendation_record["walls_energy_eff_ending"] = "Good"
# Note: often when the wall is insulatied, the internal/external insulation is not noted so we should
# test the impact of using these booleans
if recommendation["type"] == "external_wall_insulation": if recommendation["type"] == "external_wall_insulation":
recommendation_record["external_insulation"] = True recommendation_record["external_insulation"] = True
recommendation_record["internal_insulation"] = False recommendation_record["internal_insulation"] = False
@ -238,7 +239,10 @@ class Property:
recommendation_record["roof_insulation_thickness_ending"] = str(proposed_depth) recommendation_record["roof_insulation_thickness_ending"] = str(proposed_depth)
if recommendation["type"] == "loft_insulation": if recommendation["type"] == "loft_insulation":
recommendation_record["roof_energy_eff_ending"] = "Good" if proposed_depth >= 270:
recommendation_record["roof_energy_eff_ending"] = "Very Good"
else:
recommendation_record["roof_energy_eff_ending"] = "Good"
else: else:
recommendation_record["roof_energy_eff_ending"] = "Very Good" recommendation_record["roof_energy_eff_ending"] = "Very Good"
else: else:
@ -682,9 +686,16 @@ class Property:
percentage_of_roof = photo_supply_matched["photo_supply_median"].mean() percentage_of_roof = photo_supply_matched["photo_supply_median"].mean()
percentage_of_roof = percentage_of_roof / 100 percentage_of_roof = percentage_of_roof / 100
self.solar_pv_roof_area = ( self.solar_pv_percentage = percentage_of_roof
def get_solar_pv_roof_area(self, percentage_of_roof):
"""
Given a percentage of the roof, this method will return the estimated area of the solar panels
:param percentage_of_roof:
:return:
"""
return (
self.insulation_floor_area * percentage_of_roof if self.roof["is_flat"] else self.insulation_floor_area * percentage_of_roof if self.roof["is_flat"] else
self.pitched_roof_area * percentage_of_roof self.pitched_roof_area * percentage_of_roof
) )
self.solar_pv_percentage = percentage_of_roof

View file

@ -51,13 +51,14 @@ class PropertyValuation:
KNIGHT_FRANK_MAPPING = [ KNIGHT_FRANK_MAPPING = [
{"start": "D", "end": "C", "increase_percentage": 0.03}, {"start": "D", "end": "C", "increase_percentage": 0.03},
{"start": "D", "end": "B", "increase_percentage": 0.088}, {"start": "D", "end": "B", "increase_percentage": 0.088},
{"start": "D", "end": "A", "increase_percentage": 0.088},
] ]
NATIONWIDE_MAPPING = [ NATIONWIDE_MAPPING = [
{"start": "G", "end": "D", "increase_percentage": 0.035}, # {"start": "G", "end": "D", "increase_percentage": 0.035},
{"start": "F", "end": "D", "increase_percentage": 0.035}, # {"start": "F", "end": "D", "increase_percentage": 0.035},
{"start": "D", "end": "B", "increase_percentage": 0.017}, # {"start": "D", "end": "B", "increase_percentage": 0.017},
{"start": "D", "end": "A", "increase_percentage": 0.017}, # {"start": "D", "end": "A", "increase_percentage": 0.017},
] ]
EPC_BANDS = ["G", "F", "E", "D", "C", "B", "A"] EPC_BANDS = ["G", "F", "E", "D", "C", "B", "A"]

File diff suppressed because it is too large Load diff

View file

@ -37,6 +37,9 @@ MCS_SOLAR_PV_COST_DATA = {
"average_cost_per_kwh-Northern Ireland": 2126.09, "average_cost_per_kwh-Northern Ireland": 2126.09,
} }
# This is based on quotes from installers
BATTERY_COST = 3500
class Costs: class Costs:
""" """
@ -835,7 +838,7 @@ class Costs:
"labour_days": labour_days "labour_days": labour_days
} }
def solar_pv(self, wattage: float): def solar_pv(self, wattage: float, has_battery: bool = False):
""" """
Calculates the total cost for solar PV based data provided by the MCS dashboard, which contains Calculates the total cost for solar PV based data provided by the MCS dashboard, which contains
@ -847,8 +850,8 @@ class Costs:
Price can also be benchmarked against this checkatrade article: Price can also be benchmarked against this checkatrade article:
https://www.checkatrade.com/blog/cost-guides/cost-of-solar-panel-installation/ https://www.checkatrade.com/blog/cost-guides/cost-of-solar-panel-installation/
:param wattage: Peak wattage of the solar PV system :param wattage: Peak wattage of the solar PV system]
:return: :param has_battery: Bool, whether the system includes a battery
""" """
# Get the cost data relevant to the region # Get the cost data relevant to the region
@ -858,6 +861,11 @@ class Costs:
total_cost = kw * regional_cost total_cost = kw * regional_cost
subtotal_before_vat = total_cost / (1 + self.VAT_RATE) subtotal_before_vat = total_cost / (1 + self.VAT_RATE)
if has_battery:
# The battery cost is based on the £3500 quote, recieved from installers
subtotal_before_vat += BATTERY_COST
vat = total_cost - subtotal_before_vat vat = total_cost - subtotal_before_vat
# Labour hours are based on estimates from online research but an average team seems to consist of 3 people # Labour hours are based on estimates from online research but an average team seems to consist of 3 people

View file

@ -44,6 +44,9 @@ class Recommendations:
""" """
This method runs the recommendations for the individual measures and then appends them to a list for output This method runs the recommendations for the individual measures and then appends them to a list for output
The recommendations are implemented in order of suggested phase, from fabric first to heating systems, to
renewables.
:return: :return:
""" """

View file

@ -39,17 +39,56 @@ class SolarPvRecommendations:
if not is_valid_property_type or not is_valid_roof_type or not has_no_existing_solar_pv: if not is_valid_property_type or not is_valid_roof_type or not has_no_existing_solar_pv:
return return
# We now have a property which is potentially suitable for solar PV # For the solar recommendations, we produce the following scenarios:
number_solar_panels = np.floor(self.property.solar_pv_roof_area / self.SOLAR_PANEL_AREA) # 1) Solar panels only, we present a high, medium and low coverage
solar_panel_wattage = number_solar_panels * self.SOLAR_PANEL_WATTAGE # 2) With and without battery
roof_coverage_scenarios = [
self.property.solar_pv_percentage - 0.1, self.property.solar_pv_percentage,
self.property.solar_pv_percentage + 0.1
]
# We make sure we haven't gone too low or high
roof_coverage_scenarios = [v for v in roof_coverage_scenarios if 0 <= v <= 1]
battery_scenarios = [False, True]
roof_coverage_percent = round(self.property.solar_pv_percentage * 100) # I now produce the cross product of the scenarios
scenarios = [(roof, battery) for roof in roof_coverage_scenarios for battery in battery_scenarios]
# Given the wattage, we estimate the cost of the solar PV system. This is based on the MCS database for roof_coverage, has_battery in scenarios:
# of solar PV installations # We now have a property which is potentially suitable for solar PV
cost_result = self.costs.solar_pv(wattage=solar_panel_wattage) solar_pv_roof_area = self.property.get_solar_pv_roof_area(roof_coverage)
kw = np.floor(solar_panel_wattage / 100) / 10 number_solar_panels = np.floor(solar_pv_roof_area / self.SOLAR_PANEL_AREA)
solar_panel_wattage = number_solar_panels * self.SOLAR_PANEL_WATTAGE
roof_coverage_percent = round(roof_coverage * 100)
# Given the wattage, we estimate the cost of the solar PV system. This is based on the MCS database
# of solar PV installations
cost_result = self.costs.solar_pv(wattage=solar_panel_wattage, has_battery=has_battery)
kw = np.floor(solar_panel_wattage / 100) / 10
if has_battery:
description = (f"Install a {kw} kilowatt-peak (kWp) solar photovoltaic (PV) panel system on "
f"{round(roof_coverage_percent * 100)}% the roof, with a battery storage system.")
else:
description = (f"Install a {kw} kilowatt-peak (kWp) solar photovoltaic (PV) p"
f"anel system on {round(roof_coverage_percent * 100)}% the roof.")
self.recommendation.append(
{
"parts": [],
"type": "solar_pv",
"description": description,
"starting_u_value": None,
"new_u_value": None,
"sap_points": None,
**cost_result,
# This is required for simulating the SAP impact. solar_pv_percentage is between 0 & 1 so we scale
# back up here
"photo_supply": 100 * scenario
}
)
self.recommendation = [ self.recommendation = [
{ {