implementing the solar costing model (incomplete)

This commit is contained in:
Khalim Conn-Kowlessar 2024-06-25 15:53:17 +01:00
parent 83339d2cbe
commit dd825c73a7
5 changed files with 108 additions and 17 deletions

2
.idea/Model.iml generated
View file

@ -7,7 +7,7 @@
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (model_data)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.10 (backend)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyNamespacePackagesService">

2
.idea/misc.xml generated
View file

@ -3,7 +3,7 @@
<component name="Black">
<option name="sdkName" value="Python 3.10 (backend)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (model_data)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (backend)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>

View file

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

View file

@ -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"] = (

View file

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