diff --git a/backend/Property.py b/backend/Property.py index 5b96d413..6d17f0e2 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -218,7 +218,7 @@ class Property: # self.whlg_eligibility = None self.scheme = None self.funded_measures = None - self.full_project_funding = None + self.project_funding = None self.total_uplift = None self.full_project_score = None self.partial_project_score = None @@ -1347,7 +1347,7 @@ class Property: self, scheme, funded_measures, - full_project_funding, + project_funding, total_uplift, full_project_score, partial_project_score, @@ -1357,11 +1357,11 @@ class Property: This method inserts the funding into the property object """ self.scheme = scheme - self.funded_measures = funded_measures, - self.full_project_funding = full_project_funding, - self.total_uplift = total_uplift, - self.full_project_score = full_project_score, - self.partial_project_score = partial_project_score, + self.funded_measures = funded_measures + self.project_funding = project_funding + self.total_uplift = total_uplift + self.full_project_score = full_project_score + self.partial_project_score = partial_project_score self.uplift_project_score = uplift_project_score def identify_ventilation(self): diff --git a/backend/app/db/functions/funding_functions.py b/backend/app/db/functions/funding_functions.py new file mode 100644 index 00000000..2b9f73a3 --- /dev/null +++ b/backend/app/db/functions/funding_functions.py @@ -0,0 +1,60 @@ +from sqlalchemy.orm import Session +from sqlalchemy.exc import SQLAlchemyError +from backend.app.db.models.funding import FundingPackage, FundingPackageMeasures + + +def upload_funding(session: Session, p, plan_id, property_recommendations): + try: + # Prepare data for bulk insert for Recommendation + funding_package_data = { + "plan_id": plan_id, + "scheme": p.scheme, + "full_project_funding": p.full_project_funding, + "total_uplift": p.total_uplift, + "full_project_score": p.full_project_score, + "partial_project_score": p.partial_project_score, + "uplift_project_score": p.uplift_project_score + } + + # upload the funding package data and get back the ID + new_funding_package = FundingPackage(**funding_package_data) + session.add(new_funding_package) + session.flush() + session.commit() + + funding_package_id = new_funding_package.id + + # We now prepare the list of funding measures to be uploaded + funding_measures_data = [] + for part in p.funded_measures: + recommendation_id = part["id"] + recommendation = next( + (x for x in property_recommendations if x["recommendation_id"] == recommendation_id), {} + ) + material_id = None + if recommendation["parts"]: + material_id = recommendation["parts"][0]["id"] + funding_measures_data.append({ + "funding_package_id": funding_package_id, + "measure": part["type"], + "material_id": material_id, + "innovation_uplift": float(part["innovation_uplift"]), + "partial_project_score": float(part["partial_project_score"]), + "uplift_project_score": float(part["uplift_project_score"]) + }) + + session.bulk_insert_mappings(FundingPackageMeasures, funding_measures_data) + + # flush the changes to get the newly created IDs + session.flush() + + # Commit the transaction + session.commit() + + return True + + except SQLAlchemyError as e: + # Rollback the transaction in case of an error + session.rollback() + print(f"An error occurred: {e}") + return False diff --git a/backend/app/db/models/funding.py b/backend/app/db/models/funding.py new file mode 100644 index 00000000..78ba7ff1 --- /dev/null +++ b/backend/app/db/models/funding.py @@ -0,0 +1,45 @@ +import enum + +from sqlalchemy import Column, Integer, String, Float, Enum, TIMESTAMP, BigInteger, ForeignKey +from sqlalchemy.orm import declarative_base +from sqlalchemy.sql import func +from backend.app.db.models.recommendations import Plan +from backend.app.db.models.materials import MaterialType, Material + +Base = declarative_base() + + +class SchemeEnum(enum.Enum): + eco4 = "eco4" + gbis = "gbis" + whlg = "whlg" + none = "none" + + +class FundingPackage(Base): + __tablename__ = 'funding_package' + + id = Column(Integer, primary_key=True, autoincrement=True) + plan_id = Column(BigInteger, ForeignKey(Plan.id), nullable=False) + scheme = Column(String, nullable=False) # Assuming Scheme is a string representation + created_at = Column(TIMESTAMP, nullable=False, server_default=func.now()) + project_funding = Column(Float) + total_uplift = Column(Float) + full_project_score = Column(Float) + partial_project_score = Column(Float) + uplift_project_score = Column(Float) + + +class FundingPackageMeasures(Base): + __tablename__ = 'funding_package_measures' + + id = Column(Integer, primary_key=True, autoincrement=True) + funding_package_id = Column(BigInteger, ForeignKey(FundingPackage.id), nullable=False) + measure = Column( + Enum(MaterialType, values_callable=lambda x: [e.value for e in x], create_constraint=False), + nullable=False + ) + material_id = Column(BigInteger, ForeignKey(Material.id), nullable=False) # Assuming material table exists + innovation_uplift = Column(Float) + partial_project_score = Column(Float) + uplift_project_score = Column(Float) diff --git a/backend/engine/engine.py b/backend/engine/engine.py index 1a94919c..108f5091 100644 --- a/backend/engine/engine.py +++ b/backend/engine/engine.py @@ -934,8 +934,8 @@ async def model_engine(body: PlanTriggerRequest): # This is the list of measures that we will recommend funded_measures = optimal_solution["items"] solution = funded_measures + optimal_solution["unfunded_items"] - # This is the total amount of funding that the project will product (£) - full_project_funding = optimal_solution["full_project_funding"] + # This is the total amount of funding that the project will produce (including uplifts) (£) + project_funding = optimal_solution["full_project_funding"] # This is the total amount of funding associated to the uplift (£) total_uplift = optimal_solution["total_uplift"] # This is the funding scheme selected @@ -998,7 +998,7 @@ async def model_engine(body: PlanTriggerRequest): scheme = "gbis" funded_measures = solution if scheme in ["gbis", "eco4"] else [] - full_project_funding = 0 if funding.full_project_abs is not None else funding.full_project_abs + project_funding = 0 if funding.full_project_abs is not None else funding.full_project_abs total_uplift = funding.eco4_uplift full_project_score = 0 if funding.full_project_abs is not None else funding.full_project_abs partial_project_score = funding.partial_project_abs @@ -1022,7 +1022,7 @@ async def model_engine(body: PlanTriggerRequest): p.insert_funding( scheme=scheme, funded_measures=funded_measures, - full_project_funding=full_project_funding, + project_funding=project_funding, total_uplift=total_uplift, full_project_score=full_project_score, partial_project_score=partial_project_score, @@ -1055,28 +1055,6 @@ async def model_engine(body: PlanTriggerRequest): # need to be updated rec["default"] = True - # ~~~~~~~~~~~~~~~~ - # Funding - # ~~~~~~~~~~~~~~~~ - - # for p in input_properties: - # funding_calulator = Funding( - # tenure=body.housing_type, - # starting_epc=p.data["current-energy-rating"], - # starting_sap=int(p.data["current-energy-efficiency"]), - # postcode=p.postcode, - # floor_area=p.floor_area, - # council_tax_band=None, # This is seemingly always None at the moment - # property_recommendations=recommendations[p.id], - # project_scores_matrix=eco_project_scores_matrix, - # whlg_eligible_postcodes=whlg_eligible_postcodes, - # gbis_abs_rate=15, - # eco4_abs_rate=15, - # ) - # funding_calulator.check_eligibiltiy() - # # Insert finding - # p.insert_funding(funding_calulator) - logger.info("Uploading recommendations to the database") # If we have any work to do, we create a new scenario if body.scenario_id: @@ -1165,6 +1143,10 @@ async def model_engine(body: PlanTriggerRequest): session, recommendations_to_upload, p.id, new_plan_id ) + upload_funding( + session, + ) + property_valuation_increases.append( valuations["average_increased_value"] - valuations["current_value"] ) diff --git a/recommendations/SolarPvRecommendations.py b/recommendations/SolarPvRecommendations.py index f21b7bf3..dad9530a 100644 --- a/recommendations/SolarPvRecommendations.py +++ b/recommendations/SolarPvRecommendations.py @@ -276,7 +276,7 @@ class SolarPvRecommendations: self.recommendation.append( { "phase": phase, - "parts": [], + "parts": [solar_pv_product], "type": "solar_pv", "measure_type": "solar_pv", "description": description,