Merge pull request #248 from Hestia-Homes/ventilation-recommendations

Ventilation recommendations
This commit is contained in:
KhalimCK 2023-10-19 08:44:05 +11:00 committed by GitHub
commit 253c118d2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 397 additions and 27 deletions

2
.idea/Model.iml generated
View file

@ -7,7 +7,7 @@
<sourceFolder url="file://$MODULE_DIR$/open_uprn" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/recommendations" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (model_data)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.10 (backend)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyNamespacePackagesService">

2
.idea/misc.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (model_data)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (backend)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>

View file

@ -13,6 +13,7 @@ class MaterialType(enum.Enum):
external_wall_insulation = "external_wall_insulation"
internal_wall_insulation = "internal_wall_insulation"
cavity_wall_insulation = "cavity_wall_insulation"
mechanical_ventilation = "mechanical_ventilation"
class DepthUnit(enum.Enum):
@ -21,6 +22,7 @@ class DepthUnit(enum.Enum):
class CostUnit(enum.Enum):
gbp_sq_meter = "gbp_sq_meter"
gbp_per_unit = "gbp_per_unit"
class RValueUnit(enum.Enum):

View file

@ -30,6 +30,7 @@ from backend.Property import Property
from etl.epc.DataProcessor import DataProcessor
from etl.epc.settings import COLUMNS_TO_MERGE_ON
from recommendations.FloorRecommendations import FloorRecommendations
from recommendations.VentilationRecommendations import VentilationRecommendations
from recommendations.optimiser.CostOptimiser import CostOptimiser
from recommendations.optimiser.GainOptimiser import GainOptimiser
from recommendations.optimiser.optimiser_functions import prepare_input_measures
@ -125,9 +126,6 @@ async def trigger_plan(body: PlanTriggerRequest):
#
# with open("cleaned.pickle", "rb") as f:
# cleaned = pickle.load(f)
#
# with open("materials_by_type.pickle", "wb") as f:
# materials_by_type = pickle.load(f)
recommendations = {}
recommendations_scoring_data = []
@ -160,6 +158,16 @@ async def trigger_plan(body: PlanTriggerRequest):
if wall_recomender.recommendations:
property_recommendations.append(wall_recomender.recommendations)
# Ventilation recommendations
ventilation_recomender = VentilationRecommendations(
property_instance=p,
materials=materials_by_type["ventilation"]
)
ventilation_recomender.recommend()
if ventilation_recomender.recommendation:
property_recommendations.append(ventilation_recomender.recommendation)
# We insert temporary ids into the recommendations which is important for the optimiser later
property_recommendations = insert_temp_recommendation_id(property_recommendations)

View file

@ -15,7 +15,8 @@ def filter_materials(materials):
mapping = {
"walls": ["internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation"],
"floor": ["suspended_floor_insulation", "solid_floor_insulation"]
"floor": ["suspended_floor_insulation", "solid_floor_insulation"],
"ventilation": ["mechanical_ventilation"],
}
materials = [row2dict(material) for material in materials]
@ -164,9 +165,13 @@ def create_recommendation_scoring_data(
if scoring_dict["floor_insulation_thickness_ENDING"] is None:
scoring_dict["floor_insulation_thickness_ENDING"] = "none"
if recommendation["type"] not in ["wall_insulation", "floor_insulation"]:
if recommendation["type"] == "mechanical_ventilation":
scoring_dict["MECHANICAL_VENTILATION_ENDING"] = 'mechanical, extract only'
if recommendation["type"] not in ["wall_insulation", "floor_insulation", "mechanical_ventilation"]:
raise NotImplementedError("Implement me")
# Fill missing roof u-values - this fill is not based on recommended upgrades
if scoring_dict["roof_thermal_transmittance_ENDING"] is None:
scoring_dict["roof_thermal_transmittance_ENDING"] = get_roof_u_value(
insulation_thickness=property.roof["insulation_thickness"],

View file

@ -10,18 +10,19 @@ from utils.s3 import read_dataframe_from_s3_parquet
from tqdm import tqdm
# Handy code for selecting testin data
# import pickle
#
# with open("sap_change_dataset.pickle", "rb") as f:
# sap_change_dataset = pickle.load(f)
#
# search_from = sap_change_dataset[
# (sap_change_dataset["walls_thermal_transmittance_ENDING"] != sap_change_dataset["walls_thermal_transmittance"]) &
# (sap_change_dataset["is_solid_brick"])
# ]
# (sap_change_dataset["walls_thermal_transmittance_ENDING"] == sap_change_dataset["walls_thermal_transmittance"])
# ]
# search_from = search_from[
# (search_from["roof_thermal_transmittance_ENDING"] == search_from["roof_thermal_transmittance"]) &
# (search_from["floor_thermal_transmittance_ENDING"] == search_from["floor_thermal_transmittance"]) &
# (search_from["MECHANICAL_VENTILATION_ENDING"] == search_from["MECHANICAL_VENTILATION_STARTING"]) &
# (search_from["MECHANICAL_VENTILATION_ENDING"] != search_from["MECHANICAL_VENTILATION_STARTING"]) &
# (search_from["SECONDHEAT_DESCRIPTION_ENDING"] == search_from["SECONDHEAT_DESCRIPTION_STARTING"]) &
# (search_from["GLAZED_TYPE_ENDING"] == search_from["GLAZED_TYPE_STARTING"])
# ]
@ -51,16 +52,8 @@ from tqdm import tqdm
# starting_cols.append(starting_col)
#
# # We want them to be different
# if c == "walls_thermal_transmittance_ENDING":
# if row[c] == row[starting_col]:
# same = False
# break
# else:
# continue
#
# # We want them to be different
# if c == "walls_insulation_thickness_ENDING":
# if row[c] == row[starting_col]:
# if c == "MECHANICAL_VENTILATION_ENDING":
# if (row[c] == row[starting_col]) | (row[starting_col] != "natural"):
# same = False
# break
# else:
@ -78,20 +71,20 @@ from tqdm import tqdm
#
# import pandas as pd
#
# start = row[starting_cols]
# start = row[["SAP_STARTING"] + starting_cols]
# start.index = [c.replace("_STARTING", "") for c in start.index]
# end = row[ending_cols]
# end = row[["SAP_ENDING"] + ending_cols]
# end.index = [c.replace("_ENDING", "") for c in end.index]
# start["type"] = "starting"
# end["type"] = "ending"
#
# compare = pd.concat([start, end], axis=1)
#
# ending_lmk = "d0fc64d6b80db04c32998c9b846dd04c8f0b486231a11e4c062020b35af1312d"
# starting_lmk = "b0d82f468273bec55ec5676a809b8e36b55db940ffa92f482a482f6aaa38eb1d"
# ending_lmk = "96b34dbe6cebdd7e648151e070047c8cd605c539851fc5a37e325903440081ab"
# starting_lmk = "dc1a4da246562656132b8e36e0534cd90b09fa40fc584e25e644e2d9ab86a247"
#
# client = EpcClient(auth_token=EPC_AUTH_TOKEN)
# result = client.domestic.search(params={"address": "FLAT 12, WAREHOUSE W, 3 WESTERN GATEWAY", "postcode": "E16 1BD"})
# result = client.domestic.search(params={"address": "45 Shepperson Road", "postcode": "S6 4FG"})
# starting_epc = [x for x in result["rows"] if x["lmk-key"] == starting_lmk][0]
# ending_epc = [x for x in result["rows"] if x["lmk-key"] == ending_lmk][0]
@ -106,6 +99,8 @@ from tqdm import tqdm
# ) as f:
# cleaning_data = pickle.load(f)
# TODO: Need to do floors, both suspended and solid
class TestSapModelPrep:
@ -530,3 +525,184 @@ class TestSapModelPrep:
continue
assert test_record2[c].values[0] == row2[c]
def test_ventilation(self, cleaned, cleaning_data):
starting_epc3 = {
'low-energy-fixed-light-count': '', 'address': '45 Shepperson Road', 'uprn-source': 'Energy Assessor',
'floor-height': '1.87', 'heating-cost-potential': '645', 'unheated-corridor-length': '',
'hot-water-cost-potential': '69', 'construction-age-band': 'England and Wales: 1900-1929',
'potential-energy-rating': 'C', 'mainheat-energy-eff': 'Good', 'windows-env-eff': 'Average',
'lighting-energy-eff': 'Average', 'environment-impact-potential': '75',
'glazed-type': 'double glazing, unknown install date', 'heating-cost-current': '1028', 'address3': '',
'mainheatcont-description': 'Programmer, TRVs and bypass', 'sheating-energy-eff': 'N/A',
'property-type': 'House', 'local-authority-label': 'Sheffield', 'fixed-lighting-outlets-count': '21',
'energy-tariff': 'Single', 'mechanical-ventilation': 'natural', 'hot-water-cost-current': '96',
'county': '', 'postcode': 'S6 4FG', 'solar-water-heating-flag': 'N', 'constituency': 'E14000921',
'co2-emissions-potential': '2.9', 'number-heated-rooms': '5',
'floor-description': 'Suspended, no insulation (assumed)', 'energy-consumption-potential': '152',
'local-authority': 'E08000019', 'built-form': 'Enclosed Mid-Terrace', 'number-open-fireplaces': '0',
'windows-description': 'Fully double glazed', 'glazed-area': 'Normal', 'inspection-date': '2022-06-13',
'mains-gas-flag': 'Y', 'co2-emiss-curr-per-floor-area': '59', 'address1': '45 Shepperson Road',
'heat-loss-corridor': '', 'flat-storey-count': '',
'constituency-label': 'Sheffield, Brightside and Hillsborough', 'roof-energy-eff': 'Very Poor',
'total-floor-area': '107.0', 'building-reference-number': '10002892085', 'environment-impact-current': '46',
'co2-emissions-current': '6.3', 'roof-description': 'Pitched, no insulation (assumed)',
'floor-energy-eff': 'N/A', 'number-habitable-rooms': '5', 'address2': '', 'hot-water-env-eff': 'Good',
'posttown': 'SHEFFIELD', 'mainheatc-energy-eff': 'Average', 'main-fuel': 'mains gas (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 43% of fixed outlets',
'roof-env-eff': 'Very Poor', 'walls-energy-eff': 'Very Poor', 'photo-supply': '0.0',
'lighting-cost-potential': '83', 'mainheat-env-eff': 'Good', 'multi-glaze-proportion': '100',
'main-heating-controls': '', 'lodgement-datetime': '2023-05-27 12:15:21', 'flat-top-storey': '',
'current-energy-rating': 'E', 'secondheat-description': 'None', 'walls-env-eff': 'Very Poor',
'transaction-type': 'marketed sale', 'uprn': '100051073214', 'current-energy-efficiency': '54',
'energy-consumption-current': '335', 'mainheat-description': 'Boiler and radiators, mains gas',
'lighting-cost-current': '131', 'lodgement-date': '2023-05-27', 'extension-count': '1',
'mainheatc-env-eff': 'Average',
'lmk-key': 'dc1a4da246562656132b8e36e0534cd90b09fa40fc584e25e644e2d9ab86a247', 'wind-turbine-count': '0',
'tenure': 'Not defined - use in the case of a new dwelling for which the intended tenure in not known. It '
'is not to be used for an existing dwelling',
'floor-level': '', 'potential-energy-efficiency': '80', 'hot-water-energy-eff': 'Good',
'low-energy-lighting': '43',
'walls-description': 'Sandstone or limestone, as built, no insulation (assumed)',
'hotwater-description': 'From main system'
}
row3 = {
'UPRN': '100051073214', 'RDSAP_CHANGE': 2, 'HEAT_DEMAND_CHANGE': -22, 'CARBON_CHANGE': -0.39999999999999947,
'SAP_STARTING': 54, 'SAP_ENDING': 56, 'HEAT_DEMAND_STARTING': 335, 'HEAT_DEMAND_ENDING': 313,
'CARBON_STARTING': 6.3, 'CARBON_ENDING': 5.9, 'PROPERTY_TYPE': 'House', 'BUILT_FORM': 'Mid-Terrace',
'CONSTITUENCY': 'E14000921', 'NUMBER_HABITABLE_ROOMS': 5.0, 'NUMBER_HEATED_ROOMS': 5.0,
'FIXED_LIGHTING_OUTLETS_COUNT': 21.0, 'CONSTRUCTION_AGE_BAND': 'England and Wales: 1900-1929',
'TRANSACTION_TYPE_STARTING': 'marketed sale', 'MECHANICAL_VENTILATION_STARTING': 'natural',
'SECONDHEAT_DESCRIPTION_STARTING': 'None', 'ENERGY_TARIFF_STARTING': 'Single',
'SOLAR_WATER_HEATING_FLAG_STARTING': 'N', 'PHOTO_SUPPLY_STARTING': 0.0,
'GLAZED_TYPE_STARTING': 'double glazing, unknown install date', 'MULTI_GLAZE_PROPORTION_STARTING': 100.0,
'LOW_ENERGY_LIGHTING_STARTING': 43.0, 'NUMBER_OPEN_FIREPLACES_STARTING': 0.0,
'EXTENSION_COUNT_STARTING': 1.0, 'TOTAL_FLOOR_AREA_STARTING': 107.0, 'FLOOR_HEIGHT_STARTING': 1.87,
'TRANSACTION_TYPE_ENDING': 'marketed sale', 'MECHANICAL_VENTILATION_ENDING': 'mechanical, extract only',
'SECONDHEAT_DESCRIPTION_ENDING': 'None', 'ENERGY_TARIFF_ENDING': 'Single',
'SOLAR_WATER_HEATING_FLAG_ENDING': 'N', 'PHOTO_SUPPLY_ENDING': 0.0,
'GLAZED_TYPE_ENDING': 'double glazing, unknown install date', 'MULTI_GLAZE_PROPORTION_ENDING': 100.0,
'LOW_ENERGY_LIGHTING_ENDING': 43.0, 'NUMBER_OPEN_FIREPLACES_ENDING': 0.0, 'EXTENSION_COUNT_ENDING': 1.0,
'TOTAL_FLOOR_AREA_ENDING': 107.0, 'FLOOR_HEIGHT_ENDING': 1.87, 'DAYS_TO_STARTING': 3221,
'DAYS_TO_ENDING': 2874, 'walls_thermal_transmittance': 2.0, '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': True, 'is_cob': False, 'is_sandstone_or_limestone': True,
'is_park_home': False, 'walls_insulation_thickness': 'none', 'external_insulation': False,
'internal_insulation': False, 'walls_thermal_transmittance_ENDING': 2.0, 'is_park_home_ENDING': False,
'walls_insulation_thickness_ENDING': 'none', 'external_insulation_ENDING': False,
'internal_insulation_ENDING': False, 'floor_thermal_transmittance': 0.62, 'is_to_unheated_space': False,
'is_to_external_air': False, 'is_suspended': True, 'is_solid': False, 'another_property_below': False,
'floor_insulation_thickness': 'none', 'floor_thermal_transmittance_ENDING': 0.62,
'floor_insulation_thickness_ENDING': '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', 'roof_thermal_transmittance_ENDING': 2.3,
'roof_insulation_thickness_ENDING': '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',
'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': 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, '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': 'Unknown', 'charging_system': 'Unknown',
'switch_system': 'programmer', 'no_control': 'Unknown', 'dhw_control': 'Unknown',
'community_heating': 'Unknown', 'multiple_room_thermostats': False, 'auxiliary_systems': 'bypass',
'trvs': 'trvs', 'rate_control': 'Unknown', 'thermostatic_control_ENDING': 'Unknown',
'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': 'bypass', 'trvs_ENDING': 'trvs',
'rate_control_ENDING': 'Unknown', 'glazing_type': 'double', 'glazing_type_ENDING': 'double',
'fuel_type': 'mains gas', 'main-fuel_tariff_type': 'Unknown', 'is_community': False,
'no_individual_heating_or_community_network': False, 'complex_fuel_type': 'Unknown',
'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',
'estimated_perimeter_STARTING': 41.634120622393354, 'estimated_perimeter_ENDING': 41.634120622393354
}
home3 = Property(
id=0,
postcode=starting_epc3["postcode"],
address1=starting_epc3["address1"],
epc_client=EpcClient(auth_token="notoken"),
data=starting_epc3
)
home3.get_components(cleaned)
data_processor3 = DataProcessor(None, newdata=True)
data_processor3.insert_data(pd.DataFrame([home3.get_model_data()]))
data_processor3.pre_process()
starting_epc_data3 = data_processor3.get_component_features(suffix="_STARTING")
ending_epc_data3 = data_processor3.get_component_features(suffix="_ENDING")
fixed_data3 = data_processor3.get_fixed_features()
ending_lodgement_date3 = '2022-06-14'
ending_epc_data3["DAYS_TO_ENDING"] = data_processor3.calculate_days_to(ending_lodgement_date3)
recommendation3 = {
"recommendation_id": 0,
"type": "mechanical_ventilation"
}
test_record3 = create_recommendation_scoring_data(
property=home3,
recommendation=recommendation3,
starting_epc_data=starting_epc_data3,
ending_epc_data=ending_epc_data3,
fixed_data=fixed_data3,
)
test_record3 = pd.DataFrame([test_record3])
# Test the final cleaning:
test_record3 = DataProcessor.apply_averages_cleaning(
data_to_clean=test_record3,
cleaning_data=cleaning_data,
cols_to_merge_on=COLUMNS_TO_MERGE_ON + ["LOCAL_AUTHORITY"]
).drop(columns=["LOCAL_AUTHORITY"])
test_record3 = DataProcessor.clean_missings_after_description_process(
test_record3, [
c for c in test_record3.columns if
("thermal_transmittance" in c) or ("insulation_thickness" in c)
]
)
for c in test_record3.columns:
if c in ["id", "SAP_ENDING", "HEAT_DEMAND_ENDING", "CARBON_ENDING"]:
continue
assert test_record3[c].values[0] == row3[c]

View file

@ -0,0 +1,70 @@
import pandas as pd
from BaseUtility import Definitions
from backend.Property import Property
class VentilationRecommendations(Definitions):
"""
For properties that do not have ventilation, we recommend installing ventilaion
This is particularly important for properties that have insulated walls and is also
crucial for prevent overheating risks in warmer months
"""
VENTILATION_DESCRIPTIONS = [
'mechanical, extract only',
'mechanical, supply and extract'
]
def __init__(
self,
property_instance: Property,
materials
):
self.property = property_instance
self.has_ventilaion = None
self.recommendation = None
self.materials = materials
def identify_ventilation(self):
self.has_ventilaion = self.property.data["mechanical-ventilation"] in self.VENTILATION_DESCRIPTIONS
def recommend(self):
"""
If there is no ventilation, we recommend installing ventilation
Generally, best practice is to install controlled ventilation for insulated walls so we still recommend
ventilation if there is natural ventilation
:return:
"""
self.identify_ventilation()
if self.has_ventilaion:
return
if len(self.materials) != 1:
raise NotImplementedError("Only handled the case of having one venilation option")
# We recommend installing 2 units
n_units = 2
part = self.materials.copy()
estimated_cost = n_units * part[0]["cost"]
part[0]["estimated_cost"] = estimated_cost
part[0]["quantity"] = n_units
part[0]["quantity_unit"] = None
# We recommend installing two mechanical ventilation systems
self.recommendation = [
{
"parts": part,
"type": part[0]["type"],
"description": "Install %s" % part[0]["description"],
"starting_u_value": None,
"new_u_value": None,
"sap_points": None,
"cost": estimated_cost,
}
]

View file

@ -1,4 +1,3 @@
import itertools
import math
from typing import List

View file

@ -0,0 +1,110 @@
from backend.Property import Property
from unittest.mock import Mock
from recommendations.VentilationRecommendations import VentilationRecommendations
ventilation_materials = [
{
'id': 17, 'type': 'mechanical_ventilation', 'description': 'Mechanical Extract Ventilation',
'depths': 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, 'is_active': True, 'estimated_cost': 1000, 'quantity': 2, 'quantity_unit': None
}
]
class TestVentilationRecommendations:
def test_natural_ventilation(self):
input_property1 = Property(id=1, postcode="F4k3 6", address1="623 fake street", epc_client=Mock())
input_property1.data = {"mechanical-ventilation": "natural"}
recommender = VentilationRecommendations(
property_instance=input_property1,
materials=ventilation_materials
)
assert not recommender.recommendation
recommender.recommend()
assert len(recommender.recommendation) == 1
assert recommender.recommendation[0]["cost"] == 1000
assert recommender.recommendation[0]["type"] == "mechanical_ventilation"
assert len(recommender.recommendation[0]["parts"]) == 1
assert recommender.recommendation[0]["parts"][0]["description"] == 'Mechanical Extract Ventilation'
assert recommender.recommendation[0]["parts"][0]["quantity"] == 2
def test_missing_ventilation(self):
input_property2 = Property(id=1, postcode="F4k3 6", address1="623 fake street", epc_client=Mock())
input_property2.data = {"mechanical-ventilation": None}
recommender2 = VentilationRecommendations(
property_instance=input_property2,
materials=ventilation_materials
)
assert not recommender2.recommendation
recommender2.recommend()
assert len(recommender2.recommendation) == 1
assert recommender2.recommendation[0]["cost"] == 1000
assert recommender2.recommendation[0]["type"] == "mechanical_ventilation"
assert len(recommender2.recommendation[0]["parts"]) == 1
assert recommender2.recommendation[0]["parts"][0]["description"] == 'Mechanical Extract Ventilation'
assert recommender2.recommendation[0]["parts"][0]["quantity"] == 2
def test_nodata_ventilation(self):
input_property3 = Property(id=1, postcode="F4k3 6", address1="623 fake street", epc_client=Mock())
input_property3.data = {"mechanical-ventilation": "NO DATA!!"}
recommender3 = VentilationRecommendations(
property_instance=input_property3,
materials=ventilation_materials
)
assert not recommender3.recommendation
recommender3.recommend()
assert len(recommender3.recommendation) == 1
assert recommender3.recommendation[0]["cost"] == 1000
assert recommender3.recommendation[0]["type"] == "mechanical_ventilation"
assert len(recommender3.recommendation[0]["parts"]) == 1
assert recommender3.recommendation[0]["parts"][0]["description"] == 'Mechanical Extract Ventilation'
assert recommender3.recommendation[0]["parts"][0]["quantity"] == 2
def test_existing_ventilation_1(self):
input_property4 = Property(id=1, postcode="F4k3 6", address1="623 fake street", epc_client=Mock())
input_property4.data = {"mechanical-ventilation": 'mechanical, extract only'}
recommender4 = VentilationRecommendations(
property_instance=input_property4,
materials=ventilation_materials
)
assert not recommender4.recommendation
recommender4.recommend()
assert not recommender4.recommendation
assert recommender4.has_ventilaion
def test_existing_ventilation_2(self):
input_property5 = Property(id=1, postcode="F4k3 6", address1="623 fake street", epc_client=Mock())
input_property5.data = {"mechanical-ventilation": 'mechanical, supply and extract'}
recommender5 = VentilationRecommendations(
property_instance=input_property5,
materials=ventilation_materials
)
assert not recommender5.recommendation
recommender5.recommend()
assert not recommender5.recommendation
assert recommender5.has_ventilaion