mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Merge pull request #343 from Hestia-Homes/fix-unit-tests
Updated unit tests
This commit is contained in:
commit
14c62f061d
22 changed files with 1446 additions and 3094 deletions
|
|
@ -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"]:
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue