diff --git a/backend/Property.py b/backend/Property.py index f1c7e65c..f5325722 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -10,7 +10,6 @@ from etl.epc.settings import POTENTIAL_COLUMNS, EFFICIENCY_FEATURES, BUILT_FORM_ from etl.epc_clean.epc_attributes.all_cleaners import all_cleaner_map from utils.logger import setup_logger from utils.s3 import read_dataframe_from_s3_parquet -from epc_api.client import EpcClient from BaseUtility import Definitions from recommendations.rdsap_tables import england_wales_age_band_lookup, FLOOR_LEVEL_MAP from recommendations.recommendation_utils import ( @@ -89,6 +88,7 @@ class Property(Definitions): self.number_lighting_outlets = None self.floor_level = None self.number_of_windows = None + self.solar_pv_roof_area = None self.current_adjusted_energy = None self.expected_adjusted_energy = None @@ -830,19 +830,16 @@ class Property(Definitions): extension_count=float(self.data["extension-count"]), ) - def set_solar_panel_area(self, photo_supply_data): + def set_solar_panel_area(self, photo_supply_lookup, floor_area_decile_thresholds): """ Sets the approximate area of the solar panels :return: """ - # Approximate area of the solar panels - solar_panel_area = 1.6 - # Wattage per pan - solar_panel_wattage = 360 - - photo_supply_lookup = photo_supply_data["photo_supply_lookup"] - floor_area_decile_thresholds = photo_supply_data["floor_area_decile_thresholds"] + if (self.insulation_floor_area is None) and (self.pitched_roof_area is None): + raise ValueError( + "Need to set insulation floor area and pitched roof area before setting solar pv roof area" + ) # TODO: Create a class for the solar etl process and make this one of the functions, which applies a different # method depending on the data type @@ -868,4 +865,15 @@ class Property(Definitions): (photo_supply_lookup["is_roof_room"] == self.roof["is_roof_room"]) ] - # n_panels = np.floor(solar_panel_area * ) + if floor_area_decile in photo_supply_matched["floor_area_decile"].values: + photo_supply_matched = photo_supply_matched[ + photo_supply_matched["floor_area_decile"] == floor_area_decile + ] + + percentage_of_roof = photo_supply_matched["photo_supply_median"].mean() + percentage_of_roof = percentage_of_roof / 100 + + self.solar_pv_roof_area = ( + self.insulation_floor_area * percentage_of_roof if self.roof["is_flat"] else + self.pitched_roof_area * percentage_of_roof + ) diff --git a/etl/testing_data/solar_research.py b/etl/testing_data/solar_research.py index 9abacdc3..4e60fa7a 100644 --- a/etl/testing_data/solar_research.py +++ b/etl/testing_data/solar_research.py @@ -81,8 +81,8 @@ def app(): aggregated = results.groupby( [ - "PROPERTY_TYPE", "BUILT_FORM", "TENURE", "is_pitched", "is_roof_room", "is_loft", "is_flat", "is_thatched", - "is_at_rafters", "has_dwelling_above", "CONSTRUCTION_AGE_BAND", "floor_area_decile" + "PROPERTY_TYPE", "BUILT_FORM", "TENURE", "is_pitched", "is_roof_room", "is_flat", + "CONSTRUCTION_AGE_BAND", "floor_area_decile" ], observed=True ).agg( @@ -103,3 +103,10 @@ def app(): bucket_name="retrofit-data-dev", file_key=f"solar_pv_supply/photo_supply_lookup.parquet", ) + + floor_area_decile_thresholds = pd.DataFrame(decile_thresholds, columns=["floor_area_decile_thresholds"]) + save_dataframe_to_s3_parquet( + df=floor_area_decile_thresholds, + bucket_name="retrofit-data-dev", + file_key=f"solar_pv_supply/floor_area_decile_thresholds.parquet", + ) diff --git a/recommendations/Costs.py b/recommendations/Costs.py index 24ea0584..cc993143 100644 --- a/recommendations/Costs.py +++ b/recommendations/Costs.py @@ -18,6 +18,25 @@ regional_labour_variations = [ {"Region": "Northern Ireland", "Adjustment_Factor": 0.76} ] +# This data is based on the MCS database +MCS_SOLAR_PV_COST_DATA = { + "last_updated": "2024-01-04", + "average_cost_per_kwh": 2013.94, + "average_cost_per_kwh-Outer London": 2618.75, + "average_cost_per_kwh-Inner London": 2618.75, + "average_cost_per_kwh-South East England": 2083.33, + "average_cost_per_kwh-South West England": 2113, + "average_cost_per_kwh-East of England": 1973.86, + "average_cost_per_kwh-East Midlands": 1981.86, + "average_cost_per_kwh-West Midlands": 1926.55, + "average_cost_per_kwh-North East England": 2028.49, + "average_cost_per_kwh-North West England": 1620.42, + "average_cost_per_kwh-Yorkshire and the Humber": 2060.9, + "average_cost_per_kwh-Wales": 1898.83, + "average_cost_per_kwh-Scotland": 1967.97, + "average_cost_per_kwh-Northern Ireland": 2126.09, +} + class Costs: """ @@ -811,3 +830,6 @@ class Costs: "labour_cost": labour_cost, "labour_days": labour_days } + + def solar_pv(self, wattage): + pass diff --git a/recommendations/SolarPvRecommendations.py b/recommendations/SolarPvRecommendations.py index addbeb3f..e8b76988 100644 --- a/recommendations/SolarPvRecommendations.py +++ b/recommendations/SolarPvRecommendations.py @@ -1,12 +1,16 @@ +import numpy as np from recommendations.Costs import Costs class SolarPvRecommendations: + # Approximate area of the solar panels + SOLAR_PANEL_AREA = 1.6 + # Wattage per panel + SOLAR_PANEL_WATTAGE = 360 def __init__(self, property_instance): """ :param property_instance: Instance of the Property class, for the home associated to property_id - :param photo_supply_lookup: Lookup table of photo supply percentages """ self.property = property_instance @@ -35,3 +39,8 @@ class SolarPvRecommendations: return # We now have a property which is potentially suitable for solar PV + number_solar_panels = np.floor(self.property.solar_pv_roof_area / self.SOLAR_PANEL_AREA) + solar_panel_capacity = number_solar_panels * self.SOLAR_PANEL_WATTAGE + + # Given the wattage, we estimate the cost of the solar PV system. This is based on the MCS database + # of solar PV installations