mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
implementing the solar costing model (incomplete)
This commit is contained in:
parent
83339d2cbe
commit
dd825c73a7
5 changed files with 108 additions and 17 deletions
2
.idea/Model.iml
generated
2
.idea/Model.iml
generated
|
|
@ -7,7 +7,7 @@
|
||||||
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
|
||||||
</content>
|
</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" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PyNamespacePackagesService">
|
<component name="PyNamespacePackagesService">
|
||||||
|
|
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
|
@ -3,7 +3,7 @@
|
||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.10 (backend)" />
|
<option name="sdkName" value="Python 3.10 (backend)" />
|
||||||
</component>
|
</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">
|
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||||
<option name="version" value="3" />
|
<option name="version" value="3" />
|
||||||
</component>
|
</component>
|
||||||
|
|
|
||||||
|
|
@ -590,6 +590,7 @@ class Property:
|
||||||
)
|
)
|
||||||
self.set_energy_source()
|
self.set_energy_source()
|
||||||
self.find_energy_sources()
|
self.find_energy_sources()
|
||||||
|
self.set_current_energy_bill()
|
||||||
|
|
||||||
def set_current_energy_bill(self):
|
def set_current_energy_bill(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -604,6 +605,7 @@ class Property:
|
||||||
self.current_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered(
|
self.current_adjusted_energy = AnnualBillSavings.adjust_energy_to_metered(
|
||||||
epc_energy_consumption=starting_heat_demand,
|
epc_energy_consumption=starting_heat_demand,
|
||||||
current_epc_rating=self.data["current-energy-rating"],
|
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)
|
self.current_energy_bill = AnnualBillSavings.calculate_annual_bill(self.current_adjusted_energy)
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,99 @@ class GoogleSolarApi:
|
||||||
# We now start finding the solar panel configurations
|
# We now start finding the solar panel configurations
|
||||||
self.optimise_solar_configuration()
|
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):
|
def optimise_solar_configuration(self):
|
||||||
"""
|
"""
|
||||||
Optimise the solar panel configuration for the building.
|
Optimise the solar panel configuration for the building.
|
||||||
|
|
@ -153,14 +246,14 @@ class GoogleSolarApi:
|
||||||
roi_summary = []
|
roi_summary = []
|
||||||
for segment in roof_segment_summaries:
|
for segment in roof_segment_summaries:
|
||||||
wattage = segment["panelsCount"] * self.insights_data["solarPotential"]["panelCapacityWatts"]
|
wattage = segment["panelsCount"] * self.insights_data["solarPotential"]["panelCapacityWatts"]
|
||||||
generated_energy = segment["yearlyEnergyDcKwh"]
|
generated_dc_energy = segment["yearlyEnergyDcKwh"]
|
||||||
ratio = generated_energy / wattage
|
ratio = generated_dc_energy / wattage
|
||||||
cost = MCS_SOLAR_PV_COST_DATA["average_cost_per_kwh"] * (generated_energy / 1000)
|
cost = MCS_SOLAR_PV_COST_DATA["average_cost_per_kwh"] * (generated_dc_energy / 1000)
|
||||||
roi_summary.append(
|
roi_summary.append(
|
||||||
{
|
{
|
||||||
"segmentIndex": segment["segmentIndex"],
|
"segmentIndex": segment["segmentIndex"],
|
||||||
"wattage": wattage,
|
"wattage": wattage,
|
||||||
"generatedEnergy": generated_energy,
|
"generated_dc_energy": generated_dc_energy,
|
||||||
"ratio": ratio,
|
"ratio": ratio,
|
||||||
"n_panels": segment["panelsCount"],
|
"n_panels": segment["panelsCount"],
|
||||||
"cost": cost,
|
"cost": cost,
|
||||||
|
|
@ -171,15 +264,15 @@ class GoogleSolarApi:
|
||||||
roi_summary = pd.DataFrame(roi_summary)
|
roi_summary = pd.DataFrame(roi_summary)
|
||||||
|
|
||||||
weighted_ratio = np.average(
|
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_cost = roi_summary["cost"].sum()
|
||||||
total_energy = roi_summary["generatedEnergy"].sum()
|
yearly_dc_energy = roi_summary["generated_dc_energy"].sum()
|
||||||
|
|
||||||
panel_performance.append(
|
panel_performance.append(
|
||||||
{
|
{
|
||||||
"n_panels": roi_summary["n_panels"].sum(),
|
"n_panels": roi_summary["n_panels"].sum(),
|
||||||
"total_energy": total_energy,
|
"yearly_dc_energy": yearly_dc_energy,
|
||||||
"total_cost": total_cost,
|
"total_cost": total_cost,
|
||||||
"weighted_ratio": weighted_ratio,
|
"weighted_ratio": weighted_ratio,
|
||||||
"panneled_roof_area": roi_summary["panneled_roof_area"].sum(),
|
"panneled_roof_area": roi_summary["panneled_roof_area"].sum(),
|
||||||
|
|
@ -192,10 +285,8 @@ class GoogleSolarApi:
|
||||||
panel_performance = panel_performance.drop_duplicates()
|
panel_performance = panel_performance.drop_duplicates()
|
||||||
# Ensure more than 4 panels
|
# Ensure more than 4 panels
|
||||||
panel_performance = panel_performance[panel_performance["n_panels"] >= 4]
|
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[
|
self.estimate_solar_costs()
|
||||||
(panel_performance["total_energy"] / panel_performance["array_warrage"]) >= 0.5
|
|
||||||
]
|
|
||||||
|
|
||||||
# This first bracket is the value of the energy bill savings
|
# This first bracket is the value of the energy bill savings
|
||||||
panel_performance["bill_savings"] = (
|
panel_performance["bill_savings"] = (
|
||||||
|
|
|
||||||
|
|
@ -352,9 +352,10 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
|
|
||||||
logger.info("Getting spatial data")
|
logger.info("Getting spatial data")
|
||||||
for p in input_properties:
|
for p in input_properties:
|
||||||
|
p.get_components(cleaned, photo_supply_lookup, floor_area_decile_thresholds)
|
||||||
p.get_spatial_data(uprn_filenames)
|
p.get_spatial_data(uprn_filenames)
|
||||||
# Call Google Solar API
|
# 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")
|
logger.info("Getting components and epc recommendations")
|
||||||
recommendations = {}
|
recommendations = {}
|
||||||
|
|
@ -362,9 +363,6 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
representative_recommendations = {}
|
representative_recommendations = {}
|
||||||
for p in tqdm(input_properties):
|
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)
|
recommender = Recommendations(property_instance=p, materials=materials, exclusions=body.exclusions)
|
||||||
property_recommendations, property_representative_recommendations = recommender.recommend()
|
property_recommendations, property_representative_recommendations = recommender.recommend()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue