import enum import pytz import datetime from sqlalchemy import Column, Integer, Text, Boolean, Float, DateTime, Enum, ForeignKey, CheckConstraint from sqlalchemy.ext.declarative import declarative_base from backend.app.db.models.users import UserModel # noqa Base = declarative_base() class PortfolioStatus(enum.Enum): SCOPING = "scoping" ASSESSMENT = "assessment" SURVEY = "survey" 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) labour_days = Column(Float) created_at = Column(DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)) updated_at = Column(DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)) # Aggregations for summary epc_breakdown_pre_retrofit = Column(Text) epc_breakdown_post_retrofit = Column(Text) n_units_to_retrofit = Column(Integer) co2_per_unit_pre_retrofit = Column(Text) co2_per_unit_post_retrofit = Column(Text) energy_bill_per_unit_pre_retrofit = Column(Text) energy_bill_per_unit_post_retrofit = Column(Text) energy_consumption_per_unit_pre_retrofit = Column(Text) energy_consumption_per_unit_post_retrofit = Column(Text) valuation_improvement_per_unit = Column(Text) cost_per_unit = Column(Text) cost_per_co2_saved = Column(Text) cost_per_sap_point = Column(Text) valuation_return_on_investment = Column(Text) 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) building_reference_number = 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, default=datetime.datetime.now(pytz.utc)) updated_at = Column(DateTime, nullable=False, default=datetime.datetime.now(pytz.utc)) 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) current_valuation = Column(Float) class FeatureRating(enum.Enum): VERY_GOOD = 5 GOOD = 4 AVERAGE = 3 POOR = 2 VERY_POOR = 1 NA = None rating_lookup = { "Very Good": FeatureRating.VERY_GOOD, "Good": FeatureRating.GOOD, "Average": FeatureRating.AVERAGE, "Poor": FeatureRating.POOR, "Very Poor": FeatureRating.VERY_POOR, "N/A": FeatureRating.NA } def get_feature_rating_from_string(rating_str: str): return rating_lookup.get(rating_str, FeatureRating.NA) class PropertyDetailsEpcModel(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(Integer, CheckConstraint('walls_rating>=1 AND walls_rating<=5')) roof = Column(Text) roof_rating = Column(Integer, CheckConstraint('roof_rating>=1 AND roof_rating<=5')) floor = Column(Text) floor_rating = Column(Integer, CheckConstraint('floor_rating>=1 AND floor_rating<=5')) windows = Column(Text) windows_rating = Column(Integer, CheckConstraint('windows_rating>=1 AND windows_rating<=5')) heating = Column(Text) heating_rating = Column(Integer, CheckConstraint('heating_rating>=1 AND heating_rating<=5')) heating_controls = Column(Text) heating_controls_rating = Column( Integer, CheckConstraint('heating_controls_rating>=1 AND heating_controls_rating<=5') ) hot_water = Column(Text) hot_water_rating = Column(Integer, CheckConstraint('hot_water_rating>=1 AND hot_water_rating<=5')) lighting = Column(Text) lighting_rating = Column(Integer, CheckConstraint('lighting_rating>=1 AND lighting_rating<=5')) mainfuel = Column(Text) 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) current_energy_demand = Column(Float) current_energy_demand_heating_hotwater = Column(Float) estimated = Column(Boolean, default=False) # Include estimates for energy bills, across the different types of energy heating_cost_current = Column(Float) hot_water_cost_current = Column(Float) lighting_cost_current = Column(Float) appliances_cost_current = Column(Float) gas_standing_charge = Column(Float) electricity_standing_charge = Column(Float) class PropertyDetailsSpatial(Base): __tablename__ = "property_details_spatial" id = Column(Integer, primary_key=True, autoincrement=True) uprn = Column(Integer, nullable=False) x_coordinate = Column(Float) y_coordinate = Column(Float) latitude = Column(Float) longitude = Column(Float) conservation_status = Column(Boolean) is_listed_building = Column(Boolean) is_heritage_building = Column(Boolean) 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 PropertyTargetsModel(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, default=datetime.datetime.now(pytz.utc)) epc = Column(Enum(Epc)) heat_demand = Column(Text) class PortfolioUsers(Base): __tablename__ = "portfolioUsers" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey('user.id'), nullable=False) portfolioId = Column(Integer, ForeignKey('portfolio.id'), nullable=False) role = Column(Text, 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))