mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
added some basic level of override so we force solar recommendations if we have inspections
This commit is contained in:
parent
23eb26527c
commit
76716f35d3
3 changed files with 72 additions and 11 deletions
|
|
@ -479,9 +479,7 @@ class GoogleSolarApi:
|
|||
|
||||
roi_results = pd.DataFrame(roi_results)
|
||||
|
||||
panel_performance = panel_performance.merge(
|
||||
roi_results, how="left", on="n_panels"
|
||||
)
|
||||
panel_performance = panel_performance.merge(roi_results, how="left", on="n_panels")
|
||||
|
||||
# We want max roi, minimal generation deficit, and max generation value - we create a ranking score
|
||||
# Assign equal weights to each metric
|
||||
|
|
@ -742,7 +740,7 @@ class GoogleSolarApi:
|
|||
@classmethod
|
||||
def building_solar_analysis(
|
||||
cls, building_solar_config: List, input_properties: List[Property], session, google_solar_api_key: str,
|
||||
solar_materials: list
|
||||
solar_materials: list,
|
||||
):
|
||||
"""
|
||||
Perform the solar analysis for the building level
|
||||
|
|
@ -826,9 +824,21 @@ class GoogleSolarApi:
|
|||
@classmethod
|
||||
def unit_solar_analysis(
|
||||
cls, unit_solar_config: List, input_properties: List[Property], session, body, google_solar_api_key: str,
|
||||
solar_materials: list
|
||||
solar_materials: list, inspections_map: dict
|
||||
):
|
||||
|
||||
"""
|
||||
Perform the solar analysis for the unit level
|
||||
:param unit_solar_config: List of unit solar configurations
|
||||
:param input_properties: List of properties
|
||||
:param session: Database session
|
||||
:param body: PlanTriggerRequest instance
|
||||
:param google_solar_api_key: Google Solar API key
|
||||
:param solar_materials: List of solar materials
|
||||
:param inspections_map: Dictionary mapping property IDs to inspection data
|
||||
:return:
|
||||
"""
|
||||
|
||||
if not unit_solar_config:
|
||||
return input_properties
|
||||
|
||||
|
|
@ -879,6 +889,15 @@ class GoogleSolarApi:
|
|||
property_instance=property_instance,
|
||||
)
|
||||
|
||||
property_inspections = inspections_map.get(property_instance.id, {})
|
||||
|
||||
if property_inspections:
|
||||
# If we have some inspections data, we check if we have some data which indicates solar cannot
|
||||
# be installed. We're loose about this now since this is post review
|
||||
if solar_api_client.panel_performance.empty:
|
||||
# We assume solar is a suitable option
|
||||
solar_api_client.panel_performance = solar_api_client.default_panel_performance(property_instance)
|
||||
|
||||
# Store the data in the database
|
||||
solar_api_client.save_to_db(
|
||||
session=session,
|
||||
|
|
@ -923,12 +942,43 @@ class GoogleSolarApi:
|
|||
None
|
||||
)
|
||||
|
||||
if material_1_6 is None or material_3_2 is None:
|
||||
material_4_35 = next(
|
||||
(m for m in self.solar_materials if m["type"] == "solar_pv" and
|
||||
abs(m["size"] - 4.35) < 0.1 and not m["includes_battery"]),
|
||||
None
|
||||
)
|
||||
|
||||
if material_1_6 is None or material_3_2 is None or material_4_35 is None:
|
||||
raise ValueError("No suitable solar product found for the default configuration.")
|
||||
|
||||
# We return a 1.6 and 3.2 kwp system
|
||||
panel_performance = pd.DataFrame(
|
||||
[
|
||||
{
|
||||
'n_panels': 10,
|
||||
'yearly_dc_energy': 4350 * assumptions.MEDIAN_WATTAGE_TO_DC,
|
||||
'total_cost': cost_instance.solar_pv(
|
||||
solar_product=material_4_35,
|
||||
scaffolding_options=[
|
||||
{"total_cost": 1000, "size": property_instance.number_of_floors},
|
||||
{"total_cost": 1000, "size": 3}
|
||||
],
|
||||
n_floors=property_instance.number_of_floors
|
||||
)["total"],
|
||||
'weighted_ratio': None,
|
||||
'panneled_roof_area': 9 * assumptions.RDSAP_AREA_PER_PANEL,
|
||||
'array_wattage': 4350,
|
||||
'initial_ac_kwh_per_year': 4350 * assumptions.MEDIAN_WATTAGE_TO_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': 8,
|
||||
'yearly_dc_energy': 3200 * assumptions.MEDIAN_WATTAGE_TO_DC,
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ def parse_eco_packages(config: dict[str, Any]) -> tuple[list[str], int, str] | t
|
|||
"plan_type": "solar_eco4"
|
||||
},
|
||||
"Solar Eligible, Needs Heating Upgrade": {
|
||||
"measures": ["solar_pv", "loft_insulation", "high_heat_retention_storage_heater"],
|
||||
"measures": ["solar_pv", "loft_insulation", "high_heat_retention_storage_heater", "mechanical_ventilation"],
|
||||
"target_sap": 86, # High B
|
||||
"plan_type": "solar_hhrsh_eco4"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -400,8 +400,13 @@ async def model_engine(body: PlanTriggerRequest):
|
|||
plan_input = plan_input.rename(
|
||||
columns={"domna_address_1": "address", "domna_postcode": "postcode", "epc_os_uprn": "uprn"}
|
||||
)
|
||||
# Where the EPC has been estimated, that is because a UPRN wasn't avaialble and so we remote UPRN
|
||||
plan_input["uprn"] = np.where(plan_input["estimated"].isin([1, True]), None, plan_input["uprn"])
|
||||
# Where the EPC has been estimated, that is because a UPRN wasn't avaialble and so we remove UPRN
|
||||
# This will be reflexted
|
||||
plan_input["uprn"] = np.where(
|
||||
plan_input["estimated"].isin([1, True]) & (
|
||||
(plan_input["uprn"] < 0) | pd.isnull(plan_input["uprn"])
|
||||
), None, plan_input["uprn"]
|
||||
)
|
||||
# We handle the landlord property type and built form
|
||||
plan_input["property_type"] = plan_input["landlord_property_type"].copy()
|
||||
if "landlord_built_form" in plan_input.columns:
|
||||
|
|
@ -512,7 +517,9 @@ async def model_engine(body: PlanTriggerRequest):
|
|||
epc_searcher.ordnance_survey_client.property_type = config.get("property_type", None)
|
||||
# For the moment, our OS API access is unavailable, so we skip and interpolate
|
||||
epc_searcher.find_property(skip_os=True)
|
||||
if epc_searcher.newest_epc.get("estimated") and body.file_format == "domna_asset_list":
|
||||
if epc_searcher.newest_epc.get("estimated") and body.file_format == "domna_asset_list" and (
|
||||
epc_searcher.newest_epc["uprn"] < 0
|
||||
):
|
||||
epc_searcher.newest_epc["uprn-source"] = epc_searcher.UPRN_SOURCE_SIMULATED
|
||||
|
||||
# We check for an energy assessment we have performed on this property:
|
||||
|
|
@ -678,7 +685,7 @@ async def model_engine(body: PlanTriggerRequest):
|
|||
input_properties=input_properties,
|
||||
session=session,
|
||||
google_solar_api_key=get_settings().GOOGLE_SOLAR_API_KEY,
|
||||
solar_materials=[m for m in materials if m["type"] == "solar_pv"]
|
||||
solar_materials=[m for m in materials if m["type"] == "solar_pv"],
|
||||
)
|
||||
|
||||
input_properties = GoogleSolarApi.unit_solar_analysis(
|
||||
|
|
@ -688,8 +695,12 @@ async def model_engine(body: PlanTriggerRequest):
|
|||
body=body,
|
||||
solar_materials=[m for m in materials if m["type"] == "solar_pv"],
|
||||
google_solar_api_key=get_settings().GOOGLE_SOLAR_API_KEY,
|
||||
inspections_map=inspections_map
|
||||
)
|
||||
|
||||
# We also make a tweak - if the property has been flagged for solar but doesn't contain
|
||||
# any panel performance, we ensure that we have a 3kWp and 4kWp option for the property
|
||||
|
||||
logger.info("Identifying property recommendations")
|
||||
recommendations = {}
|
||||
recommendations_scoring_data = []
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue