Added expected payback years

This commit is contained in:
Khalim Conn-Kowlessar 2024-07-10 17:52:22 +01:00
parent 693698d5de
commit 28c70d0afb
4 changed files with 107 additions and 20 deletions

View file

@ -159,19 +159,42 @@ class GoogleSolarApi:
# We now start finding the solar panel configurations
self.optimise_solar_configuration(energy_consumption=energy_consumption, is_building=is_building)
def save_to_db(self, session, uprns_to_location):
def save_to_db(self, session, uprns_to_location, scenario_type):
if self.insights_data is None:
raise ValueError("No api data to store")
if scenario_type not in ["unit", "building"]:
raise Exception("Invalid scenario type. Must be either 'unit' or 'building'")
if not self.need_to_store:
return
logger.info("Storing to database")
scenarios_data = self.panel_performance.head(1)[
["n_panels", "yearly_dc_energy", "total_cost", "panneled_roof_area", "array_warrage",
"initial_ac_kwh_per_year", "lifetime_ac_kwh", "roi"]
].rename(
columns={
"n_panels": "number_panels",
"yearly_dc_energy": "yearly_dc_kwh",
"total_cost": "cost",
"panneled_roof_area": "panelled_roof_area",
"array_warrage": "array_kwhp",
"initial_ac_kwh_per_year": "yearly_ac_kwh",
"lifetime_ac_kwh": "lifetime_ac_kwh",
}
)
# Adding missing fields with default values
scenarios_data["is_default"] = True
scenarios_data["scenario_type"] = scenario_type
store_batch_data(
session=session,
api_data=self.insights_data,
uprns_to_location=uprns_to_location
uprns_to_location=uprns_to_location,
scenarios_data=scenarios_data
)
@staticmethod
@ -286,13 +309,21 @@ class GoogleSolarApi:
roi = (generation_value + surplus_value) / panel_config["total_cost"]
generation_deficit = surplus_value
# Calculate expected payback years
if generation_value > 0:
expected_payback_years = panel_config["total_cost"] / (
generation_value / self.installation_life_span)
else:
expected_payback_years = None # or some high value indicating no payback
# Generation deficit tells us how much more energy we need to meet the generation demand.
roi_results.append(
{
"n_panels": panel_config["n_panels"],
"roi": roi,
"generation_value": generation_value,
"generation_deficit": generation_deficit
"generation_deficit": generation_deficit,
"expected_payback_years": expected_payback_years
}
)
@ -309,6 +340,8 @@ class GoogleSolarApi:
["roi", "generation_deficit", "generation_value"], ascending=[False, True, False]
)
panel_performance["expected_payback_years"] = np.ceil(panel_performance["expected_payback_years"]).astype(int)
self.panel_performance = panel_performance
def exclude_north_facing_segments(self):

View file

@ -2,7 +2,7 @@ import datetime
import pytz
from sqlalchemy.orm import Session
from sqlalchemy.orm.exc import NoResultFound
from backend.app.db.models.solar import Solar
from backend.app.db.models.solar import Solar, SolarScenario
def get_solar_data(session: Session, longitude: float = None, latitude: float = None, uprn: str = None):
@ -35,28 +35,56 @@ def get_solar_data(session: Session, longitude: float = None, latitude: float =
return None, None, False
def store_batch_data(session: Session, api_data: dict, uprns_to_location: list):
def store_batch_data(session: Session, api_data: dict, uprns_to_location: list, scenarios_data: list):
"""
This function will store the API data to the solar table against all of the UPRNs with longitude and latitude.
:param session: The database session
:param api_data: The API data to store
:param data_list: A list of dictionaries containing uprn, longitude, and latitude
:param uprns_to_location: A list of dictionaries containing uprn, longitude, and latitude
:param scenarios_data: A list of dictionaries containing scenario data for each UPRN
"""
try:
# Convert the data_list to a list of dicts for bulk insert
records_to_update = []
# Insert data into the Solar table and get the IDs
solar_records = []
for data in uprns_to_location:
record = {
'uprn': data['uprn'],
'longitude': data['longitude'],
'latitude': data['latitude'],
'google_api_response': api_data,
'updated_at': datetime.datetime.now(pytz.utc)
}
records_to_update.append(record)
solar_record = Solar(
uprn=data['uprn'],
longitude=data['longitude'],
latitude=data['latitude'],
google_api_response=api_data,
updated_at=datetime.datetime.now(pytz.utc)
)
solar_records.append(solar_record)
# Perform bulk insert or update
session.bulk_insert_mappings(Solar, records_to_update)
session.bulk_save_objects(solar_records)
session.commit()
# Retrieve the IDs of the inserted records
inserted_ids = [record.id for record in solar_records]
# Prepare the data for SolarScenario
scenario_records = []
for index, solar_id in enumerate(inserted_ids):
scenarios = scenarios_data[index] # Assuming scenarios_data has the same order as uprns_to_location
for scenario in scenarios:
scenario_record = SolarScenario(
solar_id=solar_id,
scenario_type=scenario['scenario_type'],
number_panels=scenario['number_panels'],
array_kwhp=scenario['array_kwhp'],
lifetime_dc_kwh=scenario['lifetime_dc_kwh'],
yearly_dc_kwh=scenario['yearly_dc_kwh'],
lifetime_ac_kwh=scenario.get('lifetime_ac_kwh'), # Optional field
yearly_ac_kwh=scenario.get('yearly_ac_kwh'), # Optional field
cost=scenario['cost'],
expected_payback_years=scenario.get('expected_payback_years'), # Optional field
panelled_roof_area=scenario['panelled_roof_area'],
is_default=scenario['is_default']
)
scenario_records.append(scenario_record)
# Insert data into the SolarScenario table
session.bulk_save_objects(scenario_records)
session.commit()
except Exception as e:

View file

@ -1,6 +1,7 @@
import datetime
import pytz
from sqlalchemy import Column, Integer, Float, DateTime, JSON
from enum import Enum as PyEnum
from sqlalchemy import Column, Integer, Float, DateTime, JSON, BigInteger, ForeignKey, Enum, Boolean
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
@ -19,3 +20,26 @@ class Solar(Base):
DateTime, nullable=False, default=datetime.datetime.now(pytz.utc), onupdate=datetime.datetime.now(pytz.utc)
)
google_api_response = Column(JSON, nullable=False)
class ScenarioType(PyEnum):
unit = "unit"
building = "building"
class SolarScenario(Base):
__tablename__ = 'solar_scenario'
id = Column(BigInteger, primary_key=True, autoincrement=True)
solar_id = Column(BigInteger, ForeignKey('solar.id'), nullable=False)
scenario_type = Column(Enum(ScenarioType), nullable=False)
number_panels = Column(Integer, nullable=False)
array_kwhp = Column(Integer, nullable=False)
lifetime_dc_kwh = Column(Float, nullable=False)
yearly_dc_kwh = Column(Float, nullable=False)
lifetime_ac_kwh = Column(Float)
yearly_ac_kwh = Column(Float)
cost = Column(Float, nullable=False)
expected_payback_years = Column(Float)
panelled_roof_area = Column(Float, nullable=False)
is_default = Column(Boolean, nullable=False)

View file

@ -431,7 +431,9 @@ async def trigger_plan(body: PlanTriggerRequest):
}
# Store the data in the database
solar_api_client.save_to_db(session=session, uprns_to_location=building_uprns[building_id])
solar_api_client.save_to_db(
session=session, uprns_to_location=building_uprns[building_id], scenario_type="building"
)
# Insert this into the properties that have this building id
for p in input_properties: