mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Added in portfolio aggregation method
This commit is contained in:
parent
f37f6ac029
commit
776f3a48e5
3 changed files with 88 additions and 39 deletions
39
backend/app/db/functions/portfolio_functions.py
Normal file
39
backend/app/db/functions/portfolio_functions.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import func
|
||||
from backend.app.db.connection import db_engine
|
||||
from backend.app.db.models.recommendations import Plan, PlanRecommendations, Recommendation
|
||||
from backend.app.db.models.portfolio import Portfolio
|
||||
|
||||
|
||||
def aggregate_portfolio_recommendations(portfolio_id: int):
|
||||
Session = sessionmaker(bind=db_engine)
|
||||
with Session() as session:
|
||||
# Aggregate multiple fields
|
||||
aggregates = (
|
||||
session.query(
|
||||
func.sum(Recommendation.estimated_cost).label("cost"),
|
||||
# For future usage we will aggregate multiple fields in this step
|
||||
# func.sum(Recommendation.heat_demand).label("total_heat_demand"),
|
||||
# func.sum(Recommendation.energy_savings).label("total_energy_savings")
|
||||
)
|
||||
.join(PlanRecommendations, PlanRecommendations.recommendation_id == Recommendation.id)
|
||||
.join(Plan, Plan.id == PlanRecommendations.plan_id)
|
||||
.filter(Plan.portfolio_id == portfolio_id, Plan.is_default == True, Recommendation.default == True)
|
||||
.one()
|
||||
)
|
||||
|
||||
aggregates_dict = {
|
||||
"cost": aggregates.cost or 0,
|
||||
# "total_heat_demand": aggregates.total_heat_demand or 0,
|
||||
# "total_energy_savings": aggregates.total_energy_savings or 0
|
||||
}
|
||||
|
||||
# Get the portfolio and update the fields
|
||||
portfolio = session.query(Portfolio).filter_by(id=portfolio_id).one()
|
||||
# Update the data
|
||||
for key, value in aggregates_dict.items():
|
||||
setattr(portfolio, key, value)
|
||||
|
||||
# Merge the updated portfolio back into the session
|
||||
session.merge(portfolio)
|
||||
session.commit()
|
||||
|
|
@ -22,6 +22,7 @@ from backend.app.db.functions.materials_functions import get_materials
|
|||
from backend.app.db.functions.recommendations_functions import (
|
||||
create_plan, create_recommendation, create_recommendation_material, create_plan_recommendations
|
||||
)
|
||||
from backend.app.db.functions.portfolio_functions import aggregate_portfolio_recommendations
|
||||
|
||||
from model_data.optimiser.GainOptimiser import GainOptimiser
|
||||
from model_data.optimiser.CostOptimiser import CostOptimiser
|
||||
|
|
@ -105,23 +106,23 @@ def filter_materials(materials):
|
|||
return materials_by_type
|
||||
|
||||
|
||||
def insert_temp_recommendation_id(recommendations_to_upload):
|
||||
def insert_temp_recommendation_id(property_recommendations):
|
||||
"""
|
||||
Creates a temporary recommendation id which is needed for
|
||||
filtering recommendations between default and no, after the optimiser has been
|
||||
run
|
||||
:param recommendations_to_upload: nested list of recommendations, grouped by types
|
||||
:param property_recommendations: nested list of recommendations, grouped by types
|
||||
:return: Updated recommendations_to_upload, where where recommendation has a "recommendation_id"
|
||||
integer inserted
|
||||
"""
|
||||
idx = 0
|
||||
|
||||
for recs in recommendations_to_upload:
|
||||
for recs in property_recommendations:
|
||||
for rec in recs:
|
||||
rec["recommendation_id"] = idx
|
||||
idx += 1
|
||||
|
||||
return recommendations_to_upload
|
||||
return property_recommendations
|
||||
|
||||
|
||||
@router.post("/trigger")
|
||||
|
|
@ -197,6 +198,8 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
|
||||
logger.info("Getting components and properties recommendations")
|
||||
|
||||
# TODO: Move this to a class. We probably was a Recommender class which takes the injects the optimisers
|
||||
# in as a dependency and then the optimisers can take the input measures in as part of the setup() method
|
||||
recommendations = {}
|
||||
for p in input_properties:
|
||||
property_recommendations = []
|
||||
|
|
@ -264,37 +267,14 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
if wall_recomender.recommendations:
|
||||
property_recommendations.append(wall_recomender.recommendations)
|
||||
|
||||
recommendations[p.id] = property_recommendations
|
||||
# Use the optimiser to pick the default recommendations and decide if we need certain
|
||||
# recommendations to get to the goal
|
||||
property_recommendations = insert_temp_recommendation_id(property_recommendations)
|
||||
|
||||
# Once we're done, we'll store:
|
||||
# 1) the property data
|
||||
# 2) the property details (epc)
|
||||
# 3) the recommendations
|
||||
|
||||
logger.info("Uploading recommendations to the database")
|
||||
# Upload property data
|
||||
for p in input_properties:
|
||||
property_details_epc = p.get_property_details_epc(portfolio_id=body.portfolio_id, rating_lookup=rating_lookup)
|
||||
create_property_details_epc(property_details_epc)
|
||||
|
||||
property_data = p.get_full_property_data()
|
||||
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_to_upload:
|
||||
if not property_recommendations:
|
||||
continue
|
||||
|
||||
recommendations_to_upload = insert_temp_recommendation_id(recommendations_to_upload)
|
||||
|
||||
# Optimise the recommendations
|
||||
|
||||
# We need to format the recommendations for the optimiser
|
||||
input_measures = prepare_input_measures(recommendations_to_upload, body.goal)
|
||||
input_measures = prepare_input_measures(property_recommendations, body.goal)
|
||||
|
||||
if body.budget:
|
||||
optimiser = GainOptimiser(input_measures, max_cost=body.budget)
|
||||
|
|
@ -315,19 +295,41 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
selected_recommendations = {r["id"] for r in solution}
|
||||
# We'll use the set of selected recommendations to filter the recommendations to upload
|
||||
|
||||
recommendations_to_upload = [
|
||||
property_recommendations = [
|
||||
[
|
||||
{**rec, "default": True if rec["recommendation_id"] in selected_recommendations else False}
|
||||
for rec in recommendations_by_type
|
||||
]
|
||||
for recommendations_by_type in recommendations_to_upload
|
||||
for recommendations_by_type in property_recommendations
|
||||
]
|
||||
|
||||
# We'll also unlist the recommendations so they're a bit easier to handle from here onwards
|
||||
recommendations_to_upload = [
|
||||
rec for recommendations_by_type in recommendations_to_upload for rec in recommendations_by_type
|
||||
property_recommendations = [
|
||||
rec for recommendations_by_type in property_recommendations for rec in recommendations_by_type
|
||||
]
|
||||
|
||||
recommendations[p.id] = property_recommendations
|
||||
|
||||
# Once we're done, we'll store:
|
||||
# 1) the property data
|
||||
# 2) the property details (epc)
|
||||
# 3) the recommendations
|
||||
|
||||
logger.info("Uploading recommendations to the database")
|
||||
# Upload property data
|
||||
for p in input_properties:
|
||||
property_details_epc = p.get_property_details_epc(portfolio_id=body.portfolio_id, rating_lookup=rating_lookup)
|
||||
create_property_details_epc(property_details_epc)
|
||||
|
||||
property_data = p.get_full_property_data()
|
||||
update_property_data(property_id=p.id, portfolio_id=body.portfolio_id, property_data=property_data)
|
||||
|
||||
# Upload recommendations
|
||||
recommendations_to_upload = recommendations.get(p.id, [])
|
||||
|
||||
if not recommendations_to_upload:
|
||||
continue
|
||||
|
||||
# Create a plan
|
||||
new_plan_id = create_plan(
|
||||
{
|
||||
|
|
@ -371,4 +373,12 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
recommendation_ids=uploaded_recommendation_ids
|
||||
)
|
||||
|
||||
logger.info("Creating portfolio aggregations")
|
||||
# We implement this in the simplest way possible which will be just to query the database for all
|
||||
# recommendations associated to the portfolio and then aggregate them. This is not the most efficient
|
||||
# way to do this, but it's the simplest and will be a process that we can re-use since when we change a
|
||||
# recommendation from being default to not default, we'll need to re-run this process to re-calculate the
|
||||
# the portfolion level impact
|
||||
aggregate_portfolio_recommendations(portfolio_id=body.portfolio_id)
|
||||
|
||||
return Response(status_code=200)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
def prepare_input_measures(recommendations_to_upload, goal):
|
||||
def prepare_input_measures(property_recommendations, goal):
|
||||
"""
|
||||
Basic function to convert recommendations_to_upload to a format that is
|
||||
suitable for the optimiser - large
|
||||
:param recommendations_to_upload: object containing the recommendations, created in the plan trigger api
|
||||
:param property_recommendations: object containing the recommendations, created in the plan trigger api
|
||||
:param goal: goal to be optimised for, should be one of the keys in gain_map. E.g. if the gain is SAP points,
|
||||
the goal should reflect that desired gain
|
||||
:return: Nested list of input measures
|
||||
|
|
@ -17,7 +17,7 @@ def prepare_input_measures(recommendations_to_upload, goal):
|
|||
raise NotImplementedError("Not implemented this gain type - investigate me")
|
||||
|
||||
input_measures = []
|
||||
for recs in recommendations_to_upload:
|
||||
for recs in property_recommendations:
|
||||
input_measures.append(
|
||||
[
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue