mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
568 lines
28 KiB
Python
568 lines
28 KiB
Python
import pandas as pd
|
|
from backend.Property import Property
|
|
from typing import List
|
|
from itertools import groupby
|
|
from recommendations.FloorRecommendations import FloorRecommendations
|
|
from recommendations.WallRecommendations import WallRecommendations
|
|
from recommendations.RoofRecommendations import RoofRecommendations
|
|
from recommendations.VentilationRecommendations import VentilationRecommendations
|
|
from recommendations.FireplaceRecommendations import FireplaceRecommendations
|
|
from recommendations.LightingRecommendations import LightingRecommendations
|
|
from recommendations.SolarPvRecommendations import SolarPvRecommendations
|
|
from recommendations.WindowsRecommendations import WindowsRecommendations
|
|
from recommendations.HeatingRecommender import HeatingRecommender
|
|
from recommendations.HotwaterRecommendations import HotwaterRecommendations
|
|
from recommendations.SecondaryHeating import SecondaryHeating
|
|
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
|
from backend.apis.GoogleSolarApi import GoogleSolarApi
|
|
|
|
|
|
class Recommendations:
|
|
"""
|
|
High level recommendations class, which sits above the measure specific recommendation classes
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
property_instance: Property,
|
|
materials: List,
|
|
exclusions: List[str] = None,
|
|
):
|
|
"""
|
|
:param property_instance: Instance of the Property class, for the home associated to property_id
|
|
:param materials: List of materials to be used in the recommendations
|
|
"""
|
|
|
|
self.property_instance = property_instance
|
|
self.materials = materials
|
|
self.exclusions = exclusions if exclusions else []
|
|
|
|
self.floor_recommender = FloorRecommendations(property_instance=property_instance, materials=materials)
|
|
self.wall_recomender = WallRecommendations(property_instance=property_instance, materials=materials)
|
|
self.roof_recommender = RoofRecommendations(property_instance=property_instance, materials=materials)
|
|
self.ventilation_recomender = VentilationRecommendations(
|
|
property_instance=property_instance, materials=materials
|
|
)
|
|
self.fireplace_recommender = FireplaceRecommendations(property_instance=property_instance)
|
|
self.lighting_recommender = LightingRecommendations(property_instance=property_instance, materials=materials)
|
|
self.windows_recommender = WindowsRecommendations(property_instance=property_instance, materials=materials)
|
|
self.solar_recommender = SolarPvRecommendations(property_instance=property_instance)
|
|
self.heating_recommender = HeatingRecommender(property_instance=property_instance)
|
|
self.hotwater_recommender = HotwaterRecommendations(property_instance=property_instance)
|
|
self.secondary_heating_recommender = SecondaryHeating(property_instance=property_instance)
|
|
|
|
def recommend(self):
|
|
|
|
"""
|
|
This method runs the recommendations for the individual measures and then appends them to a list for output
|
|
|
|
The recommendations are implemented in order of suggested phase, from fabric first to heating systems, to
|
|
renewables.
|
|
:return:
|
|
"""
|
|
|
|
property_recommendations = []
|
|
phase = 0
|
|
|
|
# Building Fabric
|
|
if "wall_insulation" not in self.exclusions:
|
|
self.wall_recomender.recommend(phase=phase, exclusions=self.exclusions)
|
|
if self.wall_recomender.recommendations:
|
|
property_recommendations.append(self.wall_recomender.recommendations)
|
|
phase += 1
|
|
|
|
if "roof_insulation" not in self.exclusions:
|
|
self.roof_recommender.recommend(phase=phase)
|
|
if self.roof_recommender.recommendations:
|
|
property_recommendations.append(self.roof_recommender.recommendations)
|
|
phase += 1
|
|
|
|
# Ventilation recommendations
|
|
# We only produce a ventilation recommendation if the property is recommended to have wall or roof
|
|
# insulation
|
|
# We will not attribute a SAP impact to the ventilation recommendation, since we've seen that this
|
|
# has no
|
|
# real impact on the SAP score. Therefore, we don't need to include phasing for ventilation. If we
|
|
# have any
|
|
# wall or roof recommendations, we will ensure that ventilation is included in the simulation
|
|
if "ventilation" not in self.exclusions:
|
|
if self.wall_recomender.recommendations or self.roof_recommender.recommendations:
|
|
self.ventilation_recomender.recommend()
|
|
if self.ventilation_recomender.recommendation:
|
|
property_recommendations.append(self.ventilation_recomender.recommendation)
|
|
|
|
if "floor_insulation" not in self.exclusions:
|
|
self.floor_recommender.recommend(phase=phase)
|
|
if self.floor_recommender.recommendations:
|
|
property_recommendations.append(self.floor_recommender.recommendations)
|
|
phase += 1
|
|
|
|
if "windows" not in self.exclusions:
|
|
self.windows_recommender.recommend(phase=phase)
|
|
if self.windows_recommender.recommendation:
|
|
property_recommendations.append(self.windows_recommender.recommendation)
|
|
phase += 1
|
|
|
|
if "fireplace" not in self.exclusions:
|
|
self.fireplace_recommender.recommend(phase=phase)
|
|
if self.fireplace_recommender.recommendation:
|
|
property_recommendations.append(self.fireplace_recommender.recommendation)
|
|
phase += 1
|
|
|
|
# Heating and Electical systems
|
|
if "heating" not in self.exclusions:
|
|
|
|
cavity_or_loft_recommendations = [
|
|
r for r in self.wall_recomender.recommendations + self.roof_recommender.recommendations
|
|
if r["type"] in ["cavity_wall_insulation", "loft_insulation"]
|
|
]
|
|
has_cavity_or_loft_recommendations = len(cavity_or_loft_recommendations) > 0
|
|
|
|
self.heating_recommender.recommend(
|
|
phase=phase,
|
|
has_cavity_or_loft_recommendations=has_cavity_or_loft_recommendations,
|
|
exclusions=self.exclusions
|
|
)
|
|
if (
|
|
self.heating_recommender.heating_recommendations or
|
|
self.heating_recommender.heating_control_recommendations
|
|
):
|
|
|
|
# We split into first and second phase recommendations
|
|
first_phase_recommendations = [
|
|
r for r in (
|
|
self.heating_recommender.heating_recommendations +
|
|
self.heating_recommender.heating_control_recommendations
|
|
)
|
|
if r["phase"] == phase
|
|
]
|
|
second_phase_recommendations = [
|
|
r for r in (
|
|
self.heating_recommender.heating_recommendations +
|
|
self.heating_recommender.heating_control_recommendations
|
|
)
|
|
if r["phase"] == phase + 1
|
|
]
|
|
|
|
if first_phase_recommendations:
|
|
property_recommendations.append(first_phase_recommendations)
|
|
|
|
if second_phase_recommendations:
|
|
property_recommendations.append(second_phase_recommendations)
|
|
|
|
# We check if we have distinct heating and heating controls recommendations
|
|
# If so, we increment by 2 (one of the heating system, one for the heating controls)
|
|
# otherwise we incremenet by 1
|
|
max_used_phase = max(
|
|
[rec["phase"] for rec in
|
|
self.heating_recommender.heating_recommendations +
|
|
self.heating_recommender.heating_control_recommendations]
|
|
)
|
|
amount_to_increment = max_used_phase - phase + 1
|
|
phase += amount_to_increment
|
|
|
|
# Hot water
|
|
if "hot_water" not in self.exclusions:
|
|
self.hotwater_recommender.recommend(phase=phase)
|
|
if self.hotwater_recommender.recommendations:
|
|
property_recommendations.append(self.hotwater_recommender.recommendations)
|
|
phase += 1
|
|
|
|
if "lighting" not in self.exclusions:
|
|
self.lighting_recommender.recommend(phase=phase)
|
|
if self.lighting_recommender.recommendation:
|
|
property_recommendations.append(self.lighting_recommender.recommendation)
|
|
phase += 1
|
|
|
|
if "secondary_heating" not in self.exclusions:
|
|
self.secondary_heating_recommender.recommend(phase=phase)
|
|
if self.secondary_heating_recommender.recommendation:
|
|
property_recommendations.append(self.secondary_heating_recommender.recommendation)
|
|
phase += 1
|
|
|
|
# Renewables
|
|
if "solar_pv" not in self.exclusions:
|
|
self.solar_recommender.recommend(phase=phase)
|
|
if self.solar_recommender.recommendation:
|
|
property_recommendations.append(self.solar_recommender.recommendation)
|
|
phase += 1
|
|
|
|
# We insert temporary ids into the recommendations which is important for the optimiser later
|
|
property_recommendations = self.insert_temp_recommendation_id(property_recommendations)
|
|
|
|
# We also need to create the representative recommendations for each recommendation type
|
|
property_representative_recommendations = self.create_representative_recommendations(
|
|
property_recommendations, non_invasive_recommendations=self.property_instance.non_invasive_recommendations
|
|
)
|
|
|
|
return property_recommendations, property_representative_recommendations
|
|
|
|
@staticmethod
|
|
def create_representative_recommendations(property_recommendations, non_invasive_recommendations):
|
|
"""
|
|
This method will create a representative recommendation for each recommendation type
|
|
In order to create a representative recommendation, we choose the recommendation that has:
|
|
1) Where a U-value is available, has the best U-value to cost ratio
|
|
2) Where SAP points are available, has the best SAP points to cost ratio
|
|
|
|
We don't include mechanical ventilation in the representative recommendations, since we don't attribute a
|
|
SAP impact to this recommendation
|
|
:return:
|
|
"""
|
|
property_representative_recommendations = []
|
|
|
|
for recommendations_by_type in property_recommendations:
|
|
|
|
# If the property was initially surveyed as filled, but the cavity was only partially filled, we don't
|
|
# want to include the cavity wall insulation recommendation in the defaults
|
|
# if (recommendations_by_type[0].get("type") == "cavity_wall_insulation") and (
|
|
# "cavity_surveyed_as_filled_is_partial" in non_invasive_recommendations
|
|
# ):
|
|
# continue
|
|
|
|
if recommendations_by_type[0].get("type") == "mechanical_ventilation":
|
|
continue
|
|
|
|
has_u_value = recommendations_by_type[0].get("new_u_value") is not None
|
|
has_sap_points = recommendations_by_type[0].get("sap_points") is not None
|
|
has_rank = recommendations_by_type[0].get("rank") is not None
|
|
|
|
# When check if these recommendations have two different types, such as solid wall insulation
|
|
# If we have multiple types, we group by type and then select the best recommendation for each type
|
|
|
|
# If we have a heating and heating control recommendation, we use JUST the heating reommendation
|
|
has_both_heating_types = all(
|
|
x in [rec["type"] for rec in recommendations_by_type] for x in ["heating", "heating_control"]
|
|
)
|
|
if has_both_heating_types:
|
|
# Take just heating
|
|
recommendations_by_type = [
|
|
rec for rec in recommendations_by_type if rec["type"] == "heating"
|
|
]
|
|
|
|
recommendations_by_type = sorted(recommendations_by_type, key=lambda x: x["type"])
|
|
representative_recommendations = []
|
|
for _type, recommendations in groupby(recommendations_by_type, key=lambda x: x["type"]):
|
|
recommendations = list(recommendations)
|
|
# We also create an efficiency key, which is used to sort the recommendations
|
|
if has_u_value:
|
|
# We sort by the cost per U-value improvement - the lower the better
|
|
for rec in recommendations:
|
|
rec["efficiency"] = rec["total"] / rec["starting_u_value"] - rec["new_u_value"]
|
|
elif not has_u_value and has_sap_points:
|
|
# Sort the options by the cost per SAP point improvement - the lower the better
|
|
for rec in recommendations:
|
|
rec["efficiency"] = rec["total"] / rec["sap_points"]
|
|
elif has_rank:
|
|
# Sort the options by rank - the lower the better
|
|
for rec in recommendations:
|
|
rec["efficiency"] = rec["rank"]
|
|
else:
|
|
# Sort the options by cost - the lower the better
|
|
for rec in recommendations:
|
|
rec["efficiency"] = rec["total"]
|
|
|
|
recommendations.sort(
|
|
key=lambda x: x["efficiency"]
|
|
)
|
|
representative_recommendations.append(recommendations[0])
|
|
property_representative_recommendations.extend(representative_recommendations)
|
|
|
|
return property_representative_recommendations
|
|
|
|
@staticmethod
|
|
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 property_recommendations: nested list of recommendations, grouped by data_types
|
|
:return: Updated recommendations_to_upload, where where recommendation has a "recommendation_id"
|
|
integer inserted
|
|
"""
|
|
idx = 0
|
|
|
|
for recs in property_recommendations:
|
|
for rec in recs:
|
|
rec["recommendation_id"] = f"{str(idx)}_phase={str(rec['phase'])}"
|
|
idx += 1
|
|
|
|
return property_recommendations
|
|
|
|
@staticmethod
|
|
def _calculate_appliance_solar_savings(
|
|
rec, property_instance, heating_kwh_reduction, hot_water_kwh_reduction, lighting_kwh_reduction
|
|
):
|
|
"""
|
|
Calculates the impact on kwh and cost of installing solar panels on appliances
|
|
:param rec: The recommendation
|
|
:param property_instance: Instance of the Property class
|
|
:param heating_kwh_reduction: The kwh reduction from heating
|
|
:param hot_water_kwh_reduction: The kwh reduction from hot water
|
|
:param lighting_kwh_reduction: The kwh reduction from lighting
|
|
:return:
|
|
"""
|
|
|
|
if rec["type"] != "solar_pv":
|
|
return 0, 0
|
|
|
|
if property_instance.solar_panel_configuration is None:
|
|
print("PLACEHOLDER ESTIMATES")
|
|
# 50% reduction average
|
|
kwh_reduction = property_instance.energy_consumption_estimates["adjusted"]["appliances"] * 0.5
|
|
predicted_appliances_cost_reduction = kwh_reduction * AnnualBillSavings.ELECTRICITY_PRICE_CAP
|
|
return predicted_appliances_cost_reduction, kwh_reduction
|
|
|
|
# Calulate the amount of energy the solar panel array will generate for this unit
|
|
unit_energy_consumption = (
|
|
rec["initial_ac_kwh_per_year"] *
|
|
property_instance.solar_panel_configuration["unit_share_of_energy"]
|
|
)
|
|
|
|
unit_energy_utilised = unit_energy_consumption * GoogleSolarApi.SOLAR_CONSUMPTION_PROPORTION
|
|
unit_energy_exported = unit_energy_consumption - unit_energy_utilised
|
|
unit_energy_exported_value = unit_energy_exported * AnnualBillSavings.ELECTRICITY_EXPORT_PAYMENT
|
|
|
|
# We assume that 50% of the energy generated will be used by the property without a battery
|
|
# to be conservative
|
|
|
|
# of the energy utilised, some of it is used by heating, hot water and lighting so we
|
|
# remove that from the total
|
|
unit_energy_utilised -= (
|
|
heating_kwh_reduction + hot_water_kwh_reduction + lighting_kwh_reduction
|
|
)
|
|
unit_energy_utilised = 0 if unit_energy_utilised < 0 else unit_energy_utilised
|
|
|
|
# This is how much energy the appliances will use after install
|
|
post_install_appliance_kwh = (
|
|
property_instance.energy_consumption_estimates["adjusted"]["appliances"] -
|
|
unit_energy_utilised
|
|
)
|
|
post_install_appliance_kwh = (
|
|
0 if post_install_appliance_kwh < 0 else post_install_appliance_kwh
|
|
)
|
|
|
|
predicted_appliances_kwh_reduction = (
|
|
property_instance.energy_consumption_estimates["adjusted"]["appliances"] -
|
|
post_install_appliance_kwh
|
|
)
|
|
|
|
predicted_appliances_cost_reduction = unit_energy_exported_value + (
|
|
predicted_appliances_kwh_reduction * AnnualBillSavings.ELECTRICITY_PRICE_CAP
|
|
)
|
|
|
|
return predicted_appliances_cost_reduction, predicted_appliances_kwh_reduction
|
|
|
|
@classmethod
|
|
def calculate_recommendation_impact(
|
|
cls,
|
|
property_instance,
|
|
all_predictions,
|
|
recommendations,
|
|
):
|
|
|
|
"""
|
|
Given predictions from the model apis, with method will update the recommendations with the predicted
|
|
impact of the recommendation on the property
|
|
|
|
This function will return two objects:
|
|
1) Updated recommendations with the predicted impact of the recommendation
|
|
2) A list of impacts by phase, which will be used for the kwh model scoring
|
|
|
|
:param property_instance: Instance of the Property class, for the home associated to property_id
|
|
:param all_predictions: dictionary of predictions from the model apis
|
|
:param recommendations: dictionary of recommendations for the property
|
|
:return:
|
|
"""
|
|
|
|
property_predictions = {
|
|
prefix + "_predictions": all_predictions[prefix + "_predictions"][
|
|
all_predictions[prefix + "_predictions"]["property_id"] == str(property_instance.id)
|
|
].copy() for prefix in [
|
|
"sap_change", "heat_demand", "carbon_change", "lighting_cost", "heating_cost", "hot_water_cost"
|
|
]
|
|
}
|
|
|
|
# We apply adjustments to each of the heating costs
|
|
for prefix in ["lighting_cost", "heating_cost", "hot_water_cost"]:
|
|
property_predictions[f"{prefix}_predictions"]["adjusted_cost"] = (
|
|
property_predictions[f"{prefix}_predictions"]["predictions"].apply(
|
|
lambda x: AnnualBillSavings.adjust_energy_to_metered(
|
|
x, current_epc_rating=property_instance.data["current-energy-rating"]
|
|
)
|
|
)
|
|
)
|
|
|
|
property_recommendations = recommendations[property_instance.id].copy()
|
|
|
|
# We calculate the impact by phase
|
|
phase_impact = {
|
|
prefix: property_predictions[prefix + "_predictions"].groupby("phase")["predictions"].median().reset_index()
|
|
for prefix in [
|
|
"sap_change", "heat_demand", "carbon_change", "lighting_cost", "heating_cost", "hot_water_cost"
|
|
]
|
|
}
|
|
|
|
# TODO: should fabric upgrades have an impact on hot water costs/kwh?
|
|
# TODO: Generally, the costing models are just increasing. Maybe they're including something in the model
|
|
# that they shouldn't e.g. SAP, carbon, heat demand etc?
|
|
|
|
impact_summary = []
|
|
for recommendations_by_type in property_recommendations:
|
|
for rec in recommendations_by_type:
|
|
if rec["type"] == "mechanical_ventilation":
|
|
# We don't have a percieved sap impact of mechanical ventilation
|
|
continue
|
|
|
|
phase_energy_efficiency_metrics = {
|
|
prefix: property_predictions[prefix + "_predictions"][
|
|
property_predictions[prefix + "_predictions"]["recommendation_id"] == str(
|
|
rec["recommendation_id"]
|
|
)]["predictions"].values[0] for prefix in ["sap_change", "heat_demand", "carbon_change"]
|
|
}
|
|
|
|
# For phase costs, we need adusted and unadjusted values
|
|
phase_cost = {
|
|
prefix: property_predictions[prefix + "_predictions"][
|
|
property_predictions[prefix + "_predictions"]["recommendation_id"] ==
|
|
str(rec["recommendation_id"])
|
|
] for prefix in ["lighting_cost", "heating_cost", "hot_water_cost"]
|
|
}
|
|
|
|
# We structure this so that depending on the phase, we capture the previous phase impacts and
|
|
# then just have one piece of code to calculate the difference
|
|
if rec["phase"] == 0:
|
|
previous_phase_values = {
|
|
"sap": float(property_instance.data["current-energy-efficiency"]),
|
|
"carbon": float(property_instance.data["co2-emissions-current"]),
|
|
"heat_demand": float(property_instance.data["energy-consumption-current"]),
|
|
}
|
|
|
|
if rec["type"] == "low_energy_lighting":
|
|
# In this instance, heating cost and hot water cost should not change so we set the previous
|
|
# value to the new one, so the difference is zero
|
|
previous_phase_unadjusted_costs = {
|
|
"unadjusted_heating_cost": phase_cost["heating_cost"]["predictions"].values[0],
|
|
"unadjusted_hot_water_cost": phase_cost["hot_water_cost"]["predictions"].values[0],
|
|
"unadjusted_lighting_cost": (
|
|
property_instance.energy_cost_estimates["unadjusted"]["lighting"]
|
|
)
|
|
}
|
|
else:
|
|
# If the recommendaiton is not for low energy lighting, we expect the heating/hot water
|
|
# costs to change but not te lighting
|
|
previous_phase_unadjusted_costs = {
|
|
"unadjusted_heating_cost": property_instance.energy_cost_estimates["adjusted"]["heating"],
|
|
"unadjusted_hot_water_cost": (
|
|
property_instance.energy_cost_estimates["adjusted"]["hot_water"]
|
|
),
|
|
"unadjusted_lighting_cost": phase_cost["lighting_cost"]["predictions"].values[0]
|
|
}
|
|
else:
|
|
previous_phase_values = {
|
|
"sap": (
|
|
phase_impact["sap_change"][phase_impact["sap_change"]["phase"] == (rec["phase"] - 1)]
|
|
["predictions"].values[0]
|
|
),
|
|
"carbon": (
|
|
phase_impact["carbon_change"][phase_impact["carbon_change"]["phase"] == (rec["phase"] - 1)]
|
|
["predictions"].values[0]
|
|
),
|
|
"heat_demand": (
|
|
phase_impact["heat_demand"][phase_impact["heat_demand"]["phase"] == (rec["phase"] - 1)]
|
|
["predictions"].values[0]
|
|
),
|
|
}
|
|
|
|
if rec["type"] == "low_energy_lighting":
|
|
# Heating and hot water costs shouldn't change
|
|
# {'unadjusted_heating_cost': 501.8528134938132, 'unadjusted_hot_water_cost':
|
|
# 171.22534405283452, 'unadjusted_lighting_cost': 127.2}
|
|
previous_phase_unadjusted_costs = {
|
|
"unadjusted_heating_cost": phase_cost["heating_cost"]["predictions"].values[0],
|
|
"unadjusted_hot_water_cost": phase_cost["hot_water_cost"]["predictions"].values[0],
|
|
"unadjusted_lighting_cost": phase_impact["lighting_cost"][
|
|
phase_impact["lighting_cost"]["phase"] == (rec["phase"] - 1)
|
|
]["predictions"].values[0]
|
|
}
|
|
else:
|
|
# update heating and hot water costs
|
|
previous_phase_unadjusted_costs = {
|
|
"unadjusted_heating_cost": phase_impact["heating_cost"][
|
|
phase_impact["heating_cost"]["phase"] == (rec["phase"] - 1)
|
|
]["predictions"].values[0],
|
|
"unadjusted_hot_water_cost": phase_impact["hot_water_cost"][
|
|
phase_impact["hot_water_cost"]["phase"] == (rec["phase"] - 1)
|
|
]["predictions"].values[0],
|
|
"unadjusted_lighting_cost": phase_cost["lighting_cost"]["predictions"].values[0]
|
|
}
|
|
|
|
previous_phase_values.update(previous_phase_unadjusted_costs)
|
|
|
|
# We extract the values for the current phase
|
|
current_phase_values = {
|
|
"sap": phase_energy_efficiency_metrics["sap_change"],
|
|
"carbon": phase_energy_efficiency_metrics["carbon_change"],
|
|
"heat_demand": phase_energy_efficiency_metrics["heat_demand"],
|
|
"unadjusted_heating_cost": phase_cost["heating_cost"]["predictions"].values[0],
|
|
"unadjusted_hot_water_cost": phase_cost["hot_water_cost"]["predictions"].values[0],
|
|
"unadjusted_lighting_cost": phase_cost["lighting_cost"]["predictions"].values[0]
|
|
}
|
|
|
|
property_phase_impact = {
|
|
# Increasing
|
|
"sap": current_phase_values["sap"] - previous_phase_values["sap"],
|
|
# Decreasing
|
|
"carbon": previous_phase_values["carbon"] - current_phase_values["carbon"],
|
|
# Decreasing
|
|
"heat_demand": previous_phase_values["heat_demand"] - current_phase_values["heat_demand"],
|
|
# Decreasing
|
|
"unadjusted_heating_cost": (
|
|
previous_phase_values["unadjusted_heating_cost"] -
|
|
current_phase_values["unadjusted_heating_cost"]
|
|
),
|
|
# Decreasing
|
|
"unadjusted_hot_water_cost": (
|
|
previous_phase_values["unadjusted_hot_water_cost"] -
|
|
current_phase_values["unadjusted_hot_water_cost"]
|
|
),
|
|
# Decreasing
|
|
"unadjusted_lighting_cost": (
|
|
previous_phase_values["unadjusted_lighting_cost"] -
|
|
current_phase_values["unadjusted_lighting_cost"]
|
|
)
|
|
}
|
|
|
|
# Prevent from being negative
|
|
for metric in ["sap", "carbon", "heat_demand"]:
|
|
property_phase_impact[metric] = (
|
|
0 if property_phase_impact[metric] < 0 else property_phase_impact[metric]
|
|
)
|
|
if metric == "sap":
|
|
property_phase_impact[metric] = round(property_phase_impact[metric], 2)
|
|
|
|
# For the moment, we cap the number of SAP points that can be achieved by LEDs at 2
|
|
if rec["type"] == "low_energy_lighting":
|
|
property_phase_impact["sap"] = min(property_phase_impact["sap"], LightingRecommendations.SAP_LIMIT)
|
|
property_phase_impact["carbon"] = min(
|
|
property_phase_impact["carbon"], rec["co2_equivalent_savings"]
|
|
)
|
|
|
|
# Insert this information into the recommendation
|
|
rec["sap_points"] = property_phase_impact["sap"]
|
|
rec["co2_equivalent_savings"] = property_phase_impact["carbon"]
|
|
rec["heat_demand"] = property_phase_impact["heat_demand"]
|
|
|
|
if (rec["sap_points"] is None) and (rec["co2_equivalent_savings"] is None) or (
|
|
rec["heat_demand"] is None):
|
|
raise ValueError("sap points, co2 or heat demand is missing")
|
|
|
|
impact_summary.append(
|
|
{
|
|
"phase": rec["phase"],
|
|
"recommendation_id": rec["recommendation_id"],
|
|
**current_phase_values
|
|
}
|
|
)
|
|
|
|
return property_recommendations, impact_summary
|