diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index 661858b7..45d87dd3 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -59,7 +59,7 @@ def patch_epc(patch, epc_records): def extract_portfolio_aggregation_data( - input_properties, total_valuation_increase, recommendations, new_epc_bands + input_properties, total_valuation_increase, recommendations, new_epc_bands, property_value_increase_ranges ): # We aggregate a number of metrics for the portfolio: # 1) A breakdown of the number of properties in each EPC band @@ -69,7 +69,7 @@ def extract_portfolio_aggregation_data( # 3) Co2/unit # a) before retrofit # b) after retrofit - # 4) Energy bulls/unit + # 4) Energy bill/unit # a) before retrofit # b) after retrofit # 5) Average valuation improvement/unit @@ -105,6 +105,8 @@ def extract_portfolio_aggregation_data( # Get just the default recommendations default_recommendations = [r for r in property_recommendations if r["default"]] + has_recommendations = len(default_recommendations) > 0 + # We can now calculate multiple outputs based on default recommendations carbon_savings = sum([r["co2_equivalent_savings"] for r in default_recommendations]) @@ -125,6 +127,15 @@ def extract_portfolio_aggregation_data( cost = sum([r["total"] for r in default_recommendations]) sap_point_improvement = sum([r["sap_points"] for r in default_recommendations]) + lower_bound_valuation_uplift = ( + property_value_increase_ranges[p.id]["lower_bound_increased_value"] - + property_value_increase_ranges[p.id]["current_value"] + ) + upper_bound_valuation_uplift = ( + property_value_increase_ranges[p.id]["upper_bound_increased_value"] - + property_value_increase_ranges[p.id]["current_value"] + ) + agg_data.append({ "pre_retrofit_epc": p.data["current-energy-rating"], "post_retrofit_epc": new_epc_bands[p.id], @@ -135,14 +146,22 @@ def extract_portfolio_aggregation_data( "pre_retrofit_energy_consumption": pre_retrofit_energy_consumption, "post_retrofit_energy_consumption": post_retrofit_energy_consumption, "cost": cost, - "sap_point_improvement": sap_point_improvement + "sap_point_improvement": sap_point_improvement, + "lower_bound_valuation_uplift": lower_bound_valuation_uplift, + "upper_bound_valuation_uplift": upper_bound_valuation_uplift, + "has_recommendations": has_recommendations }) agg_data = pd.DataFrame(agg_data) - n_units_to_retrofit = len(agg_data) + n_units_to_retrofit = agg_data["has_recommendations"].sum() - valuation_improvment_per_unit = total_valuation_increase / n_units_to_retrofit + valuation_improvement_lower_bound_per_unit = ( + agg_data["lower_bound_valuation_uplift"].mean() + ) + valuation_improvement_upper_bound_per_unit = ( + agg_data["upper_bound_valuation_uplift"].mean() + ) total_carbon_saved = agg_data["pre_retrofit_co2"].sum() - agg_data["post_retrofit_co2"].sum() total_sap_points = agg_data["sap_point_improvement"].sum() @@ -150,6 +169,17 @@ def extract_portfolio_aggregation_data( def format_money(amount): return f"£{amount:,.0f}" + valuation_improvment_per_unit = format_money( + total_valuation_increase / n_units) + (f" ({format_money(valuation_improvement_lower_bound_per_unit)} - " + f"{format_money(valuation_improvement_upper_bound_per_unit)})") + + valuation_return_on_investment = ( + str(round(total_valuation_increase / agg_data["cost"].sum(), 2)) + + f" (" + f"{agg_data['lower_bound_valuation_uplift'].sum() / agg_data['cost'].sum():,.2f} - " + f"{agg_data['upper_bound_valuation_uplift'].sum() / agg_data['cost'].sum():,.2f})" + ) + aggregation_data = { "epc_breakdown_pre_retrofit": json.dumps( reformat_epc_data(agg_data["pre_retrofit_epc"].value_counts().to_dict()) @@ -167,11 +197,11 @@ def extract_portfolio_aggregation_data( round(agg_data["pre_retrofit_energy_consumption"].mean())) + "kWh", "energy_consumption_per_unit_post_retrofit": str( round(agg_data["post_retrofit_energy_consumption"].mean())) + "kWh", - "valuation_improvement_per_unit": format_money(valuation_improvment_per_unit), + "valuation_improvement_per_unit": valuation_improvment_per_unit, "cost_per_unit": format_money(agg_data["cost"].mean()), "cost_per_co2_saved": format_money(agg_data["cost"].sum() / total_carbon_saved), "cost_per_sap_point": format_money(agg_data["cost"].sum() / total_sap_points), - "valuation_return_on_investment": str(round(total_valuation_increase / agg_data["cost"].sum(), 2)) + "valuation_return_on_investment": valuation_return_on_investment, # TODO: Could we add 10yr carbon credits value? } @@ -446,6 +476,7 @@ async def trigger_plan(body: PlanTriggerRequest): property_valuation_increases = [] session.commit() new_epc_bands = {} + property_value_increase_ranges = {} for i in range(0, len(input_properties), BATCH_SIZE): try: # Take a slice of the input_properties list to make a batch @@ -460,6 +491,7 @@ async def trigger_plan(body: PlanTriggerRequest): new_epc_bands[p.id] = new_epc valuations = PropertyValuation.estimate(property_instance=p, target_epc=new_epc) + property_value_increase_ranges[p.id] = valuations # Your existing operations property_details_epc = p.get_property_details_epc( @@ -527,7 +559,8 @@ async def trigger_plan(body: PlanTriggerRequest): input_properties=input_properties, total_valuation_increase=total_valuation_increase, recommendations=recommendations, - new_epc_bands=new_epc_bands + new_epc_bands=new_epc_bands, + property_value_increase_ranges=property_value_increase_ranges ) aggregate_portfolio_recommendations(