### # This script contains methods for interacting with the property table in the database ### import datetime import pytz from sqlalchemy.orm import Session from backend.app.db.models.portfolio import ( PropertyModel, PropertyCreationStatus, PortfolioStatus, PropertyTargetsModel, PropertyDetailsEpcModel, PropertyDetailsSpatial ) from sqlalchemy.orm.exc import NoResultFound def create_property(session: Session, portfolio_id: int, address: str, postcode: str, uprn: str, energy_assessment: dict) -> (int, bool): """ This function will create a record for the property in the database if it does not exist. If it does exist, it will just update the updated_at field. :param session: The database session :param portfolio_id: The ID of the portfolio the property belongs to :param address: The address of the property :param postcode: The postcode of the property :return: The ID of the property and a boolean indicating whether it was created or not """ try: # Attempt to fetch the existing property existing_property = session.query(PropertyModel).filter_by( uprn=uprn, portfolio_id=portfolio_id ).one() # Update the 'updated_at' field existing_property.updated_at = datetime.datetime.now(pytz.utc) # Merge the updated property back into the session session.merge(existing_property) session.flush() return existing_property.id, False except NoResultFound: # Property doesn't exist, create a new one status = PortfolioStatus.ASSESSMENT.value if len(energy_assessment["epc"]) == 0 \ else PortfolioStatus.SURVEY.value new_property = PropertyModel( address=address, postcode=postcode, portfolio_id=portfolio_id, uprn=uprn, creation_status=PropertyCreationStatus.LOADING, status=status, has_pre_condition_report=False, has_recommendations=False ) # Add the new property to the session session.add(new_property) session.flush() return new_property.id, True def create_property_targets( session: Session, property_id: int, portfolio_id: int, epc_target=None, heat_demand_target=None ): """ This function will create a record for the property targets in the database if it does not exist. :param session: The database session :param property_id: The ID of the property the targets belong to :param portfolio_id: The ID of the portfolio the property belongs to :param epc_target: Goal EPC value for the property :param heat_demand_target: Heat demand target for the property in kwh/m^2/year :return: """ new_target = PropertyTargetsModel( property_id=property_id, portfolio_id=portfolio_id, epc=epc_target, heat_demand=heat_demand_target ) session.add(new_target) session.flush() return True def update_property_data( session: Session, property_id: int, portfolio_id: int, property_data: dict ): now = datetime.datetime.now(pytz.utc) try: # Attempt to fetch the existing property existing_property = session.query(PropertyModel).filter_by( id=property_id, portfolio_id=portfolio_id ).one() # Update the fields with the data in property_data for key, value in property_data.items(): setattr(existing_property, key, value) existing_property.updated_at = now # Merge the updated property back into the session and flush session.merge(existing_property) session.flush() except NoResultFound: raise Exception(f"Property with property_id {property_id} and portfolio_id {portfolio_id} not found") return True def create_property_details_epc( session: Session, property_details_epc: dict ): """ This function will create or update a record for the property details EPC in the database. :param session: The database session :param property_details_epc: A dictionary containing details about the property EPC. :return: True if successful, False otherwise. """ existing_record = session.query(PropertyDetailsEpcModel).filter_by( portfolio_id=property_details_epc["portfolio_id"], property_id=property_details_epc["property_id"] ).first() if existing_record: # If the record exists, update its fields for key, value in property_details_epc.items(): setattr(existing_record, key, value) else: # If the record doesn't exist, create a new one new_property_details_epc = PropertyDetailsEpcModel(**property_details_epc) session.add(new_property_details_epc) session.flush() return True def update_or_create_property_spatial_details(session: Session, uprn: int, property_details_spatial: dict): """ Update an existing property details record or create a new one based on the UPRN. :param session: The SQLAlchemy session for database interaction. :param uprn: The unique property reference number (UPRN) of the property. :param property_details_spatial: A dictionary containing the spatial property details to store or update. :return: True if the operation is successful, otherwise raises an exception. """ try: # Attempt to fetch the existing property details existing_property_details = session.query(PropertyDetailsSpatial).filter_by( uprn=uprn ).one() # Update the fields with the data in property_details for key, value in property_details_spatial.items(): setattr(existing_property_details, key, value) # Merge the updated property details back into the session and flush session.merge(existing_property_details) session.flush() except NoResultFound: # Create a new record if not found new_property_details = PropertyDetailsSpatial(uprn=uprn, **property_details_spatial) session.add(new_property_details) session.flush() return True