Merge pull request #343 from Hestia-Homes/fix-unit-tests

Updated unit tests
This commit is contained in:
KhalimCK 2024-10-01 15:59:10 +01:00 committed by GitHub
commit 14c62f061d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 1446 additions and 3094 deletions

View file

@ -502,43 +502,12 @@ class Property:
output["low_energy_lighting_ending"] = 100
output["lighting_energy_eff_ending"] = "Very Good"
if recommendation["type"] == "windows_glazing":
is_secondary_glazing = recommendation["is_secondary_glazing"]
output["multi_glaze_proportion_ending"] = 100
if output["windows_energy_eff_ending"] not in ["Average", "Good", "Very Good"]:
output["windows_energy_eff_ending"] = "Average" if not is_secondary_glazing else "Good"
if output["glazing_type_ending"] == "multiple":
pass
elif output["glazing_type_ending"] == "single":
output["glazing_type_ending"] = (
"secondary" if is_secondary_glazing else "double"
)
elif output["glazing_type_ending"] == "double":
output["glazing_type_ending"] = (
"multiple" if is_secondary_glazing else "double"
)
elif output["glazing_type_ending"] == "secondary":
output["glazing_type_ending"] = (
"secondary" if is_secondary_glazing else "multiple"
)
elif output["glazing_type_ending"] in ["triple", "high performance"]:
output["glazing_type_ending"] = "multiple"
else:
raise ValueError("Invalid glazing type - implement me")
if is_secondary_glazing:
output["glazed_type_ending"] = "secondary glazing"
else:
output["glazed_type_ending"] = (
"double glazing installed during or after 2002"
)
if recommendation["type"] in [
"heating", "hot_water_tank_insulation", "heating_control", "secondary_heating",
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation",
"cylinder_thermostat", "loft_insulation", "room_roof_insulation", "flat_roof_insulation",
"solid_floor_insulation", "suspended_floor_insulation", "mixed_glazing"
"solid_floor_insulation", "suspended_floor_insulation", "mixed_glazing",
"windows_glazing"
]:
# We update the data, as defined in the recommendaton
for prefix in ["walls", "roof", "floor"]:

View file

@ -58,7 +58,7 @@ NON_INVASIVE_SPECIFIC_MEASURES = [
# such as "external_wall_insulation", "internal_wall_insulation", "cavity_wall_insulation"
MEASURE_MAP = {
"wall_insulation": [
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation", "cavity_extract_and_refill"
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation",
],
"roof_insulation": ["loft_insulation", "flat_roof_insulation", "room_roof_insulation"],
"floor_insulation": ["suspended_floor_insulation", "solid_floor_insulation"],

View file

@ -1,9 +1,11 @@
from datetime import datetime
import pandas as pd
import pytest
from unittest.mock import Mock
from backend.Property import Property
from etl.epc_clean.EpcClean import EpcClean
from etl.epc.Record import EPCRecord
from etl.bill_savings.KwhData import KwhData
# Define some test data
mock_epc_response = {
@ -17,12 +19,13 @@ mock_epc_response = {
"built-form": "Detached",
"inspection-date": "2023-06-01",
'lodgement-datetime': '2023-06-01 20:29:01',
'lodgement-date': '2023-06-01',
"some-other-key": "some-value",
"roof-description": "pitched, no insulation",
"walls-description": "Walls Description",
"windows-description": "Windows Description",
"mainheat-description": "Main Heating Description",
"hotwater-description": "Hot Water Description",
"windows-description": "Fully double glazed",
"mainheat-description": "Boiler and radiators, mains gas",
"hotwater-description": "From main system",
"transaction-type": "rental",
"lighting-description": "Good Lighting Efficiency",
"energy-consumption-current": "50",
@ -39,7 +42,10 @@ mock_epc_response = {
"total-floor-area": 100,
"construction-age-band": "England and Wales: 1967-1975",
"floor-description": "Floor Description",
"floor-level": "Ground"
"floor-level": "Ground",
"lighting-cost-current": 123,
"heating-cost-current": 800,
"hot-water-cost-current": 200
},
{
"lmk-key": 2,
@ -49,12 +55,13 @@ mock_epc_response = {
"built-form": "Detached",
"inspection-date": "2023-05-01",
'lodgement-datetime': '2023-05-01 20:29:01',
'lodgement-date': '2023-05-01',
"some-other-key": "some-other-value",
"roof-description": "Roof Description",
"walls-description": "Walls Description",
"windows-description": "Windows Description",
"mainheat-description": "Main Heating Description",
"hotwater-description": "Hot Water Description",
"windows-description": "Fully double glazed",
"mainheat-description": "Boiler and radiators, mains gas",
"hotwater-description": "From main system",
"transaction-type": "rental",
"lighting-description": "Good Lighting Efficiency",
"energy-consumption-current": "50",
@ -71,98 +78,10 @@ mock_epc_response = {
"total-floor-area": 100,
"construction-age-band": "England and Wales: 1967-1975",
"floor-description": "Floor Description",
"floor-level": "Ground"
}
]
}
mock_epc_response_dupe = {
'rows': [
{
"lmk-key": 1,
"uprn": 1,
"number-habitable-rooms": 5,
"property-type": "House",
'inspection-date': '2023-06-01',
'lodgement-datetime': '2023-06-01 20:29:01',
'some-other-key': 'some-value', 'roof-description': 'Roof Description',
'walls-description': 'Walls Description', 'windows-description': 'Windows Description',
'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description',
"transaction-type": "rental",
"lighting-description": "Good Lighting Efficiency",
"energy-consumption-current": "50",
"co2-emissions-current": "123",
"mechanical-ventilation": "natural",
'photo-supply': 0,
"solar-water-heating-flag": "N",
"wind-turbine-count": 0,
"extension-count": 0,
"heat-loss-corridor": "no corridor",
"unheated-corridor-length": 0,
"mains-gas-flag": "Y",
"floor-height": 2.5,
"total-floor-area": 100,
"construction-age-band": "England and Wales: 1967-1975",
"floor-description": "Floor Description",
"floor-level": "Ground"
},
{
"lmk-key": 2,
"uprn": 2,
"number-habitable-rooms": 5,
"property-type": "House",
'inspection-date': '2023-05-01',
'lodgement-datetime': '2023-05-01 20:29:01',
'some-other-key': 'some-other-value',
'roof-description': 'Roof Description', 'walls-description': 'Walls Description',
'windows-description': 'Windows Description', 'mainheat-description': 'Main Heating Description',
'hotwater-description': 'Hot Water Description',
"transaction-type": "rental",
"lighting-description": "Good Lighting Efficiency",
"energy-consumption-current": "50",
"co2-emissions-current": "123",
"mechanical-ventilation": "natural",
'photo-supply': 0,
"solar-water-heating-flag": "N",
"wind-turbine-count": 0,
"extension-count": 0,
"heat-loss-corridor": "no corridor",
"unheated-corridor-length": 0,
"mains-gas-flag": "Y",
"floor-height": 2.5,
"total-floor-area": 100,
"construction-age-band": "England and Wales: 1967-1975",
"floor-description": "Floor Description",
"floor-level": "Ground"
},
{
"lmk-key": 3,
"uprn": 3,
"number-habitable-rooms": 5,
"property-type": "House",
'inspection-date': '2023-06-01',
'lodgement-datetime': '2023-06-01 20:29:01',
'some-other-key': 'duplicate-date',
'roof-description': 'Roof Description',
'walls-description': 'Walls Description', 'windows-description': 'Windows Description',
'mainheat-description': 'Main Heating Description', 'hotwater-description': 'Hot Water Description',
"transaction-type": "rental",
"lighting-description": "Good Lighting Efficiency",
"energy-consumption-current": "50",
"co2-emissions-current": "123",
"mechanical-ventilation": "natural",
'photo-supply': 0,
"solar-water-heating-flag": "N",
"wind-turbine-count": 0,
"extension-count": 0,
"heat-loss-corridor": "no corridor",
"unheated-corridor-length": 0,
"mains-gas-flag": "Y",
"floor-height": 2.5,
"total-floor-area": 100,
"construction-age-band": "England and Wales: 1967-1975",
"floor-description": "Floor Description",
"floor-level": "Ground"
"floor-level": "Ground",
"lighting-cost-current": 123,
"heating-cost-current": 800,
"hot-water-cost-current": 200
}
]
}
@ -170,34 +89,14 @@ mock_epc_response_dupe = {
class TestProperty:
@pytest.fixture(autouse=True)
def mock_photo_supply_lookup(self):
return pd.DataFrame(
[
dict(
tenure="rental (social)",
built_form="Detached",
property_type="House",
construction_age_band="England and Wales: 1967-1975",
is_flat=False,
is_pitched=True,
is_roof_room=False,
floor_area_decile=2,
photo_supply_median=40
)
]
)
@pytest.fixture(autouse=True)
def mock_floor_area_decile_thresholds(self):
return pd.DataFrame(
{"floor_area_decile_thresholds": [0, 10, 30, 50]}
)
@pytest.fixture(autouse=True)
def property_instance(self, mock_cleaner):
epc_record = EPCRecord()
epc_record.prepared_epc = mock_epc_response["rows"][0]
prepared_epc = mock_epc_response["rows"][0].copy()
# Replace hyphens with underscores
prepared_epc = {k.replace("-", "_"): v for k, v in prepared_epc.items()}
epc_record.prepared_epc = prepared_epc
epc_record.uprn = prepared_epc["uprn"]
property_instance = Property(id=1, postcode="AB12CD", address="Test Address", epc_record=epc_record)
property_instance.number_of_floors = 2
@ -206,27 +105,6 @@ class TestProperty:
property_instance.floor_height = 2.5
return property_instance
@pytest.fixture(autouse=True)
def property_instance_dupe_data(self):
epc_record = EPCRecord()
epc_record.prepared_epc = mock_epc_response_dupe["rows"][0]
property_instance_dupe_data = Property(id=2, postcode="AB12CD", address="Test Address", epc_record=epc_record)
return property_instance_dupe_data
# @pytest.fixture
# def mock_epc_client(self):
# mock_epc_client = Mock(spec=EpcClient(auth_token="mocked_auth_token"))
# mock_epc_client.domestic.search.return_value = mock_epc_response.copy()
# mock_epc_client.auth_token = "mocked_auth_token"
# return mock_epc_client
#
# @pytest.fixture
# def mock_epc_client_dupe_data(self):
# mock_epc_client_dupe_data = Mock(spec=EpcClient(auth_token="mocked_auth_token"))
# mock_epc_client_dupe_data.domestic.search.return_value = mock_epc_response_dupe.copy()
# mock_epc_client_dupe_data.auth_token = "mocked_auth_token"
# return mock_epc_client_dupe_data
@pytest.fixture
def mock_cleaner(self):
lighting_averages = [
@ -270,15 +148,59 @@ class TestProperty:
"is_roof_room": False}
],
"walls-description": [walls_data],
"windows-description": [{"original_description": "Windows Description"}],
"mainheat-description": [{"original_description": "Main Heating Description"}],
"hotwater-description": [{"original_description": "Hot Water Description"}],
"windows-description": [
{'original_description': 'Fully double glazed', 'has_glazing': True, 'glazing_coverage': 'full',
'glazing_type': 'double', 'no_data': False}
],
"mainheat-description": [
{
'original_description': 'Boiler and radiators, mains gas', 'has_radiators': True,
'has_fan_coil_units': False,
'has_pipes_in_screed_above_insulation': False, 'has_pipes_in_insulated_timber_floor': False,
'has_pipes_in_concrete_slab': False, 'has_boiler': True, 'has_air_source_heat_pump': False,
'has_room_heaters': False, 'has_electric_storage_heaters': False, 'has_warm_air': False,
'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
'has_community_scheme': False,
'has_ground_source_heat_pump': False, 'has_no_system_present': False,
'has_portable_electric_heaters': False,
'has_water_source_heat_pump': False, 'has_electric': False, 'has_mains_gas': True,
'has_wood_logs': False,
'has_coal': False, 'has_oil': False, 'has_wood_pellets': False, 'has_anthracite': False,
'has_dual_fuel_mineral_and_wood': False, 'has_smokeless_fuel': False, 'has_lpg': False,
'has_assumed': False,
'has_electricaire': False, 'has_assumed_for_most_rooms': False, 'has_underfloor_heating': False,
"has_electric_heat_pumps": False,
"has_micro-cogeneration": False
}
],
"hotwater-description": [
{'original_description': 'From main system', 'heater_type': None, 'system_type': 'from main system',
'thermostat_characteristics': None, 'heating_scope': None, 'energy_recovery': None,
'tariff_type': None,
'extra_features': None, 'chp_systems': None, 'distribution_system': None, 'no_system_present': None,
'assumed': False, "appliance": None}
],
"lighting-description": [{"original_description": "Good Lighting Efficiency"}],
"floor-description": [
{"original_description": "Floor Description", "is_suspended": True, "another_property_below": False}]
}
return mock_cleaner
@pytest.fixture
def kwh_client(self):
kwh_client = KwhData(bucket="retrofit-data-dev", read_consumption_data=False)
# We fix this pricing table for these tests
kwh_client.retail_price_comparison = pd.DataFrame(
[
{
"Date": datetime.today().strftime("%Y-%m-%d"),
'Average standard variable tariff (Large legacy suppliers)': 1
}
]
)
kwh_client.retail_price_comparison["Date"] = pd.to_datetime(kwh_client.retail_price_comparison["Date"])
return kwh_client
def test_init(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"uprn": 1}
@ -292,13 +214,26 @@ class TestProperty:
inst3 = Property(4, "AB12CD", "Test Address", epc_record=epc_record)
assert inst3.data == {"uprn": 1}
def test_get_components(
self, property_instance, mock_cleaner, mock_photo_supply_lookup, mock_floor_area_decile_thresholds
def test_set_features(
self, property_instance, mock_cleaner, kwh_client,
):
property_instance.get_components(
kwh_predictions = {
"heating_kwh_predictions": pd.DataFrame(
[
{"id": property_instance.uprn, "predictions": 12000}
]
),
"hotwater_kwh_predictions": pd.DataFrame(
[
{"id": property_instance.uprn, "predictions": 3000}
]
),
}
property_instance.set_features(
mock_cleaner.cleaned,
photo_supply_lookup=mock_photo_supply_lookup,
floor_area_decile_thresholds=mock_floor_area_decile_thresholds
kwh_client,
kwh_predictions
)
# Verify that the components are set correctly
@ -318,9 +253,32 @@ class TestProperty:
"is_sandstone_or_limestone": False,
"is_granite_or_whinstone": False,
}
assert property_instance.windows == {"original_description": "Windows Description"}
assert property_instance.main_heating == {"original_description": "Main Heating Description"}
assert property_instance.hotwater == {"original_description": "Hot Water Description"}
assert property_instance.windows == {
'original_description': 'Fully double glazed', 'has_glazing': True, 'glazing_coverage': 'full',
'glazing_type': 'double', 'no_data': False
}
assert property_instance.main_heating == {
'original_description': 'Boiler and radiators, mains gas', 'has_radiators': True,
'has_fan_coil_units': False, 'has_pipes_in_screed_above_insulation': False,
'has_pipes_in_insulated_timber_floor': False, 'has_pipes_in_concrete_slab': False, 'has_boiler': True,
'has_air_source_heat_pump': False, 'has_room_heaters': False, 'has_electric_storage_heaters': False,
'has_warm_air': False, 'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
'has_community_scheme': False, 'has_ground_source_heat_pump': False, 'has_no_system_present': False,
'has_portable_electric_heaters': False, 'has_water_source_heat_pump': False, 'has_electric': False,
'has_mains_gas': True, 'has_wood_logs': False, 'has_coal': False, 'has_oil': False,
'has_wood_pellets': False, 'has_anthracite': False, 'has_dual_fuel_mineral_and_wood': False,
'has_smokeless_fuel': False, 'has_lpg': False, 'has_assumed': False, 'has_electricaire': False,
'has_assumed_for_most_rooms': False, 'has_underfloor_heating': False, 'has_electric_heat_pumps': False,
'has_micro-cogeneration': False
}
assert property_instance.hotwater == {
'original_description': 'From main system', 'heater_type': None,
'system_type': 'from main system', 'thermostat_characteristics': None,
'heating_scope': None, 'energy_recovery': None, 'tariff_type': None,
'extra_features': None, 'chp_systems': None, 'distribution_system': None,
'no_system_present': None, 'assumed': False, 'appliance': None
}
assert property_instance.wall_type == "cavity"
@ -330,11 +288,24 @@ class TestProperty:
# Verify that ValueError is raised when EpcClean doesn't contain cleaned data
with pytest.raises(ValueError, match="Cleaner does not contain cleaned data"):
property_instance.get_components(mock_cleaner.cleaned, pd.DataFrame(), pd.DataFrame())
property_instance.set_features(mock_cleaner.cleaned, pd.DataFrame(), pd.DataFrame())
def test_get_components_no_attributes(
self, property_instance, mock_cleaner, mock_photo_supply_lookup, mock_floor_area_decile_thresholds
self, property_instance, mock_cleaner, kwh_client
):
kwh_predictions = {
"heating_kwh_predictions": pd.DataFrame(
[
{"id": property_instance.uprn, "predictions": 12000}
]
),
"hotwater_kwh_predictions": pd.DataFrame(
[
{"id": property_instance.uprn, "predictions": 3000}
]
),
}
# Modify the mock cleaner to have no attributes for a specific description
mock_cleaner.cleaned = {
"roof-description": []
@ -351,23 +322,45 @@ class TestProperty:
"is_sandstone_or_limestone": False,
"is_granite_or_whinstone": False,
}
property_instance.floor = {
"is_suspended": False,
"another_property_below": False,
"is_solid": True
}
property_instance.main_heating = {
'original_description': 'Boiler and radiators, mains gas', 'has_radiators': True,
'has_fan_coil_units': False, 'has_pipes_in_screed_above_insulation': False,
'has_pipes_in_insulated_timber_floor': False, 'has_pipes_in_concrete_slab': False, 'has_boiler': True,
'has_air_source_heat_pump': False, 'has_room_heaters': False, 'has_electric_storage_heaters': False,
'has_warm_air': False, 'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
'has_community_scheme': False, 'has_ground_source_heat_pump': False, 'has_no_system_present': False,
'has_portable_electric_heaters': False, 'has_water_source_heat_pump': False, 'has_electric': False,
'has_mains_gas': True, 'has_wood_logs': False, 'has_coal': False, 'has_oil': False,
'has_wood_pellets': False, 'has_anthracite': False, 'has_dual_fuel_mineral_and_wood': False,
'has_smokeless_fuel': False, 'has_lpg': False, 'has_assumed': False, 'has_electricaire': False,
'has_assumed_for_most_rooms': False, 'has_underfloor_heating': False, 'has_electric_heat_pumps': False,
'has_micro-cogeneration': False
}
property_instance.hotwater = {
'original_description': 'From main system', 'heater_type': None, 'system_type': 'from main system',
'thermostat_characteristics': None, 'heating_scope': None, 'energy_recovery': None,
'tariff_type': None,
'extra_features': None, 'chp_systems': None, 'distribution_system': None, 'no_system_present': None,
'assumed': False, "appliance": None
}
# Assert backup cleaning has been applied
property_instance.get_components(
mock_cleaner.cleaned, mock_photo_supply_lookup, mock_floor_area_decile_thresholds
property_instance.set_features(
mock_cleaner.cleaned,
kwh_client,
kwh_predictions
)
assert property_instance.roof["clean_description"] == "Pitched, no insulation"
assert property_instance.roof["is_pitched"]
def test_get_components_multiple_attributes(
self, property_instance, mock_cleaner, mock_photo_supply_lookup, mock_floor_area_decile_thresholds
self, property_instance, mock_cleaner, kwh_client
):
# This shouldn't happen - it would mean a cleaning error
property_instance.data["roof-description"] = "Roof Description"
@ -378,13 +371,27 @@ class TestProperty:
]
}
kwh_predictions = {
"heating_kwh_predictions": pd.DataFrame(
[
{"id": property_instance.uprn, "predictions": 12000}
]
),
"hotwater_kwh_predictions": pd.DataFrame(
[
{"id": property_instance.uprn, "predictions": 3000}
]
),
}
# Verify that ValueError is raised when multiple attributes are found
with pytest.raises(ValueError, match="Either No attributes or multiple found for roof-description"):
property_instance.get_components(cleaned, mock_photo_supply_lookup, mock_floor_area_decile_thresholds)
property_instance.set_features(cleaned, kwh_client, kwh_predictions)
def test_set_spatial(self):
epc_record = EPCRecord()
epc_record.prepared_epc = mock_epc_response["rows"][0]
epc_record.uprn = mock_epc_response["rows"][0]["uprn"]
prop = Property(1, postcode="AB12CD", address="Test Address", epc_record=epc_record)
spatial1 = pd.DataFrame([{
@ -418,6 +425,7 @@ class TestProperty:
# floor, so we should set floor_level to 0
epc_record = EPCRecord()
epc_record.prepared_epc = {'floor-level': '01', 'property-type': 'Flat'}
epc_record.uprn = 1
prop = Property(1, postcode="AB12CD", address="Test Address", epc_record=epc_record)
prop.floor = {
'original_description': 'Solid, no insulation (assumed)', 'clean_description': 'Solid, no insulation',

View file

@ -367,7 +367,7 @@ clean_floor_cases = [
'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True, 'is_to_external_air': False,
'is_suspended': False, 'is_solid': False, 'insulation_thickness': 'none', "another_property_below": False},
{'original_description': "Average thermal transmittance 1.10 W/m+é-¦K", 'thermal_transmittance': 1.1,
'thermal_transmittance_unit': 'w/m-¦k', 'is_assumed': False,
'thermal_transmittance_unit': 'w/m-¦k', 'is_assumed': False,
'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False,
'another_property_below': False, 'insulation_thickness': None},
{

View file

@ -1658,9 +1658,9 @@ mainheat_cases = [
'has_air_source_heat_pump': False, 'has_room_heaters': False, 'has_electric_storage_heaters': False,
'has_warm_air': False, 'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
'has_community_scheme': False, 'has_ground_source_heat_pump': False, 'has_no_system_present': False,
'has_portable_electric_heaters': False, 'has_water_source_heat_pump': False, 'has_electric_heat_pump': False,
'has_portable_electric_heaters': True, 'has_water_source_heat_pump': False, 'has_electric_heat_pump': False,
'has_micro-cogeneration': False, 'has_solar_assisted_heat_pump': False, 'has_exhaust_source_heat_pump': False,
'has_community_heat_pump': False, 'has_portable_electric_heating': True, 'has_electric': True,
'has_community_heat_pump': False, 'has_electric': True,
'has_mains_gas': False, 'has_wood_logs': False, 'has_coal': False, 'has_oil': False, 'has_wood_pellets': False,
'has_anthracite': False, 'has_dual_fuel_mineral_and_wood': False, 'has_smokeless_fuel': False, 'has_lpg': False,
'has_b30k': False, 'has_assumed': True, 'has_electricaire': False, 'has_assumed_for_most_rooms': True,

View file

@ -1,5 +1,5 @@
wall_cases = [
{'original_description': 'Average thermal transmittance -4.67 W/m-¦K', 'thermal_transmittance': -4.67,
{'original_description': 'Average thermal transmittance -4.67 W/m-¦K', 'thermal_transmittance': 4.67,
'thermal_transmittance_unit': 'w/m-¦k', 'is_cavity_wall': False, 'is_filled_cavity': False,
'is_solid_brick': False, 'is_system_built': False, 'is_timber_frame': False, 'is_granite_or_whinstone': False,
'is_as_built': False, 'is_cob': False, 'is_assumed': False, 'is_sandstone_or_limestone': False,
@ -692,7 +692,7 @@ wall_cases = [
'is_cob': False, 'is_assumed': True, 'is_sandstone_or_limestone': False, 'insulation_thickness': 'none',
'external_insulation': False, 'internal_insulation': False},
{'original_description': 'Average thermal transmittance 1.60 W/m+é-¦K',
'thermal_transmittance': 1.6, 'thermal_transmittance_unit': 'w/m-¦k', 'is_cavity_wall': False,
'thermal_transmittance': 1.6, 'thermal_transmittance_unit': 'w/m-¦k', 'is_cavity_wall': False,
'is_filled_cavity': False, 'is_solid_brick': False, 'is_system_built': False, 'is_timber_frame': False,
'is_granite_or_whinstone': False, 'is_as_built': False, 'is_cob': False, 'is_assumed': False,
'is_sandstone_or_limestone': False, 'insulation_thickness': None, 'external_insulation': False,

View file

@ -11,10 +11,6 @@ class TestMainHeatAttributes:
floor_attr = MainHeatAttributes(valid_description)
assert floor_attr.description == valid_description.lower()
# Test initialization with an empty description
with pytest.raises(ValueError):
MainHeatAttributes('')
# Test initialization with a description that contains none of the keywords
with pytest.raises(ValueError):
MainHeatAttributes('description without keywords')
@ -38,7 +34,6 @@ class TestMainHeatAttributes:
def test_invalid_description(self):
# Test that invalid descriptions raise a ValueError
invalid_descriptions = [
"",
"invalid description",
"description with no known heating data_types",
]

View file

@ -16,7 +16,7 @@ class TestWallAttributes:
description = 'average thermal transmittance -4.67 w/m-¦k'
wa = wall_attr(description)
result = wa.process()
assert result['thermal_transmittance'] == -4.67
assert result['thermal_transmittance'] == 4.67
assert result['thermal_transmittance_unit'] == 'w/m-¦k'
def test_wall_types(self, wall_attr):

View file

@ -446,20 +446,7 @@ class RoofRecommendations:
_, new_u_value = calculate_u_value_uplift(u_value, part_u_value)
new_u_value = math.ceil(new_u_value * 100.0) / 100.0
# If I have a lowest U value and my new u value is higher than that but lower than the
# diminishing returns threshold, it can be considered
# If I have a lowest U value and my new u value is lower than the lowest value, it's
# further into the diminishing returns threshold and can shouldn't be
# if is_diminishing_returns(
# recommendations, new_u_value, lowest_selected_u_value, self.DIMINISHING_RETURNS_U_VALUE
# ):
# continue
# We allow a small tolerance for error so we don't discount the recommendation entirely
# if new_u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE:
# lowest_selected_u_value = update_lowest_selected_u_value(lowest_selected_u_value, new_u_value)
estimated_cost = (
cost_per_unit * self.property.insulation_floor_area if
@ -504,7 +491,7 @@ class RoofRecommendations:
"type": "room_roof_insulation",
"description": "Insulate room in roof at rafters and re-decorate",
"starting_u_value": u_value,
"new_u_value": None,
"new_u_value": new_u_value,
"sap_points": sap_points,
"simulation_config": simulation_config,
"description_simulation": {

View file

@ -48,6 +48,15 @@ class WindowsRecommendations:
if not any(x in measures for x in MEASURE_MAP["windows"]):
return
if self.property.windows["glazing_type"] in ["triple", "high performance"]:
# We don't make any recommendations in this case. The property already has outstanding glazing
return
if self.property.windows["has_glazing"] & (
self.property.windows["glazing_coverage"] == "full"
):
return
# If the property is in a conservation area or is a listed building, it becomes more difficult to install
# double glazing. Therefore, we don't recommend it. It is still possible but is not practical as it
# requires planning permission and might require a more expensive window type, such as timber.
@ -67,11 +76,6 @@ class WindowsRecommendations:
if not number_of_windows:
raise ValueError("Number of windows not specified")
if self.property.windows["has_glazing"] & (
self.property.windows["glazing_coverage"] == "full"
):
return
if windows_area is not None:
# TODO - we don't have a price for this so we can't recommend it
print("We have windows area, we should use this data for our recommendations!!!")
@ -122,6 +126,98 @@ class WindowsRecommendations:
". Secondary glazing recommended due to conservation area status"
)
# Set up the simulation config
if self.property.windows["glazing_type"] == "multiple":
glazing_type_ending = "multiple"
glazed_type_ending = (
"secondary glazing" if is_secondary_glazing else "double glazing installed during or after 2002"
)
windows_energy_eff = "Good"
new_windows_description = "Multiple glazing throughout"
elif self.property.windows["glazing_type"] == "single":
# We will only recommend either secondary or double glazing
glazing_type_ending = (
"secondary" if is_secondary_glazing else "double"
)
glazed_type_ending = (
"secondary glazing" if is_secondary_glazing else "double glazing installed during or after 2002"
)
if is_secondary_glazing:
windows_energy_eff = "Good"
new_windows_description = "Full secondary glazing"
else:
windows_energy_eff = "Average"
new_windows_description = "Fully double glazed"
elif self.property.windows["glazing_type"] == "double":
glazing_type_ending = (
"multiple" if is_secondary_glazing else "double"
)
# We set glazed type depending on which window type is more prevalent. Since there is already double
# glazing in place, if we're recommending more double glazing, we set the glazed type to double glazing
# otherwise, if we're recommending secondary glazing and the proportion of glazing in place already that
# is double is less than 50% we set the glazed type to secondary glazing
if not is_secondary_glazing:
glazed_type_ending = "double glazing installed during or after 2002"
new_windows_description = "Fully double glazed"
windows_energy_eff = "Average"
else:
if self.property.data["multi-glaze-proportion"] < 50:
glazed_type_ending = "secondary glazing"
else:
glazed_type_ending = "double glazing installed during or after 2002"
new_windows_description = "Multiple glazing throughout"
windows_energy_eff = "Good"
elif self.property.windows["glazing_type"] == "secondary":
glazing_type_ending = (
"secondary" if is_secondary_glazing else "multiple"
)
windows_energy_eff = "Good"
# This is the opposite. If there is secondary glazing in place, and we're recommending double
# we set glazed_type_ending, depending on the proportion of glazing in place
if is_secondary_glazing:
glazed_type_ending = "secondary glazing"
new_windows_description = "Full secondary glazing"
else:
if self.property.data["multi-glaze-proportion"] < 50:
glazed_type_ending = "double glazing installed during or after 2002"
else:
glazed_type_ending = "secondary glazing"
new_windows_description = "Multiple glazing throughout"
else:
raise ValueError("Invalid glazing type - implement me")
if (self.property.data["windows-energy-eff"] in ["Good", "Very Good"]) and (windows_energy_eff == "Average"):
windows_energy_eff = self.property.data["windows-energy-eff"]
windows_ending_config = WindowAttributes(new_windows_description).process()
windows_simulation_config = check_simulation_difference(
new_config=windows_ending_config, old_config=self.property.windows, prefix="windows_"
)
simulation_config = {
**windows_simulation_config,
"multi_glaze_proportion_ending": 100,
"windows_energy_eff_ending": windows_energy_eff,
"glazing_type_ending": glazing_type_ending,
"glazed_type_ending": glazed_type_ending,
}
description_simulation = {
"multi-glaze-proportion": 100,
"windows-energy-eff": windows_energy_eff,
"windows-description": new_windows_description,
"glazed-type": glazed_type_ending,
}
self.recommendation = [
{
"phase": phase,
@ -134,13 +230,8 @@ class WindowsRecommendations:
"already_installed": already_installed,
**cost_result,
"is_secondary_glazing": is_secondary_glazing,
# TODO: Make this condition on is_secondary_glazing
"description_simulation": {
"multi-glaze-proportion": 100,
"windows-energy-eff": "Average",
"windows-description": "Fully double glazed",
"glazed-type": "double glazing installed during or after 2002",
}
"description_simulation": description_simulation,
"simulation_config": simulation_config,
}
]

View file

@ -1,944 +0,0 @@
import pandas as pd
import msgpack
from datetime import datetime
from utils.s3 import read_dataframe_from_s3_parquet, read_from_s3
from backend.Property import Property
from recommendations.HeatingRecommender import HeatingRecommender
from recommendations.Recommendations import Recommendations
from etl.epc.Record import EPCRecord
from etl.solar.SolarPhotoSupply import SolarPhotoSupply
from backend.ml_models.api import ModelApi
def find_examples():
""" Some scrappy helper code to find EPC examples"""
# Let's look for some testing data, where the only thing different pre and post is the installation of an
# air source heat pump
data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev",
file_key="sap_change_model/2024-03-24-15-51-13/dataset_no_cleaning.parquet"
)
# Firstly, take records where before there was no air source heat pump and afterwards there was
data = data[
data["has_air_source_heat_pump_ending"] & ~data["has_air_source_heat_pump"]
]
# Start with a property that has a boiler
data = data[data["has_boiler"]]
static_columns = [
# Walls
'walls_thermal_transmittance_ending',
'is_filled_cavity_ending',
'is_park_home_ending',
'walls_insulation_thickness_ending',
'external_insulation_ending',
'internal_insulation_ending',
# Floors
# 'floor_thermal_transmittance_ending', # Don't subset on this, because it changes based on floor area
'floor_insulation_thickness_ending',
# Roof
'roof_thermal_transmittance_ending',
'is_at_rafters_ending',
'roof_insulation_thickness_ending',
# Hot water - air source heat pump will shange the hot water system (probably from whatever it was -> main)
# 'heater_type_ending',
# 'system_type_ending',
# 'thermostat_characteristics_ending',
# 'heating_scope_ending',
# 'energy_recovery_ending',
# 'hotwater_tariff_type_ending',
# 'extra_features_ending',
# 'chp_systems_ending',
# 'distribution_system_ending',
# 'no_system_present_ending',
# 'appliance_ending',
# Heating - Will change when installing an ASHP
# 'has_radiators_ending',
# 'has_fan_coil_units_ending',
# 'has_pipes_in_screed_above_insulation_ending',
# 'has_pipes_in_insulated_timber_floor_ending',
# 'has_pipes_in_concrete_slab_ending',
# 'has_boiler_ending',
# 'has_air_source_heat_pump_ending', # We want the air source heat pump to change
# 'has_room_heaters_ending',
# 'has_electric_storage_heaters_ending',
# 'has_warm_air_ending',
# 'has_electric_underfloor_heating_ending',
# 'has_electric_ceiling_heating_ending',
# 'has_community_scheme_ending',
# 'has_ground_source_heat_pump_ending',
# 'has_no_system_present_ending',
# 'has_portable_electric_heaters_ending',
# 'has_water_source_heat_pump_ending',
# 'has_electric_heat_pump_ending',
# 'has_micro-cogeneration_ending',
# 'has_solar_assisted_heat_pump_ending',
# 'has_exhaust_source_heat_pump_ending',
# 'has_community_heat_pump_ending',
# 'has_electric_ending',
# 'has_mains_gas_ending',
# 'has_wood_logs_ending', 'has_coal_ending', 'has_oil_ending',
# 'has_wood_pellets_ending', 'has_anthracite_ending', 'has_dual_fuel_mineral_and_wood_ending',
# 'has_smokeless_fuel_ending', 'has_lpg_ending', 'has_b30k_ending', 'has_electricaire_ending',
# 'has_assumed_for_most_rooms_ending', 'has_underfloor_heating_ending',
# 'thermostatic_control_ending',
# 'charging_system_ending',
# 'switch_system_ending',
# 'no_control_ending',
# 'dhw_control_ending',
# 'community_heating_ending',
# 'multiple_room_thermostats_ending',
# 'auxiliary_systems_ending',
# 'trvs_ending',
# 'rate_control_ending',
# Window
'glazing_type_ending',
# Fuel - could change with ASHP
# 'fuel_type_ending',
# 'main-fuel_tariff_type_ending',
# 'is_community_ending',
# 'no_individual_heating_or_community_network_ending',
# 'complex_fuel_type_ending',
'mechanical_ventilation_ending', 'secondheat_description_ending', 'glazed_type_ending',
'multi_glaze_proportion_ending', 'low_energy_lighting_ending', 'number_open_fireplaces_ending',
'solar_water_heating_flag_ending',
'photo_supply_ending',
'energy_tariff_ending',
'extension_count_ending',
'total_floor_area_ending',
# 'hot_water_energy_eff_ending',
'floor_energy_eff_ending',
'windows_energy_eff_ending',
'walls_energy_eff_ending',
'sheating_energy_eff_ending',
'roof_energy_eff_ending',
# 'mainheat_energy_eff_ending',
# 'mainheatc_energy_eff_ending',
'lighting_energy_eff_ending',
'number_habitable_rooms_ending',
'number_heated_rooms_ending',
]
for col in static_columns:
base_starting = col.split("_ending")[0]
if base_starting + "_starting" in data.columns:
starting_col = base_starting + "_starting"
else:
starting_col = base_starting
# Filter
print("Column: %s" % col)
print("Starting size: %s" % data.shape[0])
data = data[data[starting_col] == data[col]]
print("Ending size: %s" % data.shape[0])
z = data[['uprn', col, starting_col]]
# Great example UPRNs
# 100030969273
# 10034685399 - Completely transforms the heating and hot water systems in the home (goes from oil -> electricity)
# 100091200828 - goes from a liquid petroleum gas boiler to ashp
# Look for starting with a gas boiler
data[
data["has_boiler"] & data["has_radiators"] & data["has_mains_gas"] & ~data["has_boiler_ending"]
]
# UPRN: 100011776843
class TestAirSourceHeatPump:
def test_eligible(self):
# This tests a house, which will be suitable for an air source heat pump
epc_record = EPCRecord()
epc_record.prepared_epc = {
"county": "Broxbourne",
"mainheat-energy-eff": "Good",
"hot-water-energy-eff": "Good",
"mainheatc-energy-eff": "Good",
"number-heated-rooms": 5,
"property-type": "House",
"built-form": "Semi-Detached"
}
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
property_instance.main_heating = {
'original_description': 'Boiler and radiators, mains gas',
"clean_description": "Boiler and radiators, mains gas",
'has_radiators': True,
'has_fan_coil_units': False, 'has_pipes_in_screed_above_insulation': False,
'has_pipes_in_insulated_timber_floor': False, 'has_pipes_in_concrete_slab': False, 'has_boiler': True,
'has_air_source_heat_pump': False,
'has_room_heaters': False, 'has_electric_storage_heaters': False,
'has_warm_air': False,
'has_electric_underfloor_heating': False,
'has_electric_ceiling_heating': False, 'has_community_scheme': False,
'has_ground_source_heat_pump': False, 'has_no_system_present': False,
'has_portable_electric_heaters': False,
'has_water_source_heat_pump': False, 'has_electric': False,
'has_mains_gas': True, 'has_wood_logs': False,
'has_coal': False, 'has_oil': False, 'has_wood_pellets': False,
'has_anthracite': False,
'has_dual_fuel_mineral_and_wood': False, 'has_smokeless_fuel': False,
'has_lpg': False, 'has_assumed': False,
'has_electricaire': False, 'has_assumed_for_most_rooms': False,
'has_underfloor_heating': False,
"has_electric_heat_pumps": False,
"has_micro-cogeneration": False
}
property_instance.main_fuel = {
'original_description': 'mains gas (not community)', 'fuel_type': 'mains gas',
'tariff_type': None,
'is_community': False, 'no_individual_heating_or_community_network': False,
'complex_fuel_type': None
}
property_instance.hotwater = {
'original_description': 'From main system',
'clean_description': 'From main system',
'heater_type': None,
'system_type': 'from main system',
'thermostat_characteristics': None, 'heating_scope': None,
'energy_recovery': None, 'tariff_type': None,
'extra_features': None, 'chp_systems': None, 'distribution_system': None,
'no_system_present': None,
'assumed': False, "appliance": None
}
property_instance.main_heating_controls = {
'original_description': 'Programmer, room thermostat and TRVs',
'thermostatic_control': 'room thermostat', 'charging_system': None, 'switch_system': 'programmer',
'no_control': None, 'dhw_control': None, 'community_heating': None, 'multiple_room_thermostats': False,
'auxiliary_systems': None, 'trvs': 'trvs', 'rate_control': None
}
recommender = HeatingRecommender(property_instance=property_instance)
assert not recommender.heating_recommendations
recommender.recommend(phase=0)
assert recommender.recommendation is None
def test_air_source_heat_pump_gas_boiler_starting(self):
starting_epc = {
'low-energy-fixed-light-count': '', 'address': '430 Gidlow Lane', 'uprn-source': 'Energy Assessor',
'floor-height': '2.62', 'heating-cost-potential': '599', 'unheated-corridor-length': '',
'hot-water-cost-potential': '67', 'construction-age-band': 'England and Wales: 1950-1966',
'potential-energy-rating': 'C', 'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Good',
'lighting-energy-eff': 'Very Good', 'environment-impact-potential': '72',
'glazed-type': 'double glazing installed during or after 2002', 'heating-cost-current': '913',
'address3': '', 'mainheatcont-description': 'Programmer, no room thermostat', 'sheating-energy-eff': 'N/A',
'property-type': 'House', 'local-authority-label': 'Wigan', 'fixed-lighting-outlets-count': '9',
'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '210',
'county': '', 'postcode': 'WN6 8RG', 'solar-water-heating-flag': 'N', 'constituency': 'E14001039',
'co2-emissions-potential': '2.6', 'number-heated-rooms': '4',
'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '180',
'local-authority': 'E08000010', 'built-form': 'Mid-Terrace', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2022-02-15',
'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '78', 'address1': '430 Gidlow Lane',
'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Wigan',
'roof-energy-eff': 'Very Poor', 'total-floor-area': '80.0', 'building-reference-number': '10002334112',
'environment-impact-current': '38', 'co2-emissions-current': '6.2',
'roof-description': 'Pitched, no insulation (assumed)', 'floor-energy-eff': 'N/A',
'number-habitable-rooms': '4', 'address2': '', 'hot-water-env-eff': 'Poor', 'posttown': 'WIGAN',
'mainheatc-energy-eff': 'Very Poor', 'main-fuel': 'mains gas (not community)',
'lighting-env-eff': 'Very Good', 'windows-energy-eff': 'Good', 'floor-env-eff': 'N/A',
'sheating-env-eff': 'N/A', 'lighting-description': 'Low energy lighting in all fixed outlets',
'roof-env-eff': 'Very Poor', 'walls-energy-eff': 'Average', 'photo-supply': '0.0',
'lighting-cost-potential': '67', 'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100',
'main-heating-controls': '', 'lodgement-datetime': '2022-02-23 16:39:41', 'flat-top-storey': '',
'current-energy-rating': 'E', 'secondheat-description': 'Room heaters, mains gas',
'walls-env-eff': 'Average', 'transaction-type': 'ECO assessment', 'uprn': '100011776843',
'current-energy-efficiency': '45', 'energy-consumption-current': '441',
'mainheat-description': 'Boiler and radiators, mains gas', 'lighting-cost-current': '67',
'lodgement-date': '2022-02-23', 'extension-count': '1', 'mainheatc-env-eff': 'Very Poor',
'lmk-key': '46cb404438a6d88ddff8965cab8b3027ec15c32d93e0b6a5f0381a5109b9bb0d', 'wind-turbine-count': '0',
'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '77',
'hot-water-energy-eff': 'Poor', 'low-energy-lighting': '100',
'walls-description': 'Cavity wall, filled cavity',
'hotwater-description': 'From main system, no cylinder thermostat'
}
ending_epc = {
'low-energy-fixed-light-count': '', 'address': '430 Gidlow Lane', 'uprn-source': 'Energy Assessor',
'floor-height': '2.62', 'heating-cost-potential': '803', 'unheated-corridor-length': '',
'hot-water-cost-potential': '292', 'construction-age-band': 'England and Wales: 1950-1966',
'potential-energy-rating': 'C', 'mainheat-energy-eff': 'Very Good', 'windows-env-eff': 'Good',
'lighting-energy-eff': 'Very Good', 'environment-impact-potential': '78',
'glazed-type': 'double glazing installed during or after 2002', 'heating-cost-current': '861',
'address3': '', 'mainheatcont-description': 'Time and temperature zone control',
'sheating-energy-eff': 'N/A', 'property-type': 'House', 'local-authority-label': 'Wigan',
'fixed-lighting-outlets-count': '9', 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural',
'hot-water-cost-current': '434', 'county': '', 'postcode': 'WN6 8RG', 'solar-water-heating-flag': 'N',
'constituency': 'E14001039', 'co2-emissions-potential': '2.0', 'number-heated-rooms': '4',
'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '147',
'local-authority': 'E08000010', 'built-form': 'Mid-Terrace', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2022-05-11',
'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '43', 'address1': '430 Gidlow Lane',
'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Wigan',
'roof-energy-eff': 'Very Poor', 'total-floor-area': '80.0', 'building-reference-number': '10002334112',
'environment-impact-current': '63', 'co2-emissions-current': '3.4',
'roof-description': 'Pitched, no insulation (assumed)', 'floor-energy-eff': 'N/A',
'number-habitable-rooms': '4', 'address2': '', 'hot-water-env-eff': 'Poor', 'posttown': 'WIGAN',
'mainheatc-energy-eff': 'Very Good', 'main-fuel': 'electricity (not community)',
'lighting-env-eff': 'Very Good', 'windows-energy-eff': 'Good', 'floor-env-eff': 'N/A',
'sheating-env-eff': 'N/A', 'lighting-description': 'Low energy lighting in all fixed outlets',
'roof-env-eff': 'Very Poor', 'walls-energy-eff': 'Average', 'photo-supply': '0.0',
'lighting-cost-potential': '67', 'mainheat-env-eff': 'Very Good', 'multi-glaze-proportion': '100',
'main-heating-controls': '', 'lodgement-datetime': '2022-06-06 13:01:20', 'flat-top-storey': '',
'current-energy-rating': 'E', 'secondheat-description': 'Room heaters, mains gas',
'walls-env-eff': 'Average', 'transaction-type': 'ECO assessment', 'uprn': '100011776843',
'current-energy-efficiency': '53', 'energy-consumption-current': '252',
'mainheat-description': 'Air source heat pump, radiators, electric', 'lighting-cost-current': '67',
'lodgement-date': '2022-06-06', 'extension-count': '1', 'mainheatc-env-eff': 'Very Good',
'lmk-key': '672d5947f3d4a55d97255af71651d6127a939418fa66a687070af77e0ba90df2', 'wind-turbine-count': '0',
'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '70',
'hot-water-energy-eff': 'Very Poor', 'low-energy-lighting': '100',
'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system'
}
# differences = []
# for k, v in ending_epc.items():
# if v != starting_epc[k]:
# differences.append(
# {
# "variable": k,
# "starting_value": starting_epc[k],
# "ending_value": v
# }
# )
# differences = pd.DataFrame(differences)
#
# diffs = differences[
# differences["variable"].isin(
# [
# "mainheat-energy-eff",
# "mainheatcont-description",
# "mainheatc-energy-eff",
# "main-fuel",
# "mainheat-env-eff",
# "mainheat-description",
# "hot-water-energy-eff",
# "hotwater-description"
# ]
# )
# ]
cleaning_data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet",
)
cleaned = read_from_s3(
s3_file_name="cleaned_epc_data/cleaned.bson",
bucket_name="retrofit-data-dev"
)
cleaned = msgpack.unpackb(cleaned, raw=False)
photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev")
epc = EPCRecord(
epc_records={
'original_epc': starting_epc,
'full_sap_epc': {},
'old_data': []
},
run_mode="newdata",
cleaning_data=cleaning_data
)
home = Property(
id=0,
address="",
postcode="",
epc_record=epc,
already_installed={},
non_invasive_recommendations={},
)
home.in_conservation_area = False
home.is_listed = False
home.is_heritage = False
home.restricted_measures = True
home.get_components(
cleaned=cleaned,
photo_supply_lookup=photo_supply_lookup,
floor_area_decile_thresholds=floor_area_decile_thresholds
)
recommender = HeatingRecommender(property_instance=home)
recommender.recommend_air_source_heat_pump(phase=0, has_cavity_or_loft_recommendations=False)
# Patch - for this property, the hot water energy efficiency is very poor. it's not clear why this is,
# but we insert this for this test
recommender.heating_recommendations[0]["simulation_config"]["hot_water_energy_eff_ending"] = "Very Poor"
property_recommendations = Recommendations.insert_temp_recommendation_id([recommender.heating_recommendations])
assert len(recommender.heating_recommendations) == 1
home.create_base_difference_epc_record(cleaned_lookup=cleaned)
home.adjust_difference_record_with_recommendations(
property_recommendations, []
)
scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop(
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
"carbon_ending"]
)
model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat())
model_api.MODEL_PREFIXES = ["sap_change_predictions"]
predictions_dict = model_api.predict_all(
df=scoring_data,
bucket="retrofit-data-dev",
prediction_buckets={
"sap_change_predictions": "retrofit-sap-predictions-dev",
}
)
assert predictions_dict["sap_change_predictions"]["predictions"].values[0] == 52.2
def test_air_source_heat_pump_gas_boiler_starting_2(self):
"""
This property seems to have miniscule movement in SAP - just 2 poins
:return:
"""
starting_epc = {
'low-energy-fixed-light-count': '', 'address': '31 Whinney Hill Park', 'uprn-source': 'Energy Assessor',
'floor-height': '2.3', 'heating-cost-potential': '394', 'unheated-corridor-length': '',
'hot-water-cost-potential': '48', 'construction-age-band': 'England and Wales: 1967-1975',
'potential-energy-rating': 'B', 'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Average',
'lighting-energy-eff': 'Good', 'environment-impact-potential': '87',
'glazed-type': 'double glazing, unknown install date', 'heating-cost-current': '487', 'address3': '',
'mainheatcont-description': 'Programmer, room thermostat and TRVs', 'sheating-energy-eff': 'N/A',
'property-type': 'Bungalow', 'local-authority-label': 'Calderdale', 'fixed-lighting-outlets-count': '5',
'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '86',
'county': '', 'postcode': 'HD6 2PX', 'solar-water-heating-flag': 'N', 'constituency': 'E14000614',
'co2-emissions-potential': '0.8', 'number-heated-rooms': '2',
'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '105',
'local-authority': 'E08000033', 'built-form': 'End-Terrace', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2021-11-25',
'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '56', 'address1': '31 Whinney Hill Park',
'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Calder Valley',
'roof-energy-eff': 'Good', 'total-floor-area': '44.0', 'building-reference-number': '10001772583',
'environment-impact-current': '62', 'co2-emissions-current': '2.5',
'roof-description': 'Pitched, 250 mm loft insulation', 'floor-energy-eff': 'N/A',
'number-habitable-rooms': '2', 'address2': '', 'hot-water-env-eff': 'Good', 'posttown': 'BRIGHOUSE',
'mainheatc-energy-eff': 'Good', 'main-fuel': 'mains gas (not community)', 'lighting-env-eff': 'Good',
'windows-energy-eff': 'Average', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A',
'lighting-description': 'Low energy lighting in 60% of fixed outlets', 'roof-env-eff': 'Good',
'walls-energy-eff': 'Average', 'photo-supply': '0.0', 'lighting-cost-potential': '40',
'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '',
'lodgement-datetime': '2021-11-25 11:39:35', 'flat-top-storey': '', 'current-energy-rating': 'D',
'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average',
'transaction-type': 'rental', 'uprn': '100051304421', 'current-energy-efficiency': '62',
'energy-consumption-current': '322', 'mainheat-description': 'Boiler and radiators, mains gas',
'lighting-cost-current': '56', 'lodgement-date': '2021-11-25', 'extension-count': '0',
'mainheatc-env-eff': 'Good', 'lmk-key': '077f70657e9c3f1f0ce5392798398398616b159493b2a8ca2338961596631c27',
'wind-turbine-count': '0', 'tenure': 'Rented (social)', 'floor-level': '',
'potential-energy-efficiency': '86', 'hot-water-energy-eff': 'Good', 'low-energy-lighting': '60',
'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system'
}
ending_epc = {
'low-energy-fixed-light-count': '', 'address': '31 Whinney Hill Park',
'uprn-source': 'Energy Assessor', 'floor-height': '2.3', 'heating-cost-potential': '277',
'unheated-corridor-length': '', 'hot-water-cost-potential': '266',
'construction-age-band': 'England and Wales: 1967-1975', 'potential-energy-rating': 'B',
'mainheat-energy-eff': 'Very Good', 'windows-env-eff': 'Average', 'lighting-energy-eff': 'Good',
'environment-impact-potential': '90', 'glazed-type': 'double glazing, unknown install date',
'heating-cost-current': '331', 'address3': '',
'mainheatcont-description': 'Programmer and room thermostat', 'sheating-energy-eff': 'N/A',
'property-type': 'Bungalow', 'local-authority-label': 'Calderdale',
'fixed-lighting-outlets-count': '5', 'energy-tariff': 'Single',
'mechanical-ventilation': 'natural', 'hot-water-cost-current': '404', 'county': '',
'postcode': 'HD6 2PX', 'solar-water-heating-flag': 'N', 'constituency': 'E14000614',
'co2-emissions-potential': '0.7', 'number-heated-rooms': '2',
'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '92',
'local-authority': 'E08000033', 'built-form': 'End-Terrace', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Normal',
'inspection-date': '2021-11-25', 'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '48',
'address1': '31 Whinney Hill Park', 'heat-loss-corridor': '', 'flat-storey-count': '',
'constituency-label': 'Calder Valley', 'roof-energy-eff': 'Good', 'total-floor-area': '44.0',
'building-reference-number': '10001772583', 'environment-impact-current': '68',
'co2-emissions-current': '2.1', 'roof-description': 'Pitched, 250 mm loft insulation',
'floor-energy-eff': 'N/A', 'number-habitable-rooms': '2', 'address2': '',
'hot-water-env-eff': 'Poor', 'posttown': 'BRIGHOUSE', 'mainheatc-energy-eff': 'Average',
'main-fuel': 'electricity (not community)', 'lighting-env-eff': 'Good',
'windows-energy-eff': 'Average', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A',
'lighting-description': 'Low energy lighting in 60% of fixed outlets', 'roof-env-eff': 'Good',
'walls-energy-eff': 'Average', 'photo-supply': '0.0', 'lighting-cost-potential': '40',
'mainheat-env-eff': 'Very Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '',
'lodgement-datetime': '2022-03-23 16:06:21', 'flat-top-storey': '', 'current-energy-rating': 'D',
'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average',
'transaction-type': 'rental', 'uprn': '100051304421', 'current-energy-efficiency': '64',
'energy-consumption-current': '283',
'mainheat-description': 'Air source heat pump, radiators, electric',
'lighting-cost-current': '57', 'lodgement-date': '2022-03-23', 'extension-count': '0',
'mainheatc-env-eff': 'Average',
'lmk-key': '6296248141447b53426a40f1c39da17dad5f4786485db55ee38737891111a4d4',
'wind-turbine-count': '0', 'tenure': 'Rented (social)', 'floor-level': '',
'potential-energy-efficiency': '89', 'hot-water-energy-eff': 'Very Poor',
'low-energy-lighting': '60', 'walls-description': 'Cavity wall, filled cavity',
'hotwater-description': 'From main system'
}
# differences = []
# for k, v in ending_epc.items():
# if v != starting_epc[k]:
# differences.append(
# {
# "variable": k,
# "starting_value": starting_epc[k],
# "ending_value": v
# }
# )
# differences = pd.DataFrame(differences)
#
# diffs = differences[
# differences["variable"].isin(
# [
# "mainheat-energy-eff",
# "mainheatcont-description",
# "mainheatc-energy-eff",
# "main-fuel",
# "mainheat-env-eff",
# "mainheat-description",
# "hot-water-energy-eff",
# "hotwater-description"
# ]
# )
# ]
cleaning_data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet",
)
cleaned = read_from_s3(
s3_file_name="cleaned_epc_data/cleaned.bson",
bucket_name="retrofit-data-dev"
)
cleaned = msgpack.unpackb(cleaned, raw=False)
photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev")
epc = EPCRecord(
epc_records={
'original_epc': starting_epc,
'full_sap_epc': {},
'old_data': []
},
run_mode="newdata",
cleaning_data=cleaning_data
)
home = Property(
id=0,
address="",
postcode="",
epc_record=epc,
already_installed={},
non_invasive_recommendations={},
)
home.in_conservation_area = False
home.is_listed = False
home.is_heritage = False
home.restricted_measures = True
home.get_components(
cleaned=cleaned,
photo_supply_lookup=photo_supply_lookup,
floor_area_decile_thresholds=floor_area_decile_thresholds
)
recommender = HeatingRecommender(property_instance=home)
recommender.recommend_air_source_heat_pump(phase=0, has_cavity_or_loft_recommendations=False)
property_recommendations = Recommendations.insert_temp_recommendation_id([recommender.heating_recommendations])
assert len(recommender.heating_recommendations) == 1
home.create_base_difference_epc_record(cleaned_lookup=cleaned)
home.adjust_difference_record_with_recommendations(
property_recommendations, []
)
scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop(
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
"carbon_ending"]
)
model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat())
model_api.MODEL_PREFIXES = ["sap_change_predictions"]
predictions_dict = model_api.predict_all(
df=scoring_data,
bucket="retrofit-data-dev",
prediction_buckets={
"sap_change_predictions": "retrofit-sap-predictions-dev",
}
)
assert predictions_dict["sap_change_predictions"]["predictions"].values[0] == 69.3
# In actuality with this property, the heating controls get downgraded, so we test a manual patch of this
patched_simulation_config = {
'mainheat_energy_eff_ending': "Very Good",
'hot_water_energy_eff_ending': 'Very Poor',
'has_boiler_ending': False,
'has_air_source_heat_pump_ending': True,
'has_electric_ending': True,
'has_mains_gas_ending': False,
'fuel_type_ending': 'electricity',
'trvs_ending': None,
"mainheatc_energy_eff_ending": 'Average'
}
# PATCHING
property_recommendations_patch = Recommendations.insert_temp_recommendation_id(
[recommender.heating_recommendations]
)
property_recommendations_patch[0][0]["simulation_config"] = patched_simulation_config
home.create_base_difference_epc_record(cleaned_lookup=cleaned)
home.adjust_difference_record_with_recommendations(
property_recommendations_patch, []
)
scoring_data_patch = pd.DataFrame(home.recommendations_scoring_data).drop(
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
"carbon_ending"]
)
model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat())
model_api.MODEL_PREFIXES = ["sap_change_predictions"]
predictions_dict_patch = model_api.predict_all(
df=scoring_data_patch,
bucket="retrofit-data-dev",
prediction_buckets={
"sap_change_predictions": "retrofit-sap-predictions-dev",
}
)
# The error is only 0.3, so the model is working
assert predictions_dict_patch["sap_change_predictions"]["predictions"].values[0] == 64.3
assert ending_epc["current-energy-efficiency"] == '64'
def test_air_source_heat_pump_lpg_boiler(self):
starting_epc = {
'low-energy-fixed-light-count': '', 'address': 'Holly Lodge, The Drive, Perry',
'uprn-source': 'Energy Assessor', 'floor-height': '2.8', 'heating-cost-potential': '1628',
'unheated-corridor-length': '', 'hot-water-cost-potential': '175',
'construction-age-band': 'England and Wales: 1950-1966', 'potential-energy-rating': 'D',
'mainheat-energy-eff': 'Poor', 'windows-env-eff': 'Average', 'lighting-energy-eff': 'Average',
'environment-impact-potential': '70', 'glazed-type': 'double glazing, unknown install date',
'heating-cost-current': '2158', 'address3': 'Perry',
'mainheatcont-description': 'No time or thermostatic control of room temperature',
'sheating-energy-eff': 'N/A', 'property-type': 'Bungalow', 'local-authority-label': 'Huntingdonshire',
'fixed-lighting-outlets-count': '12', 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural',
'hot-water-cost-current': '257', 'county': 'Cambridgeshire', 'postcode': 'PE28 0SX',
'solar-water-heating-flag': 'N', 'constituency': 'E14000757', 'co2-emissions-potential': '3.3',
'number-heated-rooms': '5', 'floor-description': 'Solid, no insulation (assumed)',
'energy-consumption-potential': '128', 'local-authority': 'E07000011', 'built-form': 'Semi-Detached',
'number-open-fireplaces': '0', 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal',
'inspection-date': '2023-08-31', 'mains-gas-flag': 'N', 'co2-emiss-curr-per-floor-area': '51',
'address1': 'Holly Lodge', 'heat-loss-corridor': '', 'flat-storey-count': '',
'constituency-label': 'Huntingdon', 'roof-energy-eff': 'Good', 'total-floor-area': '117.0',
'building-reference-number': '10005199915', 'environment-impact-current': '50',
'co2-emissions-current': '5.9', 'roof-description': 'Pitched, 270 mm loft insulation',
'floor-energy-eff': 'N/A', 'number-habitable-rooms': '5', 'address2': 'The Drive',
'hot-water-env-eff': 'Good', 'posttown': 'HUNTINGDON', 'mainheatc-energy-eff': 'Very Poor',
'main-fuel': 'LPG (not community)', 'lighting-env-eff': 'Average', 'windows-energy-eff': 'Average',
'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A',
'lighting-description': 'Low energy lighting in 33% of fixed outlets', 'roof-env-eff': 'Good',
'walls-energy-eff': 'Average', 'photo-supply': '0.0', 'lighting-cost-potential': '166',
'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '',
'lodgement-datetime': '2023-10-30 13:46:54', 'flat-top-storey': '', 'current-energy-rating': 'F',
'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average',
'transaction-type': 'ECO assessment', 'uprn': '100091200828', 'current-energy-efficiency': '32',
'energy-consumption-current': '243', 'mainheat-description': 'Boiler and radiators, LPG',
'lighting-cost-current': '277', 'lodgement-date': '2023-10-30', 'extension-count': '0',
'mainheatc-env-eff': 'Very Poor',
'lmk-key': 'f1d3bd4b8b50bc9b006231ccb158537c408523b748b3f4ef7e98cd03b144afa5', 'wind-turbine-count': '0',
'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '56',
'hot-water-energy-eff': 'Poor', 'low-energy-lighting': '33',
'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system'
}
ending_epc = {
'low-energy-fixed-light-count': '', 'address': 'Holly Lodge, The Drive, Perry',
'uprn-source': 'Energy Assessor', 'floor-height': '2.8', 'heating-cost-potential': '917',
'unheated-corridor-length': '', 'hot-water-cost-potential': '328',
'construction-age-band': 'England and Wales: 1950-1966', 'potential-energy-rating': 'A',
'mainheat-energy-eff': 'Very Good', 'windows-env-eff': 'Average', 'lighting-energy-eff': 'Average',
'environment-impact-potential': '96', 'glazed-type': 'double glazing, unknown install date',
'heating-cost-current': '1098', 'address3': 'Perry',
'mainheatcont-description': 'Programmer, TRVs and bypass', 'sheating-energy-eff': 'N/A',
'property-type': 'Bungalow', 'local-authority-label': 'Huntingdonshire',
'fixed-lighting-outlets-count': '12', 'energy-tariff': 'Single', 'mechanical-ventilation': 'natural',
'hot-water-cost-current': '328', 'county': 'Cambridgeshire', 'postcode': 'PE28 0SX',
'solar-water-heating-flag': 'N', 'constituency': 'E14000757', 'co2-emissions-potential': '0.3',
'number-heated-rooms': '5', 'floor-description': 'Solid, no insulation (assumed)',
'energy-consumption-potential': '16', 'local-authority': 'E07000011', 'built-form': 'Semi-Detached',
'number-open-fireplaces': '0', 'windows-description': 'Fully double glazed', 'glazed-area': 'Normal',
'inspection-date': '2023-10-05', 'mains-gas-flag': 'N', 'co2-emiss-curr-per-floor-area': '6',
'address1': 'Holly Lodge', 'heat-loss-corridor': '', 'flat-storey-count': '',
'constituency-label': 'Huntingdon', 'roof-energy-eff': 'Good', 'total-floor-area': '117.0',
'building-reference-number': '10005199915', 'environment-impact-current': '92',
'co2-emissions-current': '0.7', 'roof-description': 'Pitched, 270 mm loft insulation',
'floor-energy-eff': 'N/A', 'number-habitable-rooms': '5', 'address2': 'The Drive',
'hot-water-env-eff': 'Very Good', 'posttown': 'HUNTINGDON', 'mainheatc-energy-eff': 'Average',
'main-fuel': 'electricity (not community)', 'lighting-env-eff': 'Average', 'windows-energy-eff': 'Average',
'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A',
'lighting-description': 'Low energy lighting in 33% of fixed outlets', 'roof-env-eff': 'Good',
'walls-energy-eff': 'Average', 'photo-supply': '', 'lighting-cost-potential': '166',
'mainheat-env-eff': 'Very Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '',
'lodgement-datetime': '2023-11-01 16:29:16', 'flat-top-storey': '', 'current-energy-rating': 'A',
'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average',
'transaction-type': 'ECO assessment', 'uprn': '100091200828', 'current-energy-efficiency': '92',
'energy-consumption-current': '37', 'mainheat-description': 'Air source heat pump, radiators, electric',
'lighting-cost-current': '277', 'lodgement-date': '2023-11-01', 'extension-count': '0',
'mainheatc-env-eff': 'Average',
'lmk-key': 'cb7f2838b727907767c8c2a385cd22f722b1e4745463391d910d228e52124515', 'wind-turbine-count': '0',
'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '95',
'hot-water-energy-eff': 'Good', 'low-energy-lighting': '33',
'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system'
}
cleaning_data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet",
)
cleaned = read_from_s3(
s3_file_name="cleaned_epc_data/cleaned.bson",
bucket_name="retrofit-data-dev"
)
cleaned = msgpack.unpackb(cleaned, raw=False)
photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev")
epc = EPCRecord(
epc_records={
'original_epc': starting_epc,
'full_sap_epc': {},
'old_data': []
},
run_mode="newdata",
cleaning_data=cleaning_data
)
home = Property(
id=0,
address="",
postcode="",
epc_record=epc,
already_installed={},
non_invasive_recommendations={},
)
home.in_conservation_area = False
home.is_listed = False
home.is_heritage = False
home.restricted_measures = True
home.get_components(
cleaned=cleaned,
photo_supply_lookup=photo_supply_lookup,
floor_area_decile_thresholds=floor_area_decile_thresholds
)
recommender = HeatingRecommender(property_instance=home)
recommender.recommend_air_source_heat_pump(phase=0, has_cavity_or_loft_recommendations=False)
property_recommendations = Recommendations.insert_temp_recommendation_id([recommender.heating_recommendations])
assert len(recommender.heating_recommendations) == 1
home.create_base_difference_epc_record(cleaned_lookup=cleaned)
home.adjust_difference_record_with_recommendations(
property_recommendations, []
)
scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop(
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
"carbon_ending"]
)
model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat())
model_api.MODEL_PREFIXES = ["sap_change_predictions"]
predictions_dict = model_api.predict_all(
df=scoring_data,
bucket="retrofit-data-dev",
prediction_buckets={
"sap_change_predictions": "retrofit-sap-predictions-dev",
}
)
# We predict a huge uplift but not quite as much as the EPC, due to some distinct differences between our
# recommendation and the EPC
assert predictions_dict["sap_change_predictions"]["predictions"].values[0] == 81.3
assert ending_epc['current-energy-efficiency'] == '92'
# PATCH
# We patch the simulation config, to reflect the ending EPC, to see if we get the ending EPC's config
patched_simulation_config = {
'mainheat_energy_eff_ending': "Very Good",
'hot_water_energy_eff_ending': 'Good',
'has_boiler_ending': False,
'has_air_source_heat_pump_ending': True,
'has_electric_ending': True,
'has_lpg_ending': False,
'fuel_type_ending': 'electricity',
'switch_system_ending': 'programmer',
'no_control_ending': None,
'auxiliary_systems_ending': 'bypass',
'trvs_ending': 'trvs',
"mainheatc_energy_eff_ending": 'Average'
}
# PATCHING
property_recommendations_patch = Recommendations.insert_temp_recommendation_id(
[recommender.heating_recommendations]
)
property_recommendations_patch[0][0]["simulation_config"] = patched_simulation_config
home.create_base_difference_epc_record(cleaned_lookup=cleaned)
home.adjust_difference_record_with_recommendations(
property_recommendations_patch, []
)
scoring_data_patch = pd.DataFrame(home.recommendations_scoring_data).drop(
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
"carbon_ending"]
)
model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat())
model_api.MODEL_PREFIXES = ["sap_change_predictions"]
predictions_dict_patch = model_api.predict_all(
df=scoring_data_patch,
bucket="retrofit-data-dev",
prediction_buckets={
"sap_change_predictions": "retrofit-sap-predictions-dev",
}
)
assert predictions_dict_patch["sap_change_predictions"]["predictions"].values[0] == 88.9
# We still underpredict but the improvement is notable
def test_offgrid(self):
"""
We test on a property we've worked with before, where we compare two options
a) Upgrading to a boiler
b) Upgrading to a heat pump
:return:
"""
starting_epc = {
'low-energy-fixed-light-count': '', 'address': '6 Beech Road', 'uprn-source': 'Energy Assessor',
'floor-height': '2.4', 'heating-cost-potential': '612', 'unheated-corridor-length': '',
'hot-water-cost-potential': '123', 'construction-age-band': 'England and Wales: 1930-1949',
'potential-energy-rating': 'B', 'mainheat-energy-eff': 'Very Poor', 'windows-env-eff': 'Good',
'lighting-energy-eff': 'Good', 'environment-impact-potential': '87',
'glazed-type': 'double glazing installed during or after 2002', 'heating-cost-current': '2278',
'address3': '', 'mainheatcont-description': 'Appliance thermostats', 'sheating-energy-eff': 'N/A',
'property-type': 'House', 'local-authority-label': 'Dudley', 'fixed-lighting-outlets-count': '9',
'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '604',
'county': '', 'postcode': 'DY1 4BP', 'solar-water-heating-flag': 'N', 'constituency': 'E14000671',
'co2-emissions-potential': '1.0', 'number-heated-rooms': '4',
'floor-description': 'Solid, no insulation (assumed)', 'energy-consumption-potential': '93',
'local-authority': 'E08000027', 'built-form': 'End-Terrace', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2024-03-13',
'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '83', 'address1': '6 Beech Road',
'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Dudley North',
'roof-energy-eff': 'Very Poor', 'total-floor-area': '60.0', 'building-reference-number': '10005780080',
'environment-impact-current': '41', 'co2-emissions-current': '5.0',
'roof-description': 'Pitched, 12 mm loft insulation', 'floor-energy-eff': 'N/A',
'number-habitable-rooms': '4', 'address2': '', 'hot-water-env-eff': 'Poor', 'posttown': 'DUDLEY',
'mainheatc-energy-eff': 'Good', 'main-fuel': 'electricity (not community)', 'lighting-env-eff': 'Good',
'windows-energy-eff': 'Good', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A',
'lighting-description': 'Low energy lighting in 67% of fixed outlets', 'roof-env-eff': 'Very Poor',
'walls-energy-eff': 'Average', 'photo-supply': '0.0', 'lighting-cost-potential': '113',
'mainheat-env-eff': 'Poor', 'multi-glaze-proportion': '100', 'main-heating-controls': '',
'lodgement-datetime': '2024-03-13 11:29:11', 'flat-top-storey': '', 'current-energy-rating': 'F',
'secondheat-description': 'None', 'walls-env-eff': 'Average', 'transaction-type': 'rental',
'uprn': '90055152', 'current-energy-efficiency': '32', 'energy-consumption-current': '491',
'mainheat-description': 'Room heaters, electric', 'lighting-cost-current': '113',
'lodgement-date': '2024-03-13', 'extension-count': '1', 'mainheatc-env-eff': 'Good',
'lmk-key': '78ddf851b660e599a0894924d0e6b503980f5e0ad1aa711f8411718dc2989c44', 'wind-turbine-count': '0',
'tenure': 'Rented (social)', 'floor-level': '', 'potential-energy-efficiency': '87',
'hot-water-energy-eff': 'Very Poor', 'low-energy-lighting': '67',
'walls-description': 'Cavity wall, filled cavity',
'hotwater-description': 'Electric immersion, standard tariff'
}
cleaning_data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet",
)
cleaned = read_from_s3(
s3_file_name="cleaned_epc_data/cleaned.bson",
bucket_name="retrofit-data-dev"
)
cleaned = msgpack.unpackb(cleaned, raw=False)
photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev")
epc = EPCRecord(
epc_records={
'original_epc': starting_epc,
'full_sap_epc': {},
'old_data': []
},
run_mode="newdata",
cleaning_data=cleaning_data
)
home = Property(
id=0,
address="",
postcode="",
epc_record=epc,
already_installed={},
non_invasive_recommendations={},
)
home.in_conservation_area = False
home.is_listed = False
home.is_heritage = False
home.restricted_measures = True
home.get_components(
cleaned=cleaned,
photo_supply_lookup=photo_supply_lookup,
floor_area_decile_thresholds=floor_area_decile_thresholds
)
recommender = HeatingRecommender(property_instance=home)
recommender.recommend_air_source_heat_pump(phase=0, has_cavity_or_loft_recommendations=False)
recommender.recommend_boiler_upgrades(phase=0, system_change=True, exising_room_heaters=False)
assert len(recommender.heating_recommendations) == 3
property_recommendations = Recommendations.insert_temp_recommendation_id([recommender.heating_recommendations])
home.create_base_difference_epc_record(cleaned_lookup=cleaned)
home.adjust_difference_record_with_recommendations(
property_recommendations, []
)
scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop(
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
"carbon_ending"]
)
model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat())
model_api.MODEL_PREFIXES = ["sap_change_predictions"]
predictions_dict = model_api.predict_all(
df=scoring_data,
bucket="retrofit-data-dev",
prediction_buckets={
"sap_change_predictions": "retrofit-sap-predictions-dev",
}
)
# The ASHP isn't better under SAP, compared to a gas boiler with good heat controls
assert predictions_dict["sap_change_predictions"]["predictions"].tolist() == [66.9, 65.5, 65.9]

View file

@ -18,10 +18,9 @@ class TestCosts:
"description": "cwi",
"depth": 75,
"thermal_conductivity": 0.037,
"prime_cost": 5.17,
"material_cost": 5.62,
"labour_cost": 1.125,
"total_cost": 14,
"labour_hours_per_unit": 0.065,
"is_installer_quote": True
}
cwi_results = costs.cavity_wall_insulation(
@ -29,12 +28,7 @@ class TestCosts:
material=cwi_material,
)
assert cwi_results == {
'total': 1065.0661223512907, 'subtotal': 887.5551019594088, 'vat': 177.51102039188177,
'contingency': 63.396792997100626, 'preliminaries': 63.396792997100626, 'material': 539.0166061175574,
'profit': 126.79358599420125, 'labour_hours': 6.234177828761786, 'labour_cost': 94.95132385344874,
'labour_days': 0.38963611429761164
}
assert cwi_results == {'total': 1342.7459938871539, 'labour_hours': 8, 'labour_days': 1}
def test_loft_insulation(self):
mock_property = Mock()
@ -47,22 +41,17 @@ class TestCosts:
"description": "Crown Loft Roll 44 glass fibre roll",
"depth": 270,
"thermal_conductivity": 0.044,
"prime_cost": None,
"material_cost": 5.91938,
"labour_cost": 1.96,
"labour_hours_per_unit": 0.11
"total_cost": 11,
"labour_hours_per_unit": 0.11,
"is_installer_quote": True,
}
loft_results = costs.loft_insulation(
loft_results = costs.loft_and_flat_insulation(
floor_area=33.5,
material=loft_material,
)
assert loft_results == {
'total': 639.4133610000001, 'subtotal': 532.8444675000001, 'vat': 106.56889350000002,
'contingency': 71.045929, 'preliminaries': 35.5229645, 'material': 297.448845, 'profit': 71.045929,
'labour_hours': 3.685, 'labour_cost': 57.7808, 'labour_days': 0.460625
}
assert loft_results == {'total': 368.5, 'labour_hours': 8, 'labour_days': 1}
def test_internal_wall_insulation(self):
mock_property = Mock()
@ -71,87 +60,6 @@ class TestCosts:
}
costs = Costs(mock_property)
iwi_non_insulation_materials = [
{'type': 'iwi_wall_demolition',
'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping hammer; plaster to walls.',
'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 10.27,
'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55, 'link': 'SPONs', 'Notes': 0.0},
{'type': 'iwi_wall_demolition',
'description': 'Stud walls: Remove wall linings including battening behind; plasterboard and skim',
'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 6.23,
'labour_hours_per_unit': 0.2, 'plant_cost': 1.25, 'total_cost': 7.48, 'link': 'SPONs', 'Notes': 0.0},
{'type': 'iwi_wall_demolition',
'description': 'Lathe and Plaster walls: Remove wall linings including battening behind; wood lath and '
'plaster',
'depth': 0.0, 'depth_unit': 0.0, 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.0,
'thermal_conductivity_unit': 0.0, 'prime_material_cost': 0.0, 'material_cost': 0.0, 'labour_cost': 6.85,
'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94, 'link': 'SPONs', 'Notes': 0.0},
{'Notes': "",
'cost_unit': "",
'depth': "",
'depth_unit': "",
'description': 'Visqueen High Performance Vapour Barrier',
'labour_cost': 0.48,
'labour_hours_per_unit': 0.02,
'link': 'SPONs',
'material_cost': 1.21,
'plant_cost': 0,
'prime_material_cost': 0.58,
'thermal_conductivity': "",
'thermal_conductivity_unit': "",
'total_cost': 1.69,
'type': 'iwi_vapour_barrier'},
{'Notes': "",
'cost_unit': "",
'depth': "",
'depth_unit': "",
'description': 'Plaster; one coat Thistle board finish or other equal; steel trowelled; 3 mm thick work '
'to walls or ceilings; one coat; to plasterboard base; over 600mm wide',
'labour_cost': 6.58,
'labour_hours_per_unit': 0.25,
'link': "",
'material_cost': 0.06,
'plant_cost': 0,
'prime_material_cost': 0.0,
'thermal_conductivity': "",
'thermal_conductivity_unit': "",
'total_cost': 6.64,
'type': 'iwi_redecoration'},
{'Notes': "",
'cost_unit': "",
'depth': "",
'depth_unit': "",
'description': 'Two coats emulsion paint on plaster, over 40mm girth; 3.5m - '
'5m high',
'labour_cost': 0.0,
'labour_hours_per_unit': 0.21,
'link': "",
'material_cost': 0.41,
'plant_cost': 0,
'prime_material_cost': "",
'thermal_conductivity': "",
'thermal_conductivity_unit': "",
'total_cost': 4.34,
'type': 'iwi_redecoration'},
{'Notes': "",
'cost_unit': "",
'depth': "",
'depth_unit': "",
'description': 'Fitting existing softwood skirting or architrave to new '
'frames; 150mm high',
'labour_cost': 4.87,
'labour_hours_per_unit': 0.01,
'link': "",
'material_cost': 4.86,
'plant_cost': 0,
'prime_material_cost': "",
'thermal_conductivity': "",
'thermal_conductivity_unit': "",
'total_cost': 4.88,
'type': 'iwi_redecoration'}
]
iwi_material = {
"type": "internal_wall_insulation",
@ -161,26 +69,19 @@ class TestCosts:
"cost_unit": "gbp_per_m2",
"thermal_conductivity": 0.022,
"thermal_conductivity_unit": "watt_per_meter_kelvin",
"prime_material_cost": "",
"material_cost": 11.68,
"labour_cost": 3.12,
"labour_hours_per_unit": 0.18,
"plant_cost": "",
"total_cost": 14.8,
"link": "SPONs"
"total_cost": 200,
"link": "link",
"is_installer_quote": True
}
iwi_results = costs.internal_wall_insulation(
iwi_results = costs.solid_wall_insulation(
wall_area=95.9104281347967,
material=iwi_material,
non_insulation_materials=iwi_non_insulation_materials
)
assert iwi_results == {
'total': 6880.2304726777775, 'subtotal': 5733.525393898148, 'vat': 1146.7050787796295,
'contingency': 764.470052519753, 'preliminaries': 382.2350262598765, 'material': 1747.488000615996,
'profit': 764.470052519753, 'labour_hours': 88.23759388401297, 'labour_days': 2.757424808875405,
'labour_cost': 1927.1602026551818
'total': 19182.085626959342, 'labour_hours': 17.263877064263404, 'labour_days': 0.5394961582582314
}
def test_suspended_floor_insulation(self):
@ -201,7 +102,8 @@ class TestCosts:
'total_cost': 13.46, 'link': 'SPONs',
'Notes': 'Spons did not contain labour costs so we use values for similar insulations. '
'We use the '
'same values as in Crown loft roll 44, since it is also an insulation roll'
'same values as in Crown loft roll 44, since it is also an insulation roll',
"is_installer_quote": False
}
sus_floor_non_insulation_materials = [
@ -256,7 +158,7 @@ class TestCosts:
'depth': 100.0, 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.033,
'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 0,
'material_cost': 12.02, 'labour_cost': 4.4, 'labour_hours_per_unit': 0.19, 'plant_cost': 0,
'total_cost': 16.42, 'link': 'SPONs', 'Notes': 0
'total_cost': 16.42, 'link': 'SPONs', 'Notes': 0, "is_installer_quote": False
}
sol_floor_non_insulation_materials = [
@ -342,81 +244,18 @@ class TestCosts:
ewi_material = {
'type': 'external_wall_insulation', 'description': 'Ecotherm Eco-Versal PIR Insulation Board',
'depth': 150.0, 'depth_unit': 'mm', 'cost_unit': 'gbp_per_m2', 'thermal_conductivity': 0.022,
'thermal_conductivity_unit': 'watt_per_meter_kelvin', 'prime_material_cost': 23.53,
'material_cost': 34.62, 'labour_cost': 33.06, 'labour_hours_per_unit': 1.4, 'plant_cost': 0,
'total_cost': 67.68, 'link': 'SPONs', 'Notes': 0
'thermal_conductivity_unit': 'watt_per_meter_kelvin',
'labour_hours_per_unit': 1.4,
'total_cost': 300, 'link': 'SPONs', 'Notes': 0, "is_installer_quote": True
}
ewi_non_insulation_materials = [
{'type': 'ewi_wall_demolition',
'description': 'Solid & Dry Lined walls: Hack of wall finishes with chipping '
'hammer; plaster to walls.',
'depth': 0, 'depth_unit': 0, 'cost_unit': 'gbp_per_m2',
'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 10.27,
'labour_hours_per_unit': 0.33, 'plant_cost': 1.28, 'total_cost': 11.55,
'link': 'SPONs', 'Notes': 0}, {'type': 'ewi_wall_demolition',
'description': 'Stud walls: Remove wall linings '
'including battening behind; '
'plasterboard and skim',
'depth': 0, 'depth_unit': 0,
'cost_unit': 'gbp_per_m2',
'thermal_conductivity': 0,
'thermal_conductivity_unit': 0,
'prime_material_cost': 0, 'material_cost': 0,
'labour_cost': 6.23, 'labour_hours_per_unit': 0.2,
'plant_cost': 1.25, 'total_cost': 7.48,
'link': 'SPONs', 'Notes': 0},
{'type': 'ewi_wall_demolition',
'description': 'Lathe and Plaster walls: Remove wall linings including battening '
'behind; wood lath and plaster',
'depth': 0, 'depth_unit': 0, 'cost_unit': 'gbp_per_m2',
'thermal_conductivity': 0, 'thermal_conductivity_unit': 0,
'prime_material_cost': 0, 'material_cost': 0, 'labour_cost': 6.85,
'labour_hours_per_unit': 0.22, 'plant_cost': 2.09, 'total_cost': 8.94,
'link': 'SPONs', 'Notes': 0}, {'type': 'ewi_wall_preparation',
'description': 'Clean and prepare surfaces, '
'one coat Keim dilution, '
'one coat primer and two coats '
'of Keim Ecosil paint; Brick or '
'block walls; over 300 mm girth',
'depth': 0, 'depth_unit': 0, 'cost_unit': 0,
'thermal_conductivity': 0,
'thermal_conductivity_unit': 0,
'prime_material_cost': 0, 'material_cost': 7.3,
'labour_cost': 5.62, 'labour_hours_per_unit': 0.3,
'plant_cost': 0, 'total_cost': 12.92,
'link': 'SPONs',
'Notes': 'This work covers the preparation and '
'priming of the wall before insulating'},
{'type': 'ewi_wall_redecoration',
'description': 'EPS insulation fixed with adhesive to SFS structure (measured '
'separately) with horizontal PVC intermediate track and vertical '
'T-spines; with glassfibre mesh reinforcement embedded in Sto '
'Armat Classic Basecoat Render and Stolit K 1.5 Decorative '
'Topcoat Render (white)',
'depth': 0, 'depth_unit': 0, 'cost_unit': 0, 'thermal_conductivity': 0,
'thermal_conductivity_unit': 0, 'prime_material_cost': 0, 'material_cost': 0,
'labour_cost': 0, 'labour_hours_per_unit': 0, 'plant_cost': 0,
'total_cost': 69.94, 'link': 'SPONs',
'Notes': 'This material in SPONs is for 70mm EPS insulation, which comes in at a '
'cost of 99.17 per meter square. This includes the cost of insulation. '
'To get the costing for just the works and not the insulation, '
'we subtract the cost of EPS insulation, using Ravathem 75mm insulation '
'as an example, which costs £29.23 per meter square, giving us the cost '
'of the remaining works without insulation. This material gives us a '
'cost for basecoat, mesh application and a render finish'}]
ewi_results = costs.external_wall_insulation(
ewi_results = costs.solid_wall_insulation(
wall_area=95.9104281347967,
material=ewi_material,
non_insulation_materials=ewi_non_insulation_materials
)
assert ewi_results == {
'total': 15047.078622131372, 'subtotal': 12539.232185109477, 'vat': 2507.8464370218953,
'contingency': 808.9827216199662, 'preliminaries': 2022.4568040499155, 'material': 4020.565147410677,
'profit': 1617.9654432399325, 'labour_hours': 187.02533486285358, 'labour_days': 5.8445417144641745,
'labour_cost': 3921.5600094613983
'total': 28773.12844043901, 'labour_hours': 134.2745993887154, 'labour_days': 4.196081230897356
}
def test_flat_roof_insulation(self):
@ -426,120 +265,47 @@ class TestCosts:
}
costs = Costs(mock_property)
flat_roof_material = {'id': 1225, 'type': 'flat_roof_insulation',
'description': 'Kingspan Thermaroof TR21 zero OPD '
'urethene insulation board',
'depth': 100.0, 'depth_unit': 'mm', 'cost': None,
'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04,
'r_value_unit': 'square_meter_kelvin_per_watt',
'thermal_conductivity': 0.025,
'thermal_conductivity_unit': 'watt_per_meter_kelvin',
'link': 'SPONs',
'created_at': "now", 'is_active': True,
'prime_material_cost': None, 'material_cost': 50.95,
'labour_cost': 10.66, 'labour_hours_per_unit': 0.48,
'plant_cost': 0.0, 'total_cost': 61.61,
'notes': "SPONs didn't have a labour hours so we use "
"0.48 which is similar to other materials"}
flat_roof_material = {
'id': 1225, 'type': 'flat_roof_insulation',
'description': 'Kingspan Thermaroof TR21 zero OPD '
'urethene insulation board',
'depth': 100.0, 'depth_unit': 'mm', 'cost': None,
'cost_unit': 'gbp_per_m2', 'r_value_per_mm': 0.04,
'r_value_unit': 'square_meter_kelvin_per_watt',
'thermal_conductivity': 0.025,
'thermal_conductivity_unit': 'watt_per_meter_kelvin',
'link': 'SPONs',
'created_at': "now", 'is_active': True,
'prime_material_cost': None, 'material_cost': 50.95,
'labour_cost': 10.66, 'labour_hours_per_unit': 0.48,
'plant_cost': 0.0, 'total_cost': 61.61,
'notes': "SPONs didn't have a labour hours so we use "
"0.48 which is similar to other materials",
"is_installer_quote": False
}
flat_roof_non_insulation_materials = [
{'id': 17, 'type': 'mechanical_ventilation', 'description': 'Mechanical Extract Ventilation', 'depth': None,
'depth_unit': None, 'cost': 500, 'cost_unit': 'gbp_per_unit', 'r_value_per_mm': None, 'r_value_unit': None,
'thermal_conductivity': None, 'thermal_conductivity_unit': None, 'link': None,
'created_at': datetime.datetime(2023, 10, 18, 16, 39, 9, 827188), 'is_active': True,
'prime_material_cost': None,
'material_cost': None, 'labour_cost': None, 'labour_hours_per_unit': None, 'plant_cost': None,
'total_cost': None,
'notes': None},
{'id': 1221, 'type': 'flat_roof_preparation',
'description': 'clean surface to receive new damp-proof membrane',
'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None,
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
'thermal_conductivity_unit': None,
'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True,
'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 4.36, 'labour_hours_per_unit': 0.14,
'plant_cost': 0.0, 'total_cost': 4.36,
'notes': 'This data is based on concrete however forms a decent baseline for a Bituminous Felt flat roof'},
{'id': 1223, 'type': 'flat_roof_preparation',
'description': 'One coat primer; on wood surfaces before fixing; General surfaces; over 300 mm girth',
'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None,
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
'thermal_conductivity_unit': None,
'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True,
'prime_material_cost': None, 'material_cost': 2.49, 'labour_cost': 1.5, 'labour_hours_per_unit': 0.08,
'plant_cost': 0.0, 'total_cost': 3.99, 'notes': 'SPONs data gives us a baseline for a wood surface'},
{'id': 1224, 'type': 'flat_roof_vapour_barrier', 'description': 'Visqueen High Performance Vapour Barrier',
'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None,
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
'thermal_conductivity_unit': None,
'link': 'SPONs', 'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True,
'prime_material_cost': 0.58, 'material_cost': 1.21, 'labour_cost': 0.48, 'labour_hours_per_unit': 0.02,
'plant_cost': 0.0, 'total_cost': 1.69, 'notes': None},
{'id': 1234, 'type': 'flat_roof_waterproofing',
'description': '20 mm thick two coat coverings; felt isolating membrane; to concrete (or '
'timber) base; flat or to falls or slopes not exceeding 10° from horizontal',
'depth': 0.0, 'depth_unit': None, 'cost': None, 'cost_unit': 'gbp_per_m2', 'r_value_per_mm': None,
'r_value_unit': 'square_meter_kelvin_per_watt', 'thermal_conductivity': None,
'thermal_conductivity_unit': None, 'link': 'SPONs',
'created_at': datetime.datetime(2023, 12, 4, 20, 1, 49, 298076), 'is_active': True,
'prime_material_cost': None, 'material_cost': 0.0, 'labour_cost': 0.0,
'labour_hours_per_unit': 0.5, 'plant_cost': 0.0, 'total_cost': 31.13, 'notes': None}
]
flat_roof_floor_results = costs.flat_roof_insulation(
flat_roof_floor_results = costs.loft_and_flat_insulation(
floor_area=33.5,
material=flat_roof_material,
non_insulation_materials=flat_roof_non_insulation_materials
)
assert flat_roof_floor_results == {'total': 5325.327767999999, 'subtotal': 4437.773139999999,
'vat': 887.5546279999999, 'contingency': 459.07998,
'preliminaries': 306.05332, 'material': 1830.775, 'profit': 612.10664,
'labour_hours': 24.79, 'labour_days': 1.549375, 'labour_cost': 186.9032}
assert flat_roof_floor_results == {
'total': 2063.935, 'subtotal': 1719.9458333333334, 'vat': 343.9891666666665, 'labour_hours': 8,
'labour_days': 1
}
assert costs.labour_adjustment_factor == 0.88
# Mock property instance for regional tests
@pytest.fixture(params=[
("Northamptonshire", "East Midlands", 7927.44),
("Greater London Authority", "Inner London", 10475.0),
("Adur", "South East England", 8333.32),
("Bournemouth", "South West England", 8452),
("Basildon", "East of England", 7895.44),
("Birmingham", "West Midlands", 7706.2),
("County Durham", "North East England", 8113.96),
("Allerdale", "North West England", 6481.68),
("York", "Yorkshire and the Humber", 8243.6),
("Cardiff", "Wales", 7595.32),
("Glasgow City", "Scotland", 7871.88),
("Belfast", "Northern Ireland", 8504.36)
])
def mock_property_with_region(self, request):
county, region, expected_cost = request.param
mock_property = Mock()
mock_property.data = {"county": county}
return mock_property, region, expected_cost
# Test for different wattages
@pytest.mark.parametrize("wattage, expected_cost", [
(3000, 5945.58),
(4000, 7927.44),
(5000, 9909.3),
(6000, 11891.16),
@pytest.mark.parametrize("n_panels, expected_cost", [
(7, 4055.0),
(10, 4540.0),
(12, 4863.0),
(15, 5707.0),
])
def test_solar_pv_different_wattages(self, wattage, expected_cost):
def test_solar_pv_different_wattages(self, n_panels, expected_cost):
mock_property = Mock()
mock_property.data = {"county": "Mansfield"}
costs = Costs(mock_property)
result = costs.solar_pv(wattage)
assert result['total'] == pytest.approx(expected_cost, rel=0.01)
def test_solar_pv_regional_variation(self, mock_property_with_region):
# Test for regional cost variations
property_instance, expected_region, expected_cost = mock_property_with_region
costs = Costs(property_instance)
assert costs.region == expected_region
result = costs.solar_pv(4000) # Testing with a fixed wattage of 4000
result = costs.solar_pv(n_panels)
assert result['total'] == pytest.approx(expected_cost, rel=0.01)

File diff suppressed because it is too large Load diff

View file

@ -40,7 +40,7 @@ class TestFirepaceRecommendations:
assert recommender.recommendation
assert recommender.recommendation[0]["type"] == "sealing_open_fireplace"
assert recommender.recommendation[0]["total"] == 300
assert recommender.recommendation[0]["total"] == 235
def test_multiple_fireplaces(self):
epc_record = EPCRecord()
@ -59,4 +59,4 @@ class TestFirepaceRecommendations:
assert recommender.recommendation
assert recommender.recommendation[0]["type"] == "sealing_open_fireplace"
assert recommender.recommendation[0]["total"] == 900
assert recommender.recommendation[0]["total"] == 235 * 3

View file

@ -5,13 +5,18 @@ from unittest.mock import Mock
from recommendations.FloorRecommendations import FloorRecommendations
from recommendations.tests.test_data.materials import materials
from backend.Property import Property
from etl.epc.Record import EPCRecord
# import inspect
#
# file_path = inspect.getfile(lambda: None)
# with open(
# os.path.abspath(os.path.dirname(__file__)) + "/recommendations/tests/test_data/input_properties.pkl", "rb"
# os.path.abspath(os.path.dirname(file_path)) + "/recommendations/tests/test_data/input_properties.pkl", "rb"
# ) as f:
# input_properties = pickle.load(f)
class TestFloorRecommendations:
@pytest.fixture
@ -59,6 +64,7 @@ class TestFloorRecommendations:
input_properties[2].floor_type = "suspended"
input_properties[2].number_of_floors = 1
input_properties[2].floor_level = 0
input_properties[2].already_installed = []
recommender = FloorRecommendations(property_instance=input_properties[2], materials=materials)
assert recommender.estimated_u_value is None
@ -71,8 +77,8 @@ class TestFloorRecommendations:
assert types == {"suspended_floor_insulation"}
assert len(recommender.recommendations) == 6
assert recommender.recommendations[0]["total"] == 4925.205
assert len(recommender.recommendations) == 1
assert recommender.recommendations[0]["total"] == 4687.5
assert recommender.recommendations[0]["new_u_value"] == 0.21
def test_uvalue_0_12(self, input_properties):
@ -108,6 +114,7 @@ class TestFloorRecommendations:
input_properties[4].floor_type = "solid"
input_properties[4].number_of_floors = 1
input_properties[4].floor_level = 0
input_properties[4].already_installed = []
# In this case, we have no county, so in this case, it should yse the local-authority-label if possible
input_properties[4].data["county"] = ""
@ -146,123 +153,131 @@ class TestFloorRecommendations:
assert recommender.estimated_u_value is None
assert not recommender.recommendations
# def test_exposed_floor_no_insulation(self):
# input_property = Property(id=1, postcode="F4k3 2", address1="223 fake street", epc_client=Mock())
# input_property.floor = {
# 'original_description': 'To unheated space, no insulation (assumed)',
# 'clean_description': 'To unheated space, no insulation', 'thermal_transmittance': None,
# 'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True,
# 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False,
# 'insulation_thickness': 'none'
# }
# input_property.age_band = "L"
# input_property.set_floor_type()
# input_property.data = {"floor-level": 0, "property-type": "House"}
# input_property.floor_area = 100
# input_property.number_of_floors = 1
#
# recommender = FloorRecommendations(
# property_instance=input_property,
# materials=materials
# )
#
# assert not recommender.recommendations
#
# recommender.recommend()
#
# # Because of age band L, this should have a u-value of 0.22 to begin with and no recommendation
# assert not len(recommender.recommendations)
# assert recommender.estimated_u_value == 0.22
#
# # Now with an older age band
#
# input_property2 = Property(id=1, postcode="F4k3 2", address1="223 fake street", epc_client=Mock())
# input_property2.floor = {
# 'original_description': 'To unheated space, no insulation (assumed)',
# 'clean_description': 'To unheated space, no insulation', 'thermal_transmittance': None,
# 'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True,
# 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False,
# 'insulation_thickness': 'none'
# }
# input_property2.age_band = "D"
# input_property2.set_floor_type()
# input_property2.data = {"floor-level": 0, "property-type": "House"}
# input_property2.floor_area = 100
# input_property2.number_of_floors = 1
#
# recommender2 = FloorRecommendations(
# property_instance=input_property2,
# materials=materials
# )
#
# assert not recommender2.recommendations
#
# recommender2.recommend()
#
# assert len(recommender2.recommendations) == 1
#
# assert recommender2.recommendations[0]["new_u_value"] == 0.23
# assert recommender2.recommendations[0]["starting_u_value"] == 1.2
# assert recommender2.recommendations[0]["cost"] == 1500
#
# def test_exposed_floor_below_average_insulated(self):
# input_property3 = Property(id=1, postcode="F4k3 2", address1="223 fake street", epc_client=Mock())
# input_property3.floor = {
# 'original_description': 'To unheated space, below average insulation (assumed)',
# 'clean_description': 'To unheated space, below average insulation', 'thermal_transmittance': None,
# 'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True,
# 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False,
# 'insulation_thickness': 'below average'
# }
# input_property3.age_band = "C"
# input_property3.set_floor_type()
# input_property3.data = {"floor-level": 0, "property-type": "House"}
# input_property3.floor_area = 100
# input_property3.number_of_floors = 1
#
# recommender3 = FloorRecommendations(
# property_instance=input_property3,
# materials=materials
# )
#
# assert not recommender3.recommendations
#
# recommender3.recommend()
#
# assert recommender3.estimated_u_value == 0.5
#
# assert len(recommender3.recommendations) == 1
#
# assert recommender3.recommendations[0]["new_u_value"] == 0.22
# assert recommender3.recommendations[0]["starting_u_value"] == 0.5
# assert recommender3.recommendations[0]["cost"] == 1100
# assert recommender3.recommendations[0]["parts"][0]["depths"] == [100]
#
# # With average insulation, no recommendations
#
# input_property4 = Property(id=1, postcode="F4k3 2", address1="223 fake street", epc_client=Mock())
# input_property4.floor = {
# 'original_description': 'To unheated space, insulated (assumed)',
# 'clean_description': 'To unheated space, insulated', 'thermal_transmittance': None,
# 'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True,
# 'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False,
# 'insulation_thickness': 'average'
# }
# input_property4.age_band = "C"
# input_property4.set_floor_type()
# input_property4.data = {"floor-level": 0, "property-type": "House"}
# input_property4.floor_area = 100
# input_property4.number_of_floors = 1
#
# recommender4 = FloorRecommendations(
# property_instance=input_property4,
# materials=materials
# )
#
# assert not recommender4.recommendations
#
# recommender4.recommend()
#
# assert recommender4.estimated_u_value is None
#
# assert len(recommender4.recommendations) == 0
def test_exposed_floor_no_insulation(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Greater London", "floor-level": 0, "property-type": "House"}
epc_record.full_sap_epc = {}
input_property = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
input_property.floor = {
'original_description': 'To unheated space, no insulation (assumed)',
'clean_description': 'To unheated space, no insulation', 'thermal_transmittance': None,
'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True,
'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False,
'insulation_thickness': 'none'
}
input_property.age_band = "L"
input_property.set_floor_type()
input_property.floor_area = 100
input_property.number_of_floors = 1
recommender = FloorRecommendations(
property_instance=input_property,
materials=materials
)
assert not recommender.recommendations
recommender.recommend()
# Because of age band L, this should have a u-value of 0.22 to begin with and no recommendation
assert not len(recommender.recommendations)
assert recommender.estimated_u_value == 0.22
# Now with an older age band
epc_record2 = EPCRecord()
epc_record2.prepared_epc = {"county": "Greater London", "floor-level": 0, "property-type": "House"}
epc_record2.full_sap_epc = {}
input_property2 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record2)
input_property2.floor = {
'original_description': 'To unheated space, no insulation (assumed)',
'clean_description': 'To unheated space, no insulation', 'thermal_transmittance': None,
'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True,
'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False,
'insulation_thickness': 'none'
}
input_property2.age_band = "D"
input_property2.set_floor_type()
input_property2.insulation_floor_area = 100
input_property2.number_of_floors = 1
recommender2 = FloorRecommendations(
property_instance=input_property2,
materials=materials
)
assert not recommender2.recommendations
recommender2.recommend()
assert len(recommender2.recommendations) == 1
assert recommender2.recommendations[0]["new_u_value"] == 0.24
assert recommender2.recommendations[0]["starting_u_value"] == 1.2
assert recommender2.recommendations[0]["total"] == 9375
def test_exposed_floor_below_average_insulated(self):
epc_record3 = EPCRecord()
epc_record3.prepared_epc = {"county": "Greater London", "floor-level": 0, "property-type": "House"}
epc_record3.full_sap_epc = {}
input_property3 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record3)
input_property3.floor = {
'original_description': 'To unheated space, below average insulation (assumed)',
'clean_description': 'To unheated space, below average insulation', 'thermal_transmittance': None,
'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True,
'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False,
'insulation_thickness': 'below average'
}
input_property3.age_band = "C"
input_property3.set_floor_type()
input_property3.insulation_floor_area = 100
input_property3.number_of_floors = 1
recommender3 = FloorRecommendations(
property_instance=input_property3,
materials=materials
)
assert not recommender3.recommendations
recommender3.recommend()
assert recommender3.estimated_u_value == 0.5
assert len(recommender3.recommendations) == 1
assert recommender3.recommendations[0]["new_u_value"] == 0.24
assert recommender3.recommendations[0]["starting_u_value"] == 0.5
assert recommender3.recommendations[0]["total"] == 7500
assert recommender3.recommendations[0]["parts"][0]["depth"] == 50
# With average insulation, no recommendations
epc_record4 = EPCRecord()
epc_record4.prepared_epc = {"county": "Greater London", "floor-level": 0, "property-type": "House"}
epc_record4.full_sap_epc = {}
input_property4 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record4)
input_property4.floor = {
'original_description': 'To unheated space, insulated (assumed)',
'clean_description': 'To unheated space, insulated', 'thermal_transmittance': None,
'thermal_transmittance_unit': None, 'is_assumed': True, 'is_to_unheated_space': True,
'is_to_external_air': False, 'is_suspended': False, 'is_solid': False, 'another_property_below': False,
'insulation_thickness': 'average'
}
input_property4.age_band = "C"
input_property4.set_floor_type()
input_property4.insulation_floor_area = 100
input_property4.number_of_floors = 1
recommender4 = FloorRecommendations(
property_instance=input_property4,
materials=materials
)
assert not recommender4.recommendations
recommender4.recommend()
assert recommender4.estimated_u_value is None
assert len(recommender4.recommendations) == 0

View file

@ -41,8 +41,12 @@ class TestLightingRecommendations:
assert len(lr.recommendation) == 1
assert lr.recommendation == [
{'parts': [], 'type': 'low_energy_lighting', 'description': 'Install low energy lighting in 4 outlets',
'starting_u_value': None, 'new_u_value': None, 'sap_points': 0.4, 'total': 240.24,
'subtotal': 200.20000000000002, 'vat': 40.040000000000006, 'contingency': 14.3, 'preliminaries': 14.3,
'material': 80.0, 'profit': 28.6, 'labour_hours': 3.2, 'labour_days': 0.4, 'labour_cost': 63.0}
{'phase': 0, 'parts': [], 'type': 'low_energy_lighting',
'description': 'Install low energy lighting in 4 outlets', 'starting_u_value': None, 'new_u_value': None,
'already_installed': False, 'sap_points': 0.4, 'kwh_savings': 219.0, 'co2_equivalent_savings': 0.035478,
'description_simulation': {'lighting-energy-eff': 'Very Good',
'lighting-description': 'Low energy lighting in all fixed outlets',
'low-energy-lighting': 100}, 'total': 240.24, 'subtotal': 200.20000000000002,
'vat': 40.040000000000006, 'contingency': 14.3, 'preliminaries': 14.3, 'material': 80.0, 'profit': 28.6,
'labour_hours': 3.2, 'labour_days': 0.4, 'labour_cost': 63.0, 'survey': False}
]

View file

@ -88,8 +88,8 @@ class TestRecommendationUtils:
def test_get_roof_u_value_case_3(self):
inputs = {
'original_description': 'Room-in-roof, 200 mm insulation at rafters',
'clean_description': 'Room-in-roof, 200 mm insulation at rafters',
'original_description': 'Room-in-roof, insulated at rafters',
'clean_description': 'Room-in-roof, insulated at rafters',
'thermal_transmittance': None,
'thermal_transmittance_unit': None,
'is_pitched': False,
@ -101,12 +101,12 @@ class TestRecommendationUtils:
'is_assumed': False,
'has_dwelling_above': False,
'is_valid': True,
'insulation_thickness': '200',
'insulation_thickness': 'average',
'age_band': "J"
}
u_value = recommendation_utils.get_roof_u_value(**inputs)
assert u_value == 0.21, f"Expected 0.21, but got {u_value}"
assert u_value == 0.4, f"Expected 0.4, but got {u_value}"
def test_get_roof_u_value_case_4(self):
inputs = {
@ -179,8 +179,8 @@ class TestRecommendationUtils:
def test_get_roof_u_value_case_7(self):
# Test case where the roof has a room in it
inputs = {
'original_description': 'Pitched, room-in-roof, 100mm insulation',
'clean_description': 'Pitched, room-in-roof, 100mm insulation',
'original_description': 'Pitched, room-in-roof, above average insulation',
'clean_description': 'Pitched, room-in-roof, above average insulation',
'thermal_transmittance': None,
'thermal_transmittance_unit': None,
'is_pitched': True,
@ -192,12 +192,12 @@ class TestRecommendationUtils:
'is_assumed': False,
'has_dwelling_above': False,
'is_valid': True,
'insulation_thickness': '100',
'insulation_thickness': 'above average',
'age_band': "J"
}
u_value = recommendation_utils.get_roof_u_value(**inputs)
assert u_value == 0.40, f"Expected 0.40, but got {u_value}"
assert u_value == 0.16, f"Expected 0.16, but got {u_value}"
def test_get_roof_u_value_case_8(self):
# Test case where there is a dwelling above the roof, U-value should be 0
@ -437,7 +437,6 @@ def test_estimate_windows():
construction_age_band="England and Wales: 1976-1982",
floor_area=37,
number_habitable_rooms=2,
extension_count=0,
)
assert windows_case_1 == 4, f"Expected 4 windows, got {windows_case_1}"
@ -450,7 +449,6 @@ def test_estimate_windows():
construction_age_band="England and Wales: 1950-1966",
floor_area=69,
number_habitable_rooms=4,
extension_count=0,
)
assert windows_case_2 == 6, f"Expected 6 windows, got {windows_case_2}"
@ -463,7 +461,6 @@ def test_estimate_windows():
construction_age_band="England and Wales: 1967-1975",
floor_area=56,
number_habitable_rooms=3,
extension_count=0,
)
assert windows_case_3 == 5, f"Expected 5 windows, got {windows_case_3}"
@ -476,7 +473,6 @@ def test_estimate_windows():
construction_age_band="England and Wales: 1967-1975",
floor_area=77.28,
number_habitable_rooms=4,
extension_count=0,
)
assert windows_case_4 == 7, f"Expected 7 windows, got {windows_case_4}"
@ -489,7 +485,6 @@ def test_estimate_windows():
construction_age_band="England and Wales: 1950-1966",
floor_area=88.4,
number_habitable_rooms=5,
extension_count=0,
)
assert windows_case_5 == 12, f"Expected 12 windows, got {windows_case_5}"
@ -502,7 +497,6 @@ def test_estimate_windows():
construction_age_band="",
floor_area=100,
number_habitable_rooms=3,
extension_count=0,
)
assert windows_case_6 == 5, f"Expected 5 windows, got {windows_case_6}"
@ -514,7 +508,6 @@ def test_estimate_windows():
construction_age_band="England and Wales: 1967-1975",
floor_area=85,
number_habitable_rooms=4,
extension_count=0,
)
assert windows_case_7 == 10, f"Expected 10 windows, got {windows_case_7}"
@ -526,7 +519,6 @@ def test_estimate_windows():
construction_age_band="",
floor_area=50,
number_habitable_rooms=3,
extension_count=0,
)
assert windows_case_8 == 5, f"Expected 5 windows, got {windows_case_8}"

View file

@ -28,9 +28,10 @@ class TestRoofRecommendations:
assert not roof_recommender.recommendations
roof_recommender.recommend()
roof_recommender.recommend(phase=0)
assert len(roof_recommender.recommendations)
assert len(roof_recommender.recommendations) == 1
assert roof_recommender.recommendations[0]["parts"][0]["depth"] == 300
def test_loft_insulation_recommendation_50mm_insulation(self):
epc_record = EPCRecord()
@ -52,13 +53,14 @@ class TestRoofRecommendations:
assert not roof_recommender2.recommendations
roof_recommender2.recommend()
roof_recommender2.recommend(phase=0)
assert len(roof_recommender2.recommendations) == 1
assert roof_recommender2.recommendations[0]["total"] == 1936.9206000000004
assert roof_recommender2.recommendations[0]["total"] == 1610.0000000000002
assert roof_recommender2.recommendations[0]["new_u_value"] == 0.14
assert roof_recommender2.recommendations[0]["starting_u_value"] == 0.68
assert roof_recommender2.recommendations[0]["parts"][0]["depth"] == 270
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Greater London Authority"}
@ -79,7 +81,7 @@ class TestRoofRecommendations:
assert not roof_recommender3.recommendations
roof_recommender3.recommend()
roof_recommender3.recommend(phase=0)
assert roof_recommender3.recommendations
assert len(roof_recommender3.recommendations) == 1
@ -105,14 +107,14 @@ class TestRoofRecommendations:
assert not roof_recommender4.recommendations
roof_recommender4.recommend()
roof_recommender4.recommend(phase=0)
assert len(roof_recommender4.recommendations) == 4
assert len(roof_recommender4.recommendations) == 1
assert roof_recommender4.recommendations[0]["total"] == 1128.744
assert roof_recommender4.recommendations[0]["new_u_value"] == 0.15
assert roof_recommender4.recommendations[0]["total"] == 1552.5
assert roof_recommender4.recommendations[0]["new_u_value"] == 0.13
assert roof_recommender4.recommendations[0]["starting_u_value"] == 0.3
assert roof_recommender4.recommendations[0]["parts"][0]["depth"] == 150
assert roof_recommender4.recommendations[0]["parts"][0]["depth"] == 200
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Somerset"}
@ -133,12 +135,11 @@ class TestRoofRecommendations:
assert not roof_recommender5.recommendations
roof_recommender5.recommend()
roof_recommender5.recommend(phase=0)
# The 150mm insulation should be selected, since there it already 150mm
assert roof_recommender5.recommendations
assert len(roof_recommender5.recommendations) == 4
assert roof_recommender5.recommendations[0]["parts"][0]["depth"] == 150
assert len(roof_recommender5.recommendations) == 1
assert roof_recommender5.recommendations[0]["parts"][0]["depth"] == 200
def test_loft_insulation_recommendation_270mm_insulation(self):
# We shouldn't recommend anything in this case
@ -161,127 +162,121 @@ class TestRoofRecommendations:
assert not roof_recommender6.recommendations
roof_recommender6.recommend()
roof_recommender6.recommend(phase=0)
assert len(roof_recommender6.recommendations) == 0
# def test_uninsulated_room_in_roof(self):
# property_instance7 = Property(id=0, address1="fake", postcode="fake", epc_client=Mock())
# property_instance7.age_band = "F"
# property_instance7.insulation_floor_area = 100
# property_instance7.roof = {
# 'original_description': 'Roof room(s), no insulation (assumed)',
# 'clean_description': 'Roof room(s), no insulation',
# 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
# 'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False,
# 'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True, 'insulation_thickness': 'none'
# }
#
# property_instance7.pitched_roof_area = 110
# property_instance7.data = {"county": "Southampton"}
#
# roof_recommender7 = RoofRecommendations(property_instance=property_instance7, materials=materials)
#
# assert not roof_recommender7.recommendations
#
# roof_recommender7.recommend()
#
# # Even though we have 3 depths, we only end with 1 due to diminishin returns
# assert len(roof_recommender7.recommendations) == 1
#
# assert roof_recommender7.recommendations[0]["parts"][0]["depths"] == [270]
#
# assert roof_recommender7.recommendations[0]["new_u_value"] == 0.14
# assert roof_recommender7.recommendations[0]["starting_u_value"] == 0.8
# assert roof_recommender7.recommendations[0]["description"] == \
# "Insulate your room roof with 270mm of Example room roof insulation"
#
# def test_ceiling_insulated_room_in_roof(self):
# property_instance8 = Property(id=8, address1="fake", postcode="fake", epc_client=Mock())
# property_instance8.age_band = "F"
# property_instance8.insulation_floor_area = 100
# property_instance8.roof = {
# 'original_description': 'Roof room(s), ceiling insulated',
# 'clean_description': 'Roof room(s), ceiling insulated',
# 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
# 'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False,
# 'is_at_rafters': False,
# 'is_assumed': False, 'has_dwelling_above': False, 'is_valid': True,
# 'insulation_thickness': 'average'
# }
#
# property_instance8.pitched_roof_area = 110
#
# roof_recommender8 = RoofRecommendations(property_instance=property_instance8, materials=materials)
#
# assert not roof_recommender8.recommendations
#
# roof_recommender8.recommend()
#
# # No recommendations in this case
# assert not roof_recommender8.recommendations
#
# def test_insulated_room_in_roof(self):
# property_instance9 = Property(id=9, address1="fake", postcode="fake", epc_client=Mock())
# property_instance9.age_band = "F"
# property_instance9.insulation_floor_area = 100
# property_instance9.roof = {
# 'original_description': 'Roof room(s), insulated (assumed)',
# 'clean_description': 'Roof room(s), insulated',
# 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
# 'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False,
# 'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True, 'insulation_thickness': 'average'
# }
#
# property_instance9.pitched_roof_area = 110
# property_instance9.data = {"county": "Rutland"}
#
# roof_recommender9 = RoofRecommendations(property_instance=property_instance9, materials=materials)
#
# assert not roof_recommender9.recommendations
#
# roof_recommender9.recommend()
#
# # No recommendations in this case
# assert not roof_recommender9.recommendations
#
# def test_limited_insulated_room_in_roof(self):
# property_instance10 = Property(id=10, address1="fake", postcode="fake", epc_client=Mock())
# property_instance10.age_band = "F"
# property_instance10.insulation_floor_area = 100
# property_instance10.roof = {
# 'original_description': 'Roof room(s), limited insulation (assumed)',
# 'clean_description': 'Roof room(s), limited insulation',
# 'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
# 'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False,
# 'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True,
# 'insulation_thickness': 'below average'
# }
#
# property_instance10.pitched_roof_area = 110
# property_instance10.data = {"county": "Westmorland"}
#
# roof_recommender10 = RoofRecommendations(property_instance=property_instance10, materials=materials)
#
# assert not roof_recommender10.recommendations
#
# roof_recommender10.recommend()
#
# assert len(roof_recommender10.recommendations) == 2
#
# assert roof_recommender10.recommendations[0]["parts"][0]["depths"] == [220]
# assert roof_recommender10.recommendations[1]["parts"][0]["depths"] == [270]
#
# assert roof_recommender10.recommendations[0]["new_u_value"] == 0.16
# assert roof_recommender10.recommendations[1]["new_u_value"] == 0.14
#
# assert roof_recommender10.recommendations[0]["starting_u_value"] == 0.8
# assert roof_recommender10.recommendations[1]["starting_u_value"] == 0.8
#
# assert roof_recommender10.recommendations[0]["description"] == \
# "Insulate your room roof with 220mm of Example room roof insulation"
# assert roof_recommender10.recommendations[1]["description"] == \
# "Insulate your room roof with 270mm of Example room roof insulation"
def test_uninsulated_room_in_roof(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Southampton", "roof-energy-eff": "Very Poor"}
property_instance7 = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
property_instance7.age_band = "F"
property_instance7.insulation_floor_area = 100
property_instance7.roof = {
'original_description': 'Roof room(s), no insulation (assumed)',
'clean_description': 'Roof room(s), no insulation',
'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False,
'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True, 'insulation_thickness': 'none'
}
property_instance7.pitched_roof_area = 110
roof_recommender7 = RoofRecommendations(property_instance=property_instance7, materials=materials)
assert not roof_recommender7.recommendations
roof_recommender7.recommend(phase=0)
assert len(roof_recommender7.recommendations) == 1
assert roof_recommender7.recommendations[0]["new_u_value"] == 0.23
assert roof_recommender7.recommendations[0]["starting_u_value"] == 1.5
assert roof_recommender7.recommendations[0]["description"] == "Insulate room in roof at rafters and re-decorate"
def test_ceiling_insulated_room_in_roof(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Southampton", "roof-energy-eff": "Very Poor"}
property_instance8 = Property(id=8, address="fake", postcode="fake", epc_record=epc_record)
property_instance8.age_band = "F"
property_instance8.insulation_floor_area = 100
property_instance8.roof = {
'original_description': 'Roof room(s), ceiling insulated',
'clean_description': 'Roof room(s), ceiling insulated',
'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False,
'is_at_rafters': False,
'is_assumed': False, 'has_dwelling_above': False, 'is_valid': True,
'insulation_thickness': 'average'
}
property_instance8.pitched_roof_area = 110
roof_recommender8 = RoofRecommendations(property_instance=property_instance8, materials=materials)
assert not roof_recommender8.recommendations
roof_recommender8.recommend(phase=0)
# No recommendations in this case
assert not roof_recommender8.recommendations
def test_insulated_room_in_roof(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Southampton", "roof-energy-eff": "Very Poor"}
property_instance9 = Property(id=9, address="fake", postcode="fake", epc_record=epc_record)
property_instance9.age_band = "F"
property_instance9.insulation_floor_area = 100
property_instance9.roof = {
'original_description': 'Roof room(s), insulated (assumed)',
'clean_description': 'Roof room(s), insulated',
'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False,
'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True, 'insulation_thickness': 'average'
}
property_instance9.pitched_roof_area = 110
property_instance9.data = {"county": "Rutland"}
roof_recommender9 = RoofRecommendations(property_instance=property_instance9, materials=materials)
assert not roof_recommender9.recommendations
roof_recommender9.recommend(phase=0)
# No recommendations in this case
assert not roof_recommender9.recommendations
def test_limited_insulated_room_in_roof(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Westmorland", "roof-energy-eff": "Poor"}
property_instance10 = Property(id=10, address="fake", postcode="fake", epc_record=epc_record)
property_instance10.age_band = "F"
property_instance10.insulation_floor_area = 100
property_instance10.roof = {
'original_description': 'Roof room(s), limited insulation (assumed)',
'clean_description': 'Roof room(s), limited insulation',
'thermal_transmittance': None, 'thermal_transmittance_unit': None, 'is_pitched': False,
'is_roof_room': True, 'is_loft': False, 'is_flat': False, 'is_thatched': False, 'is_at_rafters': False,
'is_assumed': True, 'has_dwelling_above': False, 'is_valid': True,
'insulation_thickness': 'below average'
}
property_instance10.pitched_roof_area = 110
roof_recommender10 = RoofRecommendations(property_instance=property_instance10, materials=materials)
assert not roof_recommender10.recommendations
roof_recommender10.recommend(phase=0)
assert len(roof_recommender10.recommendations) == 1
assert roof_recommender10.recommendations[0]["new_u_value"] == 0.19
assert roof_recommender10.recommendations[0]["starting_u_value"] == 0.68
assert (roof_recommender10.recommendations[0]["description"] ==
'Insulate room in roof at rafters and re-decorate')
def test_flat_no_insulation(self):
epc_record = EPCRecord()
@ -302,12 +297,12 @@ class TestRoofRecommendations:
assert not roof_recommender11.recommendations
roof_recommender11.recommend()
roof_recommender11.recommend(phase=0)
assert len(roof_recommender11.recommendations) == 1
assert roof_recommender11.recommendations[0]["parts"][0]["depth"] == 150
assert roof_recommender11.recommendations[0]["total"] == 4380.84324
assert roof_recommender11.recommendations[0]["total"] == 6532.5
assert roof_recommender11.recommendations[0]["new_u_value"] == 0.14
assert roof_recommender11.recommendations[0]["starting_u_value"] == 2.3
assert roof_recommender11.recommendations[0]["description"] == \
@ -334,7 +329,7 @@ class TestRoofRecommendations:
assert not roof_recommender12.recommendations
roof_recommender12.recommend()
roof_recommender12.recommend(phase=0)
assert not roof_recommender12.recommendations
@ -358,13 +353,13 @@ class TestRoofRecommendations:
assert not roof_recommender13.recommendations
roof_recommender13.recommend()
roof_recommender13.recommend(phase=0)
assert len(roof_recommender13.recommendations) == 1
assert roof_recommender13.recommendations[0]["parts"][0]["depth"] == 150
assert roof_recommender13.recommendations[0]["total"] == 5199.969120000002
assert roof_recommender13.recommendations[0]["total"] == 7800
assert roof_recommender13.recommendations[0]["new_u_value"] == 0.14
assert roof_recommender13.recommendations[0]["starting_u_value"] == 2.3
@ -390,6 +385,6 @@ class TestRoofRecommendations:
assert not roof_recommender14.recommendations
roof_recommender14.recommend()
roof_recommender14.recommend(phase=0)
assert not roof_recommender14.recommendations

View file

@ -50,360 +50,64 @@ class TestSolarPvRecommendations:
epc_record = EPCRecord()
epc_record.prepared_epc = {"property-type": "House", "photo-supply": None, "county": "Huntingdonshire"}
property_instance_valid_all = Property(id=1, address="", postcode="", epc_record=epc_record)
property_instance_valid_all.solar_pv_roof_area = 20
property_instance_valid_all.solar_pv_percentage = 40
property_instance_valid_all.roof_area = 40
property_instance_valid_all.number_of_floors = 2
property_instance_valid_all.roof = {"is_flat": True}
property_instance_valid_all.solar_panel_configuration = {
"panel_performance": pd.DataFrame(
[
{
"panneled_roof_area": 20,
"n_panels": 10,
"array_wattage": 4000,
"initial_ac_kwh_per_year": 3800
}
]
)
}
return property_instance_valid_all
def test_invalid_property_type(self, property_instance_invalid_type):
solar_pv = SolarPvRecommendations(property_instance_invalid_type)
solar_pv.recommend()
solar_pv.recommend(phase=0)
assert not solar_pv.recommendation
def test_invalid_roof_type(self, property_instance_invalid_roof):
solar_pv = SolarPvRecommendations(property_instance_invalid_roof)
solar_pv.recommend()
solar_pv.recommend(phase=0)
assert not solar_pv.recommendation
def test_existing_solar_pv(self, property_instance_has_solar_pv):
solar_pv = SolarPvRecommendations(property_instance_has_solar_pv)
solar_pv.recommend()
solar_pv.recommend(phase=0)
assert not solar_pv.recommendation
def test_valid_all_conditions(self, property_instance_valid_all):
solar_pv = SolarPvRecommendations(property_instance_valid_all)
solar_pv.recommend()
solar_pv.recommend(phase=0)
assert solar_pv.recommendation == [
{
'parts': [],
'type': 'solar_pv',
'description': 'Install a 4 kilowatt-peak (kWp) solar photovoltaic (PV) panel system on the roof',
'starting_u_value': None,
'new_u_value': None,
'sap_points': None,
'total': 8527.0752,
'subtotal': 7105.896,
'vat': 1421.1791999999996,
'labour_hours': 72,
'labour_days': 2,
'photo_supply': 4000
'phase': 0, 'parts': [], 'type': 'solar_pv',
'description': 'Install a 4.0 kilowatt-peak (kWp) solar photovoltaic (PV) panel system on 50% the '
'roof.',
'starting_u_value': None, 'new_u_value': None, 'sap_points': None, 'already_installed': False,
'total': 4850.0, 'subtotal': 4041.666666666667, 'vat': 808.333333333333, 'labour_hours': 48,
'labour_days': 2, 'photo_supply': 50.0, 'has_battery': False, 'initial_ac_kwh_per_year': 3800,
'description_simulation': {'photo-supply': 50.0}
},
{
'phase': 0, 'parts': [], 'type': 'solar_pv',
'description': 'Install a 4.0 kilowatt-peak (kWp) '
'solar photovoltaic (PV) panel system '
'on 50% the roof, with a battery '
'storage system.',
'starting_u_value': None, 'new_u_value': None,
'sap_points': None, 'already_installed': False,
'total': 7550.0, 'subtotal': 6291.666666666667,
'vat': 1258.333333333333, 'labour_hours': 48,
'labour_days': 2, 'photo_supply': 50.0,
'has_battery': True, 'initial_ac_kwh_per_year': 3800,
'description_simulation': {'photo-supply': 50.0}
}
]
def test_model(self):
"""
This function tests the recommendation engine, in conjunction with the model
:return:
"""
starting_epc = {
'low-energy-fixed-light-count': '', 'address': '27 Cromwell Street', 'uprn-source': 'Energy Assessor',
'floor-height': '2.5', 'heating-cost-potential': '443', 'unheated-corridor-length': '',
'hot-water-cost-potential': '53', 'construction-age-band': 'England and Wales: before 1900',
'potential-energy-rating': 'B', 'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Average',
'lighting-energy-eff': 'Very Poor', 'environment-impact-potential': '85',
'glazed-type': 'double glazing installed before 2002', 'heating-cost-current': '904', 'address3': '',
'mainheatcont-description': 'Programmer, room thermostat and TRVs', 'sheating-energy-eff': 'N/A',
'property-type': 'House', 'local-authority-label': 'West Lindsey', 'fixed-lighting-outlets-count': '10',
'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '79',
'county': 'Lincolnshire', 'postcode': 'DN21 1DH', 'solar-water-heating-flag': 'N',
'constituency': 'E14000707', 'co2-emissions-potential': '1.5', 'number-heated-rooms': '5',
'floor-description': 'Suspended, no insulation (assumed)', 'energy-consumption-potential': '92',
'local-authority': 'E07000142', 'built-form': 'Mid-Terrace', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2021-11-17',
'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '61', 'address1': '27 Cromwell Street',
'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Gainsborough',
'roof-energy-eff': 'Very Poor', 'total-floor-area': '89.0', 'building-reference-number': '10001989430',
'environment-impact-current': '47', 'co2-emissions-current': '5.4',
'roof-description': 'Pitched, no insulation (assumed)', 'floor-energy-eff': 'N/A',
'number-habitable-rooms': '5', 'address2': '', 'hot-water-env-eff': 'Good', 'posttown': 'GAINSBOROUGH',
'mainheatc-energy-eff': 'Good', 'main-fuel': 'mains gas (not community)', 'lighting-env-eff': 'Very Poor',
'windows-energy-eff': 'Average', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A',
'lighting-description': 'No low energy lighting', 'roof-env-eff': 'Very Poor',
'walls-energy-eff': 'Very Poor', 'photo-supply': '0.0', 'lighting-cost-potential': '72',
'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '',
'lodgement-datetime': '2021-12-01 10:12:23', 'flat-top-storey': '', 'current-energy-rating': 'E',
'secondheat-description': 'Room heaters, mains gas', 'walls-env-eff': 'Very Poor',
'transaction-type': 'ECO assessment', 'uprn': '100030949912', 'current-energy-efficiency': '54',
'energy-consumption-current': '346', 'mainheat-description': 'Boiler and radiators, mains gas',
'lighting-cost-current': '144', 'lodgement-date': '2021-12-01', 'extension-count': '2',
'mainheatc-env-eff': 'Good', 'lmk-key': '3ec5533af02ec78361c1f9bea8dd2e878c2c6fa6cf59e5cc505c3eeb038e0f91',
'wind-turbine-count': '0', 'tenure': 'Owner-occupied', 'floor-level': '',
'potential-energy-efficiency': '86', 'hot-water-energy-eff': 'Good', 'low-energy-lighting': '0',
'walls-description': 'Solid brick, as built, no insulation (assumed)',
'hotwater-description': 'From main system'
}
ending_epc = {
'low-energy-fixed-light-count': '', 'address': '27 Cromwell Street', 'uprn-source': 'Energy Assessor',
'floor-height': '2.5', 'heating-cost-potential': '443', 'unheated-corridor-length': '',
'hot-water-cost-potential': '53', 'construction-age-band': 'England and Wales: before 1900',
'potential-energy-rating': 'B', 'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Average',
'lighting-energy-eff': 'Very Poor', 'environment-impact-potential': '86',
'glazed-type': 'double glazing installed before 2002', 'heating-cost-current': '904', 'address3': '',
'mainheatcont-description': 'Programmer, room thermostat and TRVs', 'sheating-energy-eff': 'N/A',
'property-type': 'House', 'local-authority-label': 'West Lindsey', 'fixed-lighting-outlets-count': '10',
'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '79',
'county': 'Lincolnshire', 'postcode': 'DN21 1DH', 'solar-water-heating-flag': 'N',
'constituency': 'E14000707', 'co2-emissions-potential': '1.4', 'number-heated-rooms': '5',
'floor-description': 'Suspended, no insulation (assumed)', 'energy-consumption-potential': '84',
'local-authority': 'E07000142', 'built-form': 'Mid-Terrace', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2021-12-21',
'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '49', 'address1': '27 Cromwell Street',
'heat-loss-corridor': '', 'flat-storey-count': '', 'constituency-label': 'Gainsborough',
'roof-energy-eff': 'Very Poor', 'total-floor-area': '89.0', 'building-reference-number': '10001989430',
'environment-impact-current': '55', 'co2-emissions-current': '4.4',
'roof-description': 'Pitched, no insulation (assumed)', 'floor-energy-eff': 'N/A',
'number-habitable-rooms': '5', 'address2': '', 'hot-water-env-eff': 'Good', 'posttown': 'GAINSBOROUGH',
'mainheatc-energy-eff': 'Good', 'main-fuel': 'mains gas (not community)', 'lighting-env-eff': 'Very Poor',
'windows-energy-eff': 'Average', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A',
'lighting-description': 'No low energy lighting', 'roof-env-eff': 'Very Poor',
'walls-energy-eff': 'Very Poor', 'photo-supply': '50.0', 'lighting-cost-potential': '72',
'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '',
'lodgement-datetime': '2021-12-21 17:33:09', 'flat-top-storey': '', 'current-energy-rating': 'D',
'secondheat-description': 'Room heaters, mains gas', 'walls-env-eff': 'Very Poor',
'transaction-type': 'ECO assessment', 'uprn': '100030949912', 'current-energy-efficiency': '65',
'energy-consumption-current': '277', 'mainheat-description': 'Boiler and radiators, mains gas',
'lighting-cost-current': '144', 'lodgement-date': '2021-12-21', 'extension-count': '2',
'mainheatc-env-eff': 'Good', 'lmk-key': 'b0b19583c59afbc69db12f4d6c98cd8837e80da3214d577c426eb3e672d424fc',
'wind-turbine-count': '0', 'tenure': 'Owner-occupied', 'floor-level': '',
'potential-energy-efficiency': '88', 'hot-water-energy-eff': 'Good', 'low-energy-lighting': '0',
'walls-description': 'Solid brick, as built, no insulation (assumed)',
'hotwater-description': 'From main system'
}
cleaning_data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet",
)
cleaned = read_from_s3(
s3_file_name="cleaned_epc_data/cleaned.bson",
bucket_name="retrofit-data-dev"
)
cleaned = msgpack.unpackb(cleaned, raw=False)
photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev")
epc = EPCRecord(
epc_records={
'original_epc': starting_epc,
'full_sap_epc': {},
'old_data': []
},
run_mode="newdata",
cleaning_data=cleaning_data
)
home = Property(
id=0,
address="",
postcode="",
epc_record=epc,
already_installed={},
non_invasive_recommendations={},
)
home.in_conservation_area = False
home.is_listed = False
home.is_heritage = False
home.restricted_measures = True
home.get_components(
cleaned=cleaned,
photo_supply_lookup=photo_supply_lookup,
floor_area_decile_thresholds=floor_area_decile_thresholds
)
recommender = SolarPvRecommendations(property_instance=home)
recommender.recommend(phase=0)
coverage_50_percent = [x for x in recommender.recommendation if x["photo_supply"] == 50]
assert len(coverage_50_percent) == 2
property_recommendations = Recommendations.insert_temp_recommendation_id([coverage_50_percent])
home.create_base_difference_epc_record(cleaned_lookup=cleaned)
home.adjust_difference_record_with_recommendations(
property_recommendations, []
)
scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop(
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
"carbon_ending"]
)
model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat())
model_api.MODEL_PREFIXES = ["sap_change_predictions"]
predictions_dict = model_api.predict_all(
df=scoring_data,
bucket="retrofit-data-dev",
prediction_buckets={
"sap_change_predictions": "retrofit-sap-predictions-dev",
}
)
assert predictions_dict["sap_change_predictions"]["predictions"].tolist() == [65.9, 65.9]
assert ending_epc["current-energy-efficiency"] == '65'
def test_model2(self):
data[["uprn", "sap_ending"]]
#
searcher = SearchEpc(
address1="",
postcode="",
auth_token="a2Nvbm5rb3dsZXNzYXJAZ21haWwuY29tOjY5MGJiMWM0NmIyOGI5ZDUxYzAxMzQzYzNiZGNlZGJjZDNmODQwMzA=",
os_api_key="",
full_address="",
uprn=100030952942,
)
searcher.find_property(False)
ending_epc = {
'low-energy-fixed-light-count': '', 'address': '6 Kenmare Crescent',
'uprn-source': 'Energy Assessor', 'floor-height': '2.49', 'heating-cost-potential': '464',
'unheated-corridor-length': '', 'hot-water-cost-potential': '46',
'construction-age-band': 'England and Wales: 1967-1975', 'potential-energy-rating': 'B',
'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Average', 'lighting-energy-eff': 'Very Good',
'environment-impact-potential': '91', 'glazed-type': 'not defined', 'heating-cost-current': '535',
'address3': '', 'mainheatcont-description': 'Programmer, room thermostat and TRVs',
'sheating-energy-eff': 'N/A', 'property-type': 'Bungalow',
'local-authority-label': 'West Lindsey', 'fixed-lighting-outlets-count': '9',
'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '69',
'county': 'Lincolnshire', 'postcode': 'DN21 1PR', 'solar-water-heating-flag': 'N',
'constituency': 'E14000707', 'co2-emissions-potential': '0.7', 'number-heated-rooms': '3',
'floor-description': 'Suspended, no insulation (assumed)', 'energy-consumption-potential': '56',
'local-authority': 'E07000142', 'built-form': 'Semi-Detached', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Much More Than Typical',
'inspection-date': '2022-08-24', 'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '18',
'address1': '6 Kenmare Crescent', 'heat-loss-corridor': '', 'flat-storey-count': '',
'constituency-label': 'Gainsborough', 'roof-energy-eff': 'Very Good', 'total-floor-area': '66.0',
'building-reference-number': '10002845316', 'environment-impact-current': '85',
'co2-emissions-current': '1.2', 'roof-description': 'Pitched, 300 mm loft insulation',
'floor-energy-eff': 'N/A', 'number-habitable-rooms': '3', 'address2': '',
'hot-water-env-eff': 'Good', 'posttown': 'GAINSBOROUGH', 'mainheatc-energy-eff': 'Good',
'main-fuel': 'mains gas (not community)', 'lighting-env-eff': 'Very Good',
'windows-energy-eff': 'Average', 'floor-env-eff': 'N/A', 'sheating-env-eff': 'N/A',
'lighting-description': 'Low energy lighting in all fixed outlets', 'roof-env-eff': 'Very Good',
'walls-energy-eff': 'Average', 'photo-supply': '40.0', 'lighting-cost-potential': '65',
'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100', 'main-heating-controls': '',
'lodgement-datetime': '2022-08-24 15:39:42', 'flat-top-storey': '', 'current-energy-rating': 'B',
'secondheat-description': 'Room heaters, electric', 'walls-env-eff': 'Average',
'transaction-type': 'ECO assessment', 'uprn': '100030952942', 'current-energy-efficiency': '87',
'energy-consumption-current': '100', 'mainheat-description': 'Boiler and radiators, mains gas',
'lighting-cost-current': '65', 'lodgement-date': '2022-08-24', 'extension-count': '0',
'mainheatc-env-eff': 'Good',
'lmk-key': 'e20be883431b1fed15db7fa1f52634fb7655d2b80c2fdad37df779f93ec4dafd',
'wind-turbine-count': '0', 'tenure': 'Owner-occupied', 'floor-level': '',
'potential-energy-efficiency': '91', 'hot-water-energy-eff': 'Good', 'low-energy-lighting': '100',
'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system'
}
starting_epc = {
'low-energy-fixed-light-count': '', 'address': '6 Kenmare Crescent', 'uprn-source': 'Energy Assessor',
'floor-height': '2.49', 'heating-cost-potential': '464', 'unheated-corridor-length': '',
'hot-water-cost-potential': '46', 'construction-age-band': 'England and Wales: 1967-1975',
'potential-energy-rating': 'B', 'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Average',
'lighting-energy-eff': 'Very Good', 'environment-impact-potential': '85', 'glazed-type': 'not defined',
'heating-cost-current': '535', 'address3': '',
'mainheatcont-description': 'Programmer, room thermostat and TRVs', 'sheating-energy-eff': 'N/A',
'property-type': 'Bungalow', 'local-authority-label': 'West Lindsey', 'fixed-lighting-outlets-count': '9',
'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '69',
'county': 'Lincolnshire', 'postcode': 'DN21 1PR', 'solar-water-heating-flag': 'N',
'constituency': 'E14000707', 'co2-emissions-potential': '1.2', 'number-heated-rooms': '3',
'floor-description': 'Suspended, no insulation (assumed)', 'energy-consumption-potential': '102',
'local-authority': 'E07000142', 'built-form': 'Semi-Detached', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Much More Than Typical',
'inspection-date': '2022-05-31', 'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '40',
'address1': '6 Kenmare Crescent', 'heat-loss-corridor': '', 'flat-storey-count': '',
'constituency-label': 'Gainsborough', 'roof-energy-eff': 'Very Good', 'total-floor-area': '66.0',
'building-reference-number': '10002845316', 'environment-impact-current': '68',
'co2-emissions-current': '2.6', 'roof-description': 'Pitched, 300 mm loft insulation',
'floor-energy-eff': 'N/A', 'number-habitable-rooms': '3', 'address2': '', 'hot-water-env-eff': 'Good',
'posttown': 'GAINSBOROUGH', 'mainheatc-energy-eff': 'Good', 'main-fuel': 'mains gas (not community)',
'lighting-env-eff': 'Very Good', 'windows-energy-eff': 'Average', 'floor-env-eff': 'N/A',
'sheating-env-eff': 'N/A', 'lighting-description': 'Low energy lighting in all fixed outlets',
'roof-env-eff': 'Very Good', 'walls-energy-eff': 'Average', 'photo-supply': '0.0',
'lighting-cost-potential': '65', 'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100',
'main-heating-controls': '', 'lodgement-datetime': '2022-06-15 08:38:02', 'flat-top-storey': '',
'current-energy-rating': 'D', 'secondheat-description': 'Room heaters, electric',
'walls-env-eff': 'Average', 'transaction-type': 'ECO assessment', 'uprn': '100030952942',
'current-energy-efficiency': '68', 'energy-consumption-current': '227',
'mainheat-description': 'Boiler and radiators, mains gas', 'lighting-cost-current': '65',
'lodgement-date': '2022-06-15', 'extension-count': '0', 'mainheatc-env-eff': 'Good',
'lmk-key': 'ce181970b7077cb9b4626242bfb010b30a0e48541b5f22427e81f1adbeeec4f2', 'wind-turbine-count': '0',
'tenure': 'Owner-occupied', 'floor-level': '', 'potential-energy-efficiency': '85',
'hot-water-energy-eff': 'Good', 'low-energy-lighting': '100',
'walls-description': 'Cavity wall, filled cavity', 'hotwater-description': 'From main system'
}
cleaning_data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet",
)
cleaned = read_from_s3(
s3_file_name="cleaned_epc_data/cleaned.bson",
bucket_name="retrofit-data-dev"
)
cleaned = msgpack.unpackb(cleaned, raw=False)
photo_supply_lookup, floor_area_decile_thresholds = SolarPhotoSupply.load(bucket="retrofit-data-dev")
epc = EPCRecord(
epc_records={
'original_epc': starting_epc,
'full_sap_epc': {},
'old_data': []
},
run_mode="newdata",
cleaning_data=cleaning_data
)
home = Property(
id=0,
address="",
postcode="",
epc_record=epc,
already_installed={},
non_invasive_recommendations={},
)
home.in_conservation_area = False
home.is_listed = False
home.is_heritage = False
home.restricted_measures = True
home.get_components(
cleaned=cleaned,
photo_supply_lookup=photo_supply_lookup,
floor_area_decile_thresholds=floor_area_decile_thresholds
)
recommender = SolarPvRecommendations(property_instance=home)
recommender.recommend(phase=0)
coverage_40_percent = [x for x in recommender.recommendation if x["photo_supply"] == 40]
assert len(coverage_40_percent) == 2
property_recommendations = Recommendations.insert_temp_recommendation_id([coverage_40_percent])
home.create_base_difference_epc_record(cleaned_lookup=cleaned)
home.adjust_difference_record_with_recommendations(
property_recommendations, []
)
scoring_data = pd.DataFrame(home.recommendations_scoring_data).drop(
columns=["rdsap_change", "heat_demand_change", "carbon_change", "sap_ending", "heat_demand_ending",
"carbon_ending"]
)
model_api = ModelApi(portfolio_id="ashp-test", timestamp=datetime.now().isoformat())
model_api.MODEL_PREFIXES = ["sap_change_predictions"]
predictions_dict = model_api.predict_all(
df=scoring_data,
bucket="retrofit-data-dev",
prediction_buckets={
"sap_change_predictions": "retrofit-sap-predictions-dev",
}
)
assert predictions_dict["sap_change_predictions"]["predictions"].tolist() == [87.1, 87.1]
assert ending_epc["current-energy-efficiency"] == '87'
assert starting_epc["current-energy-efficiency"] == '68'

View file

@ -22,7 +22,7 @@ class TestVentilationRecommendations:
assert len(recommender.recommendation) == 1
assert recommender.recommendation[0]["total"] == 1000
assert recommender.recommendation[0]["total"] == 1071.0
assert recommender.recommendation[0]["type"] == "mechanical_ventilation"
assert len(recommender.recommendation[0]["parts"]) == 1
assert recommender.recommendation[0]["parts"][0]["description"] == 'Mechanical Extract Ventilation'
@ -44,7 +44,7 @@ class TestVentilationRecommendations:
assert len(recommender2.recommendation) == 1
assert recommender2.recommendation[0]["total"] == 1000
assert recommender2.recommendation[0]["total"] == 1071.0
assert recommender2.recommendation[0]["type"] == "mechanical_ventilation"
assert len(recommender2.recommendation[0]["parts"]) == 1
assert recommender2.recommendation[0]["parts"][0]["description"] == 'Mechanical Extract Ventilation'
@ -66,7 +66,7 @@ class TestVentilationRecommendations:
assert len(recommender3.recommendation) == 1
assert recommender3.recommendation[0]["total"] == 1000
assert recommender3.recommendation[0]["total"] == 1071.0
assert recommender3.recommendation[0]["type"] == "mechanical_ventilation"
assert len(recommender3.recommendation[0]["parts"]) == 1
assert recommender3.recommendation[0]["parts"][0]["description"] == 'Mechanical Extract Ventilation'

View file

@ -10,8 +10,10 @@ from recommendations.tests.test_data.materials import materials
from etl.epc.Record import EPCRecord
# import inspect
# file_path = inspect.getfile(lambda: None)
# with open(
# os.path.abspath(os.path.dirname(__file__)) + "/recommendations/tests/test_data/input_properties.pkl", "rb"
# os.path.abspath(os.path.dirname(file_path)) + "/recommendations/tests/test_data/input_properties.pkl", "rb"
# ) as f:
# input_properties = pickle.load(f)
@ -86,17 +88,21 @@ class TestWallRecommendations:
input_properties[1].walls["is_sandstone_or_limestone"] = False
input_properties[1].age_band = "A"
input_properties[1].restricted_measures = False
input_properties[1].already_installed = []
input_properties[1].walls["is_park_home"] = False
input_properties[1].construction_age_band = "England and Wales: 1930-1949"
input_properties[1].non_invasive_recommendations = []
recommender = WallRecommendations(
property_instance=input_properties[1],
materials=materials
)
assert recommender.property.walls["original_description"] == "Solid brick, as built, no insulation (assumed)"
assert not recommender.ewi_valid
assert not recommender.ewi_valid()
assert recommender.property.in_conservation_area == "not_in_conservation_area"
assert recommender.property.data["property-type"] == "Flat"
recommender.recommend()
recommender.recommend(phase=0)
# This should result in some recommendations, all of which should be internal insulation
assert recommender.recommendations
@ -131,7 +137,7 @@ class TestWallRecommendations:
)
assert recommender.property.walls["original_description"] == "Solid brick, as built, insulated (assumed)"
assert not recommender.ewi_valid
assert not recommender.ewi_valid()
assert recommender.property.in_conservation_area == "not_in_conservation_area"
assert recommender.property.data["property-type"] == "Flat"
assert recommender.estimated_u_value is None
@ -204,6 +210,11 @@ class TestWallRecommendationsBase:
property_mock.restricted_measures = False
property_mock.insulation_wall_area = 100
property_mock.data = {"county": "Derbyshire"}
property_mock.walls = {
"is_cob": False,
"is_sandstone_or_limestone": False,
"is_cavity_wall": False
}
return property_mock
@pytest.fixture
@ -216,24 +227,24 @@ class TestWallRecommendationsBase:
def test_ewi_valid_in_conservation_area(self, wall_recommendations_instance):
wall_recommendations_instance.property.in_conservation_area = "in_conversation_area"
wall_recommendations_instance.property.restricted_measures = True
assert wall_recommendations_instance.ewi_valid is False
assert wall_recommendations_instance.ewi_valid() is False
def test_ewi_valid_is_flat(self, wall_recommendations_instance):
wall_recommendations_instance.property.data = {"property-type": "flat"}
assert wall_recommendations_instance.ewi_valid is False
assert wall_recommendations_instance.ewi_valid() is False
def test_ewi_valid_not_in_conservation_area_and_not_flat(self, wall_recommendations_instance):
wall_recommendations_instance.property.in_conservation_area = "not_in_conversation_area"
wall_recommendations_instance.property.restricted_measures = False
wall_recommendations_instance.property.data = {"property-type": "house"}
assert wall_recommendations_instance.ewi_valid is True
assert wall_recommendations_instance.ewi_valid() is True
class TestCavityWallRecommensations:
def test_fill_empty_cavity(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "Derbyshire"}
epc_record.prepared_epc = {"county": "Derbyshire", "walls-energy-eff": "Very Poor"}
input_property = Property(id=1, postcode="F4k3", address="123 fake street", epc_record=epc_record)
input_property.walls = {
'original_description': 'Cavity wall, as built, no insulation (assumed)',
@ -248,6 +259,7 @@ class TestCavityWallRecommensations:
}
input_property.age_band = "C"
input_property.insulation_wall_area = 50
input_property.construction_age_band = "England and Wales: 1930-1949"
recommender = WallRecommendations(
property_instance=input_property,
@ -261,14 +273,11 @@ class TestCavityWallRecommensations:
assert recommender.recommendations
assert recommender.estimated_u_value == 1.5
assert np.isclose(recommender.recommendations[0]["new_u_value"], 0.35)
assert np.isclose(recommender.recommendations[0]["total"], 1668.6600000000003)
assert np.isclose(recommender.recommendations[1]["new_u_value"], 0.35)
assert np.isclose(recommender.recommendations[1]["total"], 2004.6600000000003)
assert np.isclose(recommender.recommendations[0]["total"], 710.5)
def test_fill_partial_filled_cavity(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"county": "County Durham"}
epc_record.prepared_epc = {"county": "County Durham", "walls-energy-eff": "Poor"}
input_property = Property(id=1, postcode="F4k3", address="123 fake street", epc_record=epc_record)
input_property.walls = {
'original_description': 'Cavity wall, as built, partial insulation (assumed)',
@ -283,6 +292,7 @@ class TestCavityWallRecommensations:
}
input_property.age_band = "C"
input_property.insulation_wall_area = 50
input_property.construction_age_band = "England and Wales: 1930-1949"
recommender = WallRecommendations(
property_instance=input_property,
@ -296,14 +306,13 @@ class TestCavityWallRecommensations:
assert recommender.recommendations
assert recommender.estimated_u_value == 1.3
assert np.isclose(recommender.recommendations[0]["new_u_value"], 0.41)
assert np.isclose(recommender.recommendations[0]["total"], 1663.9350000000002)
assert np.isclose(recommender.recommendations[1]["new_u_value"], 0.41)
assert np.isclose(recommender.recommendations[1]["total"], 1999.9350000000002)
assert np.isclose(recommender.recommendations[0]["total"], 710.5)
def test_system_built_wall(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"property-type": "House", "county": "Derbyshire", "built-form": "Detached"}
epc_record.prepared_epc = {
"property-type": "House", "county": "Derbyshire", "built-form": "Detached", "walls-energy-eff": "Very Poor"
}
input_property2 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
input_property2.walls = {
'original_description': 'System built, as built, no insulation (assumed)',
@ -319,6 +328,7 @@ class TestCavityWallRecommensations:
input_property2.age_band = "F"
input_property2.insulation_wall_area = 120
input_property2.restricted_measures = False
input_property2.construction_age_band = "England and Wales: 1976-1982"
assert input_property2.walls["is_system_built"]
@ -332,26 +342,24 @@ class TestCavityWallRecommensations:
recommender2.recommend()
assert recommender2.recommendations
assert len(recommender2.recommendations) == 9
assert len(recommender2.recommendations) == 2
assert recommender2.estimated_u_value == 1
assert np.isclose(recommender2.recommendations[0]["new_u_value"], 0.19)
assert np.isclose(recommender2.recommendations[0]["total"], 16429.960320000002)
assert np.isclose(recommender2.recommendations[0]["new_u_value"], 0.21)
assert np.isclose(recommender2.recommendations[0]["total"], 35802.0)
assert recommender2.recommendations[0]["parts"][0]["type"] == "external_wall_insulation"
assert recommender2.recommendations[0]["parts"][0]["depth"] == 100
assert recommender2.recommendations[0]["parts"][0]["depth"] == 150
assert np.isclose(recommender2.recommendations[8]["new_u_value"], 0.23)
assert np.isclose(recommender2.recommendations[8]["total"], 11292.768)
assert recommender2.recommendations[8]["parts"][0]["type"] == "internal_wall_insulation"
assert recommender2.recommendations[8]["parts"][0]["depth"] == 72.5
assert np.isclose(recommender2.recommendations[6]["new_u_value"], 0.29)
assert np.isclose(recommender2.recommendations[6]["total"], 10988.208)
assert recommender2.recommendations[6]["parts"][0]["type"] == "internal_wall_insulation"
assert recommender2.recommendations[6]["parts"][0]["depth"] == 52.5
assert np.isclose(recommender2.recommendations[1]["new_u_value"], 0.26)
assert np.isclose(recommender2.recommendations[1]["total"], 29376)
assert recommender2.recommendations[1]["parts"][0]["type"] == "internal_wall_insulation"
assert recommender2.recommendations[1]["parts"][0]["depth"] == 95
def test_timber_frame_wall(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"property-type": "House", "county": "Derbyshire", "built-form": "Semi-Detached"}
epc_record.prepared_epc = {
"property-type": "House", "county": "Derbyshire", "built-form": "Semi-Detached",
"walls-energy-eff": "Very Poor"
}
input_property3 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
input_property3.walls = {
'original_description': 'Timber frame, as built, no insulation (assumed)',
@ -367,6 +375,7 @@ class TestCavityWallRecommensations:
input_property3.age_band = "B"
input_property3.insulation_wall_area = 99
input_property3.restricted_measures = False
input_property3.construction_age_band = "England and Wales: 1950-1966"
assert input_property3.walls["is_timber_frame"]
@ -380,21 +389,24 @@ class TestCavityWallRecommensations:
recommender3.recommend()
assert recommender3.recommendations
assert len(recommender3.recommendations) == 6
assert len(recommender3.recommendations) == 2
assert recommender3.estimated_u_value == 1.9
assert np.isclose(recommender3.recommendations[0]["new_u_value"], 0.2)
assert np.isclose(recommender3.recommendations[0]["total"], 13554.717263999999)
assert np.isclose(recommender3.recommendations[0]["new_u_value"], 0.23)
assert np.isclose(recommender3.recommendations[0]["total"], 29536.65)
assert recommender3.recommendations[0]["parts"][0]["type"] == "external_wall_insulation"
assert recommender3.recommendations[0]["parts"][0]["depth"] == 100.0
assert recommender3.recommendations[0]["parts"][0]["depth"] == 150.0
assert np.isclose(recommender3.recommendations[1]["new_u_value"], 0.23)
assert np.isclose(recommender3.recommendations[1]["total"], 35206.19308800001)
assert recommender3.recommendations[1]["parts"][0]["type"] == "external_wall_insulation"
assert recommender3.recommendations[1]["parts"][0]["depth"] == 150.0
assert np.isclose(recommender3.recommendations[1]["new_u_value"], 0.29)
assert np.isclose(recommender3.recommendations[1]["total"], 24235.2)
assert recommender3.recommendations[1]["parts"][0]["type"] == "internal_wall_insulation"
assert recommender3.recommendations[1]["parts"][0]["depth"] == 95.0
def test_granite_or_whinstone_wall(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"property-type": "Bungalow", "county": "Derbyshire", "built-form": "Detached"}
epc_record.prepared_epc = {
"property-type": "Bungalow", "county": "Derbyshire", "built-form": "Detached",
"walls-energy-eff": "Very Poor"
}
input_property4 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
input_property4.walls = {
'original_description': 'Granite or whinstone, as built, no insulation (assumed)',
@ -410,6 +422,7 @@ class TestCavityWallRecommensations:
input_property4.age_band = "A"
input_property4.insulation_wall_area = 223
input_property4.restricted_measures = False
input_property4.construction_age_band = "England and Wales: before 1900"
assert input_property4.walls["is_granite_or_whinstone"]
@ -423,21 +436,24 @@ class TestCavityWallRecommensations:
recommender4.recommend()
assert recommender4.recommendations
assert len(recommender4.recommendations) == 6
assert len(recommender4.recommendations) == 2
assert recommender4.estimated_u_value == 2.3
assert np.isclose(recommender4.recommendations[0]["new_u_value"], 0.21)
assert np.isclose(recommender4.recommendations[0]["total"], 29547.42864)
assert np.isclose(recommender4.recommendations[0]["new_u_value"], 0.23)
assert np.isclose(recommender4.recommendations[0]["total"], 66532.05)
assert recommender4.recommendations[0]["parts"][0]["type"] == "external_wall_insulation"
assert recommender4.recommendations[0]["parts"][0]["depth"] == 100
assert recommender4.recommendations[0]["parts"][0]["depth"] == 150
assert np.isclose(recommender4.recommendations[1]["new_u_value"], 0.23)
assert np.isclose(recommender4.recommendations[1]["total"], 76744.68288000001)
assert recommender4.recommendations[1]["parts"][0]["type"] == "external_wall_insulation"
assert recommender4.recommendations[1]["parts"][0]["depth"] == 150
assert np.isclose(recommender4.recommendations[1]["new_u_value"], 0.3)
assert np.isclose(recommender4.recommendations[1]["total"], 54590.4)
assert recommender4.recommendations[1]["parts"][0]["type"] == "internal_wall_insulation"
assert recommender4.recommendations[1]["parts"][0]["depth"] == 95
def test_cob_wall(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"property-type": "Bungalow", "county": "Derbyshire", "built-form": "Detached"}
epc_record.prepared_epc = {
"property-type": "Bungalow", "county": "Derbyshire", "built-form": "Detached",
"walls-energy-eff": "Very Poor"
}
input_property5 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
input_property5.walls = {
'original_description': 'Cob, as built',
@ -453,6 +469,7 @@ class TestCavityWallRecommensations:
input_property5.age_band = "E"
input_property5.insulation_wall_area = 77
input_property5.restricted_measures = False
input_property5.construction_age_band = "England and Wales: 1967-1975"
assert input_property5.walls["is_cob"]
@ -465,22 +482,15 @@ class TestCavityWallRecommensations:
recommender5.recommend()
assert recommender5.recommendations
assert len(recommender5.recommendations) == 5
assert recommender5.estimated_u_value == 0.8
assert np.isclose(recommender5.recommendations[0]["new_u_value"], 0.29)
assert np.isclose(recommender5.recommendations[0]["total"], 8963.834880000002)
assert recommender5.recommendations[0]["parts"][0]["type"] == "external_wall_insulation"
assert recommender5.recommendations[0]["parts"][0]["depth"] == 50
assert np.isclose(recommender5.recommendations[3]["new_u_value"], 0.26)
assert np.isclose(recommender5.recommendations[3]["total"], 20771.11344)
assert recommender5.recommendations[3]["parts"][0]["type"] == "internal_wall_insulation"
assert recommender5.recommendations[3]["parts"][0]["depth"] == 100
# No insulation recommendations for cob walls
assert not recommender5.recommendations
def test_sandstone_or_limestone_wall(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {"property-type": "House", "county": "Derbyshire", "built-form": "Mid-Terrace"}
epc_record.prepared_epc = {
"property-type": "House", "county": "Derbyshire", "built-form": "Mid-Terrace",
"walls-energy-eff": "Very Poor"
}
input_property6 = Property(id=1, postcode="F4k3 6", address="623 fake street", epc_record=epc_record)
input_property6.walls = {
'original_description': 'Sandstone or limestone, as built, no insulation (assumed)',
@ -496,6 +506,7 @@ class TestCavityWallRecommensations:
input_property6.age_band = "F"
input_property6.insulation_wall_area = 350
input_property6.restricted_measures = False
input_property6.construction_age_band = "England and Wales: 1976-1982"
assert input_property6.walls["is_sandstone_or_limestone"]
@ -508,20 +519,11 @@ class TestCavityWallRecommensations:
recommender6.recommend()
# For sandstone walls, we only recommend internal wall insulation
assert recommender6.recommendations
assert len(recommender6.recommendations) == 9
assert len(recommender6.recommendations) == 1
assert recommender6.estimated_u_value == 1
assert np.isclose(recommender6.recommendations[0]["new_u_value"], 0.19)
assert np.isclose(recommender6.recommendations[0]["total"], 46374.888000000006)
assert recommender6.recommendations[0]["parts"][0]["type"] == "external_wall_insulation"
assert recommender6.recommendations[0]["parts"][0]["depth"] == 100
assert np.isclose(recommender6.recommendations[2]["new_u_value"], 0.21)
assert np.isclose(recommender6.recommendations[2]["total"], 120451.29600000002)
assert recommender6.recommendations[2]["parts"][0]["type"] == "external_wall_insulation"
assert recommender6.recommendations[2]["parts"][0]["depth"] == 150
assert np.isclose(recommender6.recommendations[4]["new_u_value"], 0.28)
assert np.isclose(recommender6.recommendations[4]["total"], 94414.15199999999)
assert recommender6.recommendations[4]["parts"][0]["type"] == "internal_wall_insulation"
assert recommender6.recommendations[4]["parts"][0]["depth"] == 100
assert np.isclose(recommender6.recommendations[0]["new_u_value"], 0.26)
assert np.isclose(recommender6.recommendations[0]["total"], 85680.0)
assert recommender6.recommendations[0]["parts"][0]["type"] == "internal_wall_insulation"
assert recommender6.recommendations[0]["parts"][0]["depth"] == 95

View file

@ -2,6 +2,8 @@ from recommendations.WindowsRecommendations import WindowsRecommendations
from backend.Property import Property
from recommendations.tests.test_data.materials import materials
from etl.epc.Record import EPCRecord
import msgpack
from utils.s3 import read_dataframe_from_s3_parquet, read_from_s3
class TestWindowRecommendations:
@ -15,7 +17,8 @@ class TestWindowRecommendations:
epc_record.prepared_epc = {
"county": "Wychavon",
"multi-glaze-proportion": 0,
"uprn": 0
"uprn": 0,
"windows-energy-eff": "Very Poor"
}
property_1 = Property(
id=1,
@ -36,12 +39,26 @@ class TestWindowRecommendations:
recommender.recommend()
# The home is going from single glazing (v poor energy eff) -> double glazing (average energy eff)
assert recommender.recommendation == [
{'parts': [], 'type': 'windows_glazing', 'description': 'Install double glazing to all windows',
'starting_u_value': None, 'new_u_value': None, 'sap_points': None, 'total': 5721.943248,
'subtotal': 4768.28604, 'vat': 953.6572080000001, 'contingency': 340.59186, 'preliminaries': 340.59186,
'material': 1275.75, 'profit': 681.18372, 'labour_hours': 45.5, 'labour_cost': 994.8624,
'labour_days': 2.84375, 'is_secondary_glazing': False}]
{
'phase': 0, 'parts': [], 'type': 'windows_glazing',
'description': 'Install double glazing to all windows',
'starting_u_value': None, 'new_u_value': None, 'sap_points': None, 'already_installed': False,
'total': 7980.0, 'labour_hours': 0.0, 'labour_days': 0.0, 'is_secondary_glazing': False,
'description_simulation': {
'multi-glaze-proportion': 100, 'windows-energy-eff': 'Average',
'windows-description': 'Fully double glazed',
'glazed-type': 'double glazing installed during or after 2002'
},
'simulation_config': {
'has_glazing_ending': True, 'glazing_type_ending': 'double',
'multi_glaze_proportion_ending': 100, 'windows_energy_eff_ending': 'Average',
'glazed_type_ending': 'double glazing installed during or after 2002'
}
}
]
def test_partial_double_glazed(self):
"""
@ -53,7 +70,8 @@ class TestWindowRecommendations:
epc_record.prepared_epc = {
"county": "Wychavon",
"multi-glaze-proportion": 33,
"uprn": 0
"uprn": 0,
"windows-energy-eff": "Good" # This has been observed in the EPC data
}
property_2 = Property(
id=1,
@ -73,11 +91,24 @@ class TestWindowRecommendations:
recommender2.recommend()
assert recommender2.recommendation == [
{'parts': [], 'type': 'windows_glazing', 'description': 'Install double glazing to the remaining windows',
'starting_u_value': None, 'new_u_value': None, 'sap_points': None, 'total': 4087.10232,
'subtotal': 3405.9186, 'vat': 681.18372, 'contingency': 243.2799, 'preliminaries': 243.2799,
'material': 911.25, 'profit': 486.5598, 'labour_hours': 32.5, 'labour_cost': 710.6160000000001,
'labour_days': 2.03125, 'is_secondary_glazing': False}]
{
'phase': 0, 'parts': [], 'type': 'windows_glazing',
'description': 'Install double glazing to the remaining windows', 'starting_u_value': None,
'new_u_value': None, 'sap_points': None, 'already_installed': False, 'total': 5700.0,
'labour_hours': 0.0,
'labour_days': 0.0, 'is_secondary_glazing': False,
'description_simulation': {
'multi-glaze-proportion': 100, 'windows-energy-eff': 'Good',
'windows-description': 'Fully double glazed',
'glazed-type': 'double glazing installed during or after 2002'
},
'simulation_config': {
'glazing_coverage_ending': 'full', 'multi_glaze_proportion_ending': 100,
'windows_energy_eff_ending': 'Good', 'glazing_type_ending': 'double',
'glazed_type_ending': 'double glazing installed during or after 2002'
}
}
]
def test_fully_double_glazed(self):
"""
@ -140,7 +171,8 @@ class TestWindowRecommendations:
epc_record.prepared_epc = {
"county": "Wychavon",
"multi-glaze-proportion": 50,
"uprn": 0
"uprn": 0,
"windows-energy-eff": "Poor" # This has been observed in the EPC data
}
property_5 = Property(
id=1,
@ -160,19 +192,31 @@ class TestWindowRecommendations:
recommender5.recommend()
assert recommender5.recommendation == [
{'parts': [], 'type': 'windows_glazing',
'description': 'Install secondary glazing to the remaining windows',
'starting_u_value': None, 'new_u_value': None, 'sap_points': None, 'total': 1089.893952,
'subtotal': 908.24496, 'vat': 181.64899200000002, 'contingency': 64.87464, 'preliminaries': 64.87464,
'material': 729.0, 'profit': 129.74928, 'labour_hours': 13.0, 'labour_cost': 568.4928,
'labour_days': 0.8125, 'is_secondary_glazing': True}]
{
'phase': 0, 'parts': [], 'type': 'windows_glazing',
'description': 'Install secondary glazing to the remaining windows', 'starting_u_value': None,
'new_u_value': None, 'sap_points': None, 'already_installed': False, 'total': 4560.0,
'labour_hours': 0.0, 'labour_days': 0.0, 'is_secondary_glazing': True,
'description_simulation': {
'multi-glaze-proportion': 100, 'windows-energy-eff': 'Good',
'windows-description': 'Full secondary glazing',
'glazed-type': 'secondary glazing'
},
'simulation_config': {
'glazing_coverage_ending': 'full', 'multi_glaze_proportion_ending': 100,
'windows_energy_eff_ending': 'Good', 'glazing_type_ending': 'secondary',
'glazed_type_ending': 'secondary glazing'
}
}
]
def test_single_glazed_restricted_measures(self):
epc_record = EPCRecord()
epc_record.prepared_epc = {
"county": "Wychavon",
"multi-glaze-proportion": 0,
"uprn": 0
"uprn": 0,
"windows-energy-eff": "Very Poor"
}
property_6 = Property(
@ -195,14 +239,23 @@ class TestWindowRecommendations:
recommender6.recommend()
assert recommender6.recommendation == [
{'parts': [], 'type': 'windows_glazing',
'description': 'Install secondary glazing to all windows. Secondary '
'glazing recommended due to herigate building status',
'starting_u_value': None, 'new_u_value': None, 'sap_points': None,
'total': 1907.314416, 'subtotal': 1589.42868, 'vat': 317.885736,
'contingency': 113.53062, 'preliminaries': 113.53062,
'material': 1275.75, 'profit': 227.06124, 'labour_hours': 22.75,
'labour_cost': 994.8624, 'labour_days': 1.421875, 'is_secondary_glazing': True}
{
'phase': 0, 'parts': [], 'type': 'windows_glazing',
'description': 'Install secondary glazing to all windows. Secondary glazing recommended due to '
'herigate building status',
'starting_u_value': None, 'new_u_value': None, 'sap_points': None, 'already_installed': False,
'total': 7980.0, 'labour_hours': 0.0, 'labour_days': 0.0, 'is_secondary_glazing': True,
'description_simulation': {
'multi-glaze-proportion': 100, 'windows-energy-eff': 'Good',
'windows-description': 'Full secondary glazing',
'glazed-type': 'secondary glazing'
},
'simulation_config': {
'has_glazing_ending': True, 'glazing_coverage_ending': 'full',
'glazing_type_ending': 'secondary', 'multi_glaze_proportion_ending': 100,
'windows_energy_eff_ending': 'Good', 'glazed_type_ending': 'secondary glazing'
}
}
]
def test_full_triple_glazed(self):
@ -233,7 +286,7 @@ class TestWindowRecommendations:
def test_partial_triple_glazed(self):
"""
We should just recommend double glazing to the remaining windows, since it's a cheaper option
We don't recommend anything here
"""
epc_record = EPCRecord()
epc_record.prepared_epc = {
@ -258,9 +311,362 @@ class TestWindowRecommendations:
recommender8.recommend()
assert recommender8.recommendation == [
{'parts': [], 'type': 'windows_glazing', 'description': 'Install double glazing to the remaining windows',
'starting_u_value': None, 'new_u_value': None, 'sap_points': None, 'total': 1634.840928,
'subtotal': 1362.36744, 'vat': 272.47348800000003, 'contingency': 97.31196, 'preliminaries': 97.31196,
'material': 364.5, 'profit': 194.62392, 'labour_hours': 13.0, 'labour_cost': 284.2464,
'labour_days': 0.8125, 'is_secondary_glazing': False}]
assert not recommender8.recommendation
def test_simulating_outcome_single_glazed(self):
# Could move these to fixtures
cleaning_data = read_dataframe_from_s3_parquet(
bucket_name="retrofit-data-dev", file_key="sap_change_model/cleaning_dataset.parquet",
)
cleaned = read_from_s3(s3_file_name="cleaned_epc_data/cleaned.bson", bucket_name="retrofit-data-dev")
cleaned = msgpack.unpackb(cleaned, raw=False)
epc = {
'lmk-key': 'f4cf43c90ab3140112a9d1c8cfb21ec1bf73f5a2ca3c75118f289d3447dddf15', 'address1': '3 The Green',
'address2': 'Old Dalby', 'address3': None, 'postcode': 'LE14 3LL',
'building-reference-number': 10006291833, 'current-energy-rating': 'E', 'potential-energy-rating': 'B',
'current-energy-efficiency': 47, 'potential-energy-efficiency': 82, 'property-type': 'House',
'built-form': 'Semi-Detached', 'inspection-date': '2024-07-19', 'local-authority': 'E07000133',
'constituency': 'E14000909', 'county': 'Leicestershire', 'lodgement-date': '2024-07-21',
'transaction-type': 'rental', 'environment-impact-current': 41, 'environment-impact-potential': 79,
'energy-consumption-current': 478, 'energy-consumption-potential': 155.0, 'co2-emissions-current': 5.1,
'co2-emiss-curr-per-floor-area': 85, 'co2-emissions-potential': 1.7, 'lighting-cost-current': 91.0,
'lighting-cost-potential': 91.0, 'heating-cost-current': 1677.0, 'heating-cost-potential': 874.0,
'hot-water-cost-current': 161.0, 'hot-water-cost-potential': 109.0, 'total-floor-area': 61.0,
'energy-tariff': 'dual', 'mains-gas-flag': 'Y', 'floor-level': None, 'flat-top-storey': None,
'flat-storey-count': None, 'main-heating-controls': None, 'multi-glaze-proportion': 0.0,
'glazed-type': 'not defined', 'glazed-area': 'Normal', 'extension-count': 3.0,
'number-habitable-rooms': 4.0, 'number-heated-rooms': 4.0, 'low-energy-lighting': 100.0,
'number-open-fireplaces': 0.0, 'hotwater-description': 'From main system',
'hot-water-energy-eff': 'Good', 'hot-water-env-eff': 'Good',
'floor-description': 'Solid, no insulation (assumed)', 'floor-energy-eff': None, 'floor-env-eff': None,
'windows-description': 'Single glazed', 'windows-energy-eff': 'Very Poor',
'windows-env-eff': 'Very Poor', 'walls-description': 'Solid brick, as built, no insulation (assumed)',
'walls-energy-eff': 'Very Poor', 'walls-env-eff': 'Very Poor', 'secondheat-description': 'None',
'sheating-energy-eff': None, 'sheating-env-eff': None,
'roof-description': 'Pitched, no insulation (assumed)', 'roof-energy-eff': 'Very Poor',
'roof-env-eff': 'Very Poor', 'mainheat-description': 'Boiler and radiators, mains gas',
'mainheat-energy-eff': 'Good', 'mainheat-env-eff': 'Good',
'mainheatcont-description': 'Programmer and room thermostat', 'mainheatc-energy-eff': 'Average',
'mainheatc-env-eff': 'Average', 'lighting-description': 'Low energy lighting in all fixed outlets',
'lighting-energy-eff': 'Very Good', 'lighting-env-eff': 'Very Good',
'main-fuel': 'mains gas (not community)', 'wind-turbine-count': 0.0, 'heat-loss-corridor': None,
'unheated-corridor-length': None, 'floor-height': 2.37, 'photo-supply': 0.0,
'solar-water-heating-flag': 'N', 'mechanical-ventilation': 'natural',
'address': '3 The Green, Old Dalby', 'local-authority-label': 'Melton',
'constituency-label': 'Rutland and Melton', 'posttown': 'MELTON MOWBRAY',
'construction-age-band': 'England and Wales: before 1900', 'lodgement-datetime': '2024-07-21 19:29:04',
'tenure': 'Rented (private)', 'fixed-lighting-outlets-count': 7.0, 'low-energy-fixed-light-count': None,
'uprn': 200001041444.0, 'uprn-source': 'Energy Assessor'
}
epc_records = {
"original_epc": epc,
"full_sap_epc": {},
"old_data": []
}
epc_record = EPCRecord(
epc_records=epc_records,
run_mode="newdata",
cleaning_data=cleaning_data
)
property_9 = Property(
id=1,
postcode='1',
address='1',
epc_record=epc_record
)
property_9.windows = {
'original_description': 'Single glazed', 'has_glazing': False, 'glazing_coverage': None,
'glazing_type': 'single',
'no_data': False
}
property_9.number_of_windows = 7
property_9.restricted_measures = False
property_9.is_heritage = False
recommender9 = WindowsRecommendations(property_instance=property_9, materials=materials)
assert not recommender9.recommendation
recommender9.recommend()
assert recommender9.recommendation == [
{
'phase': 0, 'parts': [], 'type': 'windows_glazing',
'description': 'Install double glazing to all windows', 'starting_u_value': None, 'new_u_value': None,
'sap_points': None, 'already_installed': False, 'total': 7980.0, 'labour_hours': 0.0,
'labour_days': 0.0, 'is_secondary_glazing': False,
'description_simulation': {
'multi-glaze-proportion': 100, 'windows-energy-eff': 'Average',
'windows-description': 'Fully double glazed',
'glazed-type': 'double glazing installed during or after 2002'
},
'simulation_config': {
'has_glazing_ending': True, 'glazing_coverage_ending': 'full',
'glazing_type_ending': 'double', 'multi_glaze_proportion_ending': 100,
'windows_energy_eff_ending': 'Average',
'glazed_type_ending': 'double glazing installed during or after 2002'
}
}
]
# We now simulate the outcome
windows_rec = recommender9.recommendation.copy()
windows_rec[0]["recommendation_id"] = 1
property_recommendations = [windows_rec]
property_9.create_base_difference_epc_record(cleaned_lookup=cleaned)
starting_record = property_9.base_difference_record.df.to_dict("records")[0]
expected_base_difference_record = {
'uprn': 200001041444, 'rdsap_change': 0, 'heat_demand_change': 0, 'carbon_change': 0.0,
'potential_energy_efficiency': 82.0, 'environment_impact_potential': 79.0,
'energy_consumption_potential': 155.0, 'co2_emissions_potential': 1.7, 'property_type': 'House',
'built_form': 'Semi-Detached', 'constituency': 'E14000909', 'number_habitable_rooms': 4.0,
'number_heated_rooms': 4.0, 'construction_age_band': 'England and Wales: before 1900',
'fixed_lighting_outlets_count': 7.0, 'walls_thermal_transmittance': 1.7,
'walls_thermal_transmittance_unit': 'Unknown', 'is_cavity_wall': False, 'is_filled_cavity': False,
'is_solid_brick': True, 'is_system_built': False, 'is_timber_frame': False,
'is_granite_or_whinstone': False, 'is_as_built': True, 'is_cob': False, 'walls_is_assumed': True,
'is_sandstone_or_limestone': False, 'is_park_home': False, 'walls_insulation_thickness': 'none',
'external_insulation': False, 'internal_insulation': False, 'floor_thermal_transmittance': 0.96,
'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': True,
'another_property_below': False, 'floor_insulation_thickness': 'none', 'roof_thermal_transmittance': 2.3,
'is_pitched': True, 'is_roof_room': False, 'is_loft': False, 'is_flat': False, 'is_thatched': False,
'is_at_rafters': False, 'has_dwelling_above': False, 'roof_insulation_thickness': 'none',
'heater_type': 'Unknown', 'system_type': 'from main system', 'thermostat_characteristics': 'Unknown',
'heating_scope': 'Unknown', 'energy_recovery': 'Unknown', 'hotwater_tariff_type': 'Unknown',
'extra_features': 'Unknown', 'chp_systems': 'Unknown', 'distribution_system': 'Unknown',
'no_system_present': 'Unknown', 'appliance': 'Unknown', 'has_radiators': True, 'has_fan_coil_units': False,
'has_pipes_in_screed_above_insulation': False, 'has_pipes_in_insulated_timber_floor': False,
'has_pipes_in_concrete_slab': False, 'has_boiler': True, 'has_air_source_heat_pump': False,
'has_room_heaters': False, 'has_electric_storage_heaters': False, 'has_warm_air': False,
'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
'has_community_scheme': False, 'has_ground_source_heat_pump': False, 'has_no_system_present': False,
'has_portable_electric_heaters': False, 'has_water_source_heat_pump': False,
'has_electric_heat_pump': False, 'has_micro-cogeneration': False, 'has_solar_assisted_heat_pump': False,
'has_exhaust_source_heat_pump': False, 'has_community_heat_pump': False, 'has_electric': False,
'has_mains_gas': True, 'has_wood_logs': False, 'has_coal': False, 'has_oil': False,
'has_wood_pellets': False, 'has_anthracite': False, 'has_dual_fuel_mineral_and_wood': False,
'has_smokeless_fuel': False, 'has_lpg': False, 'has_b30k': False, 'has_electricaire': False,
'has_assumed_for_most_rooms': False, 'has_underfloor_heating': False,
'thermostatic_control': 'room thermostat', 'charging_system': 'Unknown', 'switch_system': 'programmer',
'no_control': 'Unknown', 'dhw_control': 'Unknown', 'community_heating': 'Unknown',
'multiple_room_thermostats': False, 'auxiliary_systems': 'Unknown', 'trvs': 'Unknown',
'rate_control': 'Unknown', 'glazing_type': 'single', 'fuel_type': 'mains gas',
'main-fuel_tariff_type': 'Unknown', 'is_community': False,
'no_individual_heating_or_community_network': False, 'complex_fuel_type': 'Unknown',
'walls_thermal_transmittance_ending': 1.7, 'walls_thermal_transmittance_unit_ending': 'Unknown',
'is_filled_cavity_ending': False, 'is_as_built_ending': True, 'walls_is_assumed_ending': True,
'is_park_home_ending': False, 'walls_insulation_thickness_ending': 'none',
'external_insulation_ending': False, 'internal_insulation_ending': False,
'floor_thermal_transmittance_ending': 0.96, 'floor_insulation_thickness_ending': 'none',
'roof_thermal_transmittance_ending': 2.3, 'is_at_rafters_ending': False,
'roof_insulation_thickness_ending': 'none', 'heater_type_ending': 'Unknown',
'system_type_ending': 'from main system', 'thermostat_characteristics_ending': 'Unknown',
'heating_scope_ending': 'Unknown', 'energy_recovery_ending': 'Unknown',
'hotwater_tariff_type_ending': 'Unknown', 'extra_features_ending': 'Unknown',
'chp_systems_ending': 'Unknown', 'distribution_system_ending': 'Unknown',
'no_system_present_ending': 'Unknown', 'appliance_ending': 'Unknown', 'has_radiators_ending': True,
'has_fan_coil_units_ending': False, 'has_pipes_in_screed_above_insulation_ending': False,
'has_pipes_in_insulated_timber_floor_ending': False, 'has_pipes_in_concrete_slab_ending': False,
'has_boiler_ending': True, 'has_air_source_heat_pump_ending': False, 'has_room_heaters_ending': False,
'has_electric_storage_heaters_ending': False, 'has_warm_air_ending': False,
'has_electric_underfloor_heating_ending': False, 'has_electric_ceiling_heating_ending': False,
'has_community_scheme_ending': False, 'has_ground_source_heat_pump_ending': False,
'has_no_system_present_ending': False, 'has_portable_electric_heaters_ending': False,
'has_water_source_heat_pump_ending': False, 'has_electric_heat_pump_ending': False,
'has_micro-cogeneration_ending': False, 'has_solar_assisted_heat_pump_ending': False,
'has_exhaust_source_heat_pump_ending': False, 'has_community_heat_pump_ending': False,
'has_electric_ending': False, 'has_mains_gas_ending': True, 'has_wood_logs_ending': False,
'has_coal_ending': False, 'has_oil_ending': False, 'has_wood_pellets_ending': False,
'has_anthracite_ending': False, 'has_dual_fuel_mineral_and_wood_ending': False,
'has_smokeless_fuel_ending': False, 'has_lpg_ending': False, 'has_b30k_ending': False,
'has_electricaire_ending': False, 'has_assumed_for_most_rooms_ending': False,
'has_underfloor_heating_ending': False, 'thermostatic_control_ending': 'room thermostat',
'charging_system_ending': 'Unknown', 'switch_system_ending': 'programmer', 'no_control_ending': 'Unknown',
'dhw_control_ending': 'Unknown', 'community_heating_ending': 'Unknown',
'multiple_room_thermostats_ending': False, 'auxiliary_systems_ending': 'Unknown', 'trvs_ending': 'Unknown',
'rate_control_ending': 'Unknown', 'glazing_type_ending': 'single', 'fuel_type_ending': 'mains gas',
'main-fuel_tariff_type_ending': 'Unknown', 'is_community_ending': False,
'no_individual_heating_or_community_network_ending': False, 'complex_fuel_type_ending': 'Unknown',
'sap_starting': 47, 'sap_ending': 47, 'heat_demand_starting': 478, 'heat_demand_ending': 478,
'carbon_starting': 5.1, 'carbon_ending': 5.1, 'lighting_cost_starting': 91.0, 'lighting_cost_ending': 91.0,
'heating_cost_starting': 1677.0, 'heating_cost_ending': 1677.0, 'hot_water_cost_starting': 161.0,
'hot_water_cost_ending': 161.0, 'mechanical_ventilation_starting': 'natural',
'mechanical_ventilation_ending': 'natural', 'secondheat_description_starting': 'None',
'secondheat_description_ending': 'None', 'glazed_type_starting': 'not defined',
'glazed_type_ending': 'not defined', 'multi_glaze_proportion_starting': 0.0,
'multi_glaze_proportion_ending': 0.0, 'low_energy_lighting_starting': 100.0,
'low_energy_lighting_ending': 100.0, 'number_open_fireplaces_starting': 0.0,
'number_open_fireplaces_ending': 0.0, 'solar_water_heating_flag_starting': 'N',
'solar_water_heating_flag_ending': 'N', 'photo_supply_starting': 0.0, 'photo_supply_ending': 0.0,
'transaction_type_starting': 'rental', 'transaction_type_ending': 'rental',
'energy_tariff_starting': 'dual', 'energy_tariff_ending': 'dual', 'extension_count_starting': 3.0,
'extension_count_ending': 3.0, 'total_floor_area_starting': 61.0, 'total_floor_area_ending': 61.0,
'floor_height_starting': 2.37, 'floor_height_ending': 2.37, 'hot_water_energy_eff_starting': 'Good',
'hot_water_energy_eff_ending': 'Good', 'floor_energy_eff_starting': 'NO_RATING',
'floor_energy_eff_ending': 'NO_RATING', 'windows_energy_eff_starting': 'Very Poor',
'windows_energy_eff_ending': 'Very Poor', 'walls_energy_eff_starting': 'Very Poor',
'walls_energy_eff_ending': 'Very Poor', 'sheating_energy_eff_starting': 'NO_RATING',
'sheating_energy_eff_ending': 'NO_RATING', 'roof_energy_eff_starting': 'Very Poor',
'roof_energy_eff_ending': 'Very Poor', 'mainheat_energy_eff_starting': 'Good',
'mainheat_energy_eff_ending': 'Good', 'mainheatc_energy_eff_starting': 'Average',
'mainheatc_energy_eff_ending': 'Average', 'lighting_energy_eff_starting': 'Very Good',
'lighting_energy_eff_ending': 'Very Good', 'number_habitable_rooms_starting': 4.0,
'number_habitable_rooms_ending': 4.0, 'number_heated_rooms_starting': 4.0,
'number_heated_rooms_ending': 4.0, 'days_to_starting': 3642, 'days_to_ending': 3642,
'estimated_perimeter_starting': 23.430749027719962, 'estimated_perimeter_ending': 23.430749027719962
}
assert starting_record == expected_base_difference_record
# Simulate outcome
property_9.adjust_difference_record_with_recommendations(
property_recommendations, windows_rec
)
simulated_data = property_9.recommendations_scoring_data.copy()
assert len(simulated_data) == 1
expected_simulated_outcome = {
'uprn': 200001041444, 'rdsap_change': 0, 'heat_demand_change': 0, 'carbon_change': 0.0,
'potential_energy_efficiency': 82.0, 'environment_impact_potential': 79.0,
'energy_consumption_potential': 155.0, 'co2_emissions_potential': 1.7, 'property_type': 'House',
'built_form': 'Semi-Detached', 'constituency': 'E14000909', 'number_habitable_rooms': 4.0,
'number_heated_rooms': 4.0, 'construction_age_band': 'England and Wales: before 1900',
'fixed_lighting_outlets_count': 7.0, 'walls_thermal_transmittance': 1.7,
'walls_thermal_transmittance_unit': 'Unknown', 'is_cavity_wall': False, 'is_filled_cavity': False,
'is_solid_brick': True, 'is_system_built': False, 'is_timber_frame': False,
'is_granite_or_whinstone': False, 'is_as_built': True, 'is_cob': False, 'walls_is_assumed': True,
'is_sandstone_or_limestone': False, 'is_park_home': False, 'walls_insulation_thickness': 'none',
'external_insulation': False, 'internal_insulation': False, 'floor_thermal_transmittance': 0.96,
'is_to_unheated_space': False, 'is_to_external_air': False, 'is_suspended': False, 'is_solid': True,
'another_property_below': False, 'floor_insulation_thickness': 'none', 'roof_thermal_transmittance': 2.3,
'is_pitched': True, 'is_roof_room': False, 'is_loft': False, 'is_flat': False, 'is_thatched': False,
'is_at_rafters': False, 'has_dwelling_above': False, 'roof_insulation_thickness': 'none',
'heater_type': 'Unknown', 'system_type': 'from main system', 'thermostat_characteristics': 'Unknown',
'heating_scope': 'Unknown', 'energy_recovery': 'Unknown', 'hotwater_tariff_type': 'Unknown',
'extra_features': 'Unknown', 'chp_systems': 'Unknown', 'distribution_system': 'Unknown',
'no_system_present': 'Unknown', 'appliance': 'Unknown', 'has_radiators': True, 'has_fan_coil_units': False,
'has_pipes_in_screed_above_insulation': False, 'has_pipes_in_insulated_timber_floor': False,
'has_pipes_in_concrete_slab': False, 'has_boiler': True, 'has_air_source_heat_pump': False,
'has_room_heaters': False, 'has_electric_storage_heaters': False, 'has_warm_air': False,
'has_electric_underfloor_heating': False, 'has_electric_ceiling_heating': False,
'has_community_scheme': False, 'has_ground_source_heat_pump': False, 'has_no_system_present': False,
'has_portable_electric_heaters': False, 'has_water_source_heat_pump': False,
'has_electric_heat_pump': False, 'has_micro-cogeneration': False, 'has_solar_assisted_heat_pump': False,
'has_exhaust_source_heat_pump': False, 'has_community_heat_pump': False, 'has_electric': False,
'has_mains_gas': True, 'has_wood_logs': False, 'has_coal': False, 'has_oil': False,
'has_wood_pellets': False, 'has_anthracite': False, 'has_dual_fuel_mineral_and_wood': False,
'has_smokeless_fuel': False, 'has_lpg': False, 'has_b30k': False, 'has_electricaire': False,
'has_assumed_for_most_rooms': False, 'has_underfloor_heating': False,
'thermostatic_control': 'room thermostat', 'charging_system': 'Unknown', 'switch_system': 'programmer',
'no_control': 'Unknown', 'dhw_control': 'Unknown', 'community_heating': 'Unknown',
'multiple_room_thermostats': False, 'auxiliary_systems': 'Unknown', 'trvs': 'Unknown',
'rate_control': 'Unknown', 'glazing_type': 'single', 'fuel_type': 'mains gas',
'main-fuel_tariff_type': 'Unknown', 'is_community': False,
'no_individual_heating_or_community_network': False, 'complex_fuel_type': 'Unknown',
'walls_thermal_transmittance_ending': 1.7, 'walls_thermal_transmittance_unit_ending': 'Unknown',
'is_filled_cavity_ending': False, 'is_as_built_ending': True, 'walls_is_assumed_ending': True,
'is_park_home_ending': False, 'walls_insulation_thickness_ending': 'none',
'external_insulation_ending': False, 'internal_insulation_ending': False,
'floor_thermal_transmittance_ending': 0.96, 'floor_insulation_thickness_ending': 'none',
'roof_thermal_transmittance_ending': 2.3, 'is_at_rafters_ending': False,
'roof_insulation_thickness_ending': 'none', 'heater_type_ending': 'Unknown',
'system_type_ending': 'from main system', 'thermostat_characteristics_ending': 'Unknown',
'heating_scope_ending': 'Unknown', 'energy_recovery_ending': 'Unknown',
'hotwater_tariff_type_ending': 'Unknown', 'extra_features_ending': 'Unknown',
'chp_systems_ending': 'Unknown', 'distribution_system_ending': 'Unknown',
'no_system_present_ending': 'Unknown', 'appliance_ending': 'Unknown', 'has_radiators_ending': True,
'has_fan_coil_units_ending': False, 'has_pipes_in_screed_above_insulation_ending': False,
'has_pipes_in_insulated_timber_floor_ending': False, 'has_pipes_in_concrete_slab_ending': False,
'has_boiler_ending': True, 'has_air_source_heat_pump_ending': False, 'has_room_heaters_ending': False,
'has_electric_storage_heaters_ending': False, 'has_warm_air_ending': False,
'has_electric_underfloor_heating_ending': False, 'has_electric_ceiling_heating_ending': False,
'has_community_scheme_ending': False, 'has_ground_source_heat_pump_ending': False,
'has_no_system_present_ending': False, 'has_portable_electric_heaters_ending': False,
'has_water_source_heat_pump_ending': False, 'has_electric_heat_pump_ending': False,
'has_micro-cogeneration_ending': False, 'has_solar_assisted_heat_pump_ending': False,
'has_exhaust_source_heat_pump_ending': False, 'has_community_heat_pump_ending': False,
'has_electric_ending': False, 'has_mains_gas_ending': True, 'has_wood_logs_ending': False,
'has_coal_ending': False, 'has_oil_ending': False, 'has_wood_pellets_ending': False,
'has_anthracite_ending': False, 'has_dual_fuel_mineral_and_wood_ending': False,
'has_smokeless_fuel_ending': False, 'has_lpg_ending': False, 'has_b30k_ending': False,
'has_electricaire_ending': False, 'has_assumed_for_most_rooms_ending': False,
'has_underfloor_heating_ending': False, 'thermostatic_control_ending': 'room thermostat',
'charging_system_ending': 'Unknown', 'switch_system_ending': 'programmer', 'no_control_ending': 'Unknown',
'dhw_control_ending': 'Unknown', 'community_heating_ending': 'Unknown',
'multiple_room_thermostats_ending': False, 'auxiliary_systems_ending': 'Unknown', 'trvs_ending': 'Unknown',
'rate_control_ending': 'Unknown', 'glazing_type_ending': 'double', 'fuel_type_ending': 'mains gas',
'main-fuel_tariff_type_ending': 'Unknown', 'is_community_ending': False,
'no_individual_heating_or_community_network_ending': False, 'complex_fuel_type_ending': 'Unknown',
'sap_starting': 47, 'sap_ending': 47, 'heat_demand_starting': 478, 'heat_demand_ending': 478,
'carbon_starting': 5.1, 'carbon_ending': 5.1, 'lighting_cost_starting': 91.0, 'lighting_cost_ending': 91.0,
'heating_cost_starting': 1677.0, 'heating_cost_ending': 1677.0, 'hot_water_cost_starting': 161.0,
'hot_water_cost_ending': 161.0, 'mechanical_ventilation_starting': 'natural',
'mechanical_ventilation_ending': 'natural', 'secondheat_description_starting': 'None',
'secondheat_description_ending': 'None', 'glazed_type_starting': 'not defined',
'glazed_type_ending': 'double glazing installed during or after 2002',
'multi_glaze_proportion_starting': 0.0, 'multi_glaze_proportion_ending': 100,
'low_energy_lighting_starting': 100.0, 'low_energy_lighting_ending': 100.0,
'number_open_fireplaces_starting': 0.0, 'number_open_fireplaces_ending': 0.0,
'solar_water_heating_flag_starting': 'N', 'solar_water_heating_flag_ending': 'N',
'photo_supply_starting': 0.0, 'photo_supply_ending': 0.0, 'transaction_type_starting': 'rental',
'transaction_type_ending': 'rental', 'energy_tariff_starting': 'dual', 'energy_tariff_ending': 'dual',
'extension_count_starting': 3.0, 'extension_count_ending': 3.0, 'total_floor_area_starting': 61.0,
'total_floor_area_ending': 61.0, 'floor_height_starting': 2.37, 'floor_height_ending': 2.37,
'hot_water_energy_eff_starting': 'Good', 'hot_water_energy_eff_ending': 'Good',
'floor_energy_eff_starting': 'NO_RATING', 'floor_energy_eff_ending': 'NO_RATING',
'windows_energy_eff_starting': 'Very Poor', 'windows_energy_eff_ending': 'Average',
'walls_energy_eff_starting': 'Very Poor', 'walls_energy_eff_ending': 'Very Poor',
'sheating_energy_eff_starting': 'NO_RATING', 'sheating_energy_eff_ending': 'NO_RATING',
'roof_energy_eff_starting': 'Very Poor', 'roof_energy_eff_ending': 'Very Poor',
'mainheat_energy_eff_starting': 'Good', 'mainheat_energy_eff_ending': 'Good',
'mainheatc_energy_eff_starting': 'Average', 'mainheatc_energy_eff_ending': 'Average',
'lighting_energy_eff_starting': 'Very Good', 'lighting_energy_eff_ending': 'Very Good',
'number_habitable_rooms_starting': 4.0, 'number_habitable_rooms_ending': 4.0,
'number_heated_rooms_starting': 4.0, 'number_heated_rooms_ending': 4.0, 'days_to_starting': 3642,
'days_to_ending': 3713, 'estimated_perimeter_starting': 23.430749027719962,
'estimated_perimeter_ending': 23.430749027719962, 'has_glazing_ending': True,
'glazing_coverage_ending': 'full', 'id': '1+1'
}
assert simulated_data[0] == expected_simulated_outcome
# has_glazing_ending and glazing_coverage_ending are not in the starting record - test for this in case it
# changes
assert "has_glazing_ending" not in starting_record
assert "glazing_coverage_ending" not in starting_record
# Check which keys are different
different = []
for k in simulated_data[0].keys():
if k in ["id", 'has_glazing_ending', 'glazing_coverage_ending']:
continue
if simulated_data[0][k] != starting_record[k]:
different.append(
{
"variable": k,
"starting": starting_record[k],
"simulated": simulated_data[0][k],
}
)
expected_different = [
{'variable': 'glazing_type_ending', 'starting': 'single', 'simulated': 'double'},
{'variable': 'glazed_type_ending', 'starting': 'not defined',
'simulated': 'double glazing installed during or after 2002'},
{'variable': 'multi_glaze_proportion_ending', 'starting': 0.0, 'simulated': 100},
{'variable': 'windows_energy_eff_ending', 'starting': 'Very Poor', 'simulated': 'Average'},
{'variable': 'days_to_ending', 'starting': 3642, 'simulated': 3713}
]
assert different == expected_different