mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
handling edge cases for solar api
This commit is contained in:
parent
37bd18642b
commit
e4aa4cbb2f
5 changed files with 107 additions and 3 deletions
|
|
@ -18,6 +18,7 @@ from recommendations.recommendation_utils import (
|
||||||
get_wall_type,
|
get_wall_type,
|
||||||
estimate_external_wall_area,
|
estimate_external_wall_area,
|
||||||
estimate_windows,
|
estimate_windows,
|
||||||
|
estimate_pitched_roof_area
|
||||||
)
|
)
|
||||||
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
||||||
from backend.app.utils import sap_to_epc
|
from backend.app.utils import sap_to_epc
|
||||||
|
|
@ -631,7 +632,17 @@ class Property:
|
||||||
self.solar_panel_configuration = solar_panel_configuration
|
self.solar_panel_configuration = solar_panel_configuration
|
||||||
|
|
||||||
# We also set the roof area
|
# We also set the roof area
|
||||||
self.roof_area = roof_area
|
if roof_area is None:
|
||||||
|
if self.roof["is_flat"]:
|
||||||
|
self.roof_area = estimate_pitched_roof_area(
|
||||||
|
floor_area=self.insulation_floor_area,
|
||||||
|
floor_height=self.floor_height
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.roof_area = self.insulation_floor_area
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.roof_area = roof_area
|
||||||
|
|
||||||
def set_current_energy_bill(self, kwh_client, kwh_predictions):
|
def set_current_energy_bill(self, kwh_client, kwh_predictions):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -589,3 +589,61 @@ class GoogleSolarApi:
|
||||||
# we need to do is perform the solar analysis and then half the results. We set an indicator which
|
# we need to do is perform the solar analysis and then half the results. We set an indicator which
|
||||||
# implies we should do this
|
# implies we should do this
|
||||||
self.double_property = True
|
self.double_property = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_panel_performance(cls, property_instance):
|
||||||
|
"""
|
||||||
|
In a small number of cases, where properties have simulated uprns, we do not have a longitude and latitude
|
||||||
|
value and therefore we just return a default panel performance
|
||||||
|
:param property_instance:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
cost_instance = Costs(property_instance=property_instance)
|
||||||
|
|
||||||
|
# We return a 2.4 and 4 kwp system
|
||||||
|
panel_performance = pd.DataFrame(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'n_panels': 10,
|
||||||
|
'yearly_dc_energy': 4000 * 0.99, # Assumed 99% efficient wattage -> dc
|
||||||
|
'total_cost': cost_instance.solar_pv(
|
||||||
|
n_panels=10, has_battery=False, n_floors=property_instance.number_of_floors
|
||||||
|
)["total"],
|
||||||
|
'weighted_ratio': None,
|
||||||
|
'panneled_roof_area': 10 * 1.8,
|
||||||
|
'array_wattage': 4000,
|
||||||
|
'initial_ac_kwh_per_year': 4000 * 0.95, # Assumed 95% efficient wattage -> ac
|
||||||
|
'lifetime_ac_kwh': None,
|
||||||
|
'lifetime_dc_kwh': None,
|
||||||
|
'roi': None,
|
||||||
|
'generation_value': None,
|
||||||
|
'generation_deficit': None,
|
||||||
|
'expected_payback_years': None,
|
||||||
|
'surplus': None,
|
||||||
|
'combined_score': None,
|
||||||
|
'rank': None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'n_panels': 6,
|
||||||
|
'yearly_dc_energy': 2400 * 0.99, # Assumed 99% efficient wattage -> dc
|
||||||
|
'total_cost': cost_instance.solar_pv(
|
||||||
|
n_panels=6, has_battery=False, n_floors=property_instance.number_of_floors
|
||||||
|
)["total"],
|
||||||
|
'weighted_ratio': None,
|
||||||
|
'panneled_roof_area': 6 * 1.8,
|
||||||
|
'array_wattage': 2400,
|
||||||
|
'initial_ac_kwh_per_year': 2400 * 0.95, # Assumed 95% efficient wattage -> ac
|
||||||
|
'lifetime_ac_kwh': None,
|
||||||
|
'lifetime_dc_kwh': None,
|
||||||
|
'roi': None,
|
||||||
|
'generation_value': None,
|
||||||
|
'generation_deficit': None,
|
||||||
|
'expected_payback_years': None,
|
||||||
|
'surplus': None,
|
||||||
|
'combined_score': None,
|
||||||
|
'rank': None
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return panel_performance
|
||||||
|
|
|
||||||
|
|
@ -636,6 +636,21 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
if not property_instance.is_solar_pv_valid():
|
if not property_instance.is_solar_pv_valid():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if unit["longitude"] is None or unit["latitude"] is None:
|
||||||
|
# At this point, we've checked that solar PV is valid, and so we provide some defaults
|
||||||
|
|
||||||
|
property_instance.set_solar_panel_configuration(
|
||||||
|
solar_panel_configuration={
|
||||||
|
"insights_data": None,
|
||||||
|
"panel_performance": GoogleSolarApi.default_panel_performance(
|
||||||
|
property_instance=property_instance
|
||||||
|
),
|
||||||
|
"unit_share_of_energy": 1
|
||||||
|
},
|
||||||
|
roof_area=None
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
# We check if we have a solar non-invasive recommendation
|
# We check if we have a solar non-invasive recommendation
|
||||||
if [r for r in property_instance.non_invasive_recommendations if r["type"] == "solar_pv"]:
|
if [r for r in property_instance.non_invasive_recommendations if r["type"] == "solar_pv"]:
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import geopandas as gpd
|
||||||
from utils.logger import setup_logger
|
from utils.logger import setup_logger
|
||||||
from utils.s3 import read_io_from_s3, save_dataframe_to_s3_parquet, read_dataframe_from_s3_parquet
|
from utils.s3 import read_io_from_s3, save_dataframe_to_s3_parquet, read_dataframe_from_s3_parquet
|
||||||
from backend.Property import Property
|
from backend.Property import Property
|
||||||
|
from backend.SearchEpc import SearchEpc
|
||||||
|
|
||||||
logger = setup_logger()
|
logger = setup_logger()
|
||||||
|
|
||||||
|
|
@ -151,7 +152,7 @@ class OpenUprnClient:
|
||||||
bucket_name=bucket_name, file_key="spatial/filename_meta.parquet"
|
bucket_name=bucket_name, file_key="spatial/filename_meta.parquet"
|
||||||
)
|
)
|
||||||
|
|
||||||
uprns = [p.uprn for p in input_properties]
|
uprns = [p.uprn for p in input_properties if p.uprn_source != SearchEpc.UPRN_SOURCE_SIMULATED]
|
||||||
uprn_map = cls.make_uprn_map(uprns, uprn_filenames)
|
uprn_map = cls.make_uprn_map(uprns, uprn_filenames)
|
||||||
|
|
||||||
for filename, associated_uprn in tqdm(uprn_map.items(), total=len(uprn_map)):
|
for filename, associated_uprn in tqdm(uprn_map.items(), total=len(uprn_map)):
|
||||||
|
|
@ -165,6 +166,9 @@ class OpenUprnClient:
|
||||||
if p.uprn in associated_uprn:
|
if p.uprn in associated_uprn:
|
||||||
p.set_spatial(spatial_df[spatial_df["UPRN"] == p.uprn])
|
p.set_spatial(spatial_df[spatial_df["UPRN"] == p.uprn])
|
||||||
|
|
||||||
|
if p.uprn_source == SearchEpc.UPRN_SOURCE_SIMULATED:
|
||||||
|
p.set_spatial(cls.empty_spatial_df())
|
||||||
|
|
||||||
# Perform a final check to ensure that all properties have spatial data
|
# Perform a final check to ensure that all properties have spatial data
|
||||||
for p in input_properties:
|
for p in input_properties:
|
||||||
if p.spatial is None:
|
if p.spatial is None:
|
||||||
|
|
@ -172,6 +176,22 @@ class OpenUprnClient:
|
||||||
|
|
||||||
return input_properties
|
return input_properties
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def empty_spatial_df():
|
||||||
|
return pd.DataFrame(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"X_COORDINATE": None,
|
||||||
|
"Y_COORDINATE": None,
|
||||||
|
"LATITUDE": None,
|
||||||
|
"LONGITUDE": None,
|
||||||
|
"conservation_status": False,
|
||||||
|
"is_listed_building": False,
|
||||||
|
"is_heritage_building": False,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_spatial_data(cls, uprns: list[int], bucket_name):
|
def get_spatial_data(cls, uprns: list[int], bucket_name):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -655,7 +655,7 @@ def convert_thickness_to_numeric(string_thickness, is_pitched, is_flat):
|
||||||
return int(string_thickness)
|
return int(string_thickness)
|
||||||
|
|
||||||
|
|
||||||
def esimtate_pitched_roof_area(floor_area: float, floor_height: float) -> float:
|
def estimate_pitched_roof_area(floor_area: float, floor_height: float) -> float:
|
||||||
"""
|
"""
|
||||||
This function will estimate the area of a pitched roof, given the floor area below the roof and the floor
|
This function will estimate the area of a pitched roof, given the floor area below the roof and the floor
|
||||||
height of the property.
|
height of the property.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue