Added labour days to cavity walls costs

This commit is contained in:
Khalim Conn-Kowlessar 2023-11-28 12:39:11 +00:00
parent 1da9433ee2
commit 7ebfb3b99c
9 changed files with 51 additions and 16 deletions

View file

@ -3,15 +3,16 @@ from backend.app.db.models.recommendations import Plan, PlanRecommendations, Rec
from backend.app.db.models.portfolio import Portfolio
def aggregate_portfolio_recommendations(session, portfolio_id: int):
def aggregate_portfolio_recommendations(session, portfolio_id: int, total_valuation_increase: float):
# Aggregate multiple fields
aggregates = (
session.query(
func.sum(Recommendation.estimated_cost).label("cost"),
func.sum(Recommendation.total_work_hours).label("total_work_hours"),
func.sum(Recommendation.heat_demand).label("total_heat_demand"),
func.sum(Recommendation.energy_savings).label("total_energy_savings"),
func.sum(Recommendation.energy_cost_savings).label("energy_cost_savings")
func.sum(Recommendation.heat_demand).label("energy_savings"),
func.sum(Recommendation.co2_equivalent_savings).label("co2_equivalent_savings"),
func.sum(Recommendation.energy_cost_savings).label("energy_cost_savings"),
func.sum(Recommendation.labour_days).label("labour_days"),
)
.join(PlanRecommendations, PlanRecommendations.recommendation_id == Recommendation.id)
.join(Plan, Plan.id == PlanRecommendations.plan_id)
@ -22,9 +23,10 @@ def aggregate_portfolio_recommendations(session, portfolio_id: int):
aggregates_dict = {
"cost": aggregates.cost or 0,
"total_work_hours": aggregates.total_work_hours or 0,
"total_heat_demand": aggregates.total_heat_demand or 0,
"total_energy_savings": aggregates.total_energy_savings or 0,
"energy_savings": aggregates.energy_savings or 0,
"co2_equivalent_savings": aggregates.co2_equivalent_savings or 0,
"energy_cost_savings": aggregates.energy_cost_savings or 0,
"labour_days": aggregates.labour_days or 0,
}
# Get the portfolio and update the fields
@ -33,6 +35,9 @@ def aggregate_portfolio_recommendations(session, portfolio_id: int):
for key, value in aggregates_dict.items():
setattr(portfolio, key, value)
# Insert total valuation increase
portfolio.property_valuation_increase = total_valuation_increase
# Merge the updated portfolio back into the session
session.merge(portfolio)
session.flush()

View file

@ -83,7 +83,8 @@ def upload_recommendations(session: Session, recommendations_to_upload, property
"heat_demand": rec["heat_demand"],
"co2_equivalent_savings": rec["co2_equivalent_savings"],
"total_work_hours": rec["labour_hours"],
"energy_cost_savings": rec["energy_cost_savings"]
"energy_cost_savings": rec["energy_cost_savings"],
"labour_days": rec["labour_days"]
}
for rec in recommendations_to_upload
]

View file

@ -42,6 +42,7 @@ class Portfolio(Base):
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))

View file

@ -28,6 +28,7 @@ class Recommendation(Base):
property_valuation_increase = Column(Float)
rental_yield_increase = Column(Float)
total_work_hours = Column(Float)
labour_days = Column(Float)
class RecommendationMaterials(Base):

View file

@ -21,7 +21,7 @@ from backend.app.db.models.portfolio import rating_lookup
from backend.app.dependencies import validate_token
from backend.app.plan.schemas import PlanTriggerRequest
from backend.app.plan.utils import create_recommendation_scoring_data, get_cleaned
from backend.app.utils import epc_to_sap_lower_bound, read_csv_from_s3, read_parquet_from_s3
from backend.app.utils import epc_to_sap_lower_bound, read_csv_from_s3, read_parquet_from_s3, sap_to_epc
from backend.ml_models.api import ModelApi
from backend.Property import Property
@ -33,6 +33,7 @@ from recommendations.optimiser.optimiser_functions import prepare_input_measures
from recommendations.Recommendations import Recommendations
from utils.logger import setup_logger
from utils.s3 import read_dataframe_from_s3_parquet
from backend.ml_models.Valuation import PropertyValuation
logger = setup_logger()
@ -250,6 +251,7 @@ async def trigger_plan(body: PlanTriggerRequest):
# 3) the recommendations
logger.info("Uploading recommendations to the database")
property_valuation_increases = []
session.commit()
for i in range(0, len(input_properties), BATCH_SIZE):
try:
@ -289,6 +291,16 @@ async def trigger_plan(body: PlanTriggerRequest):
session, plan_id=new_plan_id, recommendation_ids=uploaded_recommendation_ids
)
# Get defaults
default_recommendations = [r for r in recommendations_to_upload if r["default"]]
total_sap_points = sum([r["sap_points"] for r in default_recommendations])
new_sap_points = float(p.data["current-energy-efficiency"]) + total_sap_points
new_epc = sap_to_epc(new_sap_points)
property_valuation_increases.append(
PropertyValuation.estimate(property_instance=p, target_epc=new_epc)
)
# Commit the session after each batch
session.commit()
@ -304,7 +316,12 @@ async def trigger_plan(body: PlanTriggerRequest):
# 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(session, portfolio_id=body.portfolio_id)
total_valuation_increase = sum(property_valuation_increases)
aggregate_portfolio_recommendations(
session, portfolio_id=body.portfolio_id, total_valuation_increase=total_valuation_increase
)
# Commit final changes
session.commit()

View file

@ -12,12 +12,12 @@ class PropertyValuation:
{
"starting_epc": "D",
"ending_epc": "C",
"increase_percentage": 0.057,
"increase_percentage": 0.03625,
},
{
"starting_epc": "D",
"ending_epc": "B",
"increase_percentage": 0.057,
"increase_percentage": 0.05725,
},
]
@ -30,7 +30,7 @@ class PropertyValuation:
valuation_increases = [
v for v in cls.VALUE_INCREASE_MAPPING if
v["starting_epc"] == property_instance.epc_band and v["ending_epc"] == target_epc
v["starting_epc"] == property_instance.data["current-energy-rating"] and v["ending_epc"] == target_epc
]
if len(valuation_increases) != 1:

View file

@ -115,6 +115,9 @@ class Costs:
labour_hours = material["labour_hours_per_unit"] * wall_area
# Assume a team of 2
labour_days = (labour_hours / 8) / 2
return {
"total": total_cost,
"subtotal": subtotal_before_vat,
@ -124,7 +127,8 @@ class Costs:
"material": base_material_cost,
"profit": profit_cost,
"labour_hours": labour_hours,
"labour_cost": labour_cost
"labour_cost": labour_cost,
"labour_days": labour_days
}
def loft_insulation(self, floor_area, material):
@ -153,6 +157,9 @@ class Costs:
labour_hours = material["labour_hours_per_unit"] * floor_area
# Assume a team of 1 person
labour_days = labour_hours / 8
return {
"total": total_cost,
"subtotal": subtotal_before_vat,
@ -162,7 +169,8 @@ class Costs:
"material": base_material_cost,
"profit": profit_cost,
"labour_hours": labour_hours,
"labour_cost": labour_cost
"labour_cost": labour_cost,
"labour_days": labour_days
}
def internal_wall_insulation(self, wall_area, material, non_insulation_materials):

View file

@ -45,6 +45,7 @@ class FireplaceRecommendations(Definitions):
"sap_points": None,
"total": estimated_cost,
# Take a very basic estimate of 6 hours, multipled by the number of open fireplaces to seal
"labour_hours": 6 * number_open_fireplaces
"labour_hours": 6 * number_open_fireplaces,
"labour_days": 6 * number_open_fireplaces / 8, # Assume 8 hour day
}
]

View file

@ -67,6 +67,7 @@ class VentilationRecommendations(Definitions):
"sap_points": None,
"total": estimated_cost,
# We use a very simple and rough estimate of 4 hours per unit
"labour_hours": 4 * n_units
"labour_hours": 4 * n_units,
"labour_days": 4 * n_units / 8.0 # Assume 8 hour day
}
]