adding new installer costs for solar pv to cost class

This commit is contained in:
Khalim Conn-Kowlessar 2024-09-18 18:53:04 +01:00
parent 7d907ce8c0
commit 8a09a29956
4 changed files with 96 additions and 26 deletions

View file

@ -20,7 +20,7 @@ SPECIFIC_MEASURES = [
# Walls
"internal_wall_insulation",
"external_wall_insulation",
"cavity_wall_insulation"
"cavity_wall_insulation",
# Roof
"loft_insulation",
"flat_roof_insulation",
@ -32,7 +32,20 @@ SPECIFIC_MEASURES = [
"boiler_upgrade",
"high_heat_retention_storage_heater",
"air_source_heat_pump",
"secondary_heating",
# Solar
"solar_pv",
# Windows Glazing
"windows",
# Mechanical ventilation
"ventilation",
# Other
"low_energy_lighting",
"fireplace",
"hot_water",
]
NON_INVASIVE_SPECIFIC_MEASURES = [
# Specific measures that will typically come from an energy assessment
"trickle_vents",
"draught_proofing",

View file

@ -37,6 +37,30 @@ MCS_SOLAR_PV_COST_DATA = {
"average_cost_per_kwh-Northern Ireland": 1347,
}
INSTALLER_SOLAR_COSTS = [
{'n_panels': 4, 'array_kwp': 1.6, 'cost': 3040.00, 'installer': 'CEG'},
{'n_panels': 5, 'array_kwp': 2.1, 'cost': 3201.00, 'installer': 'CEG'},
{'n_panels': 6, 'array_kwp': 2.5, 'cost': 3363.00, 'installer': 'CEG'},
{'n_panels': 7, 'array_kwp': 2.9, 'cost': 3524.00, 'installer': 'CEG'},
{'n_panels': 8, 'array_kwp': 3.3, 'cost': 3686.00, 'installer': 'CEG'},
{'n_panels': 9, 'array_kwp': 3.7, 'cost': 3847.00, 'installer': 'CEG'},
{'n_panels': 10, 'array_kwp': 4.1, 'cost': 4009.00, 'installer': 'CEG'},
{'n_panels': 11, 'array_kwp': 4.5, 'cost': 4170.00, 'installer': 'CEG'},
{'n_panels': 12, 'array_kwp': 4.9, 'cost': 4332.00, 'installer': 'CEG'},
{'n_panels': 13, 'array_kwp': 5.3, 'cost': 4835.00, 'installer': 'CEG'},
{'n_panels': 14, 'array_kwp': 5.7, 'cost': 5015.00, 'installer': 'CEG'},
{'n_panels': 15, 'array_kwp': 6.2, 'cost': 5176.00, 'installer': 'CEG'},
{'n_panels': 16, 'array_kwp': 6.6, 'cost': 5338.00, 'installer': 'CEG'},
{'n_panels': 17, 'array_kwp': 7.0, 'cost': 5500.00, 'installer': 'CEG'},
{'n_panels': 18, 'array_kwp': 7.4, 'cost': 6021.00, 'installer': 'CEG'}
]
INSTALLER_SCAFFOLDING_COSTS = [
{'stories': 1, 'description': '1 Story Scaffold', 'cost': 531.00, 'installer': 'CEG'},
{'stories': 2, 'description': '2 Story Scaffold', 'cost': 841.00, 'installer': 'CEG'},
{'stories': 3, 'description': '3 Story Scaffold', 'cost': 1077.00, 'installer': 'CEG'}
]
# This data is based on the MCS database, We use the larger figure between the 2023 and 2024 average,
# to be conservative
MCS_AIR_SOURCE_HEAT_PUMP_COST_DATA = {
@ -54,10 +78,27 @@ MCS_AIR_SOURCE_HEAT_PUMP_COST_DATA = {
"Scotland": 12586,
"Northern Ireland": 12000, # There are hardly any air source heat pump installs going on in Northern Ireland
}
INSTALLER_ASHP_COSTS = [
{'capacity_kw': 5.0, 'brand': 'Mitsubishi', 'tank_size_liters': 150, 'cost': 10149.53, 'installer': 'CEG'},
{'capacity_kw': 6.0, 'brand': 'Mitsubishi', 'tank_size_liters': 170, 'cost': 10823.48, 'installer': 'CEG'},
{'capacity_kw': 8.5, 'brand': 'Mitsubishi', 'tank_size_liters': 200, 'cost': 11312.43, 'installer': 'CEG'},
{'capacity_kw': 11.2, 'brand': 'Mitsubishi', 'tank_size_liters': 250, 'cost': 12156.75, 'installer': 'CEG'},
{'capacity_kw': 14.0, 'brand': 'Mitsubishi', 'tank_size_liters': 300, 'cost': 14405.54, 'installer': 'CEG'},
{'capacity_kw': 14.0, 'brand': 'Mitsubishi', 'tank_size_liters': 300, 'cost': 14405.54, 'installer': 'CEG'},
{'capacity_kw': 17.0, 'brand': 'Grant', 'tank_size_liters': 300, 'cost': 14445.00, 'installer': 'CEG'},
{'capacity_kw': 20.0, 'brand': 'Ecoforest', 'tank_size_liters': 400, 'cost': 21189.41, 'installer': 'CEG'},
{'capacity_kw': None, 'brand': '2 x cascaded ASHPs', 'tank_size_liters': 500, 'cost': 22950.00, 'installer': 'CEG'}
]
BOILER_UPGRADE_SCHEME_ASHP_VALUE = 7500
# This is based on quotes from installers
BATTERY_COST = 3500
INSTALLER_SOLAR_BATTERY_COSTS = [
{'capacity_kwh': 5, 'description': 'Battery Add on', 'cost': 2700.00, 'installer': 'CEG'},
{'capacity_kwh': 10, 'description': 'Battery Add on', 'cost': 4300.00, 'installer': 'CEG'},
{'capacity_kwh': 5, 'description': 'Battery Retrofit existing system', 'cost': 4250.00, 'installer': 'CEG'},
{'capacity_kwh': 10, 'description': 'Battery Retrofit Existing system', 'cost': 5950.00, 'installer': 'CEG'}
]
# This is based on https://www.checkatrade.com/blog/cost-guides/cost-smart-thermostat/
SMART_APPLIANCE_THERMOSTAT_COST = 400
@ -1013,7 +1054,14 @@ class Costs:
"labour_days": labour_days
}
def solar_pv(self, wattage: float, has_battery: bool = False, array_cost=None):
def solar_pv(
self, wattage: float,
n_panels: int | float,
has_battery: bool = False,
array_cost=None,
n_floors: int = 1,
battery_kwh: int = 5,
):
"""
Calculates the total cost for solar PV based data provided by the MCS dashboard, which contains
@ -1025,23 +1073,26 @@ class Costs:
Price can also be benchmarked against this checkatrade article:
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
:param n_panels: Number of solar panels
:param has_battery: Bool, whether the system includes a battery
:param array_cost: float, containing the cost of the solar PV array
:param n_floors: int, number of floors in the property, used to estimate the cost of scaffolding
:param battery_kwh: int, capacity of the battery in kWh. Defaulted to 5
"""
# Get the cost data relevant to the region
regional_cost = MCS_SOLAR_PV_COST_DATA["-".join(["average_cost_per_kwh", self.region])]
system_cost = [c for c in INSTALLER_SOLAR_COSTS if c["n_panels"] == n_panels][0]["cost"]
if array_cost is not None:
total_cost = array_cost
else:
kw = wattage / 1000
total_cost = kw * regional_cost
total_cost = array_cost if array_cost is not None else system_cost
if has_battery:
# The battery cost is based on the £3500 quote, recieved from installers
total_cost += BATTERY_COST
battery_cost = [c for c in INSTALLER_SOLAR_BATTERY_COSTS if c["capacity_kwh"] == battery_kwh][0]["cost"]
total_cost += battery_cost
scaffolding_cost = [c for c in INSTALLER_SCAFFOLDING_COSTS if c["stories"] == n_floors][0]["cost"]
total_cost += scaffolding_cost
# We add an additional cost for scaffolding
subtotal_before_vat = total_cost / (1 + self.VAT_RATE)

View file

@ -18,9 +18,8 @@ from recommendations.DraughtProofingRecommendations import DraughtProofingRecomm
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
from backend.apis.GoogleSolarApi import GoogleSolarApi
import backend.app.assumptions as assumptions
from backend.app.plan.schemas import TYPICAL_MEASURE_TYPES, SPECIFIC_MEASURES, MEASURE_MAP
from backend.app.plan.schemas import SPECIFIC_MEASURES, MEASURE_MAP, NON_INVASIVE_SPECIFIC_MEASURES
ASHP_COP = 3
STARTING_DUMMY_ID_VALUE = -9999
@ -50,8 +49,11 @@ class Recommendations:
self.exclusions = exclusions if exclusions else []
self.inclusions = inclusions if inclusions else []
self.all_typical_measures = TYPICAL_MEASURE_TYPES
self.all_specific_measures = SPECIFIC_MEASURES
self.all_non_invase_measures = NON_INVASIVE_SPECIFIC_MEASURES
self.non_invasive_recommendation_types = [
r["type"] for r in self.property_instance.non_invasive_recommendations
]
self.floor_recommender = FloorRecommendations(property_instance=property_instance, materials=materials)
self.wall_recomender = WallRecommendations(property_instance=property_instance, materials=materials)
@ -82,14 +84,18 @@ class Recommendations:
# If inclusions and exclusions are empty, it means that nothing was specified, so we allow
# all recommendation types
if not inclusions_full and not exclusions_full:
# All typical measures
return self.all_specific_measures
# All typical measures - this does not include non-invasive measures inless they are specified
return self.all_specific_measures + self.non_invasive_recommendation_types
if inclusions_full:
return inclusions_full
if exclusions_full:
return [m for m in self.all_specific_measures if m not in exclusions_full]
measures = [
m for m in self.all_specific_measures + self.non_invasive_recommendation_types
if m not in exclusions_full
]
return measures
def recommend(self):
@ -146,11 +152,10 @@ class Recommendations:
if self.draught_proofing_recommender.recommendation:
property_recommendations.append(self.draught_proofing_recommender.recommendation)
if "floor_insulation" in measures:
self.floor_recommender.recommend(phase=phase, measures=measures)
if self.floor_recommender.recommendations:
property_recommendations.append(self.floor_recommender.recommendations)
phase += 1
self.floor_recommender.recommend(phase=phase, measures=measures)
if self.floor_recommender.recommendations:
property_recommendations.append(self.floor_recommender.recommendations)
phase += 1
if "windows" in measures and "mixed_glazing" not in non_invasive_recommendation_types:
# If we have a mixed glazing recommendation, we prioritise this over the windows recommendation

View file

@ -196,7 +196,8 @@ class SolarPvRecommendations:
cost_result = self.costs.solar_pv(
wattage=recommendation_config["array_wattage"],
has_battery=has_battery,
array_cost=non_invasive_recommendation.get("cost", None)
array_cost=non_invasive_recommendation.get("cost", None),
n_panels=recommendation_config["n_panels"],
)
kw = np.floor(recommendation_config["array_wattage"] / 100) / 10
if has_battery: