mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +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,
|
||||
estimate_external_wall_area,
|
||||
estimate_windows,
|
||||
estimate_pitched_roof_area
|
||||
)
|
||||
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
||||
from backend.app.utils import sap_to_epc
|
||||
|
|
@ -631,7 +632,17 @@ class Property:
|
|||
self.solar_panel_configuration = solar_panel_configuration
|
||||
|
||||
# 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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# implies we should do this
|
||||
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():
|
||||
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
|
||||
if [r for r in property_instance.non_invasive_recommendations if r["type"] == "solar_pv"]:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import geopandas as gpd
|
|||
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 backend.Property import Property
|
||||
from backend.SearchEpc import SearchEpc
|
||||
|
||||
logger = setup_logger()
|
||||
|
||||
|
|
@ -151,7 +152,7 @@ class OpenUprnClient:
|
|||
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)
|
||||
|
||||
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:
|
||||
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
|
||||
for p in input_properties:
|
||||
if p.spatial is None:
|
||||
|
|
@ -172,6 +176,22 @@ class OpenUprnClient:
|
|||
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
height of the property.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue