mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
added initial test for recommendation impact calculation with adjustment
This commit is contained in:
parent
3ebb8667c8
commit
2b071e6afd
3 changed files with 960 additions and 28 deletions
|
|
@ -65,7 +65,7 @@ data["Wall Insulation"].value_counts()
|
|||
data["Wall Construction"].value_counts()
|
||||
|
||||
as_built_map = {
|
||||
"Cavity": {"insulated_age_bands":[], "partial_insulated_age_bands": []},
|
||||
"Cavity": {"insulated_age_bands": [], "partial_insulated_age_bands": []},
|
||||
"Solid Brick": {"insulated_age_bands": [], "partial_insulated_age_bands": []},
|
||||
"System": {"insulated_age_bands": [], "partial_insulated_age_bands": []},
|
||||
"Timber Frame": {"insulated_age_bands": [], "partial_insulated_age_bands": []},
|
||||
|
|
@ -74,6 +74,7 @@ as_built_map = {
|
|||
"Cob": {"insulated_age_bands": [], "partial_insulated_age_bands": []},
|
||||
}
|
||||
|
||||
|
||||
def map_wall_construction(wall_constuction, wall_insulation, construction_age_band):
|
||||
if wall_insulation == "AsBuilt":
|
||||
# Deduce based on wall construction and age band
|
||||
|
|
@ -83,13 +84,10 @@ def map_wall_construction(wall_constuction, wall_insulation, construction_age_ba
|
|||
|
||||
# We check if the age band is in insulated or partial insulated, and if neither, we assume uninsulated
|
||||
|
||||
|
||||
|
||||
|
||||
# Variables we want to map
|
||||
'Org Ref', 'Address 1', 'Address 2', 'Address 3', 'Postcode', 'Type',
|
||||
'Attachment', 'Construction Years', 'Wall Construction',
|
||||
'Wall Insulation', 'Roof Construction', 'Roof Insulation',
|
||||
'Floor Construction', 'Floor Insulation', 'Glazing', 'Heating',
|
||||
'Boiler Efficiency', 'Main Fuel', 'Controls Adequacy', 'UPRN',
|
||||
'Total Floor Area (m2)'
|
||||
# 'Org Ref', 'Address 1', 'Address 2', 'Address 3', 'Postcode', 'Type',
|
||||
# 'Attachment', 'Construction Years', 'Wall Construction',
|
||||
# 'Wall Insulation', 'Roof Construction', 'Roof Insulation',
|
||||
# 'Floor Construction', 'Floor Insulation', 'Glazing', 'Heating',
|
||||
# 'Boiler Efficiency', 'Main Fuel', 'Controls Adequacy', 'UPRN',
|
||||
# 'Total Floor Area (m2)'
|
||||
|
|
|
|||
|
|
@ -486,6 +486,34 @@ class Recommendations:
|
|||
|
||||
return predicted_appliances_cost_reduction, predicted_appliances_kwh_reduction
|
||||
|
||||
@staticmethod
|
||||
def _check_veniltation_out_of_bounds(sap_impact, ventilation_sap_limit):
|
||||
return (sap_impact < ventilation_sap_limit) or (sap_impact >= 0)
|
||||
|
||||
@staticmethod
|
||||
def _adjust_ventilation_sap(sap_impact, ventilation_sap_limit):
|
||||
if sap_impact >= 0:
|
||||
return -1
|
||||
if sap_impact < ventilation_sap_limit:
|
||||
return ventilation_sap_limit
|
||||
|
||||
@staticmethod
|
||||
def _filter_phase_adjustment(phase_adjustments):
|
||||
"""
|
||||
Utility function to select the entry from the dictionary, by phase, with the largest
|
||||
phase adjustment
|
||||
:param phase_adjustments: List of phase adjustments, in the form
|
||||
[{"recommendation_id": str, "phase": int, "adjustment_amount": float}]
|
||||
:return:
|
||||
"""
|
||||
filtered_adjustments = []
|
||||
phase_adjustments = sorted(phase_adjustments, key=lambda x: x["phase"])
|
||||
for phase, adjustments in groupby(phase_adjustments, key=lambda x: x["phase"]):
|
||||
adjustments = list(adjustments)
|
||||
adjustments.sort(key=lambda x: x["sap_adjustment"], reverse=True)
|
||||
filtered_adjustments.append(adjustments[0])
|
||||
return filtered_adjustments
|
||||
|
||||
@classmethod
|
||||
def calculate_recommendation_impact(
|
||||
cls,
|
||||
|
|
@ -493,6 +521,7 @@ class Recommendations:
|
|||
all_predictions,
|
||||
recommendations,
|
||||
representative_recommendations,
|
||||
debug=False
|
||||
):
|
||||
|
||||
"""
|
||||
|
|
@ -507,6 +536,9 @@ class Recommendations:
|
|||
:param all_predictions: dictionary of predictions from the model apis
|
||||
:param recommendations: dictionary of recommendations for the property
|
||||
:param representative_recommendations: dictionary of representative recommendations for the property
|
||||
:param debug: boolean, indicating if the function is running in debug mode. The only difference is that
|
||||
adjustments are returned for testing
|
||||
|
||||
:return:
|
||||
"""
|
||||
|
||||
|
|
@ -533,7 +565,9 @@ class Recommendations:
|
|||
rec["phase"] for recs in property_recommendations for rec in recs
|
||||
)
|
||||
|
||||
impact_summary = []
|
||||
# We keep a history of adjustments we have made, so that we ensure that we adjust future
|
||||
# phases for SAP
|
||||
impact_summary, adjustments = [], []
|
||||
for recommendations_by_type in property_recommendations:
|
||||
for rec in recommendations_by_type:
|
||||
if rec["type"] in ["trickle_vents", "draught_proofing", "extension_cavity_wall_insulation"]:
|
||||
|
|
@ -610,6 +644,16 @@ class Recommendations:
|
|||
current_phase_sap = rec["sap_points"] + previous_phase_values["sap"]
|
||||
else:
|
||||
current_phase_sap = phase_energy_efficiency_metrics["sap_change"]
|
||||
# If we have an adjustment, we apply it here. We de-dupe, taking the
|
||||
# largest adjustment by phase - though, they should all be the same
|
||||
phase_adjustments = [a for a in adjustments if a["phase"] < rec["phase"]]
|
||||
if phase_adjustments:
|
||||
phase_adjustments = cls._filter_phase_adjustment(phase_adjustments)
|
||||
total_adjustment = sum(
|
||||
a["sap_adjustment"] for a in phase_adjustments
|
||||
)
|
||||
# Take the max, by phase, subtract from the current phase sap
|
||||
current_phase_sap -= total_adjustment
|
||||
|
||||
current_phase_values = {
|
||||
"sap": current_phase_sap,
|
||||
|
|
@ -687,26 +731,46 @@ class Recommendations:
|
|||
if rec["type"] == "mechanical_ventilation":
|
||||
# ventilation is capped by having no greater and a -4 impact
|
||||
ventilation_sap_limit = -4
|
||||
|
||||
def _check_veniltation_out_of_bounds(sap_impact):
|
||||
return (sap_impact < ventilation_sap_limit) or (sap_impact >= 0)
|
||||
|
||||
def _adjust_ventilation_sap(sap_impact):
|
||||
if sap_impact >= 0:
|
||||
return -1
|
||||
if sap_impact < ventilation_sap_limit:
|
||||
return ventilation_sap_limit
|
||||
|
||||
ventilation_out_of_bounds = _check_veniltation_out_of_bounds(property_phase_impact["sap"])
|
||||
ventilation_out_of_bounds = cls._check_veniltation_out_of_bounds(
|
||||
property_phase_impact["sap"], ventilation_sap_limit
|
||||
)
|
||||
|
||||
if ventilation_out_of_bounds:
|
||||
previous_modelled_sap = previous_phase_values.get("sap_prediction", 0)
|
||||
proposed_sap_impact = current_phase_sap - previous_modelled_sap
|
||||
proposal_out_of_bounds = _check_veniltation_out_of_bounds(proposed_sap_impact)
|
||||
proposal_out_of_bounds = cls._check_veniltation_out_of_bounds(
|
||||
proposed_sap_impact, ventilation_sap_limit
|
||||
)
|
||||
if proposal_out_of_bounds:
|
||||
property_phase_impact["sap"] = _adjust_ventilation_sap(proposed_sap_impact)
|
||||
else:
|
||||
property_phase_impact["sap"] = proposed_sap_impact
|
||||
proposed_sap_impact = cls._adjust_ventilation_sap(
|
||||
proposed_sap_impact, ventilation_sap_limit
|
||||
)
|
||||
|
||||
# We keep track of the adjustment
|
||||
# In this case, if the SAP impact has increased, then the adustment should be negative
|
||||
# otherwise it should be positive
|
||||
# When we add the total adjustment, it's an addition
|
||||
# Example
|
||||
# Before: 60, impact -2 => 58
|
||||
# After: 60, impact -1 (So the impact is bigger) => 59
|
||||
# So in this case, we need to make sure we add 1 to all future predictions so
|
||||
# the adjustment should be positive
|
||||
# Before: 60, impact 1 => 61
|
||||
# After: 60, impact -1 => 59
|
||||
# So in this case, we need to make sure we subtract 1 to all future predictions so
|
||||
# the adjustment should be negative
|
||||
# Both cases are reflected in sap adjustment
|
||||
sap_adjustment = proposed_sap_impact - float(property_phase_impact["sap"])
|
||||
|
||||
adjustments.append(
|
||||
{
|
||||
"recommendation_id": rec["recommendation_id"],
|
||||
"phase": rec["phase"],
|
||||
"sap_adjustment": sap_adjustment,
|
||||
}
|
||||
)
|
||||
|
||||
property_phase_impact["sap"] = proposed_sap_impact
|
||||
|
||||
# Update the current phase values
|
||||
current_phase_values["sap"] = previous_phase_values["sap"] + property_phase_impact["sap"]
|
||||
|
|
@ -720,16 +784,40 @@ class Recommendations:
|
|||
property_instance.data["roof-energy-eff"], property_instance.roof["insulation_thickness"]
|
||||
)
|
||||
if li_sap_limit is not None:
|
||||
property_phase_impact["sap"] = min(property_phase_impact["sap"], li_sap_limit)
|
||||
new_value = min(property_phase_impact["sap"], li_sap_limit)
|
||||
# If we've made an adjustment, keep track of it
|
||||
if new_value != property_phase_impact["sap"]:
|
||||
adjustments.append(
|
||||
{
|
||||
"recommendation_id": rec["recommendation_id"],
|
||||
"phase": rec["phase"],
|
||||
# If we've made an adjustment, it will be negative
|
||||
"sap_adjustment": property_phase_impact["sap"] - new_value,
|
||||
}
|
||||
)
|
||||
property_phase_impact["sap"] = new_value
|
||||
# Update the current phase values
|
||||
current_phase_values["sap"] = previous_phase_values["sap"] + property_phase_impact["sap"]
|
||||
|
||||
if rec["type"] == "solar_pv":
|
||||
# We use the SAP points in the recommendation as a minimum
|
||||
property_phase_impact["sap"] = (
|
||||
proposed_impact = (
|
||||
rec["sap_points"] if property_phase_impact["sap"] < rec["sap_points"] else
|
||||
property_phase_impact["sap"]
|
||||
)
|
||||
|
||||
# SAP adjustments should be negative
|
||||
if proposed_impact != property_phase_impact["sap"]:
|
||||
adjustments.append(
|
||||
{
|
||||
"recommendation_id": rec["recommendation_id"],
|
||||
"phase": rec["phase"],
|
||||
# If we've made an adjustment, it will be positive
|
||||
"sap_adjustment": proposed_impact - property_phase_impact["sap"],
|
||||
}
|
||||
)
|
||||
property_phase_impact["sap"] = proposed_impact
|
||||
|
||||
# Update the current phase values
|
||||
current_phase_values["sap"] = previous_phase_values["sap"] + property_phase_impact["sap"]
|
||||
|
||||
|
|
@ -757,6 +845,9 @@ class Recommendations:
|
|||
}
|
||||
)
|
||||
|
||||
if debug:
|
||||
return property_recommendations, impact_summary, adjustments
|
||||
|
||||
return property_recommendations, impact_summary
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
843
recommendations/tests/test_recommendations.py
Normal file
843
recommendations/tests/test_recommendations.py
Normal file
|
|
@ -0,0 +1,843 @@
|
|||
import datetime
|
||||
import pandas as pd
|
||||
from pandas import Timestamp
|
||||
import numpy as np
|
||||
from numpy import nan
|
||||
from unittest.mock import Mock
|
||||
|
||||
from recommendations.Recommendations import Recommendations
|
||||
|
||||
|
||||
def test__filter_phase_adjustment():
|
||||
eg1 = [
|
||||
{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': 1.7},
|
||||
{'recommendation_id': '1_phase=0', 'phase': 0, 'sap_adjustment': 1.7},
|
||||
{'recommendation_id': '2_phase=0', 'phase': 0, 'sap_adjustment': 1.7}
|
||||
]
|
||||
|
||||
res1 = Recommendations._filter_phase_adjustment(eg1)
|
||||
|
||||
assert res1 == [{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': 1.7}]
|
||||
|
||||
eg2 = [
|
||||
{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': 1},
|
||||
{'recommendation_id': '1_phase=0', 'phase': 1, 'sap_adjustment': 2},
|
||||
{'recommendation_id': '2_phase=0', 'phase': 2, 'sap_adjustment': 3}
|
||||
]
|
||||
|
||||
res2 = Recommendations._filter_phase_adjustment(eg2)
|
||||
|
||||
assert res2 == [
|
||||
{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': 1},
|
||||
{'recommendation_id': '1_phase=0', 'phase': 1, 'sap_adjustment': 2},
|
||||
{'recommendation_id': '2_phase=0', 'phase': 2, 'sap_adjustment': 3}
|
||||
]
|
||||
|
||||
eg3 = [
|
||||
{'recommendation_id': 'third', 'phase': 3, 'sap_adjustment': 1},
|
||||
{'recommendation_id': 'first', 'phase': 1, 'sap_adjustment': 2},
|
||||
{'recommendation_id': 'second', 'phase': 2, 'sap_adjustment': 3}
|
||||
]
|
||||
|
||||
res3 = Recommendations._filter_phase_adjustment(eg3)
|
||||
|
||||
assert res3 == [
|
||||
{'recommendation_id': 'first', 'phase': 1, 'sap_adjustment': 2},
|
||||
{'recommendation_id': 'second', 'phase': 2, 'sap_adjustment': 3},
|
||||
{'recommendation_id': 'third', 'phase': 3, 'sap_adjustment': 1},
|
||||
]
|
||||
|
||||
eg4 = [
|
||||
{'recommendation_id': 'third_0', 'phase': 3, 'sap_adjustment': 1},
|
||||
{'recommendation_id': 'third_1', 'phase': 3, 'sap_adjustment': 2},
|
||||
{'recommendation_id': 'first_0', 'phase': 1, 'sap_adjustment': 2},
|
||||
{'recommendation_id': 'first_1', 'phase': 1, 'sap_adjustment': 2},
|
||||
{'recommendation_id': 'first_2', 'phase': 1, 'sap_adjustment': 100},
|
||||
{'recommendation_id': 'second', 'phase': 2, 'sap_adjustment': 3}
|
||||
]
|
||||
|
||||
res4 = Recommendations._filter_phase_adjustment(eg4)
|
||||
|
||||
assert res4 == [
|
||||
{'recommendation_id': 'first_2', 'phase': 1, 'sap_adjustment': 100},
|
||||
{'recommendation_id': 'second', 'phase': 2, 'sap_adjustment': 3},
|
||||
{'recommendation_id': 'third_1', 'phase': 3, 'sap_adjustment': 2},
|
||||
]
|
||||
|
||||
|
||||
def test_calculate_recommendation_impact():
|
||||
all_predictions = {
|
||||
"sap_change_predictions": pd.DataFrame(
|
||||
[
|
||||
{'id': '614626+0_phase=0', 'predictions': 66.7, 'property_id': '614626',
|
||||
'recommendation_id': '0_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+1_phase=0', 'predictions': 66.7, 'property_id': '614626',
|
||||
'recommendation_id': '1_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+2_phase=0', 'predictions': 66.7, 'property_id': '614626',
|
||||
'recommendation_id': '2_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+3_phase=1', 'predictions': 65.3, 'property_id': '614626',
|
||||
'recommendation_id': '3_phase=1',
|
||||
'phase': 1},
|
||||
{'id': '614626+4_phase=2', 'predictions': 66.3, 'property_id': '614626',
|
||||
'recommendation_id': '4_phase=2',
|
||||
'phase': 2},
|
||||
{'id': '614626+5_phase=3', 'predictions': 67.3, 'property_id': '614626',
|
||||
'recommendation_id': '5_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+6_phase=3', 'predictions': 68.1, 'property_id': '614626',
|
||||
'recommendation_id': '6_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+7_phase=3', 'predictions': 70.1, 'property_id': '614626',
|
||||
'recommendation_id': '7_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+8_phase=4', 'predictions': 67.3, 'property_id': '614626',
|
||||
'recommendation_id': '8_phase=4',
|
||||
'phase': 4},
|
||||
{'id': '614626+9_phase=5', 'predictions': 85.3, 'property_id': '614626',
|
||||
'recommendation_id': '9_phase=5',
|
||||
'phase': 5}, {'id': '614626+10_phase=5', 'predictions': 85.3, 'property_id': '614626',
|
||||
'recommendation_id': '10_phase=5', 'phase': 5},
|
||||
{'id': '614626+11_phase=5', 'predictions': 85.3, 'property_id': '614626',
|
||||
'recommendation_id': '11_phase=5', 'phase': 5},
|
||||
{'id': '614626+12_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '12_phase=5', 'phase': 5},
|
||||
{'id': '614626+13_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '13_phase=5', 'phase': 5},
|
||||
{'id': '614626+14_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '14_phase=5', 'phase': 5},
|
||||
{'id': '614626+15_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '15_phase=5', 'phase': 5},
|
||||
{'id': '614626+16_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '16_phase=5', 'phase': 5},
|
||||
{'id': '614626+17_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '17_phase=5', 'phase': 5},
|
||||
{'id': '614626+18_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '18_phase=5', 'phase': 5},
|
||||
{'id': '614626+19_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '19_phase=5', 'phase': 5},
|
||||
{'id': '614626+20_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '20_phase=5', 'phase': 5},
|
||||
{'id': '614626+21_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '21_phase=5', 'phase': 5},
|
||||
{'id': '614626+22_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '22_phase=5', 'phase': 5},
|
||||
{'id': '614626+23_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '23_phase=5', 'phase': 5},
|
||||
{'id': '614626+24_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '24_phase=5', 'phase': 5},
|
||||
{'id': '614626+25_phase=5', 'predictions': 86.7, 'property_id': '614626',
|
||||
'recommendation_id': '25_phase=5', 'phase': 5},
|
||||
{'id': '614626+26_phase=5', 'predictions': 86.7, 'property_id': '614626',
|
||||
'recommendation_id': '26_phase=5', 'phase': 5},
|
||||
{'id': '614626+27_phase=5', 'predictions': 86.7, 'property_id': '614626',
|
||||
'recommendation_id': '27_phase=5', 'phase': 5},
|
||||
{'id': '614626+28_phase=5', 'predictions': 86.7, 'property_id': '614626',
|
||||
'recommendation_id': '28_phase=5', 'phase': 5},
|
||||
{'id': '614626+29_phase=5', 'predictions': 83.8, 'property_id': '614626',
|
||||
'recommendation_id': '29_phase=5', 'phase': 5},
|
||||
{'id': '614626+30_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '30_phase=5', 'phase': 5},
|
||||
{'id': '614626+31_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '31_phase=5', 'phase': 5},
|
||||
{'id': '614626+32_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '32_phase=5', 'phase': 5},
|
||||
{'id': '614626+33_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '33_phase=5', 'phase': 5},
|
||||
{'id': '614626+34_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '34_phase=5', 'phase': 5},
|
||||
{'id': '614626+35_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '35_phase=5', 'phase': 5},
|
||||
{'id': '614626+36_phase=5', 'predictions': 86.4, 'property_id': '614626',
|
||||
'recommendation_id': '36_phase=5', 'phase': 5},
|
||||
{'id': '614626+37_phase=5', 'predictions': 81.2, 'property_id': '614626',
|
||||
'recommendation_id': '37_phase=5', 'phase': 5},
|
||||
{'id': '614626+38_phase=5', 'predictions': 81.2, 'property_id': '614626',
|
||||
'recommendation_id': '38_phase=5', 'phase': 5},
|
||||
{'id': '614626+39_phase=5', 'predictions': 81.2, 'property_id': '614626',
|
||||
'recommendation_id': '39_phase=5', 'phase': 5},
|
||||
{'id': '614626+40_phase=5', 'predictions': 83.4, 'property_id': '614626',
|
||||
'recommendation_id': '40_phase=5', 'phase': 5},
|
||||
{'id': '614626+41_phase=5', 'predictions': 83.4, 'property_id': '614626',
|
||||
'recommendation_id': '41_phase=5', 'phase': 5},
|
||||
{'id': '614626+42_phase=5', 'predictions': 83.4, 'property_id': '614626',
|
||||
'recommendation_id': '42_phase=5', 'phase': 5},
|
||||
{'id': '614626+43_phase=5', 'predictions': 83.4, 'property_id': '614626',
|
||||
'recommendation_id': '43_phase=5', 'phase': 5},
|
||||
{'id': '614626+44_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '44_phase=5', 'phase': 5},
|
||||
{'id': '614626+45_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '45_phase=5', 'phase': 5},
|
||||
{'id': '614626+46_phase=5', 'predictions': 85.5, 'property_id': '614626',
|
||||
'recommendation_id': '46_phase=5', 'phase': 5},
|
||||
{'id': '614626+47_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '47_phase=5', 'phase': 5},
|
||||
{'id': '614626+48_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '48_phase=5', 'phase': 5},
|
||||
{'id': '614626+49_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '49_phase=5', 'phase': 5},
|
||||
{'id': '614626+50_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '50_phase=5', 'phase': 5},
|
||||
{'id': '614626+51_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '51_phase=5', 'phase': 5},
|
||||
{'id': '614626+52_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '52_phase=5', 'phase': 5},
|
||||
{'id': '614626+53_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '53_phase=5', 'phase': 5},
|
||||
{'id': '614626+54_phase=5', 'predictions': 85.4, 'property_id': '614626',
|
||||
'recommendation_id': '54_phase=5', 'phase': 5},
|
||||
{'id': '614626+55_phase=5', 'predictions': 79.4, 'property_id': '614626',
|
||||
'recommendation_id': '55_phase=5', 'phase': 5},
|
||||
{'id': '614626+56_phase=5', 'predictions': 81.2, 'property_id': '614626',
|
||||
'recommendation_id': '56_phase=5', 'phase': 5},
|
||||
{'id': '614626+57_phase=5', 'predictions': 81.2, 'property_id': '614626',
|
||||
'recommendation_id': '57_phase=5', 'phase': 5}]
|
||||
),
|
||||
"heat_demand_predictions": pd.DataFrame(
|
||||
[
|
||||
{'id': '614626+0_phase=0', 'predictions': 256.6, 'property_id': '614626',
|
||||
'recommendation_id': '0_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+1_phase=0', 'predictions': 256.6, 'property_id': '614626',
|
||||
'recommendation_id': '1_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+2_phase=0', 'predictions': 256.6, 'property_id': '614626',
|
||||
'recommendation_id': '2_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+3_phase=1', 'predictions': 263.1, 'property_id': '614626',
|
||||
'recommendation_id': '3_phase=1',
|
||||
'phase': 1},
|
||||
{'id': '614626+4_phase=2', 'predictions': 259.0, 'property_id': '614626',
|
||||
'recommendation_id': '4_phase=2',
|
||||
'phase': 2},
|
||||
{'id': '614626+5_phase=3', 'predictions': 250.5, 'property_id': '614626',
|
||||
'recommendation_id': '5_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+6_phase=3', 'predictions': 245.7, 'property_id': '614626',
|
||||
'recommendation_id': '6_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+7_phase=3', 'predictions': 199.7, 'property_id': '614626',
|
||||
'recommendation_id': '7_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+8_phase=4', 'predictions': 250.5, 'property_id': '614626',
|
||||
'recommendation_id': '8_phase=4',
|
||||
'phase': 4},
|
||||
{'id': '614626+9_phase=5', 'predictions': 139.5, 'property_id': '614626',
|
||||
'recommendation_id': '9_phase=5',
|
||||
'phase': 5}, {'id': '614626+10_phase=5', 'predictions': 139.5, 'property_id': '614626',
|
||||
'recommendation_id': '10_phase=5', 'phase': 5},
|
||||
{'id': '614626+11_phase=5', 'predictions': 139.5, 'property_id': '614626',
|
||||
'recommendation_id': '11_phase=5', 'phase': 5},
|
||||
{'id': '614626+12_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '12_phase=5', 'phase': 5},
|
||||
{'id': '614626+13_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '13_phase=5', 'phase': 5},
|
||||
{'id': '614626+14_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '14_phase=5', 'phase': 5},
|
||||
{'id': '614626+15_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '15_phase=5', 'phase': 5},
|
||||
{'id': '614626+16_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '16_phase=5', 'phase': 5},
|
||||
{'id': '614626+17_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '17_phase=5', 'phase': 5},
|
||||
{'id': '614626+18_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '18_phase=5', 'phase': 5},
|
||||
{'id': '614626+19_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '19_phase=5', 'phase': 5},
|
||||
{'id': '614626+20_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '20_phase=5', 'phase': 5},
|
||||
{'id': '614626+21_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '21_phase=5', 'phase': 5},
|
||||
{'id': '614626+22_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '22_phase=5', 'phase': 5},
|
||||
{'id': '614626+23_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '23_phase=5', 'phase': 5},
|
||||
{'id': '614626+24_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '24_phase=5', 'phase': 5},
|
||||
{'id': '614626+25_phase=5', 'predictions': 102.5, 'property_id': '614626',
|
||||
'recommendation_id': '25_phase=5', 'phase': 5},
|
||||
{'id': '614626+26_phase=5', 'predictions': 102.5, 'property_id': '614626',
|
||||
'recommendation_id': '26_phase=5', 'phase': 5},
|
||||
{'id': '614626+27_phase=5', 'predictions': 102.5, 'property_id': '614626',
|
||||
'recommendation_id': '27_phase=5', 'phase': 5},
|
||||
{'id': '614626+28_phase=5', 'predictions': 102.5, 'property_id': '614626',
|
||||
'recommendation_id': '28_phase=5', 'phase': 5},
|
||||
{'id': '614626+29_phase=5', 'predictions': 82.5, 'property_id': '614626',
|
||||
'recommendation_id': '29_phase=5', 'phase': 5},
|
||||
{'id': '614626+30_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '30_phase=5', 'phase': 5},
|
||||
{'id': '614626+31_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '31_phase=5', 'phase': 5},
|
||||
{'id': '614626+32_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '32_phase=5', 'phase': 5},
|
||||
{'id': '614626+33_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '33_phase=5', 'phase': 5},
|
||||
{'id': '614626+34_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '34_phase=5', 'phase': 5},
|
||||
{'id': '614626+35_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '35_phase=5', 'phase': 5},
|
||||
{'id': '614626+36_phase=5', 'predictions': 114.3, 'property_id': '614626',
|
||||
'recommendation_id': '36_phase=5', 'phase': 5},
|
||||
{'id': '614626+37_phase=5', 'predictions': 169.2, 'property_id': '614626',
|
||||
'recommendation_id': '37_phase=5', 'phase': 5},
|
||||
{'id': '614626+38_phase=5', 'predictions': 169.2, 'property_id': '614626',
|
||||
'recommendation_id': '38_phase=5', 'phase': 5},
|
||||
{'id': '614626+39_phase=5', 'predictions': 169.2, 'property_id': '614626',
|
||||
'recommendation_id': '39_phase=5', 'phase': 5},
|
||||
{'id': '614626+40_phase=5', 'predictions': 155.1, 'property_id': '614626',
|
||||
'recommendation_id': '40_phase=5', 'phase': 5},
|
||||
{'id': '614626+41_phase=5', 'predictions': 155.1, 'property_id': '614626',
|
||||
'recommendation_id': '41_phase=5', 'phase': 5},
|
||||
{'id': '614626+42_phase=5', 'predictions': 155.1, 'property_id': '614626',
|
||||
'recommendation_id': '42_phase=5', 'phase': 5},
|
||||
{'id': '614626+43_phase=5', 'predictions': 155.1, 'property_id': '614626',
|
||||
'recommendation_id': '43_phase=5', 'phase': 5},
|
||||
{'id': '614626+44_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '44_phase=5', 'phase': 5},
|
||||
{'id': '614626+45_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '45_phase=5', 'phase': 5},
|
||||
{'id': '614626+46_phase=5', 'predictions': 133.6, 'property_id': '614626',
|
||||
'recommendation_id': '46_phase=5', 'phase': 5},
|
||||
{'id': '614626+47_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '47_phase=5', 'phase': 5},
|
||||
{'id': '614626+48_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '48_phase=5', 'phase': 5},
|
||||
{'id': '614626+49_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '49_phase=5', 'phase': 5},
|
||||
{'id': '614626+50_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '50_phase=5', 'phase': 5},
|
||||
{'id': '614626+51_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '51_phase=5', 'phase': 5},
|
||||
{'id': '614626+52_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '52_phase=5', 'phase': 5},
|
||||
{'id': '614626+53_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '53_phase=5', 'phase': 5},
|
||||
{'id': '614626+54_phase=5', 'predictions': 130.0, 'property_id': '614626',
|
||||
'recommendation_id': '54_phase=5', 'phase': 5},
|
||||
{'id': '614626+55_phase=5', 'predictions': 182.6, 'property_id': '614626',
|
||||
'recommendation_id': '55_phase=5', 'phase': 5},
|
||||
{'id': '614626+56_phase=5', 'predictions': 169.2, 'property_id': '614626',
|
||||
'recommendation_id': '56_phase=5', 'phase': 5},
|
||||
{'id': '614626+57_phase=5', 'predictions': 169.2, 'property_id': '614626',
|
||||
'recommendation_id': '57_phase=5', 'phase': 5}
|
||||
]
|
||||
|
||||
),
|
||||
"carbon_change_predictions": pd.DataFrame(
|
||||
[
|
||||
{'id': '614626+0_phase=0', 'predictions': 2.2, 'property_id': '614626',
|
||||
'recommendation_id': '0_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+1_phase=0', 'predictions': 2.2, 'property_id': '614626',
|
||||
'recommendation_id': '1_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+2_phase=0', 'predictions': 2.2, 'property_id': '614626',
|
||||
'recommendation_id': '2_phase=0',
|
||||
'phase': 0},
|
||||
{'id': '614626+3_phase=1', 'predictions': 2.2, 'property_id': '614626',
|
||||
'recommendation_id': '3_phase=1',
|
||||
'phase': 1},
|
||||
{'id': '614626+4_phase=2', 'predictions': 2.2, 'property_id': '614626',
|
||||
'recommendation_id': '4_phase=2',
|
||||
'phase': 2},
|
||||
{'id': '614626+5_phase=3', 'predictions': 2.1, 'property_id': '614626',
|
||||
'recommendation_id': '5_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+6_phase=3', 'predictions': 2.1, 'property_id': '614626',
|
||||
'recommendation_id': '6_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+7_phase=3', 'predictions': 1.4, 'property_id': '614626',
|
||||
'recommendation_id': '7_phase=3',
|
||||
'phase': 3},
|
||||
{'id': '614626+8_phase=4', 'predictions': 2.1, 'property_id': '614626',
|
||||
'recommendation_id': '8_phase=4',
|
||||
'phase': 4},
|
||||
{'id': '614626+9_phase=5', 'predictions': 1.3, 'property_id': '614626',
|
||||
'recommendation_id': '9_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+10_phase=5', 'predictions': 1.3, 'property_id': '614626',
|
||||
'recommendation_id': '10_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+11_phase=5', 'predictions': 1.3, 'property_id': '614626',
|
||||
'recommendation_id': '11_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+12_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '12_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+13_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '13_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+14_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '14_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+15_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '15_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+16_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '16_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+17_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '17_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+18_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '18_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+19_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '19_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+20_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '20_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+21_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '21_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+22_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '22_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+23_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '23_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+24_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '24_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+25_phase=5', 'predictions': 0.9, 'property_id': '614626',
|
||||
'recommendation_id': '25_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+26_phase=5', 'predictions': 0.9, 'property_id': '614626',
|
||||
'recommendation_id': '26_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+27_phase=5', 'predictions': 0.9, 'property_id': '614626',
|
||||
'recommendation_id': '27_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+28_phase=5', 'predictions': 0.9, 'property_id': '614626',
|
||||
'recommendation_id': '28_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+29_phase=5', 'predictions': 0.8, 'property_id': '614626',
|
||||
'recommendation_id': '29_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+30_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '30_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+31_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '31_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+32_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '32_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+33_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '33_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+34_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '34_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+35_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '35_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+36_phase=5', 'predictions': 1.0, 'property_id': '614626',
|
||||
'recommendation_id': '36_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+37_phase=5', 'predictions': 1.5, 'property_id': '614626',
|
||||
'recommendation_id': '37_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+38_phase=5', 'predictions': 1.5, 'property_id': '614626',
|
||||
'recommendation_id': '38_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+39_phase=5', 'predictions': 1.5, 'property_id': '614626',
|
||||
'recommendation_id': '39_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+40_phase=5', 'predictions': 1.4, 'property_id': '614626',
|
||||
'recommendation_id': '40_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+41_phase=5', 'predictions': 1.4, 'property_id': '614626',
|
||||
'recommendation_id': '41_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+42_phase=5', 'predictions': 1.4, 'property_id': '614626',
|
||||
'recommendation_id': '42_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+43_phase=5', 'predictions': 1.4, 'property_id': '614626',
|
||||
'recommendation_id': '43_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+44_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '44_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+45_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '45_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+46_phase=5', 'predictions': 1.2, 'property_id': '614626',
|
||||
'recommendation_id': '46_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+47_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '47_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+48_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '48_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+49_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '49_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+50_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '50_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+51_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '51_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+52_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '52_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+53_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '53_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+54_phase=5', 'predictions': 1.1, 'property_id': '614626',
|
||||
'recommendation_id': '54_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+55_phase=5', 'predictions': 1.6, 'property_id': '614626',
|
||||
'recommendation_id': '55_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+56_phase=5', 'predictions': 1.5, 'property_id': '614626',
|
||||
'recommendation_id': '56_phase=5',
|
||||
'phase': 5},
|
||||
{'id': '614626+57_phase=5', 'predictions': 1.5, 'property_id': '614626',
|
||||
'recommendation_id': '57_phase=5',
|
||||
'phase': 5}
|
||||
]
|
||||
),
|
||||
"hotwater_kwh_predictions": pd.DataFrame([]),
|
||||
"heating_kwh_predictions": pd.DataFrame([]),
|
||||
}
|
||||
|
||||
# Mock the property - we need id and some of the data
|
||||
p = Mock(
|
||||
id=614626,
|
||||
data={
|
||||
"current-energy-efficiency": 65,
|
||||
"co2-emissions-current": 2.4,
|
||||
"energy-consumption-current": 284,
|
||||
"roof-energy-eff": "Good",
|
||||
"lighting-energy-eff": "Good"
|
||||
},
|
||||
roof={
|
||||
'original_description': 'Pitched, 250 mm loft insulation',
|
||||
'clean_description': 'Pitched, 250 mm loft insulation', 'thermal_transmittance': None,
|
||||
'thermal_transmittance_unit': None, 'is_pitched': True, 'is_roof_room': False, 'is_loft': True,
|
||||
'is_flat': False, 'is_thatched': False, 'is_at_rafters': False, 'is_assumed': False,
|
||||
'has_dwelling_above': False, 'is_valid': True, 'insulation_thickness': '250'
|
||||
},
|
||||
lighting={
|
||||
'original_description': 'Low energy lighting in 50% of fixed outlets',
|
||||
'clean_description': 'Low energy lighting in 50% of fixed outlets', 'low_energy_proportion': 0.5
|
||||
}
|
||||
)
|
||||
|
||||
recommendations = {
|
||||
614626: [
|
||||
[
|
||||
{
|
||||
'phase': 0, 'parts': [
|
||||
{'id': 3362, 'type': 'loft_insulation', 'description': 'Fibre loft insulation', 'depth': 300.0,
|
||||
'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273,
|
||||
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044,
|
||||
'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'Warm Front',
|
||||
'created_at': Timestamp('2025-08-15 16:31:52.995292'), 'is_active': True,
|
||||
'prime_material_cost': None,
|
||||
'material_cost': 0.0, 'labour_cost': 0.0, 'labour_hours_per_unit': 0.0, 'plant_cost': 0.0,
|
||||
'total_cost': 21.0, 'notes': None, 'is_installer_quote': True, 'innovation_rate': 0.0,
|
||||
'size': None,
|
||||
'size_unit': None, 'includes_scaffolding': False, 'includes_battery': False, 'battery_size': None,
|
||||
'quantity': 54.125488565924286, 'quantity_unit': 'm2', 'total': 1029.0, 'contingency': 102.9,
|
||||
'contingency_rate': 0.1, 'labour_hours': 8, 'labour_days': 1}], 'type': 'loft_insulation',
|
||||
'measure_type': 'loft_insulation',
|
||||
'description': 'Install 300mm of Fibre loft insulation in your loft',
|
||||
'starting_u_value': np.float64(0.17), 'new_u_value': np.float64(0.14), 'sap_points': 0,
|
||||
'already_installed': False, 'simulation_config': {'roof_insulation_thickness_ending': '300',
|
||||
'roof_thermal_transmittance_ending': np.float64(
|
||||
0.14),
|
||||
'roof_energy_eff_ending': 'Very Good'},
|
||||
'description_simulation': {'roof-description': 'Pitched, 300mm loft insulation',
|
||||
'roof-energy-eff': 'Very Good'}, 'total': 1029.0, 'contingency': 102.9,
|
||||
'contingency_rate': 0.1, 'labour_hours': 8, 'labour_days': 1, 'survey': False,
|
||||
'innovation_rate': 0.0,
|
||||
'recommendation_id': '0_phase=0', 'efficiency': np.float64(6052.801176470587),
|
||||
'co2_equivalent_savings': np.float64(0.19999999999999973),
|
||||
'heat_demand': np.float64(27.399999999999977)},
|
||||
],
|
||||
[
|
||||
{
|
||||
'phase': 1, 'parts': [{'id': 3337, 'type': 'mechanical_ventilation',
|
||||
'description': 'Decentralised mechanical extract ventilation', 'depth': 0.0,
|
||||
'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_unit',
|
||||
'r_value_per_mm': nan, 'r_value_unit': 'square_meter_kelvin_per_watt',
|
||||
'thermal_conductivity': None, 'thermal_conductivity_unit': None,
|
||||
'link': 'CRG',
|
||||
'created_at': datetime.datetime(2025, 8, 15, 16, 31, 52, 995292),
|
||||
'is_active': True, 'prime_material_cost': None, 'material_cost': 0.0,
|
||||
'labour_cost': 0.0, 'labour_hours_per_unit': 0.0, 'plant_cost': 0.0,
|
||||
'total_cost': 280.0, 'notes': None, 'is_installer_quote': True,
|
||||
'innovation_rate': 0.0, 'size': None, 'size_unit': None,
|
||||
'includes_scaffolding': False, 'includes_battery': False,
|
||||
'battery_size': None,
|
||||
'total': 560.0, 'quantity': 2, 'quantity_unit': 'part'}],
|
||||
'type': 'mechanical_ventilation', 'measure_type': 'mechanical_ventilation',
|
||||
'description': 'Install 2 Decentralised mechanical extract ventilation units',
|
||||
'starting_u_value': None,
|
||||
'new_u_value': None, 'already_installed': False, 'sap_points': np.float64(-1.4000000000000057),
|
||||
'heat_demand': np.float64(-6.5), 'kwh_savings': 0, 'co2_equivalent_savings': np.float64(0.0),
|
||||
'energy_cost_savings': 0, 'total': 560.0, 'labour_hours': 8, 'labour_days': 1.0,
|
||||
'simulation_config': {'mechanical_ventilation_ending': 'mechanical, extract only'},
|
||||
'description_simulation': {'mechanical-ventilation': 'mechanical, extract only'},
|
||||
'innovation_rate': 0.0,
|
||||
'recommendation_id': '3_phase=1', 'efficiency': 0}
|
||||
],
|
||||
[
|
||||
{'phase': 2, 'parts': [], 'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
|
||||
'description': 'Install low energy lighting in 3 outlets', 'starting_u_value': None,
|
||||
'new_u_value': None,
|
||||
'already_installed': False, 'sap_points': 1, 'kwh_savings': 164.25,
|
||||
'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
|
||||
'description_simulation': {'lighting-energy-eff': 'Very Good',
|
||||
'lighting-description': 'Low energy lighting in all fixed outlets',
|
||||
'low-energy-lighting': 100}, 'total': 10.5, 'contingency': 2.73,
|
||||
'contingency_rate': 0.26, 'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
|
||||
'innovation_rate': 0.0,
|
||||
'recommendation_id': '4_phase=2', 'efficiency': 10.5, 'heat_demand': np.float64(4.100000000000023)}
|
||||
],
|
||||
[
|
||||
{'type': 'heating', 'measure_type': 'roomstat_programmer_trvs', 'phase': 3, 'parts': [],
|
||||
'description': 'Upgrade heating controls to Room thermostat, programmer and TRVs', 'total': 70,
|
||||
'contingency': 7.0, 'contingency_rate': 0.1, 'subtotal': 58.333333333333336, 'vat': 11.666666666666664,
|
||||
'labour_hours': 0.5, 'labour_days': 1, 'starting_u_value': None, 'new_u_value': None,
|
||||
'sap_points': np.float64(1.0), 'already_installed': False,
|
||||
'simulation_config': {'trvs_ending': 'trvs', 'mainheatc_energy_eff_ending': 'Good'},
|
||||
'description_simulation': {'mainheatcont-description': 'Programmer, room thermostat and TRVS',
|
||||
'mainheatc-energy-eff': 'Good'}, 'innovation_rate': 0.0,
|
||||
'recommendation_id': '5_phase=3', 'efficiency': 70,
|
||||
'co2_equivalent_savings': np.float64(0.10000000000000009), 'heat_demand': np.float64(8.5)},
|
||||
{'type': 'heating', 'phase': 3, 'measure_type': 'time_temperature_zone_control', 'parts': [],
|
||||
'description': 'Upgrade heating controls to Smart Thermostats, room sensors and smart radiator '
|
||||
'valves (time & temperature zone control)',
|
||||
'total': 604.5840000000001, 'contingency': 60.45840000000001, 'contingency_rate': 0.1,
|
||||
'subtotal': 571.32,
|
||||
'vat': 33.264, 'labour_hours': 3.08, 'labour_days': np.float64(1.0), 'starting_u_value': None,
|
||||
'new_u_value': None, 'sap_points': np.float64(1.8), 'already_installed': False,
|
||||
'simulation_config': {'thermostatic_control_ending': 'time and temperature zone control',
|
||||
'switch_system_ending': None, 'mainheatc_energy_eff_ending': 'Very Good'},
|
||||
'description_simulation': {'mainheatcont-description': 'Time and temperature zone control',
|
||||
'mainheatc-energy-eff': 'Very Good'}, 'innovation_rate': 0.0,
|
||||
'recommendation_id': '6_phase=3', 'efficiency': 604.5840000000001,
|
||||
'co2_equivalent_savings': np.float64(0.10000000000000009),
|
||||
'heat_demand': np.float64(13.300000000000011)},
|
||||
{'phase': 3, 'parts': [], 'type': 'heating', 'measure_type': 'air_source_heat_pump',
|
||||
'description': 'Install a 5KW air source heat pump, and upgrade heating controls to Smart '
|
||||
'Thermostats, room sensors and smart radiator valves (time & temperature zone '
|
||||
'control). Ensure you have a single tariff',
|
||||
'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(3.8),
|
||||
'already_installed': False,
|
||||
'simulation_config': {'mainheat_energy_eff_ending': 'Good', 'hot_water_energy_eff_ending': 'Average',
|
||||
'has_boiler_ending': False, 'has_air_source_heat_pump_ending': True,
|
||||
'has_electric_ending': True, 'has_mains_gas_ending': False,
|
||||
'fuel_type_ending': 'electricity',
|
||||
'thermostatic_control_ending': 'time and temperature zone control',
|
||||
'switch_system_ending': None, 'mainheatc_energy_eff_ending': 'Very Good'},
|
||||
'description_simulation': {'mainheat-description': 'Air source heat pump, radiators, electric',
|
||||
'mainheat-energy-eff': 'Good', 'hot-water-energy-eff': 'Average',
|
||||
'hotwater-description': 'From main system',
|
||||
'main-fuel': 'electricity (not community)',
|
||||
'mainheatcont-description': 'Time and temperature zone control',
|
||||
'mainheatc-energy-eff': 'Very Good'}, 'total': 17144.924,
|
||||
'contingency': 4195.5434000000005, 'contingency_rate': 0.35, 'vat': 33.264, 'labour_hours': 83.08,
|
||||
'labour_days': np.float64(11.0), 'innovation_rate': 0, 'recommendation_id': '7_phase=3',
|
||||
'efficiency': 17144.924, 'co2_equivalent_savings': np.float64(0.8000000000000003),
|
||||
'heat_demand': np.float64(59.30000000000001)}
|
||||
],
|
||||
[
|
||||
{'phase': 4, 'parts': [], 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
|
||||
'description': 'Remove the secondary heating system', 'starting_u_value': None, 'new_u_value': None,
|
||||
'sap_points': np.float64(0.0), 'already_installed': False, 'total': 60.0, 'contingency': 6.0,
|
||||
'contingency_rate': 0.1, 'subtotal': 50.0, 'vat': 10.0, 'labour_hours': 6.0,
|
||||
'labour_days': np.float64(1.0), 'simulation_config': {'secondheat_description_ending': 'None'},
|
||||
'description_simulation': {'secondheat-description': 'None'}, 'innovation_rate': 0.0,
|
||||
'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
|
||||
'heat_demand': np.float64(0.0)}
|
||||
],
|
||||
[
|
||||
{
|
||||
'phase': 5, 'parts': [
|
||||
{'id': 3516, 'type': 'solar_pv', 'description': 'Trina Vertex S3 445W solar panels', 'depth': 0.0,
|
||||
'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan,
|
||||
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
|
||||
'thermal_conductivity_unit': None, 'link': 'Coactivation',
|
||||
'created_at': datetime.datetime(2025, 8, 15, 16, 31, 52, 995292), 'is_active': True,
|
||||
'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0,
|
||||
'labour_hours_per_unit': 0.0,
|
||||
'plant_cost': 0.0, 'total_cost': 5892.21, 'notes': '445W panels', 'is_installer_quote': True,
|
||||
'innovation_rate': 0.0, 'size': 5.34, 'size_unit': 'kWp', 'includes_scaffolding': True,
|
||||
'includes_battery': False, 'battery_size': None, 'panel_size': 445}], 'type': 'solar_pv',
|
||||
'measure_type': 'solar_pv',
|
||||
'description': 'Trina Vertex S3 445W solar panels - 5.34 kWp '
|
||||
'system',
|
||||
'starting_u_value': None, 'new_u_value': None,
|
||||
'sap_points': np.float64(16.0), 'already_installed': False,
|
||||
'total': 5892.21, 'subtotal': 5892.21, 'contingency': 883.8315,
|
||||
'contingency_rate': 0.15, 'vat': 0, 'labour_hours': 48,
|
||||
'labour_days': 2, 'has_battery': False,
|
||||
'simulation_config': {'photo_supply_ending': np.float64(80.0)},
|
||||
'initial_ac_kwh_per_year': np.float64(4844.465553999999),
|
||||
'description_simulation': {'photo-supply': np.float64(80.0)},
|
||||
'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
|
||||
'efficiency': np.float64(368.263125)
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
representative_recommendations = {
|
||||
614626: [
|
||||
{
|
||||
'phase': 0, 'parts': [
|
||||
{'id': 3362, 'type': 'loft_insulation', 'description': 'Fibre loft insulation', 'depth': 300.0,
|
||||
'depth_unit': 'mm', 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.022727273,
|
||||
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': 0.044,
|
||||
'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'link': 'Warm Front',
|
||||
'created_at': Timestamp('2025-08-15 16:31:52.995292'), 'is_active': True,
|
||||
'prime_material_cost': None,
|
||||
'material_cost': 0.0, 'labour_cost': 0.0, 'labour_hours_per_unit': 0.0, 'plant_cost': 0.0,
|
||||
'total_cost': 21.0, 'notes': None, 'is_installer_quote': True, 'innovation_rate': 0.0,
|
||||
'size': None,
|
||||
'size_unit': None, 'includes_scaffolding': False, 'includes_battery': False, 'battery_size': None,
|
||||
'quantity': 54.125488565924286, 'quantity_unit': 'm2', 'total': 1029.0, 'contingency': 102.9,
|
||||
'contingency_rate': 0.1, 'labour_hours': 8, 'labour_days': 1}], 'type': 'loft_insulation',
|
||||
'measure_type': 'loft_insulation',
|
||||
'description': 'Install 300mm of Fibre loft insulation in your loft',
|
||||
'starting_u_value': np.float64(0.17), 'new_u_value': np.float64(0.14), 'sap_points': 0,
|
||||
'already_installed': False, 'simulation_config': {'roof_insulation_thickness_ending': '300',
|
||||
'roof_thermal_transmittance_ending': np.float64(
|
||||
0.14),
|
||||
'roof_energy_eff_ending': 'Very Good'},
|
||||
'description_simulation': {'roof-description': 'Pitched, 300mm loft insulation',
|
||||
'roof-energy-eff': 'Very Good'}, 'total': 1029.0, 'contingency': 102.9,
|
||||
'contingency_rate': 0.1, 'labour_hours': 8, 'labour_days': 1, 'survey': False,
|
||||
'innovation_rate': 0.0,
|
||||
'recommendation_id': '0_phase=0', 'efficiency': np.float64(6052.801176470587),
|
||||
'co2_equivalent_savings': np.float64(0.19999999999999973),
|
||||
'heat_demand': np.float64(27.399999999999977)
|
||||
},
|
||||
{
|
||||
'phase': 1, 'parts': [
|
||||
{'id': 3337, 'type': 'mechanical_ventilation',
|
||||
'description': 'Decentralised mechanical extract ventilation', 'depth': 0.0, 'depth_unit': None,
|
||||
'cost': None, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': nan,
|
||||
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
|
||||
'thermal_conductivity_unit': None, 'link': 'CRG',
|
||||
'created_at': datetime.datetime(2025, 8, 15, 16, 31, 52, 995292), 'is_active': True,
|
||||
'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0, 'labour_hours_per_unit': 0.0,
|
||||
'plant_cost': 0.0, 'total_cost': 280.0, 'notes': None, 'is_installer_quote': True,
|
||||
'innovation_rate': 0.0,
|
||||
'size': None, 'size_unit': None, 'includes_scaffolding': False, 'includes_battery': False,
|
||||
'battery_size': None, 'total': 560.0, 'quantity': 2, 'quantity_unit': 'part'}],
|
||||
'type': 'mechanical_ventilation',
|
||||
'measure_type': 'mechanical_ventilation',
|
||||
'description': 'Install 2 Decentralised mechanical '
|
||||
'extract ventilation units',
|
||||
'starting_u_value': None, 'new_u_value': None,
|
||||
'already_installed': False,
|
||||
'sap_points': np.float64(-1.4000000000000057),
|
||||
'heat_demand': np.float64(-6.5), 'kwh_savings': 0,
|
||||
'co2_equivalent_savings': np.float64(0.0),
|
||||
'energy_cost_savings': 0, 'total': 560.0,
|
||||
'labour_hours': 8, 'labour_days': 1.0,
|
||||
'simulation_config': {
|
||||
'mechanical_ventilation_ending': 'mechanical, '
|
||||
'extract only'},
|
||||
'description_simulation': {
|
||||
'mechanical-ventilation': 'mechanical, '
|
||||
'extract only'},
|
||||
'innovation_rate': 0.0,
|
||||
'recommendation_id': '3_phase=1', 'efficiency': 0},
|
||||
{
|
||||
'phase': 2, 'parts': [], 'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
|
||||
'description': 'Install low energy lighting in 3 outlets', 'starting_u_value': None,
|
||||
'new_u_value': None, 'already_installed': False, 'sap_points': 1, 'kwh_savings': 164.25,
|
||||
'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
|
||||
'description_simulation': {'lighting-energy-eff': 'Very Good',
|
||||
'lighting-description': 'Low energy lighting in all fixed outlets',
|
||||
'low-energy-lighting': 100}, 'total': 10.5, 'contingency': 2.73,
|
||||
'contingency_rate': 0.26, 'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
|
||||
'innovation_rate': 0.0, 'recommendation_id': '4_phase=2', 'efficiency': 10.5,
|
||||
'heat_demand': np.float64(4.100000000000023)
|
||||
},
|
||||
{
|
||||
'type': 'heating', 'measure_type': 'roomstat_programmer_trvs', 'phase': 3, 'parts': [],
|
||||
'description': 'Upgrade heating controls to Room thermostat, programmer and TRVs', 'total': 70,
|
||||
'contingency': 7.0, 'contingency_rate': 0.1, 'subtotal': 58.333333333333336,
|
||||
'vat': 11.666666666666664, 'labour_hours': 0.5, 'labour_days': 1, 'starting_u_value': None,
|
||||
'new_u_value': None, 'sap_points': np.float64(1.0), 'already_installed': False,
|
||||
'simulation_config': {'trvs_ending': 'trvs', 'mainheatc_energy_eff_ending': 'Good'},
|
||||
'description_simulation': {'mainheatcont-description': 'Programmer, room thermostat and TRVS',
|
||||
'mainheatc-energy-eff': 'Good'}, 'innovation_rate': 0.0,
|
||||
'recommendation_id': '5_phase=3', 'efficiency': 70,
|
||||
'co2_equivalent_savings': np.float64(0.10000000000000009), 'heat_demand': np.float64(8.5)
|
||||
},
|
||||
{
|
||||
'phase': 4, 'parts': [], 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
|
||||
'description': 'Remove the secondary heating system', 'starting_u_value': None, 'new_u_value': None,
|
||||
'sap_points': np.float64(0.0), 'already_installed': False, 'total': 60.0, 'contingency': 6.0,
|
||||
'contingency_rate': 0.1, 'subtotal': 50.0, 'vat': 10.0, 'labour_hours': 6.0,
|
||||
'labour_days': np.float64(1.0), 'simulation_config': {'secondheat_description_ending': 'None'},
|
||||
'description_simulation': {'secondheat-description': 'None'}, 'innovation_rate': 0.0,
|
||||
'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
|
||||
'heat_demand': np.float64(0.0)},
|
||||
{
|
||||
'phase': 5, 'parts': [
|
||||
{'id': 3516, 'type': 'solar_pv', 'description': 'Trina Vertex S3 445W solar panels', 'depth': 0.0,
|
||||
'depth_unit': None, 'cost': None, 'cost_unit': None, 'r_value_per_mm': nan,
|
||||
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
|
||||
'thermal_conductivity_unit': None, 'link': 'Coactivation',
|
||||
'created_at': datetime.datetime(2025, 8, 15, 16, 31, 52, 995292), 'is_active': True,
|
||||
'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0,
|
||||
'labour_hours_per_unit': 0.0,
|
||||
'plant_cost': 0.0, 'total_cost': 5892.21, 'notes': '445W panels', 'is_installer_quote': True,
|
||||
'innovation_rate': 0.0, 'size': 5.34, 'size_unit': 'kWp', 'includes_scaffolding': True,
|
||||
'includes_battery': False, 'battery_size': None, 'panel_size': 445}], 'type': 'solar_pv',
|
||||
'measure_type': 'solar_pv',
|
||||
'description': 'Trina Vertex S3 445W solar panels - 5.34 kWp '
|
||||
'system',
|
||||
'starting_u_value': None, 'new_u_value': None,
|
||||
'sap_points': np.float64(16.0), 'already_installed': False,
|
||||
'total': 5892.21, 'subtotal': 5892.21, 'contingency': 883.8315,
|
||||
'contingency_rate': 0.15, 'vat': 0, 'labour_hours': 48,
|
||||
'labour_days': 2, 'has_battery': False,
|
||||
'simulation_config': {'photo_supply_ending': np.float64(80.0)},
|
||||
'initial_ac_kwh_per_year': np.float64(4844.465553999999),
|
||||
'description_simulation': {'photo-supply': np.float64(80.0)},
|
||||
'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
|
||||
'efficiency': np.float64(368.263125)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
recommendations_with_impact, impact_summary, adjustments = (
|
||||
Recommendations.calculate_recommendation_impact(
|
||||
property_instance=p,
|
||||
all_predictions=all_predictions,
|
||||
recommendations=recommendations,
|
||||
representative_recommendations=representative_recommendations,
|
||||
debug=True
|
||||
)
|
||||
)
|
||||
|
||||
# We expect an adjustment to be made for loft insulation, reducing the impact by
|
||||
# 1.7
|
||||
assert adjustments == [{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)}]
|
||||
|
||||
# We expect that adjustment to flow through to the final recommendation so that the solar recommendation has
|
||||
# a 1.7 sap point reduction in impact
|
||||
|
||||
assert float(impact_summary[-1]["sap"]) == 82.1
|
||||
assert float(impact_summary[-1]["sap_prediction"]) == 83.8
|
||||
|
||||
assert impact_summary[-1] == {
|
||||
'phase': 5, 'representative': True, 'recommendation_id': '29_phase=5', 'measure_type': 'solar_pv',
|
||||
'sap': np.float64(82.1), 'carbon': np.float64(0.8), 'heat_demand': np.float64(82.5),
|
||||
'sap_prediction': np.float64(83.8)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue