mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
adding new installer costs for solar pv to cost class
This commit is contained in:
parent
7d907ce8c0
commit
8a09a29956
4 changed files with 96 additions and 26 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue