mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
creating new aggregations for front end
This commit is contained in:
parent
5d3440815d
commit
d6fa81939d
6 changed files with 146 additions and 8 deletions
2
.idea/Model.iml
generated
2
.idea/Model.iml
generated
|
|
@ -7,7 +7,7 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="non_invasive_surveys-photos" jdkType="Python SDK" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10 (backend)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyNamespacePackagesService">
|
||||
|
|
|
|||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
|
@ -3,7 +3,7 @@
|
|||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.10 (backend)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="non_invasive_surveys-photos" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (backend)" project-jdk-type="Python SDK" />
|
||||
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
|
|
|
|||
|
|
@ -142,6 +142,8 @@ class Property:
|
|||
|
||||
self.current_adjusted_energy = None
|
||||
self.expected_adjusted_energy = None
|
||||
self.current_energy_bill = None
|
||||
self.expected_energy_bill = None
|
||||
|
||||
self.recommendations_scoring_data = []
|
||||
|
||||
|
|
@ -892,12 +894,16 @@ class Property:
|
|||
|
||||
return component_data
|
||||
|
||||
def set_adjusted_energy(self, current_adjusted_energy, expected_adjusted_energy):
|
||||
def set_adjusted_energy(
|
||||
self, current_adjusted_energy, expected_adjusted_energy, current_energy_bill, expected_energy_bill
|
||||
):
|
||||
"""
|
||||
Stores these values for usage later
|
||||
"""
|
||||
self.current_adjusted_energy = current_adjusted_energy
|
||||
self.expected_adjusted_energy = expected_adjusted_energy
|
||||
self.current_energy_bill = current_energy_bill
|
||||
self.expected_energy_bill = expected_energy_bill
|
||||
|
||||
def set_windows_count(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from backend.app.db.models.portfolio import Portfolio
|
|||
|
||||
|
||||
def aggregate_portfolio_recommendations(
|
||||
session, portfolio_id: int, total_valuation_increase: float, labour_days: float
|
||||
session, portfolio_id: int, total_valuation_increase: float, labour_days: float, aggregated_data: dict
|
||||
):
|
||||
# Aggregate multiple fields
|
||||
aggregates = (
|
||||
|
|
@ -27,6 +27,7 @@ def aggregate_portfolio_recommendations(
|
|||
"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,
|
||||
**aggregated_data
|
||||
}
|
||||
|
||||
# Get the portfolio and update the fields
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from tqdm import tqdm
|
||||
|
|
@ -57,6 +58,109 @@ def patch_epc(patch, epc_records):
|
|||
return epc_records
|
||||
|
||||
|
||||
def extract_portfolio_aggregation_data(
|
||||
input_properties, total_valuation_increase, recommendations, new_epc_bands
|
||||
):
|
||||
# We aggregate a number of metrics for the portfolio:
|
||||
# 1) A breakdown of the number of properties in each EPC band
|
||||
# a) before retrofit
|
||||
# b) after retrofit
|
||||
# 2) Number of units
|
||||
# 3) Co2/unit
|
||||
# a) before retrofit
|
||||
# b) after retrofit
|
||||
# 4) Energy bulls/unit
|
||||
# a) before retrofit
|
||||
# b) after retrofit
|
||||
# 5) Average valuation improvement/unit
|
||||
# 6) Total cost
|
||||
# 7) Cost per unit
|
||||
# 8) £ per CO2 saved
|
||||
# 9) £ per SAP point
|
||||
|
||||
# We need to construct the underlyind data for this
|
||||
|
||||
# Helper function to reformat the EPC data
|
||||
def reformat_epc_data(epc_counts):
|
||||
# Define all possible EPC bands in the required order
|
||||
epc_bands = ["G", "F", "E", "D", "C", "B", "A"]
|
||||
|
||||
# Create the formatted data list by checking each band in the order
|
||||
formatted_data = []
|
||||
for band in epc_bands:
|
||||
# Get the count from the dictionary, defaulting to 0 if not present
|
||||
count = epc_counts.get(band, 0)
|
||||
# Append the formatted dictionary to the list
|
||||
formatted_data.append({"name": band, band: count})
|
||||
|
||||
return formatted_data
|
||||
|
||||
n_units = len(input_properties)
|
||||
|
||||
agg_data = []
|
||||
for p in input_properties:
|
||||
# Get the recommendations for the property
|
||||
property_recommendations = recommendations.get(p.id, [])
|
||||
if not property_recommendations:
|
||||
continue
|
||||
# Get just the default recommendations
|
||||
default_recommendations = [r for r in property_recommendations if r["default"]]
|
||||
|
||||
# We can now calculate multiple outputs based on default recommendations
|
||||
carbon_savings = sum([r["co2_equivalent_savings"] for r in default_recommendations])
|
||||
|
||||
pre_retrofit_co2 = p.data["co2-emissions-current"]
|
||||
post_retrofit_co2 = pre_retrofit_co2 - carbon_savings
|
||||
|
||||
pre_retrofit_energy_bill = p.current_energy_bill
|
||||
post_retrofit_energy_bill = p.expected_energy_bill
|
||||
|
||||
cost = sum([r["total"] for r in default_recommendations])
|
||||
sap_point_improvement = sum([r["sap_points"] for r in default_recommendations])
|
||||
|
||||
agg_data.append({
|
||||
"pre_retrofit_epc": p.data["current-energy-rating"],
|
||||
"post_retrofit_epc": new_epc_bands[p.id],
|
||||
"pre_retrofit_co2": pre_retrofit_co2,
|
||||
"post_retrofit_co2": post_retrofit_co2,
|
||||
"pre_retrofit_energy_bill": pre_retrofit_energy_bill,
|
||||
"post_retrofit_energy_bill": post_retrofit_energy_bill,
|
||||
"cost": cost,
|
||||
"sap_point_improvement": sap_point_improvement
|
||||
})
|
||||
|
||||
agg_data = pd.DataFrame(agg_data)
|
||||
|
||||
n_units_to_retrofit = len(agg_data)
|
||||
|
||||
valuation_improvment_per_unit = total_valuation_increase / n_units_to_retrofit
|
||||
|
||||
total_carbon_saved = agg_data["pre_retrofit_co2"].sum() - agg_data["post_retrofit_co2"].sum()
|
||||
total_sap_points = agg_data["sap_point_improvement"].sum()
|
||||
|
||||
aggregation_data = {
|
||||
"epc_breakdown_pre_retrofit": json.dumps(
|
||||
reformat_epc_data(agg_data["pre_retrofit_epc"].value_counts().to_dict())
|
||||
),
|
||||
"epc_breakdown_post_retrofit": json.dumps(
|
||||
reformat_epc_data(agg_data["post_retrofit_epc"].value_counts().to_dict())
|
||||
),
|
||||
"number_of_properties": n_units,
|
||||
"n_units_to_retrofit": n_units_to_retrofit,
|
||||
"co2_per_unit_pre_retrofit": agg_data["pre_retrofit_co2"].mean(),
|
||||
"co2_per_unit_post_retrofit": agg_data["post_retrofit_co2"].mean(),
|
||||
"energy_bill_per_unit_pre_retrofit": agg_data["pre_retrofit_energy_bill"].mean(),
|
||||
"energy_bill_per_unit_post_retrofit": agg_data["post_retrofit_energy_bill"].mean(),
|
||||
"valuation_improvement_per_unit": valuation_improvment_per_unit,
|
||||
"total_cost": agg_data["cost"].sum(),
|
||||
"cost_per_unit": agg_data["cost"].mean(),
|
||||
"cost_per_co2_saved": agg_data["cost"].sum() / total_carbon_saved,
|
||||
"cost_per_sap_point": agg_data["cost"].sum() / total_sap_points
|
||||
}
|
||||
|
||||
return aggregation_data
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/plan",
|
||||
tags=["plan"],
|
||||
|
|
@ -243,7 +347,13 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
|
||||
property_instance = [p for p in input_properties if p.id == property_id][0]
|
||||
|
||||
recommendations_with_impact, current_adjusted_energy, expected_adjusted_energy = (
|
||||
(
|
||||
recommendations_with_impact,
|
||||
current_adjusted_energy,
|
||||
expected_adjusted_energy,
|
||||
current_energy_bill,
|
||||
expected_energy_bill
|
||||
) = (
|
||||
Recommendations.calculate_recommendation_impact(
|
||||
property_instance=property_instance,
|
||||
all_predictions=all_predictions,
|
||||
|
|
@ -254,7 +364,9 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
# Store the resulting adjusted energy in the property instance
|
||||
property_instance.set_adjusted_energy(
|
||||
current_adjusted_energy=current_adjusted_energy,
|
||||
expected_adjusted_energy=expected_adjusted_energy
|
||||
expected_adjusted_energy=expected_adjusted_energy,
|
||||
current_energy_bill=current_energy_bill,
|
||||
expected_energy_bill=expected_energy_bill
|
||||
)
|
||||
|
||||
input_measures = prepare_input_measures(recommendations_with_impact, body.goal)
|
||||
|
|
@ -316,6 +428,7 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
logger.info("Uploading recommendations to the database")
|
||||
property_valuation_increases = []
|
||||
session.commit()
|
||||
new_epc_bands = {}
|
||||
for i in range(0, len(input_properties), BATCH_SIZE):
|
||||
try:
|
||||
# Take a slice of the input_properties list to make a batch
|
||||
|
|
@ -327,6 +440,7 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
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)
|
||||
new_epc_bands[p.id] = new_epc
|
||||
|
||||
valuations = PropertyValuation.estimate(property_instance=p, target_epc=new_epc)
|
||||
|
||||
|
|
@ -392,11 +506,19 @@ async def trigger_plan(body: PlanTriggerRequest):
|
|||
[sum(r["labour_days"] for r in rec_group if r["default"]) for p_id, rec_group in recommendations.items()]
|
||||
))
|
||||
|
||||
aggregated_data = extract_portfolio_aggregation_data(
|
||||
input_properties=input_properties,
|
||||
total_valuation_increase=total_valuation_increase,
|
||||
recommendations=recommendations,
|
||||
new_epc_bands=new_epc_bands
|
||||
)
|
||||
|
||||
aggregate_portfolio_recommendations(
|
||||
session,
|
||||
portfolio_id=body.portfolio_id,
|
||||
total_valuation_increase=total_valuation_increase,
|
||||
labour_days=labour_days
|
||||
labour_days=labour_days,
|
||||
aggregated_data=aggregated_data
|
||||
)
|
||||
|
||||
# Commit final changes
|
||||
|
|
|
|||
|
|
@ -281,6 +281,9 @@ class Recommendations:
|
|||
current_adjusted_energy - expected_adjusted_energy
|
||||
)
|
||||
|
||||
current_energy_bill = AnnualBillSavings.calculate_annual_bill(current_adjusted_energy)
|
||||
expected_energy_bill = AnnualBillSavings.calculate_annual_bill(expected_adjusted_energy)
|
||||
|
||||
for recommendations_by_type in property_recommendations:
|
||||
for rec in recommendations_by_type:
|
||||
|
||||
|
|
@ -355,4 +358,10 @@ class Recommendations:
|
|||
rec["heat_demand"] is None) or (rec["energy_cost_savings"] is None):
|
||||
raise ValueError("sap points, co2 or heat demand is missing")
|
||||
|
||||
return property_recommendations, current_adjusted_energy, expected_adjusted_energy
|
||||
return (
|
||||
property_recommendations,
|
||||
current_adjusted_energy,
|
||||
expected_adjusted_energy,
|
||||
current_energy_bill,
|
||||
expected_energy_bill
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue