From 01496cd1e656ae4a1a0bf73e0d4aa0e26e8ed66e Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 11 Aug 2023 11:59:30 +0100 Subject: [PATCH 1/2] Added plan creation to recommendations api --- .../db/functions/recommendations_functions.py | 18 ++++++ backend/app/db/models/recommendations.py | 60 +++++++++++++++++++ backend/app/plan/router.py | 11 ++++ 3 files changed, 89 insertions(+) create mode 100644 backend/app/db/functions/recommendations_functions.py create mode 100644 backend/app/db/models/recommendations.py diff --git a/backend/app/db/functions/recommendations_functions.py b/backend/app/db/functions/recommendations_functions.py new file mode 100644 index 00000000..11001a7f --- /dev/null +++ b/backend/app/db/functions/recommendations_functions.py @@ -0,0 +1,18 @@ +from sqlalchemy.orm import sessionmaker +from backend.app.db.connection import db_engine +from backend.app.db.models.recommendations import Plan + + +def create_plan(plan): + """ + This function will create a record for the plan in the database if it does not exist. + :param plan: dictionary of data representing a plan to be created + """ + + Session = sessionmaker(bind=db_engine) + with Session() as session: + new_plan = Plan(**plan) + session.add(new_plan) + session.commit() + + return new_plan.id diff --git a/backend/app/db/models/recommendations.py b/backend/app/db/models/recommendations.py new file mode 100644 index 00000000..435c07dd --- /dev/null +++ b/backend/app/db/models/recommendations.py @@ -0,0 +1,60 @@ +from sqlalchemy import Column, BigInteger, String, Float, Boolean, TIMESTAMP, ForeignKey +from sqlalchemy.orm import declarative_base, relationship +from sqlalchemy.sql import func +from backend.app.db.models.portfolio import Portfolio + +Base = declarative_base() + + +class Recommendation(Base): + __tablename__ = 'recommendation' + + id = Column(BigInteger, primary_key=True, autoincrement=True) + property_id = Column(BigInteger, ForeignKey('property.id'), nullable=False) + created_at = Column(TIMESTAMP, nullable=False, server_default=func.now()) + type = Column(String, nullable=False) + description = Column(String, nullable=False) + estimated_cost = Column(Float) + default = Column(Boolean, nullable=False) + starting_u_value = Column(Float) + new_u_value = Column(Float) + sap_points = Column(Float) + heat_demand = Column(Float) + co2_equivalent_savings = Column(Float) + energy_savings = Column(Float) + energy_cost_savings = Column(Float) + property_valuation_increase = Column(Float) + rental_yield_increase = Column(Float) + total_work_hours = Column(Float) + + +class RecommendationMaterials(Base): + __tablename__ = 'recommendation_materials' + + id = Column(BigInteger, primary_key=True, autoincrement=True) + recommendation_id = Column(BigInteger, ForeignKey('recommendation.id'), nullable=False) + material_id = Column(BigInteger, ForeignKey('material.id'), nullable=False) + created_at = Column(TIMESTAMP, nullable=False, server_default=func.now()) + + # recommendation = relationship('Recommendation') + # material = relationship('Material') + + +class Plan(Base): + __tablename__ = 'plan' + + id = Column(BigInteger, primary_key=True, autoincrement=True) + portfolio_id = Column(BigInteger, ForeignKey(Portfolio.id), nullable=False) + created_at = Column(TIMESTAMP, nullable=False, server_default=func.now()) + is_default = Column(Boolean, nullable=False) + + +class PlanRecommendations(Base): + __tablename__ = 'plan_recommendations' + + id = Column(BigInteger, primary_key=True, autoincrement=True) + plan_id = Column(BigInteger, ForeignKey('plan.id'), nullable=False) + recommendation_id = Column(BigInteger, ForeignKey('recommendation.id'), nullable=False) + + plan = relationship('Plan') + recommendation = relationship('Recommendation') diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index 5fbc5f1a..e1ee5dea 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -19,6 +19,7 @@ from backend.app.db.functions.property_functions import ( create_property, create_property_targets, update_property_data, create_property_details_epc ) from backend.app.db.functions.materials_functions import get_materials +from backend.app.db.functions.recommendations_functions import create_plan # TODO: This is placeholder until data is stored in DB from backend.app.plan.uvalue_estimates_walls import uvalue_estimates_walls @@ -253,5 +254,15 @@ async def trigger_plan(body: PlanTriggerRequest): if not recommendations: continue # Create a plan + new_plan_id = create_plan( + { + "portfolio_id": body.portfolio_id, + "is_default": True + } + ) + + # upload recommendations + + # create the bridging between the plan and the recommendation return Response(status_code=200) From 4a338f990183a44c4b3819841054cca8a37fc2b0 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 11 Aug 2023 14:16:28 +0100 Subject: [PATCH 2/2] implemented structure of recommendation upload --- .../db/functions/recommendations_functions.py | 55 ++++++++++++++++++- backend/app/db/models/recommendations.py | 13 ++--- backend/app/plan/router.py | 39 ++++++++++++- 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/backend/app/db/functions/recommendations_functions.py b/backend/app/db/functions/recommendations_functions.py index 11001a7f..b544b43b 100644 --- a/backend/app/db/functions/recommendations_functions.py +++ b/backend/app/db/functions/recommendations_functions.py @@ -1,6 +1,6 @@ from sqlalchemy.orm import sessionmaker from backend.app.db.connection import db_engine -from backend.app.db.models.recommendations import Plan +from backend.app.db.models.recommendations import Plan, Recommendation, RecommendationMaterials def create_plan(plan): @@ -16,3 +16,56 @@ def create_plan(plan): session.commit() return new_plan.id + + +def create_recommendation(recommendation): + """ + This function will create a record for the recommendation in the database if it does not exist. + :param recommendation: dictionary of data representing a recommendation to be created + """ + + Session = sessionmaker(bind=db_engine) + with Session() as session: + new_recommendation = Recommendation(**recommendation) + session.add(new_recommendation) + session.commit() + + return new_recommendation.id + + +def create_recommendation_material(recommendation_id, material_id, depth): + """ + This function will create a record for the recommendation_material in the database if it does not exist. + :param recommendation_id: ID of the recommendation + :param material_id: ID of the material + :param depth: depth of the material, may be null if a material where depth is not applicable + """ + + Session = sessionmaker(bind=db_engine) + with Session() as session: + new_recommendation_material = RecommendationMaterials( + recommendation_id=recommendation_id, + material_id=material_id, + depth=depth + ) + session.add(new_recommendation_material) + session.commit() + + return new_recommendation_material.id + + +def create_plan_recommendations(plan_id, recommendation_ids): + """ + This function will create a record for the plan_recommendation in the database if it does not exist. + :param plan_id: ID of the plan + :param recommendation_ids: list of recommendation IDs + """ + + Session = sessionmaker(bind=db_engine) + with Session() as session: + for recommendation_id in recommendation_ids: + session.execute( + 'INSERT INTO plan_recommendations (plan_id, recommendation_id) VALUES (:plan_id, :recommendation_id)', + {'plan_id': plan_id, 'recommendation_id': recommendation_id} + ) + session.commit() diff --git a/backend/app/db/models/recommendations.py b/backend/app/db/models/recommendations.py index 435c07dd..66d1a063 100644 --- a/backend/app/db/models/recommendations.py +++ b/backend/app/db/models/recommendations.py @@ -1,7 +1,8 @@ from sqlalchemy import Column, BigInteger, String, Float, Boolean, TIMESTAMP, ForeignKey from sqlalchemy.orm import declarative_base, relationship from sqlalchemy.sql import func -from backend.app.db.models.portfolio import Portfolio +from backend.app.db.models.portfolio import Portfolio, PropertyModel +from backend.app.db.models.materials import Material Base = declarative_base() @@ -10,7 +11,7 @@ class Recommendation(Base): __tablename__ = 'recommendation' id = Column(BigInteger, primary_key=True, autoincrement=True) - property_id = Column(BigInteger, ForeignKey('property.id'), nullable=False) + property_id = Column(BigInteger, ForeignKey(PropertyModel.id), nullable=False) created_at = Column(TIMESTAMP, nullable=False, server_default=func.now()) type = Column(String, nullable=False) description = Column(String, nullable=False) @@ -33,12 +34,9 @@ class RecommendationMaterials(Base): id = Column(BigInteger, primary_key=True, autoincrement=True) recommendation_id = Column(BigInteger, ForeignKey('recommendation.id'), nullable=False) - material_id = Column(BigInteger, ForeignKey('material.id'), nullable=False) + material_id = Column(BigInteger, ForeignKey(Material.id), nullable=False) created_at = Column(TIMESTAMP, nullable=False, server_default=func.now()) - # recommendation = relationship('Recommendation') - # material = relationship('Material') - class Plan(Base): __tablename__ = 'plan' @@ -55,6 +53,3 @@ class PlanRecommendations(Base): id = Column(BigInteger, primary_key=True, autoincrement=True) plan_id = Column(BigInteger, ForeignKey('plan.id'), nullable=False) recommendation_id = Column(BigInteger, ForeignKey('recommendation.id'), nullable=False) - - plan = relationship('Plan') - recommendation = relationship('Recommendation') diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index e1ee5dea..cb21009e 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -19,7 +19,9 @@ from backend.app.db.functions.property_functions import ( create_property, create_property_targets, update_property_data, create_property_details_epc ) from backend.app.db.functions.materials_functions import get_materials -from backend.app.db.functions.recommendations_functions import create_plan +from backend.app.db.functions.recommendations_functions import ( + create_plan, create_recommendation, create_recommendation_material, create_plan_recommendations +) # TODO: This is placeholder until data is stored in DB from backend.app.plan.uvalue_estimates_walls import uvalue_estimates_walls @@ -250,6 +252,9 @@ async def trigger_plan(body: PlanTriggerRequest): update_property_data(property_id=p.id, portfolio_id=body.portfolio_id, property_data=property_data) # Upload recommendations + + # TODO: We start off by optimising the recommendations + recommendations_to_upload = recommendations[p.id] if not recommendations: continue @@ -262,7 +267,37 @@ async def trigger_plan(body: PlanTriggerRequest): ) # upload recommendations + uploaded_recommendation_ids = [] + for rec in recommendations_to_upload: + # TODO: implement costs (at least a placeholder) + estimated_cost = sum([x["cost"] if x["cost"] else 0 for x in rec["parts"]]) - # create the bridging between the plan and the recommendation + recommendation_id = create_recommendation( + { + "type": rec["type"], # TODO: Add this to output + "description": rec["description"], # TODO: Add this to output + "estimated_cost": estimated_cost, + "default": True, + "starting_u_value": rec.get("starting_u_value"), # TODO: Add this to output + "new_u_value": rec.get("new_u_value"), + "sap_points": rec["sap_points"] # TODO: Add this to output + # Remaining outputs yet to be handled + } + ) + uploaded_recommendation_ids.append(recommendation_id) + + # create the bridging between the recommendation and the materials + for part in rec["parts"]: + create_recommendation_material( + recommendation_id=recommendation_id, + material_id=part["id"], + depth=part["depths"][0] if part["depths"] else None, + ) + + # Finally, match the recommendation to the plan + create_plan_recommendations( + plan_id=new_plan_id, + recommendation_ids=uploaded_recommendation_ids + ) return Response(status_code=200)