mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
setting up storage to db
This commit is contained in:
parent
fdfac81d23
commit
389e9f51da
5 changed files with 137 additions and 4 deletions
|
|
@ -5,6 +5,10 @@ from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
||||||
import requests
|
import requests
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
import time
|
import time
|
||||||
|
from backend.app.db.functions.solar_functions import get_solar_data, store_batch_data
|
||||||
|
from utils.logger import setup_logger
|
||||||
|
|
||||||
|
logger = setup_logger()
|
||||||
|
|
||||||
|
|
||||||
class GoogleSolarApi:
|
class GoogleSolarApi:
|
||||||
|
|
@ -98,7 +102,10 @@ class GoogleSolarApi:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@lru_cache(maxsize=128)
|
@lru_cache(maxsize=128)
|
||||||
def get(self, longitude, latitude, energy_consumption, required_quality="MEDIUM", is_building=False):
|
def get(
|
||||||
|
self, longitude, latitude, energy_consumption, required_quality="MEDIUM", is_building=False, session=None,
|
||||||
|
uprn=None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Wrapper function that calls get_building_insights and extracts roof segments, with caching.
|
Wrapper function that calls get_building_insights and extracts roof segments, with caching.
|
||||||
|
|
||||||
|
|
@ -107,10 +114,21 @@ class GoogleSolarApi:
|
||||||
:param energy_consumption: The energy consumption of the building/unit associated to the longitude and latitude.
|
:param energy_consumption: The energy consumption of the building/unit associated to the longitude and latitude.
|
||||||
:param required_quality: The required quality of the data (default is "MEDIUM").
|
:param required_quality: The required quality of the data (default is "MEDIUM").
|
||||||
:param is_building: Whether the energy consumption is for a building or a unit.
|
:param is_building: Whether the energy consumption is for a building or a unit.
|
||||||
|
:param session: The database session to use for the query (default is None).
|
||||||
|
:param uprn: The unique property reference number (default is None).
|
||||||
:return: The JSON response containing the building insights data.
|
:return: The JSON response containing the building insights data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.insights_data = self.get_building_insights(longitude, latitude, required_quality)
|
is_outdated = False
|
||||||
|
if session is not None:
|
||||||
|
# Check if the data is already in the database
|
||||||
|
self.insights_data, _, is_outdated = get_solar_data(
|
||||||
|
session, longitude=longitude, latitude=latitude, uprn=uprn
|
||||||
|
)
|
||||||
|
|
||||||
|
# If we have no data in the db, or updated_at is more than 6 months
|
||||||
|
if self.insights_data is None or is_outdated:
|
||||||
|
self.insights_data = self.get_building_insights(longitude, latitude, required_quality)
|
||||||
|
|
||||||
# Extract key data from the insights response
|
# Extract key data from the insights response
|
||||||
self.roof_segments = self.insights_data["solarPotential"].get('roofSegmentStats', [])
|
self.roof_segments = self.insights_data["solarPotential"].get('roofSegmentStats', [])
|
||||||
|
|
@ -137,6 +155,18 @@ class GoogleSolarApi:
|
||||||
# We now start finding the solar panel configurations
|
# We now start finding the solar panel configurations
|
||||||
self.optimise_solar_configuration(energy_consumption=energy_consumption, is_building=is_building)
|
self.optimise_solar_configuration(energy_consumption=energy_consumption, is_building=is_building)
|
||||||
|
|
||||||
|
def save_to_db(self, session, uprns_to_location):
|
||||||
|
if self.insights_data is None:
|
||||||
|
raise ValueError("No api data to store")
|
||||||
|
|
||||||
|
logger.info("Storing to database")
|
||||||
|
|
||||||
|
store_batch_data(
|
||||||
|
session=session,
|
||||||
|
api_data=self.insights_data,
|
||||||
|
uprns_to_location=uprns_to_location
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def lifetime_production_ac_kwh(
|
def lifetime_production_ac_kwh(
|
||||||
row,
|
row,
|
||||||
|
|
|
||||||
64
backend/app/db/functions/solar_functions.py
Normal file
64
backend/app/db/functions/solar_functions.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import datetime
|
||||||
|
import pytz
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
from backend.app.db.models.solar import Solar
|
||||||
|
|
||||||
|
|
||||||
|
def get_solar_data(session: Session, longitude: float = None, latitude: float = None, uprn: str = None):
|
||||||
|
"""
|
||||||
|
This function will fetch data from the solar table based on longitude and latitude or UPRN.
|
||||||
|
:param session: The database session
|
||||||
|
:param longitude: The longitude to search for
|
||||||
|
:param latitude: The latitude to search for
|
||||||
|
:param uprn: The UPRN to search for (overrides longitude and latitude if provided)
|
||||||
|
:return: The google_api_response and updated_at fields
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if uprn:
|
||||||
|
# Search by UPRN
|
||||||
|
solar_data = session.query(Solar.google_api_response, Solar.updated_at).filter_by(uprn=uprn).one()
|
||||||
|
else:
|
||||||
|
# Search by longitude and latitude
|
||||||
|
solar_data = session.query(Solar.google_api_response, Solar.updated_at).filter(
|
||||||
|
Solar.longitude == longitude,
|
||||||
|
Solar.latitude == latitude
|
||||||
|
).one()
|
||||||
|
|
||||||
|
# Check if updated_at is more than 6 months old
|
||||||
|
six_months_ago = datetime.datetime.now(pytz.utc) - datetime.timedelta(days=6 * 30) # Approximate 6 months
|
||||||
|
is_outdated = solar_data.updated_at < six_months_ago
|
||||||
|
|
||||||
|
return solar_data.google_api_response, solar_data.updated_at, is_outdated
|
||||||
|
|
||||||
|
except NoResultFound:
|
||||||
|
return None, None, False
|
||||||
|
|
||||||
|
|
||||||
|
def store_batch_data(session: Session, api_data: dict, uprns_to_location: 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
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Convert the data_list to a list of dicts for bulk insert
|
||||||
|
records_to_update = []
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Perform bulk insert or update
|
||||||
|
session.bulk_insert_mappings(Solar, records_to_update)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
session.rollback()
|
||||||
|
raise e
|
||||||
21
backend/app/db/models/solar.py
Normal file
21
backend/app/db/models/solar.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import datetime
|
||||||
|
import pytz
|
||||||
|
from sqlalchemy import Column, Integer, Float, DateTime, JSON
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class Solar(Base):
|
||||||
|
__tablename__ = 'solar'
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
longitude = Column(Float, nullable=False)
|
||||||
|
latitude = Column(Float, nullable=False)
|
||||||
|
uprn = Column(Integer, nullable=False)
|
||||||
|
created_at = Column(
|
||||||
|
DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)
|
||||||
|
)
|
||||||
|
updated_at = Column(
|
||||||
|
DateTime, nullable=False, default=datetime.datetime.now(pytz.utc), onupdate=datetime.datetime.now(pytz.utc)
|
||||||
|
)
|
||||||
|
google_api_response = Column(JSON, nullable=False)
|
||||||
|
|
@ -380,12 +380,14 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
target_rating=body.goal_value,
|
target_rating=body.goal_value,
|
||||||
current_consumption=p.current_adjusted_energy
|
current_consumption=p.current_adjusted_energy
|
||||||
),
|
),
|
||||||
"property_id": p.id
|
"property_id": p.id,
|
||||||
|
"uprn": p.uprn
|
||||||
} for p in input_properties if p.building_id is not None
|
} for p in input_properties if p.building_id is not None
|
||||||
]
|
]
|
||||||
if building_ids:
|
if building_ids:
|
||||||
# Find the unique longitude and latitude pairs for each building id
|
# Find the unique longitude and latitude pairs for each building id
|
||||||
unique_coordinates = {}
|
unique_coordinates = {}
|
||||||
|
building_uprns = {}
|
||||||
for entry in building_ids:
|
for entry in building_ids:
|
||||||
building_id = entry['building_id']
|
building_id = entry['building_id']
|
||||||
coordinate_pair = {'longitude': entry['longitude'], 'latitude': entry['latitude']}
|
coordinate_pair = {'longitude': entry['longitude'], 'latitude': entry['latitude']}
|
||||||
|
|
@ -396,6 +398,16 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
if coordinate_pair not in unique_coordinates[building_id]:
|
if coordinate_pair not in unique_coordinates[building_id]:
|
||||||
unique_coordinates[building_id].append(coordinate_pair)
|
unique_coordinates[building_id].append(coordinate_pair)
|
||||||
|
|
||||||
|
if building_id not in building_uprns:
|
||||||
|
building_uprns[building_id] = []
|
||||||
|
|
||||||
|
if entry['uprn'] not in building_uprns[building_id]:
|
||||||
|
building_uprns[building_id].append(
|
||||||
|
{
|
||||||
|
"uprn": entry['uprn'], "longitude": entry['longitude'], "latitude": entry['latitude']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
solar_panel_configuration = {}
|
solar_panel_configuration = {}
|
||||||
for building_id, coordinates in unique_coordinates.items():
|
for building_id, coordinates in unique_coordinates.items():
|
||||||
if len(coordinates) > 1:
|
if len(coordinates) > 1:
|
||||||
|
|
@ -410,6 +422,7 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
latitude=coordinates["latitude"],
|
latitude=coordinates["latitude"],
|
||||||
energy_consumption=energy_consumption,
|
energy_consumption=energy_consumption,
|
||||||
is_building=True,
|
is_building=True,
|
||||||
|
session=session
|
||||||
)
|
)
|
||||||
solar_panel_configuration[building_id] = {
|
solar_panel_configuration[building_id] = {
|
||||||
"insights_data": solar_api_client.insights_data,
|
"insights_data": solar_api_client.insights_data,
|
||||||
|
|
@ -417,6 +430,9 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
"n_units": len([entry for entry in building_ids if entry['building_id'] == building_id])
|
"n_units": len([entry for entry in building_ids if entry['building_id'] == building_id])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Store the data in the database
|
||||||
|
solar_api_client.save_to_db(session=session, uprns_to_location=building_uprns[building_id])
|
||||||
|
|
||||||
# Insert this into the properties that have this building id
|
# Insert this into the properties that have this building id
|
||||||
for p in input_properties:
|
for p in input_properties:
|
||||||
if p.building_id == building_id:
|
if p.building_id == building_id:
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,9 @@ class SolarPvRecommendations:
|
||||||
)
|
)
|
||||||
n_units = self.property.solar_panel_configuration["n_units"]
|
n_units = self.property.solar_panel_configuration["n_units"]
|
||||||
|
|
||||||
best_configurations = panel_performance.head(3).reset_index(drop=True)
|
# At a building level, we take a single configuration so that all properties a guaranteed to use
|
||||||
|
# the same configuration
|
||||||
|
best_configurations = panel_performance.head(1).reset_index(drop=True)
|
||||||
|
|
||||||
for rank, recommendation_config in best_configurations.iterrows():
|
for rank, recommendation_config in best_configurations.iterrows():
|
||||||
roof_coverage_percent = round(recommendation_config["panneled_roof_area"] / total_roof_area * 100)
|
roof_coverage_percent = round(recommendation_config["panneled_roof_area"] / total_roof_area * 100)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue