diff --git a/.idea/Model.iml b/.idea/Model.iml index b0f9c00d..4413bb06 100644 --- a/.idea/Model.iml +++ b/.idea/Model.iml @@ -7,7 +7,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 1122b380..6f308057 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,7 @@ - + diff --git a/backend/Property.py b/backend/Property.py index fde0802d..411a4db0 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -590,6 +590,7 @@ class Property: ) self.set_energy_source() self.find_energy_sources() + self.set_current_energy_bill() def set_current_energy_bill(self): """ @@ -604,6 +605,7 @@ class Property: self.current_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered( epc_energy_consumption=starting_heat_demand, current_epc_rating=self.data["current-energy-rating"], + total_floor_area=self.floor_area ) self.current_energy_bill = AnnualBillSavings.calculate_annual_bill(self.current_adjusted_energy) diff --git a/backend/apis/GoogleSolarApi.py b/backend/apis/GoogleSolarApi.py index 99c49b2f..6d2ddf6c 100644 --- a/backend/apis/GoogleSolarApi.py +++ b/backend/apis/GoogleSolarApi.py @@ -135,6 +135,99 @@ class GoogleSolarApi: # We now start finding the solar panel configurations self.optimise_solar_configuration() + @staticmethod + def lifetime_production_ac_kwh( + row, + efficiency_depreciation_factor, + installation_life_span + ): + """ + Mimics the function described in the Google Solar API documentation, presenting the lifetime production + AC KWH as a geometri sum + """ + + return ( + row["initial_ac_kwh_per_year"] * + (1 - pow( + efficiency_depreciation_factor, + installation_life_span)) / + (1 - efficiency_depreciation_factor)) + + @staticmethod + def annualUtilityBillEstimate( + yearlyKWhEnergyConsumption, + initialAcKwhPerYear, + efficiencyDepreciationFactor, + year, + costIncreaseFactor, + discountRate): + """ + Implements the bill costing model for esimating annual bill + :param yearlyKWhEnergyConsumption: + :param initialAcKwhPerYear: + :param efficiencyDepreciationFactor: + :param year: + :param costIncreaseFactor: + :param discountRate: + :return: + """ + + return ( + billCostModel( + yearlyKWhEnergyConsumption - + annualProduction( + initialAcKwhPerYear, + efficiencyDepreciationFactor, + year)) * + pow(costIncreaseFactor, year) / + pow(discountRate, year)) + + def lifetimeUtilityBill( + yearlyKWhEnergyConsumption, + initialAcKwhPerYear, + efficiencyDepreciationFactor, + installationLifeSpan, + costIncreaseFactor, + discountRate): + bill = [0] * installationLifeSpan + for year in range(installationLifeSpan): + bill[year] = annualUtilityBillEstimate( + yearlyKWhEnergyConsumption, + initialAcKwhPerYear, + efficiencyDepreciationFactor, + year, + costIncreaseFactor, + discountRate) + return bill + + def estimate_solar_costs(self, panel_performance): + """ + This method implements the recommended costing approach, to estimate the ROI of a solar panel + configuration, as described in the Google Solar API documentation + :param panel_performance: dataframe containing the solar panel array configuration and energy generation data + :return: + """ + + # we now estiamte the financial benefits of solar panels for the household, using the framework described + # by the Google Solar API + # 1) Convert Solar Energy AD production from the DC production + panel_performance["initial_ac_kwh_per_year"] = panel_performance["yearly_dc_energy"] * self.dc_to_ac_rate + + # Remove anything where the total ac energy is less than half of the array wattage + panel_performance = panel_performance[ + (panel_performance["initial_ac_kwh_per_year"] / panel_performance["array_warrage"]) >= 0.5 + ] + + # 2) Calculate the liftime solar energy production + panel_performance['lifetime_ac_kwh'] = panel_performance.apply( + self.lifetime_production_ac_kwh, + axis=1, + efficiency_depreciation_factor=self.efficiency_depreciation_factor, + installation_life_span=self.installation_life_span + ) + + # TODO: Complete the rest of the solar model + def optimise_solar_configuration(self): """ Optimise the solar panel configuration for the building. @@ -153,14 +246,14 @@ class GoogleSolarApi: roi_summary = [] for segment in roof_segment_summaries: wattage = segment["panelsCount"] * self.insights_data["solarPotential"]["panelCapacityWatts"] - generated_energy = segment["yearlyEnergyDcKwh"] - ratio = generated_energy / wattage - cost = MCS_SOLAR_PV_COST_DATA["average_cost_per_kwh"] * (generated_energy / 1000) + generated_dc_energy = segment["yearlyEnergyDcKwh"] + ratio = generated_dc_energy / wattage + cost = MCS_SOLAR_PV_COST_DATA["average_cost_per_kwh"] * (generated_dc_energy / 1000) roi_summary.append( { "segmentIndex": segment["segmentIndex"], "wattage": wattage, - "generatedEnergy": generated_energy, + "generated_dc_energy": generated_dc_energy, "ratio": ratio, "n_panels": segment["panelsCount"], "cost": cost, @@ -171,15 +264,15 @@ class GoogleSolarApi: roi_summary = pd.DataFrame(roi_summary) weighted_ratio = np.average( - roi_summary["ratio"].values, weights=roi_summary["generatedEnergy"].values + roi_summary["ratio"].values, weights=roi_summary["generated_dc_energy"].values ) total_cost = roi_summary["cost"].sum() - total_energy = roi_summary["generatedEnergy"].sum() + yearly_dc_energy = roi_summary["generated_dc_energy"].sum() panel_performance.append( { "n_panels": roi_summary["n_panels"].sum(), - "total_energy": total_energy, + "yearly_dc_energy": yearly_dc_energy, "total_cost": total_cost, "weighted_ratio": weighted_ratio, "panneled_roof_area": roi_summary["panneled_roof_area"].sum(), @@ -192,10 +285,8 @@ class GoogleSolarApi: panel_performance = panel_performance.drop_duplicates() # Ensure more than 4 panels panel_performance = panel_performance[panel_performance["n_panels"] >= 4] - # Remove anything where the total energy is less than half of the array wattage - panel_performance = panel_performance[ - (panel_performance["total_energy"] / panel_performance["array_warrage"]) >= 0.5 - ] + + self.estimate_solar_costs() # This first bracket is the value of the energy bill savings panel_performance["bill_savings"] = ( diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index 0957b2d2..bfe5a9e4 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -352,9 +352,10 @@ async def trigger_plan(body: PlanTriggerRequest): logger.info("Getting spatial data") for p in input_properties: + p.get_components(cleaned, photo_supply_lookup, floor_area_decile_thresholds) p.get_spatial_data(uprn_filenames) # Call Google Solar API - solar_api_client.get(longitude=p.spatial["longitude"], latitude=p.spatial["latitude"]) + solar_performance = solar_api_client.get(longitude=p.spatial["longitude"], latitude=p.spatial["latitude"]) logger.info("Getting components and epc recommendations") recommendations = {} @@ -362,9 +363,6 @@ async def trigger_plan(body: PlanTriggerRequest): representative_recommendations = {} for p in tqdm(input_properties): - # Property recommendations - p.get_components(cleaned, photo_supply_lookup, floor_area_decile_thresholds) - recommender = Recommendations(property_instance=p, materials=materials, exclusions=body.exclusions) property_recommendations, property_representative_recommendations = recommender.recommend()