mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
implemented further scenarios into solar recommendations
This commit is contained in:
parent
7a219285fc
commit
ec80473f3e
6 changed files with 1586 additions and 30 deletions
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue