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()