mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
1764 lines
88 KiB
Python
1764 lines
88 KiB
Python
import pytest
|
|
import pandas as pd
|
|
import numpy as np
|
|
from unittest.mock import Mock
|
|
|
|
from recommendations.Recommendations import Recommendations
|
|
|
|
|
|
@pytest.fixture
|
|
def heat_demand_predictions():
|
|
return 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}
|
|
]
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def carbon_predictions():
|
|
return 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}
|
|
]
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def property_instance():
|
|
return 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={
|
|
"is_loft": True,
|
|
"insulation_thickness": "250",
|
|
"is_valid": True,
|
|
},
|
|
lighting={
|
|
"low_energy_proportion": 0.5
|
|
}
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"input_data, expected",
|
|
[
|
|
(
|
|
[
|
|
{"recommendation_id": "a", "phase": 0, "sap_adjustment": 1.7},
|
|
{"recommendation_id": "b", "phase": 0, "sap_adjustment": 1.7},
|
|
],
|
|
[{"recommendation_id": "a", "phase": 0, "sap_adjustment": 1.7}],
|
|
),
|
|
(
|
|
[
|
|
{"recommendation_id": "a", "phase": 1, "sap_adjustment": 2},
|
|
{"recommendation_id": "b", "phase": 2, "sap_adjustment": 3},
|
|
],
|
|
[
|
|
{"recommendation_id": "a", "phase": 1, "sap_adjustment": 2},
|
|
{"recommendation_id": "b", "phase": 2, "sap_adjustment": 3},
|
|
],
|
|
),
|
|
],
|
|
)
|
|
def test_filter_phase_adjustment(input_data, expected):
|
|
assert Recommendations._filter_phase_adjustment(input_data) == expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"sap_impact, limit, expected",
|
|
[
|
|
(1.0, -4, True), # positive SAP not allowed
|
|
(0.0, -4, False), # zero is allowed
|
|
(-1.0, -4, False), # valid range
|
|
(-3.9, -4, False), # valid range
|
|
(-4.0, -4, False), # exact lower bound allowed
|
|
(-4.1, -4, True), # below lower bound
|
|
],
|
|
)
|
|
def test_check_ventilation_out_of_bounds(sap_impact, limit, expected):
|
|
assert Recommendations._check_ventilation_out_of_bounds(
|
|
sap_impact, limit
|
|
) is expected
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"sap_impact, limit, expected",
|
|
[
|
|
(1.2, -4, -1), # positive → capped to -1
|
|
(0.0, -4, -1), # zero → capped to -1
|
|
(-5.0, -4, -4), # below limit → clamp
|
|
(-3.0, -4, -3.0), # already valid → unchanged
|
|
],
|
|
)
|
|
def test_adjust_ventilation_sap(sap_impact, limit, expected):
|
|
assert Recommendations._adjust_ventilation_sap(
|
|
sap_impact, limit
|
|
) == expected
|
|
|
|
|
|
def test_get_previous_phase_values_starting_phase(property_instance):
|
|
result = Recommendations._get_previous_phase_values(
|
|
rec_phase=0,
|
|
starting_phase=0,
|
|
impact_summary=[],
|
|
property_instance=property_instance,
|
|
)
|
|
|
|
assert result == {
|
|
"sap": 65.0,
|
|
"carbon": 2.4,
|
|
"heat_demand": 284.0,
|
|
}
|
|
|
|
|
|
def test_get_previous_phase_values_single_rep(property_instance):
|
|
impact_summary = [
|
|
{
|
|
"phase": 0,
|
|
"representative": True,
|
|
"sap": 66,
|
|
"carbon": 2.2,
|
|
"heat_demand": 260,
|
|
}
|
|
]
|
|
|
|
result = Recommendations._get_previous_phase_values(
|
|
rec_phase=1,
|
|
starting_phase=0,
|
|
impact_summary=impact_summary,
|
|
property_instance=property_instance,
|
|
)
|
|
|
|
assert result["sap"] == 66
|
|
assert result["carbon"] == 2.2
|
|
assert result["heat_demand"] == 260
|
|
|
|
|
|
def test_get_previous_phase_values_median(property_instance):
|
|
impact_summary = [
|
|
{"phase": 1, "representative": True, "sap": 70, "carbon": 2.0, "heat_demand": 250},
|
|
{"phase": 1, "representative": True, "sap": 74, "carbon": 1.6, "heat_demand": 230},
|
|
]
|
|
|
|
result = Recommendations._get_previous_phase_values(
|
|
rec_phase=2,
|
|
starting_phase=0,
|
|
impact_summary=impact_summary,
|
|
property_instance=property_instance,
|
|
)
|
|
|
|
assert result["sap"] == np.median([70, 74])
|
|
assert result["carbon"] == np.median([2.0, 1.6])
|
|
assert result["heat_demand"] == np.median([250, 230])
|
|
|
|
|
|
def test_compute_phase_impact_standard():
|
|
previous = {"sap": 65, "carbon": 2.4, "heat_demand": 284}
|
|
current = {"sap": 64, "carbon": 2.6, "heat_demand": 300}
|
|
|
|
impact = Recommendations._compute_phase_impact(
|
|
rec_type="loft_insulation",
|
|
previous_phase_values=previous,
|
|
current_phase_values=current,
|
|
)
|
|
|
|
# monotonicity enforced
|
|
assert impact["sap"] == 0
|
|
assert impact["carbon"] == 0
|
|
assert impact["heat_demand"] == 0
|
|
|
|
|
|
def test_compute_phase_impact_mechanical_ventilation():
|
|
previous = {"sap": 65, "carbon": 2.4, "heat_demand": 284}
|
|
current = {"sap": 63, "carbon": 2.4, "heat_demand": 284}
|
|
|
|
impact = Recommendations._compute_phase_impact(
|
|
rec_type="mechanical_ventilation",
|
|
previous_phase_values=previous,
|
|
current_phase_values=current,
|
|
)
|
|
|
|
assert impact["sap"] == -2
|
|
|
|
|
|
def test_resolve_current_phase_sap_with_adjustments():
|
|
rec = {"phase": 3, "survey": False}
|
|
previous = {"sap": 65}
|
|
phase_metrics = {"sap_change": 70}
|
|
adjustments = [
|
|
{"phase": 1, "sap_adjustment": 1.5},
|
|
{"phase": 2, "sap_adjustment": 2.0},
|
|
]
|
|
|
|
sap = Recommendations._resolve_current_phase_sap(
|
|
rec=rec,
|
|
previous_phase_values=previous,
|
|
phase_energy_efficiency_metrics=phase_metrics,
|
|
adjustments=adjustments,
|
|
)
|
|
|
|
assert sap == 70 - (1.5 + 2.0)
|
|
|
|
|
|
def test_validate_recommendation_updates_raises():
|
|
rec = {
|
|
"sap_points": None,
|
|
"co2_equivalent_savings": None,
|
|
"heat_demand": None,
|
|
}
|
|
|
|
with pytest.raises(ValueError):
|
|
Recommendations._validate_recommendation_updates(rec)
|
|
|
|
|
|
def test_calculate_recommendation_impact(property_instance, heat_demand_predictions, carbon_predictions):
|
|
#######
|
|
# Case 3
|
|
#######
|
|
# Here, the solar impact falls below our threshold and so we expect a solar adjustment to increase the impact
|
|
# above the minimum threshold
|
|
|
|
all_predictions3 = {
|
|
"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": heat_demand_predictions,
|
|
"carbon_change_predictions": carbon_predictions,
|
|
"hotwater_kwh_predictions": pd.DataFrame([]),
|
|
"heating_kwh_predictions": pd.DataFrame([]),
|
|
}
|
|
|
|
recommendations3 = {
|
|
614626: [
|
|
[
|
|
{
|
|
'phase': 0,
|
|
'type': 'loft_insulation',
|
|
'measure_type': 'loft_insulation',
|
|
'sap_points': 0,
|
|
'survey': False,
|
|
'recommendation_id': '0_phase=0',
|
|
'co2_equivalent_savings': np.float64(0.19999999999999973),
|
|
'heat_demand': np.float64(27.399999999999977)},
|
|
],
|
|
[
|
|
{
|
|
'phase': 1,
|
|
'type': 'mechanical_ventilation',
|
|
'measure_type': 'mechanical_ventilation',
|
|
'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,
|
|
'innovation_rate': 0.0,
|
|
'recommendation_id': '3_phase=1', 'efficiency': 0}
|
|
],
|
|
[
|
|
{
|
|
'phase': 2,
|
|
'type': 'low_energy_lighting',
|
|
'measure_type': 'low_energy_lighting',
|
|
'already_installed': False, 'sap_points': 5, 'kwh_savings': 164.25,
|
|
'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
|
|
'total': 10.5, 'contingency': 2.73,
|
|
'contingency_rate': 0.26, 'labour_hours': 1, 'labour_days': 0.125, 'survey': True,
|
|
'recommendation_id': '4_phase=2', 'efficiency': 10.5, 'heat_demand': np.float64(4.100000000000023)}
|
|
],
|
|
[
|
|
{
|
|
'type': 'heating', 'measure_type': 'roomstat_programmer_trvs', 'phase': 3,
|
|
'total': 70,
|
|
'contingency': 7.0, 'contingency_rate': 0.1, 'subtotal': 58.333333333333336,
|
|
'vat': 11.666666666666664,
|
|
'labour_hours': 0.5, 'labour_days': 1,
|
|
'sap_points': np.float64(1.0), 'already_installed': False,
|
|
'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',
|
|
'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),
|
|
'sap_points': np.float64(1.8), 'already_installed': False,
|
|
'recommendation_id': '6_phase=3', 'efficiency': 604.5840000000001,
|
|
'co2_equivalent_savings': np.float64(0.10000000000000009),
|
|
'heat_demand': np.float64(13.300000000000011)
|
|
},
|
|
{
|
|
'phase': 3, 'type': 'heating', 'measure_type': 'air_source_heat_pump',
|
|
'sap_points': np.float64(3.8),
|
|
'already_installed': False,
|
|
'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, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
|
|
'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), '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,
|
|
'type': 'solar_pv',
|
|
'measure_type': 'solar_pv',
|
|
'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,
|
|
'initial_ac_kwh_per_year': np.float64(4844.465553999999),
|
|
'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
|
|
'efficiency': np.float64(368.263125)
|
|
}
|
|
]
|
|
]
|
|
}
|
|
|
|
representative_recommendations3 = {
|
|
614626: [
|
|
{
|
|
'phase': 0,
|
|
'type': 'loft_insulation',
|
|
'measure_type': 'loft_insulation',
|
|
'sap_points': 0,
|
|
'already_installed': False,
|
|
'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,
|
|
'type': 'mechanical_ventilation',
|
|
'measure_type': 'mechanical_ventilation',
|
|
'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,
|
|
'innovation_rate': 0.0,
|
|
'recommendation_id': '3_phase=1', 'efficiency': 0},
|
|
{
|
|
'phase': 2,
|
|
'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
|
|
'already_installed': False, 'sap_points': 5, 'kwh_savings': 164.25,
|
|
'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
|
|
'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,
|
|
'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,
|
|
'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, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
|
|
'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),
|
|
'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
|
|
'heat_demand': np.float64(0.0)},
|
|
{
|
|
'phase': 5,
|
|
'type': 'solar_pv',
|
|
'measure_type': 'solar_pv',
|
|
|
|
'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,
|
|
'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
|
|
'efficiency': np.float64(368.263125)
|
|
}
|
|
]
|
|
}
|
|
|
|
recommendations_with_impact3, impact_summary3, adjustments3 = (
|
|
Recommendations.calculate_recommendation_impact(
|
|
property_instance=property_instance,
|
|
all_predictions=all_predictions3,
|
|
recommendations=recommendations3,
|
|
representative_recommendations=representative_recommendations3,
|
|
debug=True
|
|
)
|
|
)
|
|
|
|
# We expect adjustments for loft insulation, lighting and solar
|
|
|
|
assert adjustments3 == [
|
|
{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)},
|
|
{'recommendation_id': '4_phase=2', 'phase': 2, 'sap_adjustment': np.float64(4.0)},
|
|
{'recommendation_id': '29_phase=5', 'phase': 5, 'sap_adjustment': np.float64(-2.5)}
|
|
]
|
|
|
|
# Check the impact has slowed through to solar - the final on the impact summary. The 5
|
|
# point prediction isn't associated to the prediction from the model so the adjustment
|
|
# should be
|
|
|
|
df = all_predictions3["sap_change_predictions"]
|
|
raw_prediction = 83.8
|
|
# We expect 1.7 decrease from loft, 4 decrease from lighting, and 2.5 increase from solar
|
|
# for a total of a 3.2 decrease
|
|
expected_adjusted_prediction = raw_prediction - 3.2
|
|
|
|
assert impact_summary3[-1]["sap"] == expected_adjusted_prediction
|
|
|
|
|
|
def test_loft_adjustment_flows_to_solar(property_instance, heat_demand_predictions, carbon_predictions):
|
|
########################
|
|
# Case 1
|
|
########################
|
|
# Just an adjustment to loft insulation
|
|
|
|
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}]
|
|
)
|
|
|
|
all_predictions = {
|
|
"sap_change_predictions": sap_change_predictions,
|
|
"heat_demand_predictions": heat_demand_predictions,
|
|
"carbon_change_predictions": carbon_predictions,
|
|
"hotwater_kwh_predictions": pd.DataFrame([]),
|
|
"heating_kwh_predictions": pd.DataFrame([]),
|
|
}
|
|
|
|
recommendations = {
|
|
614626: [
|
|
[
|
|
{
|
|
'phase': 0, 'type': 'loft_insulation',
|
|
'measure_type': 'loft_insulation',
|
|
'sap_points': 0,
|
|
'already_installed': False,
|
|
'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,
|
|
'type': 'mechanical_ventilation', 'measure_type': 'mechanical_ventilation',
|
|
'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,
|
|
'innovation_rate': 0.0,
|
|
'recommendation_id': '3_phase=1', 'efficiency': 0}
|
|
],
|
|
[
|
|
{'phase': 2, 'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
|
|
'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),
|
|
'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,
|
|
'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,
|
|
'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',
|
|
'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,
|
|
'recommendation_id': '6_phase=3',
|
|
'co2_equivalent_savings': np.float64(0.10000000000000009),
|
|
'heat_demand': np.float64(13.300000000000011)
|
|
},
|
|
{
|
|
'phase': 3, 'type': 'heating', 'measure_type': 'air_source_heat_pump',
|
|
'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(3.8),
|
|
'already_installed': False,
|
|
'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, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
|
|
'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),
|
|
'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
|
|
'heat_demand': np.float64(0.0)}
|
|
],
|
|
[
|
|
{
|
|
'phase': 5, 'type': 'solar_pv',
|
|
'measure_type': 'solar_pv',
|
|
'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,
|
|
'initial_ac_kwh_per_year': np.float64(4844.465553999999),
|
|
'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
|
|
'efficiency': np.float64(368.263125)
|
|
}
|
|
]
|
|
]
|
|
}
|
|
|
|
representative_recommendations = {
|
|
614626: [
|
|
{
|
|
'phase': 0, 'type': 'loft_insulation',
|
|
'measure_type': 'loft_insulation',
|
|
'starting_u_value': np.float64(0.17), 'new_u_value': np.float64(0.14), 'sap_points': 0,
|
|
'already_installed': False,
|
|
'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,
|
|
'type': 'mechanical_ventilation',
|
|
'measure_type': 'mechanical_ventilation',
|
|
'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,
|
|
'recommendation_id': '3_phase=1', 'efficiency': 0},
|
|
{
|
|
'phase': 2, 'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
|
|
'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),
|
|
'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,
|
|
'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,
|
|
'recommendation_id': '5_phase=3', 'efficiency': 70,
|
|
'co2_equivalent_savings': np.float64(0.10000000000000009), 'heat_demand': np.float64(8.5)
|
|
},
|
|
{
|
|
'phase': 4, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
|
|
'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,
|
|
'recommendation_id': '8_phase=4', 'efficiency': 60.0, 'co2_equivalent_savings': np.float64(0.0),
|
|
'heat_demand': np.float64(0.0)},
|
|
{
|
|
'phase': 5, 'type': 'solar_pv',
|
|
'measure_type': 'solar_pv',
|
|
'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,
|
|
'recommendation_id': '29_phase=5',
|
|
}
|
|
]
|
|
}
|
|
|
|
recommendations_with_impact, impact_summary, adjustments = (
|
|
Recommendations.calculate_recommendation_impact(
|
|
property_instance=property_instance,
|
|
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
|
|
|
|
final_impact_summary = impact_summary[-1]
|
|
assert float(final_impact_summary["sap"]) == 82.1
|
|
assert float(final_impact_summary["sap_prediction"]) == 83.8
|
|
assert final_impact_summary["measure_type"] == "solar_pv"
|
|
assert recommendations_with_impact[0][0]["sap_points"] == 0
|
|
|
|
|
|
def test_lighting_and_loft_adjustment_combined(property_instance, heat_demand_predictions, carbon_predictions):
|
|
########################
|
|
# Case 2
|
|
########################
|
|
# Example case with both a loft insulation and lighting adjustment
|
|
# lighting now has a SAP point impact of 5 - the affected recommendation is
|
|
# recommendation_id=4_phase=2
|
|
all_predictions2 = {
|
|
"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': 71.3, 'property_id': '614626',
|
|
'recommendation_id': '4_phase=2',
|
|
'phase': 2},
|
|
{'id': '614626+5_phase=3', 'predictions': 72.3, 'property_id': '614626',
|
|
'recommendation_id': '5_phase=3',
|
|
'phase': 3},
|
|
{'id': '614626+6_phase=3', 'predictions': 73.1, 'property_id': '614626',
|
|
'recommendation_id': '6_phase=3',
|
|
'phase': 3},
|
|
{'id': '614626+7_phase=3', 'predictions': 75.1, 'property_id': '614626',
|
|
'recommendation_id': '7_phase=3',
|
|
'phase': 3},
|
|
{'id': '614626+8_phase=4', 'predictions': 72.3, 'property_id': '614626',
|
|
'recommendation_id': '8_phase=4',
|
|
'phase': 4},
|
|
{'id': '614626+9_phase=5', 'predictions': 90.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': 90.3, 'property_id': '614626',
|
|
'recommendation_id': '11_phase=5', 'phase': 5},
|
|
{'id': '614626+12_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '12_phase=5', 'phase': 5},
|
|
{'id': '614626+13_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '13_phase=5', 'phase': 5},
|
|
{'id': '614626+14_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '14_phase=5', 'phase': 5},
|
|
{'id': '614626+15_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '15_phase=5', 'phase': 5},
|
|
{'id': '614626+16_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '16_phase=5', 'phase': 5},
|
|
{'id': '614626+17_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '17_phase=5', 'phase': 5},
|
|
{'id': '614626+18_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '18_phase=5', 'phase': 5},
|
|
{'id': '614626+19_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '19_phase=5', 'phase': 5},
|
|
{'id': '614626+20_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '20_phase=5', 'phase': 5},
|
|
{'id': '614626+21_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '21_phase=5', 'phase': 5},
|
|
{'id': '614626+22_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '22_phase=5', 'phase': 5},
|
|
{'id': '614626+23_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '23_phase=5', 'phase': 5},
|
|
{'id': '614626+24_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '24_phase=5', 'phase': 5},
|
|
{'id': '614626+25_phase=5', 'predictions': 91.7, 'property_id': '614626',
|
|
'recommendation_id': '25_phase=5', 'phase': 5},
|
|
{'id': '614626+26_phase=5', 'predictions': 91.7, 'property_id': '614626',
|
|
'recommendation_id': '26_phase=5', 'phase': 5},
|
|
{'id': '614626+27_phase=5', 'predictions': 91.7, 'property_id': '614626',
|
|
'recommendation_id': '27_phase=5', 'phase': 5},
|
|
{'id': '614626+28_phase=5', 'predictions': 91.7, 'property_id': '614626',
|
|
'recommendation_id': '28_phase=5', 'phase': 5},
|
|
{'id': '614626+29_phase=5', 'predictions': 88.8, 'property_id': '614626',
|
|
'recommendation_id': '29_phase=5', 'phase': 5},
|
|
{'id': '614626+30_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '30_phase=5', 'phase': 5},
|
|
{'id': '614626+31_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '31_phase=5', 'phase': 5},
|
|
{'id': '614626+32_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '32_phase=5', 'phase': 5},
|
|
{'id': '614626+33_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '33_phase=5', 'phase': 5},
|
|
{'id': '614626+34_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '34_phase=5', 'phase': 5},
|
|
{'id': '614626+35_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '35_phase=5', 'phase': 5},
|
|
{'id': '614626+36_phase=5', 'predictions': 91.4, 'property_id': '614626',
|
|
'recommendation_id': '36_phase=5', 'phase': 5},
|
|
{'id': '614626+37_phase=5', 'predictions': 86.2, 'property_id': '614626',
|
|
'recommendation_id': '37_phase=5', 'phase': 5},
|
|
{'id': '614626+38_phase=5', 'predictions': 86.2, 'property_id': '614626',
|
|
'recommendation_id': '38_phase=5', 'phase': 5},
|
|
{'id': '614626+39_phase=5', 'predictions': 86.2, 'property_id': '614626',
|
|
'recommendation_id': '39_phase=5', 'phase': 5},
|
|
{'id': '614626+40_phase=5', 'predictions': 88.4, 'property_id': '614626',
|
|
'recommendation_id': '40_phase=5', 'phase': 5},
|
|
{'id': '614626+41_phase=5', 'predictions': 88.4, 'property_id': '614626',
|
|
'recommendation_id': '41_phase=5', 'phase': 5},
|
|
{'id': '614626+42_phase=5', 'predictions': 88.4, 'property_id': '614626',
|
|
'recommendation_id': '42_phase=5', 'phase': 5},
|
|
{'id': '614626+43_phase=5', 'predictions': 88.4, 'property_id': '614626',
|
|
'recommendation_id': '43_phase=5', 'phase': 5},
|
|
{'id': '614626+44_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '44_phase=5', 'phase': 5},
|
|
{'id': '614626+45_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '45_phase=5', 'phase': 5},
|
|
{'id': '614626+46_phase=5', 'predictions': 90.5, 'property_id': '614626',
|
|
'recommendation_id': '46_phase=5', 'phase': 5},
|
|
{'id': '614626+47_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '47_phase=5', 'phase': 5},
|
|
{'id': '614626+48_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '48_phase=5', 'phase': 5},
|
|
{'id': '614626+49_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '49_phase=5', 'phase': 5},
|
|
{'id': '614626+50_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '50_phase=5', 'phase': 5},
|
|
{'id': '614626+51_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '51_phase=5', 'phase': 5},
|
|
{'id': '614626+52_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '52_phase=5', 'phase': 5},
|
|
{'id': '614626+53_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '53_phase=5', 'phase': 5},
|
|
{'id': '614626+54_phase=5', 'predictions': 90.4, 'property_id': '614626',
|
|
'recommendation_id': '54_phase=5', 'phase': 5},
|
|
{'id': '614626+55_phase=5', 'predictions': 84.4, 'property_id': '614626',
|
|
'recommendation_id': '55_phase=5', 'phase': 5},
|
|
{'id': '614626+56_phase=5', 'predictions': 86.2, 'property_id': '614626',
|
|
'recommendation_id': '56_phase=5', 'phase': 5},
|
|
{'id': '614626+57_phase=5', 'predictions': 86.2, 'property_id': '614626',
|
|
'recommendation_id': '57_phase=5', 'phase': 5}]
|
|
),
|
|
"heat_demand_predictions": heat_demand_predictions,
|
|
"carbon_change_predictions": carbon_predictions,
|
|
"hotwater_kwh_predictions": pd.DataFrame([]),
|
|
"heating_kwh_predictions": pd.DataFrame([]),
|
|
}
|
|
|
|
recommendations2 = {
|
|
614626: [
|
|
[
|
|
{
|
|
'phase': 0,
|
|
'type': 'loft_insulation',
|
|
'measure_type': 'loft_insulation',
|
|
'sap_points': 0,
|
|
'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,
|
|
'type': 'mechanical_ventilation', 'measure_type': 'mechanical_ventilation',
|
|
'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,
|
|
'recommendation_id': '3_phase=1',
|
|
}
|
|
],
|
|
[
|
|
{
|
|
'phase': 2, 'type': 'low_energy_lighting', 'measure_type': 'low_energy_lighting',
|
|
'new_u_value': None,
|
|
'already_installed': False, 'sap_points': 5, 'kwh_savings': 164.25,
|
|
'energy_cost_savings': 45.480824999999996, 'co2_equivalent_savings': np.float64(0.0),
|
|
'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,
|
|
'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,
|
|
'recommendation_id': '5_phase=3',
|
|
'co2_equivalent_savings': np.float64(0.10000000000000009), 'heat_demand': np.float64(8.5)},
|
|
{
|
|
'type': 'heating', 'phase': 3, 'measure_type': '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,
|
|
'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, 'type': 'heating', 'measure_type': 'air_source_heat_pump',
|
|
'starting_u_value': None, 'new_u_value': None, 'sap_points': np.float64(3.8),
|
|
'already_installed': False,
|
|
'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, 'type': 'secondary_heating', 'measure_type': 'secondary_heating',
|
|
'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), '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, 'type': 'solar_pv',
|
|
'measure_type': 'solar_pv',
|
|
'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,
|
|
'innovation_rate': 0.0, 'recommendation_id': '29_phase=5',
|
|
'efficiency': np.float64(368.263125)
|
|
}
|
|
]
|
|
]
|
|
}
|
|
|
|
representative_recommendations2 = {
|
|
614626: [
|
|
{
|
|
'phase': 0,
|
|
'type': 'loft_insulation',
|
|
'measure_type': 'loft_insulation',
|
|
'sap_points': 0,
|
|
'survey': False,
|
|
'recommendation_id': '0_phase=0',
|
|
},
|
|
{
|
|
'phase': 1,
|
|
'type': 'mechanical_ventilation',
|
|
'measure_type': 'mechanical_ventilation',
|
|
'sap_points': np.float64(-1.4000000000000057),
|
|
'recommendation_id': '3_phase=1'
|
|
},
|
|
{
|
|
'phase': 2,
|
|
'type': 'low_energy_lighting',
|
|
'measure_type': 'low_energy_lighting',
|
|
'sap_points': 5,
|
|
'survey': True,
|
|
'recommendation_id': '4_phase=2',
|
|
},
|
|
{
|
|
'type': 'heating',
|
|
'measure_type': 'roomstat_programmer_trvs',
|
|
'phase': 3,
|
|
'sap_points': np.float64(1.0),
|
|
'recommendation_id': '5_phase=3',
|
|
},
|
|
{
|
|
'phase': 4,
|
|
'type': 'secondary_heating',
|
|
'measure_type': 'secondary_heating',
|
|
'sap_points': np.float64(0.0),
|
|
'recommendation_id': '8_phase=4',
|
|
},
|
|
{
|
|
'phase': 5,
|
|
'type': 'solar_pv',
|
|
'measure_type': 'solar_pv',
|
|
'sap_points': np.float64(16.0),
|
|
'recommendation_id': '29_phase=5',
|
|
}
|
|
]
|
|
}
|
|
|
|
recommendations_with_impact2, impact_summary2, adjustments2 = (
|
|
Recommendations.calculate_recommendation_impact(
|
|
property_instance=property_instance,
|
|
all_predictions=all_predictions2,
|
|
recommendations=recommendations2,
|
|
representative_recommendations=representative_recommendations2,
|
|
debug=True
|
|
)
|
|
)
|
|
|
|
assert adjustments2 == [
|
|
{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)},
|
|
{'recommendation_id': '4_phase=2', 'phase': 2, 'sap_adjustment': np.float64(4.0)},
|
|
{'recommendation_id': '5_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0)},
|
|
{'recommendation_id': '6_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0000000000000027)}
|
|
]
|
|
|
|
|
|
def test_mechanical_ventilation_sap_floor(property_instance):
|
|
rec = {
|
|
"type": "mechanical_ventilation",
|
|
"recommendation_id": "mv_test",
|
|
"phase": 1,
|
|
}
|
|
|
|
previous_phase_values = {"sap": 2.0}
|
|
current_phase_values = {"sap": 0.5} # model prediction already below 1
|
|
property_phase_impact = {"sap": -1.5, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec=rec,
|
|
property_phase_impact=property_phase_impact,
|
|
previous_phase_values=previous_phase_values,
|
|
current_phase_values=current_phase_values,
|
|
adjustments=adjustments,
|
|
property_instance=property_instance,
|
|
model_predicted_sap=0
|
|
)
|
|
)
|
|
|
|
# SAP should be clamped to minimum 1
|
|
assert updated_current["sap"] == 1.0
|
|
|
|
# Original final SAP would have been 0.5 → so adjustment = 1 - 0.5 = 0.5
|
|
assert updated_adjustments == [
|
|
{
|
|
"recommendation_id": "mv_test",
|
|
"phase": 1,
|
|
"sap_adjustment": 0.5,
|
|
}
|
|
]
|
|
|
|
# Impact should now reflect new clamped SAP
|
|
assert updated_impact["sap"] == -1.0 # 2.0 → 1.0
|
|
|
|
|
|
def test_mechanical_ventilation_no_floor_adjustment(property_instance):
|
|
rec = {
|
|
"type": "mechanical_ventilation",
|
|
"recommendation_id": "mv_test",
|
|
"phase": 1,
|
|
}
|
|
|
|
previous_phase_values = {"sap": 5.0}
|
|
current_phase_values = {"sap": 3.0}
|
|
property_phase_impact = {"sap": -2.0, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec=rec,
|
|
property_phase_impact=property_phase_impact,
|
|
previous_phase_values=previous_phase_values,
|
|
current_phase_values=current_phase_values,
|
|
adjustments=adjustments,
|
|
property_instance=property_instance,
|
|
model_predicted_sap=0
|
|
)
|
|
)
|
|
|
|
# No adjustment expected
|
|
assert updated_adjustments == []
|
|
|
|
# SAP unchanged
|
|
assert updated_current["sap"] == 3.0
|
|
assert updated_impact["sap"] == -2.0
|
|
|
|
|
|
def test_mechanical_ventilation_exactly_one_no_adjustment(property_instance):
|
|
# Test when SAP = 1
|
|
rec = {
|
|
"type": "mechanical_ventilation",
|
|
"recommendation_id": "mv_test",
|
|
"phase": 1,
|
|
}
|
|
|
|
previous_phase_values = {"sap": 2.0}
|
|
current_phase_values = {"sap": 1.0}
|
|
property_phase_impact = {"sap": -1.0, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec=rec,
|
|
property_phase_impact=property_phase_impact,
|
|
previous_phase_values=previous_phase_values,
|
|
current_phase_values=current_phase_values,
|
|
adjustments=adjustments,
|
|
property_instance=property_instance,
|
|
model_predicted_sap=0
|
|
)
|
|
)
|
|
|
|
# Exactly 1 → no adjustment
|
|
assert updated_adjustments == []
|
|
assert updated_current["sap"] == 1.0
|
|
assert updated_impact["sap"] == -1.0
|
|
|
|
|
|
def test_mechanical_ventilation_sap_zero_no_adjustment(property_instance):
|
|
# Test when SAP = 0
|
|
rec = {
|
|
"type": "mechanical_ventilation",
|
|
"recommendation_id": "mv_test",
|
|
"phase": 1,
|
|
}
|
|
|
|
previous_phase_values = {'phase': 0, 'representative': True, 'recommendation_id': '0_phase=0',
|
|
'measure_type': 'flat_roof_insulation', 'sap': 68.0, 'carbon': np.float64(0.5),
|
|
'heat_demand': np.float64(300.1), 'sap_prediction': np.float64(71.7)}
|
|
current_phase_values = {'sap': 68.0, 'carbon': np.float64(0.5), 'heat_demand': np.float64(307.0)}
|
|
property_phase_impact = {'sap': 0, 'carbon': 0, 'heat_demand': np.float64(-6.899999999999977)}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec=rec,
|
|
property_phase_impact=property_phase_impact,
|
|
previous_phase_values=previous_phase_values,
|
|
current_phase_values=current_phase_values,
|
|
adjustments=adjustments,
|
|
property_instance=property_instance,
|
|
model_predicted_sap=0
|
|
)
|
|
)
|
|
|
|
# SAP is already at 0 → no adjustment expected
|
|
assert updated_adjustments == []
|
|
assert updated_current["sap"] == 68.0
|
|
assert updated_impact["sap"] == 0
|
|
|
|
|
|
def test_mv_valid_negative_no_adjustment(property_instance):
|
|
rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}
|
|
|
|
previous = {"sap": 70.0}
|
|
current = {"sap": 67.0}
|
|
impact = {"sap": -3.0, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec, impact, previous, current, adjustments, property_instance, 0
|
|
)
|
|
)
|
|
|
|
assert updated_adjustments == []
|
|
assert updated_current["sap"] == 67.0
|
|
assert updated_impact["sap"] == -3.0
|
|
|
|
|
|
def test_mv_zero_impact_allowed(property_instance):
|
|
rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}
|
|
|
|
previous = {"sap": 68.0, "sap_prediction": 71.7}
|
|
current = {"sap": 68.0}
|
|
impact = {"sap": 0.0, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec, impact, previous, current, adjustments, property_instance, 0
|
|
)
|
|
)
|
|
|
|
assert updated_adjustments == []
|
|
assert updated_current["sap"] == 68.0
|
|
assert updated_impact["sap"] == 0.0
|
|
|
|
|
|
def test_mv_positive_impact_corrected(property_instance):
|
|
rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}
|
|
|
|
previous = {"sap": 60.0}
|
|
current = {"sap": 61.0}
|
|
impact = {"sap": 1.0, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec, impact, previous, current, adjustments, property_instance, 0
|
|
)
|
|
)
|
|
|
|
assert len(updated_adjustments) == 1
|
|
assert updated_current["sap"] == previous["sap"] + updated_impact["sap"]
|
|
assert updated_impact["sap"] <= 0
|
|
|
|
|
|
def test_mv_below_lower_bound_corrected(property_instance):
|
|
rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}
|
|
|
|
previous = {"sap": 70.0}
|
|
current = {"sap": 64.0}
|
|
impact = {"sap": -6.0, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec, impact, previous, current, adjustments, property_instance, 0
|
|
)
|
|
)
|
|
|
|
assert len(updated_adjustments) == 1
|
|
assert updated_impact["sap"] >= -4
|
|
|
|
|
|
def test_mv_floor_triggered(property_instance):
|
|
rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}
|
|
|
|
previous = {"sap": 2.0}
|
|
current = {"sap": 0.5}
|
|
impact = {"sap": -1.5, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec, impact, previous, current, adjustments, property_instance, 0
|
|
)
|
|
)
|
|
|
|
assert updated_current["sap"] == 1.0
|
|
assert updated_adjustments[0]["sap_adjustment"] > 0
|
|
|
|
|
|
def test_mv_exactly_one_no_floor(property_instance):
|
|
rec = {"type": "mechanical_ventilation", "recommendation_id": "mv", "phase": 1}
|
|
|
|
previous = {"sap": 2.0}
|
|
current = {"sap": 1.0}
|
|
impact = {"sap": -1.0, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec, impact, previous, current, adjustments, property_instance, 0
|
|
)
|
|
)
|
|
|
|
assert updated_adjustments == []
|
|
assert updated_current["sap"] == 1.0
|
|
|
|
|
|
def test_lighting_no_cap(property_instance):
|
|
rec = {"type": "low_energy_lighting", "recommendation_id": "led", "phase": 1,
|
|
"co2_equivalent_savings": 0}
|
|
|
|
previous = {"sap": 60.0, "carbon": 2.0}
|
|
current = {"sap": 61.0, "carbon": 2.0}
|
|
impact = {"sap": 1.0, "carbon": 0, "heat_demand": 0}
|
|
adjustments = []
|
|
|
|
updated_impact, updated_current, updated_adjustments = (
|
|
Recommendations._apply_measure_specific_rules(
|
|
rec, impact, previous, current, adjustments, property_instance, 0
|
|
)
|
|
)
|
|
|
|
assert updated_adjustments == []
|
|
|
|
|
|
def test_filter_phase_adjustments():
|
|
example_adjustments = [
|
|
{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)},
|
|
{'recommendation_id': '4_phase=2', 'phase': 2, 'sap_adjustment': np.float64(4.0)},
|
|
{'recommendation_id': '5_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0)},
|
|
{'recommendation_id': '6_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0000000000000027)}
|
|
]
|
|
|
|
res = Recommendations._filter_phase_adjustment(example_adjustments)
|
|
|
|
assert res == [
|
|
{'recommendation_id': '0_phase=0', 'phase': 0, 'sap_adjustment': np.float64(1.7)},
|
|
{'recommendation_id': '4_phase=2', 'phase': 2, 'sap_adjustment': np.float64(4.0)},
|
|
{'recommendation_id': '6_phase=3', 'phase': 3, 'sap_adjustment': np.float64(1.0000000000000027)}
|
|
]
|