mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Merge pull request #113 from Hestia-Homes/main
Added creation of properties to lambda
This commit is contained in:
commit
8bd981ff8a
4 changed files with 241 additions and 8 deletions
|
|
@ -29,7 +29,8 @@ class Property(BaseUtility):
|
||||||
|
|
||||||
coordinates = None
|
coordinates = None
|
||||||
|
|
||||||
def __init__(self, postcode, address1, epc_client=None, data=None):
|
def __init__(self, id, postcode, address1, epc_client=None, data=None):
|
||||||
|
self.id = id
|
||||||
self.postcode = postcode
|
self.postcode = postcode
|
||||||
self.address1 = address1
|
self.address1 = address1
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
|
||||||
59
backend/app/db/functions/property_functions.py
Normal file
59
backend/app/db/functions/property_functions.py
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
###
|
||||||
|
# This script contains methods for interacting with the property table in the database
|
||||||
|
###
|
||||||
|
import datetime
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from backend.app.db.models.portfolio import PropertyModel, PropertyCreationStatus, PortfolioStatus
|
||||||
|
from backend.app.db.connection import db_engine
|
||||||
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
|
|
||||||
|
def create_property(portfolio_id: int, address: str, postcode: str) -> (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 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
|
||||||
|
"""
|
||||||
|
Session = sessionmaker(bind=db_engine)
|
||||||
|
with Session() as session:
|
||||||
|
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Attempt to fetch the existing property
|
||||||
|
existing_property = session.query(PropertyModel).filter_by(
|
||||||
|
address=address, postcode=postcode, portfolio_id=portfolio_id
|
||||||
|
).one()
|
||||||
|
|
||||||
|
# Update the 'updated_at' field
|
||||||
|
existing_property.updated_at = now
|
||||||
|
|
||||||
|
# Merge the updated property back into the session
|
||||||
|
session.merge(existing_property)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return existing_property.id, False
|
||||||
|
|
||||||
|
except NoResultFound:
|
||||||
|
# Property doesn't exist, create a new one
|
||||||
|
new_property = PropertyModel(
|
||||||
|
address=address,
|
||||||
|
postcode=postcode,
|
||||||
|
portfolio_id=portfolio_id,
|
||||||
|
created_at=now,
|
||||||
|
updated_at=now,
|
||||||
|
creation_status=PropertyCreationStatus.LOADING,
|
||||||
|
status=PortfolioStatus.ASSESSMENT.value,
|
||||||
|
has_pre_condition_report=False,
|
||||||
|
has_recommendations=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add the new property to the session
|
||||||
|
session.add(new_property)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return new_property.id, True
|
||||||
153
backend/app/db/models/portfolio.py
Normal file
153
backend/app/db/models/portfolio.py
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
import enum
|
||||||
|
from sqlalchemy import Column, Integer, Text, Boolean, Float, DateTime, Enum, ForeignKey
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class PortfolioStatus(enum.Enum):
|
||||||
|
SCOPING = "scoping"
|
||||||
|
ASSESSMENT = "assessment"
|
||||||
|
TENDERING = "tendering"
|
||||||
|
PROJECT_UNDERWAY = "project underway"
|
||||||
|
COMPLETION_ON_TRACK = "completion; status: on track"
|
||||||
|
COMPLETION_DELAYED = "completion; status: delayed"
|
||||||
|
COMPLETION_AT_RISK = "completion; status: at risk"
|
||||||
|
COMPLETED = "completion; status: completed"
|
||||||
|
NEEDS_REVIEW = "needs review"
|
||||||
|
|
||||||
|
|
||||||
|
class PortfolioGoal(enum.Enum):
|
||||||
|
VALUATION_IMPROVEMENT = "Valuation Improvement"
|
||||||
|
INCREASING_EPC = "Increasing EPC"
|
||||||
|
REDUCING_CO2_EMISSIONS = "Reducing CO2 emissions"
|
||||||
|
ENERGY_SAVINGS = "Energy Savings"
|
||||||
|
NONE = "None"
|
||||||
|
|
||||||
|
|
||||||
|
class Portfolio(Base):
|
||||||
|
__tablename__ = 'portfolio'
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
name = Column(Text, nullable=False)
|
||||||
|
budget = Column(Float)
|
||||||
|
status = Column(Enum(PortfolioStatus, values_callable=lambda x: [e.value for e in x]), nullable=False)
|
||||||
|
goal = Column(Enum(PortfolioGoal, values_callable=lambda x: [e.value for e in x]), nullable=False)
|
||||||
|
cost = Column(Float)
|
||||||
|
number_of_properties = Column(Integer)
|
||||||
|
co2_equivalent_savings = Column(Float) # Unit is always tonnes so we don't need to store the unit
|
||||||
|
energy_savings = Column(Float) # Unit is always kWh so we don't need to store the unit
|
||||||
|
energy_cost_savings = Column(Float) # Unit is always £ so we don't need to store the unit for the moment
|
||||||
|
property_valuation_increase = Column(Float) # Unit is always £ so we don't need to store the unit for the moment
|
||||||
|
rental_yield_increase = Column(Float) # Unit is always £ so we don't need to store the unit for the moment
|
||||||
|
total_work_hours = Column(Float)
|
||||||
|
created_at = Column(DateTime, nullable=False)
|
||||||
|
updated_at = Column(DateTime, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyCreationStatus(enum.Enum):
|
||||||
|
LOADING = "LOADING"
|
||||||
|
READY = "READY"
|
||||||
|
ERROR = "ERROR"
|
||||||
|
|
||||||
|
|
||||||
|
class Epc(enum.Enum):
|
||||||
|
A = "A"
|
||||||
|
B = "B"
|
||||||
|
C = "C"
|
||||||
|
D = "D"
|
||||||
|
E = "E"
|
||||||
|
F = "F"
|
||||||
|
G = "G"
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyModel(Base):
|
||||||
|
__tablename__ = 'property'
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
portfolio_id = Column(Integer, ForeignKey('portfolio.id'), nullable=False)
|
||||||
|
creation_status = Column(Enum(PropertyCreationStatus), nullable=False)
|
||||||
|
uprn = Column(Integer)
|
||||||
|
status = Column(Enum(PortfolioStatus, values_callable=lambda x: [e.value for e in x]), nullable=False)
|
||||||
|
address = Column(Text)
|
||||||
|
postcode = Column(Text)
|
||||||
|
has_pre_condition_report = Column(Boolean)
|
||||||
|
has_recommendations = Column(Boolean)
|
||||||
|
created_at = Column(DateTime, nullable=False)
|
||||||
|
updated_at = Column(DateTime, nullable=False)
|
||||||
|
property_type = Column(Text)
|
||||||
|
built_form = Column(Text)
|
||||||
|
local_authority = Column(Text)
|
||||||
|
constituency = Column(Text)
|
||||||
|
number_of_rooms = Column(Integer)
|
||||||
|
year_built = Column(Text)
|
||||||
|
tenure = Column(Text)
|
||||||
|
current_epc_rating = Column(Enum(Epc))
|
||||||
|
current_sap_points = Column(Float)
|
||||||
|
|
||||||
|
|
||||||
|
class FeatureRating(enum.Enum):
|
||||||
|
VERY_GOOD = "Very good"
|
||||||
|
GOOD = "Good"
|
||||||
|
POOR = "Poor"
|
||||||
|
VERY_POOR = "Very poor"
|
||||||
|
NA = "N/A"
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyDetailsEpc(Base):
|
||||||
|
__tablename__ = 'property_details_epc'
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
property_id = Column(Integer, ForeignKey('property.id'), nullable=False)
|
||||||
|
portfolio_id = Column(Integer, ForeignKey('portfolio.id'), nullable=False)
|
||||||
|
full_address = Column(Text)
|
||||||
|
total_floor_area = Column(Float)
|
||||||
|
walls = Column(Text)
|
||||||
|
walls_rating = Column(Enum(FeatureRating, values_callable=lambda x: [e.value for e in x]))
|
||||||
|
roof = Column(Text)
|
||||||
|
roof_rating = Column(Enum(FeatureRating, values_callable=lambda x: [e.value for e in x]))
|
||||||
|
floor = Column(Text)
|
||||||
|
floor_rating = Column(Enum(FeatureRating, values_callable=lambda x: [e.value for e in x]))
|
||||||
|
windows = Column(Text)
|
||||||
|
windows_rating = Column(Enum(FeatureRating, values_callable=lambda x: [e.value for e in x]))
|
||||||
|
heating = Column(Text)
|
||||||
|
heating_rating = Column(Enum(FeatureRating, values_callable=lambda x: [e.value for e in x]))
|
||||||
|
heating_contols = Column(Text)
|
||||||
|
heating_contols_rating = Column(Enum(FeatureRating, values_callable=lambda x: [e.value for e in x]))
|
||||||
|
hot_water = Column(Text)
|
||||||
|
hot_water_rating = Column(Enum(FeatureRating, values_callable=lambda x: [e.value for e in x]))
|
||||||
|
lighting = Column(Text)
|
||||||
|
lighting_rating = Column(Enum(FeatureRating, values_callable=lambda x: [e.value for e in x]))
|
||||||
|
ventilation = Column(Text)
|
||||||
|
solar_pv = Column(Text)
|
||||||
|
solar_hot_water = Column(Text)
|
||||||
|
wind_turbine = Column(Text)
|
||||||
|
floor_height = Column(Float)
|
||||||
|
number_heated_rooms = Column(Integer)
|
||||||
|
heat_loss_corridor = Column(Boolean)
|
||||||
|
unheated_corridor_length = Column(Float)
|
||||||
|
number_of_open_fireplaces = Column(Integer)
|
||||||
|
number_of_extensions = Column(Integer)
|
||||||
|
number_of_storeys = Column(Integer)
|
||||||
|
mains_gas = Column(Boolean)
|
||||||
|
energy_tariff = Column(Text)
|
||||||
|
primary_energy_consumption = Column(Float)
|
||||||
|
co2_emissions = Column(Float)
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyDetailsMeter(Base):
|
||||||
|
__tablename__ = 'property_details_meter'
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
uprn = Column(Integer, nullable=False)
|
||||||
|
energy_supplier = Column(Text)
|
||||||
|
gas_supplier = Column(Text)
|
||||||
|
meter_reading_total = Column(Float)
|
||||||
|
meter_reading_electricity = Column(Float)
|
||||||
|
meter_reading_gas = Column(Float)
|
||||||
|
|
||||||
|
|
||||||
|
class PropertyTargets(Base):
|
||||||
|
__tablename__ = 'property_targets'
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
property_id = Column(Integer, ForeignKey('property.id'), nullable=False)
|
||||||
|
portfolio_id = Column(Integer, ForeignKey('portfolio.id'), nullable=False)
|
||||||
|
created_at = Column(DateTime, nullable=False)
|
||||||
|
epc = Column(Enum(Epc))
|
||||||
|
heat_demand = Column(Text)
|
||||||
|
|
@ -10,6 +10,9 @@ from recommendations.FloorRecommendations import FloorRecommendations
|
||||||
from recommendations.WallRecommendations import WallRecommendations
|
from recommendations.WallRecommendations import WallRecommendations
|
||||||
from utils.uvalue_estimates import classify_decile_newvalues
|
from utils.uvalue_estimates import classify_decile_newvalues
|
||||||
|
|
||||||
|
# database interaction functions
|
||||||
|
from backend.app.db.functions.property_functions import create_property
|
||||||
|
|
||||||
# TODO: This is placeholder until data is stored in DB
|
# TODO: This is placeholder until data is stored in DB
|
||||||
from backend.app.plan.temp_cleaned_data import cleaned
|
from backend.app.plan.temp_cleaned_data import cleaned
|
||||||
from backend.app.plan.uvalue_estimates_walls import uvalue_estimates_walls
|
from backend.app.plan.uvalue_estimates_walls import uvalue_estimates_walls
|
||||||
|
|
@ -72,17 +75,34 @@ async def trigger_plan(body: PlanTriggerRequest):
|
||||||
logger.info("Getting the inputs")
|
logger.info("Getting the inputs")
|
||||||
# Read in the trigger file from s3
|
# Read in the trigger file from s3
|
||||||
bucket_name = get_settings().PLAN_TRIGGER_BUCKET
|
bucket_name = get_settings().PLAN_TRIGGER_BUCKET
|
||||||
|
epc_client = EpcClient(auth_token=get_settings().EPC_AUTH_TOKEN)
|
||||||
|
|
||||||
plan_input = read_csv_from_s3(bucket_name=bucket_name, filepath=body.trigger_file_path)
|
plan_input = read_csv_from_s3(bucket_name=bucket_name, filepath=body.trigger_file_path)
|
||||||
|
|
||||||
# TODO: Add validation to the file
|
input_properties = []
|
||||||
|
for config in plan_input:
|
||||||
|
# We validate each record in the file. If the record is NOT valid, we need to handle this accordingly
|
||||||
|
# TODO: implment validation
|
||||||
|
|
||||||
|
# Create a record in db
|
||||||
|
property_id, is_new = create_property(
|
||||||
|
portfolio_id=body.portfolio_id, address=config['address'], postcode=config['postcode']
|
||||||
|
)
|
||||||
|
|
||||||
|
# if a new record was not created, we don't produduce recommendations
|
||||||
|
if not is_new:
|
||||||
|
continue
|
||||||
|
|
||||||
|
input_properties.append(
|
||||||
|
Property(
|
||||||
|
postcode=config['postcode'],
|
||||||
|
address1=config['address'],
|
||||||
|
epc_client=epc_client,
|
||||||
|
id=property_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("Getting EPC data")
|
logger.info("Getting EPC data")
|
||||||
epc_client = EpcClient(auth_token=get_settings().EPC_AUTH_TOKEN)
|
|
||||||
input_properties = [
|
|
||||||
Property(postcode=config['postcode'], address1=config['address'], epc_client=epc_client)
|
|
||||||
for config in plan_input
|
|
||||||
]
|
|
||||||
|
|
||||||
for p in input_properties:
|
for p in input_properties:
|
||||||
p.search_address_epc()
|
p.search_address_epc()
|
||||||
p.set_year_built()
|
p.set_year_built()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue