mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
fixed unit tests
This commit is contained in:
parent
a700ead260
commit
316623454a
12 changed files with 328 additions and 505 deletions
29
.github/workflows/integration_tests.yml
vendored
Normal file
29
.github/workflows/integration_tests.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
name: Rebaselining Integration Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
rebaselining-integration-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install tox via Makefile
|
||||
run: |
|
||||
make setup
|
||||
|
||||
- name: Run only rebaselining integration test
|
||||
env:
|
||||
EPC_AUTH_TOKEN: ${{ secrets.DEV_EPC_AUTH_TOKEN }}
|
||||
run: |
|
||||
pytest backend/tests/test_rebaselining_pipeline.py -k test_rebaselining_pipeline_with_real_data
|
||||
|
||||
|
|
@ -1,138 +1,89 @@
|
|||
# --- Integration Test with Real Data ---
|
||||
import os
|
||||
import pickle
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def load_sample_certificates():
|
||||
"""Load sample_certificates.csv as a list of dicts."""
|
||||
# Always look for the file relative to the project root (cwd)
|
||||
import pandas as pd
|
||||
"""Load sample_certificates.csv as a DataFrame with normalized columns."""
|
||||
csv_path = os.path.join(os.getcwd(), 'backend', 'tests', 'test_data', 'sample_certificates.csv')
|
||||
if os.path.exists(csv_path):
|
||||
df = pd.read_csv(csv_path)
|
||||
# Normalize columns: lowercase, replace underscores with hyphens, strip spaces
|
||||
df.columns = [c.strip().lower().replace('_', '-') for c in df.columns]
|
||||
df = df[~pd.isnull(df["uprn"])]
|
||||
df = df[~pd.isnull(df["low-energy-fixed-light-count"])]
|
||||
df = df.fillna("")
|
||||
for col in ["uprn", "low-energy-fixed-light-count"]:
|
||||
df[col] = df[col].astype(int).astype(str)
|
||||
df = df.astype(str)
|
||||
return df
|
||||
raise FileNotFoundError(
|
||||
f"sample_certificates.csv not found at {csv_path}. Make sure it exists relative to the project root.")
|
||||
if not os.path.exists(csv_path):
|
||||
raise FileNotFoundError(
|
||||
f"sample_certificates.csv not found at {csv_path}. Make sure it exists relative to the project root.")
|
||||
df = pd.read_csv(csv_path)
|
||||
df.columns = [c.strip().lower().replace('_', '-') for c in df.columns]
|
||||
df = df[~pd.isnull(df["uprn"])]
|
||||
df = df[~pd.isnull(df["low-energy-fixed-light-count"])]
|
||||
df = df.fillna("")
|
||||
for col in ["uprn", "low-energy-fixed-light-count"]:
|
||||
df[col] = df[col].astype(int).astype(str)
|
||||
df = df.astype(str)
|
||||
return df
|
||||
|
||||
|
||||
def make_property_from_row(row, cleaning_data):
|
||||
# Convert row to dict with correct keys (hyphens, lower case)
|
||||
# Convert all keys to snake_case (replace hyphens with underscores, lower case)
|
||||
from etl.epc.Record import EPCRecord
|
||||
|
||||
from backend.Property import Property
|
||||
row_dict = row.to_dict()
|
||||
|
||||
epc_records = {
|
||||
"original_epc": row_dict.copy(),
|
||||
"full_sap_epc": row_dict.copy(),
|
||||
"old_data": []
|
||||
}
|
||||
|
||||
from etl.epc.Record import InputEpcRecords
|
||||
epc_records = InputEpcRecords(
|
||||
original_epc=row_dict.copy(),
|
||||
full_sap_epc=row_dict.copy(),
|
||||
old_data=[]
|
||||
)
|
||||
epc_record = EPCRecord(
|
||||
epc_records=epc_records,
|
||||
run_mode="newdata",
|
||||
cleaning_data=cleaning_data
|
||||
)
|
||||
# Extract required fields for Property constructor
|
||||
# Use lmk-key as id if present, else fallback to uprn or index
|
||||
id_val = row.get('uprn')
|
||||
postcode_val = row.get('postcode')
|
||||
address_val = row.get('address') or row.get('address1')
|
||||
from backend.Property import Property
|
||||
property_obj = Property(
|
||||
return Property(
|
||||
id=id_val,
|
||||
postcode=postcode_val,
|
||||
address=address_val,
|
||||
epc_record=epc_record,
|
||||
uprn=int(row['uprn']) if 'uprn' in row and not pd.isnull(row['uprn']) else None,
|
||||
# Provide defaults for other optional args as needed
|
||||
)
|
||||
return property_obj
|
||||
|
||||
|
||||
def load_cleaned():
|
||||
import pickle
|
||||
with open("recommendations/tests/test_data/cleaned.pkl", "rb") as f:
|
||||
df = pickle.load(f)
|
||||
|
||||
return df
|
||||
return pickle.load(f)
|
||||
|
||||
|
||||
def load_cleaning_data():
|
||||
import pickle
|
||||
with open("recommendations/tests/test_data/cleaning_data.pkl", "rb") as f:
|
||||
df = pickle.load(f)
|
||||
|
||||
return df
|
||||
return pickle.load(f)
|
||||
|
||||
|
||||
def test_rebaselining_pipeline_with_real_data(mock_model_api):
|
||||
def test_rebaselining_pipeline_with_real_data():
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
from backend.ml_models.api import ModelApi
|
||||
from backend.app.utils import sap_to_epc
|
||||
from backend.app.config import get_prediction_buckets
|
||||
|
||||
df = load_sample_certificates()
|
||||
|
||||
cleaning_data = load_cleaning_data()
|
||||
input_properties = [make_property_from_row(row, cleaning_data=cleaning_data) for _, row in df.iterrows()]
|
||||
cleaned = load_cleaned()
|
||||
rebaselining_scoring_data = []
|
||||
# List of required columns for the model pipeline
|
||||
required_columns = [
|
||||
'secondheat_description_ending',
|
||||
'windows_description_ending',
|
||||
'low_energy_lighting_ending',
|
||||
'solar_water_heating_flag_ending',
|
||||
'photo_supply_ending',
|
||||
'floor_height_ending',
|
||||
'floor_energy_eff_ending',
|
||||
'sheating_energy_eff_ending',
|
||||
'lighting_energy_eff_ending',
|
||||
'is_post_sap10_ending',
|
||||
'secondheat_description_starting',
|
||||
'windows_description_starting',
|
||||
'low_energy_lighting_starting',
|
||||
'solar_water_heating_flag_starting',
|
||||
'photo_supply_starting',
|
||||
'floor_height_starting',
|
||||
'floor_energy_eff_starting',
|
||||
'sheating_energy_eff_starting',
|
||||
'lighting_energy_eff_starting',
|
||||
'is_post_sap10_starting',
|
||||
'fixed_lighting_outlets_count',
|
||||
]
|
||||
for p in input_properties:
|
||||
# Already rebaseline for tests
|
||||
p.create_base_difference_epc_record(cleaned_lookup=cleaned)
|
||||
scoring_data = p.base_difference_record.df.copy()
|
||||
rebaselining_scoring_data.append(scoring_data)
|
||||
if not rebaselining_scoring_data:
|
||||
assert False, "No properties required rebaselining in the sample data."
|
||||
rebaselining_scoring_data = pd.concat(rebaselining_scoring_data)
|
||||
# Set is_post_sap10_starting after concatenation
|
||||
rebaselining_scoring_data["is_post_sap10_starting"] = False
|
||||
|
||||
# Instantiate ModelApi as in engine.py
|
||||
portfolio_id = "test-portfolio"
|
||||
timestamp = datetime.now().isoformat()
|
||||
from backend.app.config import get_prediction_buckets
|
||||
prediction_buckets = get_prediction_buckets()
|
||||
model_api = ModelApi(
|
||||
portfolio_id=portfolio_id,
|
||||
timestamp=timestamp,
|
||||
prediction_buckets=prediction_buckets,
|
||||
portfolio_id="test-portfolio",
|
||||
timestamp=datetime.now().isoformat(),
|
||||
prediction_buckets=get_prediction_buckets(),
|
||||
max_retries=1
|
||||
)
|
||||
|
||||
# Use the real model_api and bucket
|
||||
bucket = "retrofit-data-dev"
|
||||
model_prefixes = model_api.BASELINE_MODEL_PREFIXES
|
||||
rebaselining_response = model_api.predict_all(
|
||||
|
|
@ -149,7 +100,6 @@ def test_rebaselining_pipeline_with_real_data(mock_model_api):
|
|||
"retrofit_heat_baseline_predictions",
|
||||
]
|
||||
predictions_by_model_and_uprn = {}
|
||||
# Build a mapping from uprn to original values for easy lookup
|
||||
uprn_to_originals = {}
|
||||
for p in input_properties:
|
||||
if p.uprn is not None and hasattr(p, 'epc_record') and hasattr(p.epc_record, 'original_epc'):
|
||||
|
|
@ -170,33 +120,19 @@ def test_rebaselining_pipeline_with_real_data(mock_model_api):
|
|||
(df[actual_col] != 0)
|
||||
)
|
||||
if valid.sum() == 0:
|
||||
return None # No valid rows
|
||||
mape = (
|
||||
(df.loc[valid, pred_col] - df.loc[valid, actual_col]).abs()
|
||||
/ df.loc[valid, actual_col].abs()
|
||||
).mean() * 100
|
||||
return None
|
||||
mape = ((df.loc[valid, pred_col] - df.loc[valid, actual_col]).abs() / df.loc[
|
||||
valid, actual_col].abs()).mean() * 100
|
||||
return mape
|
||||
|
||||
mape_results = {}
|
||||
for model in model_names:
|
||||
df_pred = rebaselining_response[model]
|
||||
# Map originals
|
||||
df_pred['original_sap'] = df_pred['uprn'].map(
|
||||
lambda u: uprn_to_originals.get(int(u), {}).get('original_sap')
|
||||
)
|
||||
df_pred['original_sap'] = df_pred['uprn'].map(lambda u: uprn_to_originals.get(int(u), {}).get('original_sap'))
|
||||
df_pred['original_carbon'] = df_pred['uprn'].map(
|
||||
lambda u: uprn_to_originals.get(int(u), {}).get('original_carbon')
|
||||
)
|
||||
df_pred['original_heat'] = df_pred['uprn'].map(
|
||||
lambda u: uprn_to_originals.get(int(u), {}).get('original_heat')
|
||||
)
|
||||
# Save predictions
|
||||
predictions_by_model_and_uprn[model] = dict(
|
||||
zip(df_pred["uprn"].astype(int), df_pred["predictions"])
|
||||
)
|
||||
# For debugging
|
||||
# df_pred.to_csv(f"rebaselining_{model}.csv", index=False)
|
||||
# Select correct actual column
|
||||
lambda u: uprn_to_originals.get(int(u), {}).get('original_carbon'))
|
||||
df_pred['original_heat'] = df_pred['uprn'].map(lambda u: uprn_to_originals.get(int(u), {}).get('original_heat'))
|
||||
predictions_by_model_and_uprn[model] = dict(zip(df_pred["uprn"].astype(int), df_pred["predictions"]))
|
||||
if model == "retrofit_sap_baseline_predictions":
|
||||
actual_col = "original_sap"
|
||||
metric_name = "sap"
|
||||
|
|
@ -214,14 +150,11 @@ def test_rebaselining_pipeline_with_real_data(mock_model_api):
|
|||
print(f"MAPE ({metric_name}): {mape:.2f}%")
|
||||
else:
|
||||
print(f"MAPE ({metric_name}): No valid data")
|
||||
# --- ASSERT PERFORMANCE ---
|
||||
# each model has varying impacts under SAP 10. We see a small SAP movement
|
||||
# but much higher carbon and heat changes. We expect this. E.g. we see
|
||||
# cases where EPC C properties had 0.2 carbon which should be higher
|
||||
|
||||
MAX_MAPE = {
|
||||
"sap": 4.6, # %
|
||||
"carbon": 21.0, # %
|
||||
"heat": 16.0, # %
|
||||
"sap": 4.6,
|
||||
"carbon": 21.0,
|
||||
"heat": 16.0,
|
||||
}
|
||||
for metric, mape in mape_results.items():
|
||||
max_allowed = MAX_MAPE.get(metric, 100.0)
|
||||
|
|
@ -240,149 +173,6 @@ def test_rebaselining_pipeline_with_real_data(mock_model_api):
|
|||
new_carbon=new_carbon,
|
||||
new_heat_demand=new_heat_demand,
|
||||
)
|
||||
# Assert that EPC records were updated for the right properties
|
||||
updated = 0
|
||||
for p in input_properties:
|
||||
if p.epc_record.has_been_remodelled:
|
||||
updated += 1
|
||||
updated = sum(1 for p in input_properties if getattr(p.epc_record, 'has_been_remodelled', False))
|
||||
assert updated > 0, "No EPC records were updated."
|
||||
|
||||
# Optionally: Add accuracy/performance checks here if you have ground truth
|
||||
# For now, just print a summary
|
||||
print(f"Updated {updated} EPC records with new predictions.")
|
||||
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
import pandas as pd
|
||||
|
||||
|
||||
# Import the relevant classes and functions
|
||||
# from backend.Property import Property # Uncomment and adjust as needed
|
||||
# from etl.epc.Record import EpcRecord # Uncomment and adjust as needed
|
||||
# from backend.engine.engine import sap_to_epc # Uncomment and adjust as needed
|
||||
|
||||
# --- Fixtures ---
|
||||
@pytest.fixture
|
||||
def sample_input_properties():
|
||||
"""Return a list of mock property objects with required attributes for rebaselining."""
|
||||
|
||||
class MockEpcRecord:
|
||||
def __init__(self):
|
||||
self.landlord_differences = {'wall_insulation': 'yes'}
|
||||
self.current_energy_efficiency = 60
|
||||
self.lodgement_date = '2020-01-01'
|
||||
self.original_epc = {'wall-insulation': 'no'}
|
||||
|
||||
def insert_new_performance_values(self, new_sap, new_epc, new_carbon, new_heat_demand):
|
||||
self.new_sap = new_sap
|
||||
self.new_epc = new_epc
|
||||
self.new_carbon = new_carbon
|
||||
self.new_heat_demand = new_heat_demand
|
||||
|
||||
class MockProperty:
|
||||
def __init__(self, uprn, expired=False, estimated=False):
|
||||
self.uprn = uprn
|
||||
self.epc_is_expired = expired
|
||||
self.epc_is_estimated = estimated
|
||||
self.epc_record = MockEpcRecord()
|
||||
|
||||
def create_base_difference_epc_record(self, cleaned_lookup=None):
|
||||
# Simulate creation of base_difference_record
|
||||
self.base_difference_record = MagicMock()
|
||||
self.base_difference_record.df = pd.DataFrame({
|
||||
'uprn': [self.uprn],
|
||||
'feature1': [1],
|
||||
'feature2': [2],
|
||||
})
|
||||
|
||||
return [MockProperty(1001, expired=True), MockProperty(1002, estimated=True), MockProperty(1003)]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_model_api():
|
||||
mock = MagicMock()
|
||||
# Simulate model_api.predict_all returning a dict of DataFrames
|
||||
mock.predict_all.return_value = {
|
||||
'retrofit_sap_baseline_predictions': pd.DataFrame({'uprn': [1001, 1002], 'predictions': [70, 65]}),
|
||||
'retrofit_carbon_baseline_predictions': pd.DataFrame({'uprn': [1001, 1002], 'predictions': [1.2, 1.1]}),
|
||||
'retrofit_heat_baseline_predictions': pd.DataFrame({'uprn': [1001, 1002], 'predictions': [10000, 9500]}),
|
||||
}
|
||||
mock.BASELINE_MODEL_PREFIXES = ['retrofit_sap_baseline_predictions', 'retrofit_carbon_baseline_predictions',
|
||||
'retrofit_heat_baseline_predictions']
|
||||
return mock
|
||||
|
||||
|
||||
# --- Integration Test ---
|
||||
def test_rebaselining_pipeline(sample_input_properties, mock_model_api):
|
||||
# Simulate the rebaselining process
|
||||
input_properties = sample_input_properties
|
||||
cleaned = None # Placeholder for cleaned_lookup
|
||||
rebaselining_scoring_data = []
|
||||
for p in input_properties:
|
||||
needs_rebaselining = True # Force rebaselining for all properties
|
||||
if needs_rebaselining:
|
||||
p.create_base_difference_epc_record(cleaned_lookup=cleaned)
|
||||
scoring_data = p.base_difference_record.df.copy()
|
||||
rebaselining_scoring_data.append(scoring_data)
|
||||
rebaselining_scoring_data = pd.concat(rebaselining_scoring_data)
|
||||
if not rebaselining_scoring_data.empty:
|
||||
rebaselining_scoring_data["is_post_sap10_starting"] = True
|
||||
# Patch sap_to_epc if needed
|
||||
with patch('backend.engine.engine.sap_to_epc', lambda x: 'C'):
|
||||
rebaselining_response = mock_model_api.predict_all(
|
||||
df=rebaselining_scoring_data,
|
||||
bucket='dummy-bucket',
|
||||
model_prefixes=mock_model_api.BASELINE_MODEL_PREFIXES,
|
||||
extract_ids=False,
|
||||
extract_uprn=True
|
||||
)
|
||||
input_properties_by_uprn = {int(p.uprn): p for p in input_properties if p.uprn is not None}
|
||||
model_names = [
|
||||
"retrofit_sap_baseline_predictions",
|
||||
"retrofit_carbon_baseline_predictions",
|
||||
"retrofit_heat_baseline_predictions",
|
||||
]
|
||||
predictions_by_model_and_uprn = {}
|
||||
for model in model_names:
|
||||
df = rebaselining_response[model]
|
||||
predictions_by_model_and_uprn[model] = dict(zip(df["uprn"].astype(int), df["predictions"]))
|
||||
for uprn_int in rebaselining_scoring_data["uprn"].unique().astype(int):
|
||||
property_instance = input_properties_by_uprn.get(uprn_int)
|
||||
if property_instance is None:
|
||||
continue
|
||||
new_sap = predictions_by_model_and_uprn["retrofit_sap_baseline_predictions"].get(uprn_int)
|
||||
new_carbon = predictions_by_model_and_uprn["retrofit_carbon_baseline_predictions"].get(uprn_int)
|
||||
new_heat_demand = predictions_by_model_and_uprn["retrofit_heat_baseline_predictions"].get(uprn_int)
|
||||
property_instance.epc_record.insert_new_performance_values(
|
||||
new_sap=new_sap,
|
||||
new_epc='C',
|
||||
new_carbon=new_carbon,
|
||||
new_heat_demand=new_heat_demand,
|
||||
)
|
||||
# Assert that EPC records were updated for the right properties
|
||||
# Only properties that were marked as expired or estimated should have new_sap set
|
||||
for p in input_properties:
|
||||
needs_rebaselining = p.epc_is_expired or p.epc_is_estimated or (
|
||||
len(getattr(p.epc_record, 'landlord_differences', {})) > 0)
|
||||
if needs_rebaselining:
|
||||
assert hasattr(p.epc_record, 'new_sap')
|
||||
else:
|
||||
assert not hasattr(p.epc_record, 'new_sap')
|
||||
|
||||
|
||||
# --- Unit Test Example ---
|
||||
def test_insert_new_performance_values():
|
||||
class DummyEpcRecord:
|
||||
def insert_new_performance_values(self, new_sap, new_epc, new_carbon, new_heat_demand):
|
||||
self.new_sap = new_sap
|
||||
self.new_epc = new_epc
|
||||
self.new_carbon = new_carbon
|
||||
self.new_heat_demand = new_heat_demand
|
||||
|
||||
record = DummyEpcRecord()
|
||||
record.insert_new_performance_values(80, 'B', 1.0, 9000)
|
||||
assert record.new_sap == 80
|
||||
assert record.new_epc == 'B'
|
||||
assert record.new_carbon == 1.0
|
||||
assert record.new_heat_demand == 9000
|
||||
|
|
|
|||
|
|
@ -144,7 +144,8 @@ class WallRecommendations(Definitions):
|
|||
"""
|
||||
Checks if the wall is of a suitable type for internal/external wall insulation
|
||||
"""
|
||||
if self.property.walls["is_cavity_wall"] or self.property.walls["is_cob"]:
|
||||
if self.property.walls["is_cavity_wall"] or self.property.walls["is_cob"] or self.property.walls[
|
||||
"is_granite_or_whinstone"] or self.property.walls["is_sandstone_or_limestone"]:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -818,6 +818,7 @@ epc_wall_description_map = {
|
|||
############################
|
||||
# Cob wall mappings
|
||||
############################
|
||||
"Cob, as built, no insulation": "Cob as built",
|
||||
"Cob, as built": "Cob as built",
|
||||
"Cob, with external insulation": "Cob with 100 mm external or internal insulation",
|
||||
"Cob, with internal insulation": "Cob with 100 mm external or internal insulation",
|
||||
|
|
|
|||
|
|
@ -183,9 +183,8 @@ class TestCosts:
|
|||
|
||||
def test_flat_roof_insulation(self):
|
||||
mock_property = Mock()
|
||||
mock_property.data = {
|
||||
"county": "Northamptonshire"
|
||||
}
|
||||
mock_property.epc_record = Mock()
|
||||
mock_property.epc_record.county = "Northamptonshire"
|
||||
|
||||
costs = Costs(mock_property)
|
||||
flat_roof_material = {
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -24,52 +24,33 @@ class TestFirepaceRecommendations:
|
|||
|
||||
def test_no_fireplaces(self, fireplace_materials):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"number-open-fireplaces": 0,
|
||||
}
|
||||
|
||||
epc_record.number_open_fireplaces = 0
|
||||
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
||||
|
||||
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
||||
|
||||
assert recommender.recommendation is None
|
||||
|
||||
recommender.recommend()
|
||||
|
||||
assert recommender.recommendation is None
|
||||
|
||||
def test_one_fireplace(self, fireplace_materials):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"number-open-fireplaces": 1,
|
||||
}
|
||||
epc_record.number_open_fireplaces = 1
|
||||
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
||||
property_instance.already_installed = []
|
||||
|
||||
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
||||
|
||||
assert recommender.recommendation is None
|
||||
|
||||
recommender.recommend()
|
||||
|
||||
assert recommender.recommendation
|
||||
assert recommender.recommendation[0]["type"] == "sealing_open_fireplace"
|
||||
assert recommender.recommendation[0]["total"] == 185
|
||||
|
||||
def test_multiple_fireplaces(self, fireplace_materials):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"number-open-fireplaces": 3,
|
||||
}
|
||||
epc_record.number_open_fireplaces = 3
|
||||
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
||||
property_instance.already_installed = []
|
||||
|
||||
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
||||
|
||||
assert recommender.recommendation is None
|
||||
|
||||
recommender.recommend()
|
||||
|
||||
assert recommender.recommendation
|
||||
assert recommender.recommendation[0]["type"] == "sealing_open_fireplace"
|
||||
assert recommender.recommendation[0]["total"] == 185 * 3
|
||||
|
|
|
|||
|
|
@ -19,29 +19,36 @@ from etl.epc.Record import EPCRecord
|
|||
|
||||
class TestFloorRecommendations:
|
||||
|
||||
@pytest.fixture
|
||||
def input_properties(self):
|
||||
with open(
|
||||
os.path.abspath(os.path.dirname(__file__)) + "/test_data/input_properties.pkl", "rb"
|
||||
) as f:
|
||||
return pickle.load(f)
|
||||
|
||||
def test_init(self, input_properties):
|
||||
input_properties[0].insulation_floor_area = 50
|
||||
input_properties[0].insulation_wall_area = 90
|
||||
def test_init(self):
|
||||
p = Mock()
|
||||
p.epc_record = Mock()
|
||||
p.epc_record.county = "Greater London"
|
||||
p.epc_record.local_authority_label = "London"
|
||||
p.epc_record.insulation_floor_area = 50
|
||||
p.epc_record.insulation_wall_area = 90
|
||||
p.insulation_floor_area = 50
|
||||
p.insulation_wall_area = 90
|
||||
p.floor = {"another_property_below": False}
|
||||
obj = FloorRecommendations(
|
||||
property_instance=input_properties[0],
|
||||
property_instance=p,
|
||||
materials=materials
|
||||
)
|
||||
assert obj
|
||||
assert obj.property
|
||||
|
||||
def test_other_premises_below(self, input_properties):
|
||||
input_properties[0].insulation_floor_area = 100
|
||||
input_properties[0].insulation_wall_area = 999
|
||||
input_properties[0].number_of_floors = 1
|
||||
def test_other_premises_below(self):
|
||||
p = Mock()
|
||||
p.epc_record = Mock()
|
||||
p.epc_record.county = "Greater London"
|
||||
p.epc_record.local_authority_label = "London"
|
||||
p.epc_record.insulation_floor_area = 100
|
||||
p.epc_record.insulation_wall_area = 999
|
||||
p.insulation_floor_area = 100
|
||||
p.insulation_wall_area = 999
|
||||
p.number_of_floors = 1
|
||||
p.floor = {"another_property_below": True, "thermal_transmittance": None, "insulation_thickness": None}
|
||||
recommender = FloorRecommendations(
|
||||
property_instance=input_properties[0],
|
||||
property_instance=p,
|
||||
materials=materials
|
||||
)
|
||||
recommender.recommend()
|
||||
|
|
@ -49,25 +56,41 @@ class TestFloorRecommendations:
|
|||
|
||||
assert not recommender.recommendations
|
||||
|
||||
def test_suspended_no_insulation(self, input_properties):
|
||||
def test_suspended_no_insulation(self):
|
||||
"""
|
||||
For a suspended floor without insulation, we use the rdsap methogology to estimate a U-value for the floor
|
||||
:return:
|
||||
"""
|
||||
|
||||
input_properties[2].insulation_floor_area = 50
|
||||
input_properties[2].insulation_wall_area = 50
|
||||
input_properties[2].walls["is_park_home"] = False
|
||||
input_properties[2].age_band = "A"
|
||||
input_properties[2].perimeter = 20
|
||||
input_properties[2].wall_type = "solid brick"
|
||||
input_properties[2].floor_type = "suspended"
|
||||
input_properties[2].number_of_floors = 1
|
||||
input_properties[2].floor_level = 0
|
||||
input_properties[2].already_installed = []
|
||||
input_properties[2].non_invasive_recommendations = {}
|
||||
|
||||
recommender = FloorRecommendations(property_instance=input_properties[2], materials=materials)
|
||||
p = Mock()
|
||||
p.epc_record = Mock()
|
||||
p.epc_record.county = "Greater London"
|
||||
p.epc_record.local_authority_label = "London"
|
||||
p.epc_record.insulation_floor_area = 50
|
||||
p.epc_record.insulation_wall_area = 50
|
||||
p.insulation_floor_area = 50
|
||||
p.insulation_wall_area = 50
|
||||
p.walls = {"is_park_home": False}
|
||||
p.age_band = "A"
|
||||
p.perimeter = 20
|
||||
p.wall_type = "solid brick"
|
||||
p.floor_type = "suspended"
|
||||
p.number_of_floors = 1
|
||||
p.floor_level = 0
|
||||
p.already_installed = []
|
||||
p.non_invasive_recommendations = {}
|
||||
p.floor = {
|
||||
"is_suspended": True,
|
||||
"is_solid": False,
|
||||
"another_property_below": False,
|
||||
"thermal_transmittance": None,
|
||||
"insulation_thickness": None,
|
||||
"thermal_transmittance_unit": None,
|
||||
"is_assumed": False,
|
||||
"is_to_unheated_space": False,
|
||||
"is_to_external_air": False,
|
||||
}
|
||||
p.full_sap_epc = {}
|
||||
recommender = FloorRecommendations(property_instance=p, materials=materials)
|
||||
assert recommender.estimated_u_value is None
|
||||
recommender.recommend()
|
||||
assert recommender.property.floor["is_suspended"]
|
||||
|
|
@ -82,18 +105,33 @@ class TestFloorRecommendations:
|
|||
assert recommender.recommendations[0]["total"] == 4687.5
|
||||
assert recommender.recommendations[0]["new_u_value"] == 0.21
|
||||
|
||||
def test_uvalue_0_12(self, input_properties):
|
||||
def test_uvalue_0_12(self):
|
||||
"""
|
||||
This is a home that doesn't have a property below but it's highly performant already and therefore
|
||||
does not need floor insulation
|
||||
:return:
|
||||
"""
|
||||
input_properties[3].insulation_floor_area = 100
|
||||
input_properties[3].insulation_wall_area = 100
|
||||
input_properties[3].number_of_floors = 1
|
||||
input_properties[3].floor_level = 0
|
||||
|
||||
recommender = FloorRecommendations(property_instance=input_properties[3], materials=materials)
|
||||
p = Mock()
|
||||
p.epc_record = Mock()
|
||||
p.epc_record.county = "Greater London"
|
||||
p.epc_record.local_authority_label = "London"
|
||||
p.epc_record.insulation_floor_area = 100
|
||||
p.epc_record.insulation_wall_area = 100
|
||||
p.insulation_floor_area = 100
|
||||
p.insulation_wall_area = 100
|
||||
p.number_of_floors = 1
|
||||
p.floor_level = 0
|
||||
p.floor = {
|
||||
"is_suspended": False,
|
||||
"is_solid": False,
|
||||
"another_property_below": False,
|
||||
"thermal_transmittance": 0.12,
|
||||
"insulation_thickness": None,
|
||||
"is_to_unheated_space": False,
|
||||
"is_to_external_air": False,
|
||||
}
|
||||
p.full_sap_epc = {}
|
||||
recommender = FloorRecommendations(property_instance=p, materials=materials)
|
||||
assert recommender.estimated_u_value is None
|
||||
recommender.recommend()
|
||||
assert not recommender.property.floor["is_suspended"]
|
||||
|
|
@ -101,26 +139,41 @@ class TestFloorRecommendations:
|
|||
assert recommender.estimated_u_value is None
|
||||
assert not recommender.recommendations
|
||||
|
||||
def test_solid_no_insulation(self, input_properties):
|
||||
def test_solid_no_insulation(self):
|
||||
"""
|
||||
:return:
|
||||
"""
|
||||
|
||||
input_properties[4].insulation_floor_area = 100
|
||||
input_properties[4].insulation_wall_area = 100
|
||||
input_properties[4].walls["is_park_home"] = False
|
||||
input_properties[4].age_band = "B"
|
||||
input_properties[4].perimeter = 50
|
||||
input_properties[4].wall_type = "solid brick"
|
||||
input_properties[4].floor_type = "solid"
|
||||
input_properties[4].number_of_floors = 1
|
||||
input_properties[4].floor_level = 0
|
||||
input_properties[4].already_installed = []
|
||||
input_properties[4].non_invasive_recommendations = {}
|
||||
|
||||
# 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"] = ""
|
||||
recommender = FloorRecommendations(property_instance=input_properties[4], materials=materials)
|
||||
p = Mock()
|
||||
p.epc_record = Mock()
|
||||
p.epc_record.county = ""
|
||||
p.epc_record.local_authority_label = "London"
|
||||
p.epc_record.insulation_floor_area = 100
|
||||
p.epc_record.insulation_wall_area = 100
|
||||
p.insulation_floor_area = 100
|
||||
p.insulation_wall_area = 100
|
||||
p.walls = {"is_park_home": False}
|
||||
p.age_band = "B"
|
||||
p.perimeter = 50
|
||||
p.wall_type = "solid brick"
|
||||
p.floor_type = "solid"
|
||||
p.number_of_floors = 1
|
||||
p.floor_level = 0
|
||||
p.already_installed = []
|
||||
p.non_invasive_recommendations = {}
|
||||
p.data = {"county": ""}
|
||||
p.floor = {
|
||||
"is_suspended": False,
|
||||
"is_solid": True,
|
||||
"another_property_below": False,
|
||||
"thermal_transmittance": None,
|
||||
"insulation_thickness": None,
|
||||
"is_to_unheated_space": False,
|
||||
"is_to_external_air": False,
|
||||
"thermal_transmittance_unit": None,
|
||||
"is_assumed": True,
|
||||
}
|
||||
p.full_sap_epc = {}
|
||||
recommender = FloorRecommendations(property_instance=p, materials=materials)
|
||||
assert recommender.estimated_u_value is None
|
||||
recommender.recommend()
|
||||
assert not recommender.property.floor["is_suspended"]
|
||||
|
|
@ -148,16 +201,27 @@ class TestFloorRecommendations:
|
|||
'floor-description': 'Solid, insulated'
|
||||
}
|
||||
|
||||
def test_another_dwelling_below(self, input_properties):
|
||||
def test_another_dwelling_below(self):
|
||||
"""
|
||||
This is another description we see when there is a property below
|
||||
"""
|
||||
|
||||
input_properties[6].insulation_floor_area = 100
|
||||
input_properties[6].insulation_wall_area = 1
|
||||
|
||||
input_properties[6].number_of_floors = 1
|
||||
recommender = FloorRecommendations(property_instance=input_properties[6], materials=materials)
|
||||
p = Mock()
|
||||
p.epc_record = Mock()
|
||||
p.epc_record.county = "Greater London"
|
||||
p.epc_record.local_authority_label = "London"
|
||||
p.epc_record.insulation_floor_area = 100
|
||||
p.epc_record.insulation_wall_area = 1
|
||||
p.insulation_floor_area = 100
|
||||
p.insulation_wall_area = 1
|
||||
p.number_of_floors = 1
|
||||
p.floor = {
|
||||
"is_suspended": False,
|
||||
"is_solid": False,
|
||||
"another_property_below": True,
|
||||
"thermal_transmittance": None,
|
||||
"insulation_thickness": None,
|
||||
}
|
||||
recommender = FloorRecommendations(property_instance=p, materials=materials)
|
||||
assert recommender.estimated_u_value is None
|
||||
recommender.recommend()
|
||||
assert not recommender.property.floor["is_suspended"]
|
||||
|
|
@ -167,7 +231,9 @@ class TestFloorRecommendations:
|
|||
|
||||
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.county = "Greater London"
|
||||
epc_record.floor_level = "0"
|
||||
epc_record.property_type = "House"
|
||||
epc_record.full_sap_epc = {}
|
||||
|
||||
input_property = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
|
||||
|
|
@ -199,7 +265,9 @@ class TestFloorRecommendations:
|
|||
|
||||
# Now with an older age band
|
||||
epc_record2 = EPCRecord()
|
||||
epc_record2.prepared_epc = {"county": "Greater London", "floor-level": 0, "property-type": "House"}
|
||||
epc_record2.county = "Greater London"
|
||||
epc_record2.floor_level = "0"
|
||||
epc_record2.property_type = "House"
|
||||
epc_record2.full_sap_epc = {}
|
||||
|
||||
input_property2 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record2)
|
||||
|
|
@ -233,7 +301,9 @@ class TestFloorRecommendations:
|
|||
|
||||
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.county = "Greater London"
|
||||
epc_record3.floor_level = "0"
|
||||
epc_record3.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 = {
|
||||
|
|
@ -269,7 +339,9 @@ class TestFloorRecommendations:
|
|||
|
||||
# With average insulation, no recommendations
|
||||
epc_record4 = EPCRecord()
|
||||
epc_record4.prepared_epc = {"county": "Greater London", "floor-level": 0, "property-type": "House"}
|
||||
epc_record4.county = "Greater London"
|
||||
epc_record4.floor_level = "0"
|
||||
epc_record4.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 = {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class TestLightingRecommendations:
|
|||
|
||||
def test_init_invalid_materials(self):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {"county": "Greater London Authority"}
|
||||
epc_record.county = "Greater London Authority"
|
||||
input_property0 = Property(id=1, postcode="F4k3 6", address="623 fake street", epc_record=epc_record)
|
||||
input_property0.lighting = {"low_energy_proportion": 0}
|
||||
input_property0.already_installed = []
|
||||
|
|
@ -21,7 +21,7 @@ class TestLightingRecommendations:
|
|||
def test_recommend_no_action_needed(self):
|
||||
# Case where no recommendation is needed
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {"county": "Greater London Authority"}
|
||||
epc_record.county = "Greater London Authority"
|
||||
input_property1 = Property(id=1, postcode="F4k3 6", address="623 fake street", epc_record=epc_record)
|
||||
input_property1.lighting = {"low_energy_proportion": 100}
|
||||
input_property1.already_installed = []
|
||||
|
|
@ -33,7 +33,7 @@ class TestLightingRecommendations:
|
|||
def test_recommend_action_needed(self):
|
||||
# Case where recommendation is needed
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {"county": "Greater London Authority"}
|
||||
epc_record.county = "Greater London Authority"
|
||||
input_property1 = Property(id=1, postcode="F4k3 6", address="623 fake street", epc_record=epc_record)
|
||||
input_property1.lighting = {"low_energy_proportion": 0.80}
|
||||
input_property1.number_lighting_outlets = 20
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ class TestSolarPvRecommendations:
|
|||
def property_instance_invalid_type(self):
|
||||
# Setup the property_instance with an invalid property type
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"property-type": "InvalidType", "county": "Broxbourne", "photo-supply": None
|
||||
}
|
||||
epc_record.property_type = "InvalidType"
|
||||
epc_record.county = "Broxbourne"
|
||||
epc_record.photo_supply = None
|
||||
property_instance_invalid_type = Property(id=1, address="", postcode="", epc_record=epc_record)
|
||||
property_instance_invalid_type.roof = {"is_flat": False, "is_pitched": False, "is_roof_room": False}
|
||||
property_instance_invalid_type.already_installed = []
|
||||
|
|
@ -24,9 +24,9 @@ class TestSolarPvRecommendations:
|
|||
def property_instance_invalid_roof(self):
|
||||
# Setup the property_instance with invalid roof type
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Huntingdonshire", "property-type": "House", "photo-supply": None
|
||||
}
|
||||
epc_record.county = "Huntingdonshire"
|
||||
epc_record.property_type = "House"
|
||||
epc_record.photo_supply = None
|
||||
property_instance_invalid_roof = Property(id=1, address="", postcode="", epc_record=epc_record)
|
||||
property_instance_invalid_roof.roof = {
|
||||
"is_flat": False, "is_pitched": False, "is_roof_room": False, "thermal_transmittance": None
|
||||
|
|
@ -36,10 +36,11 @@ class TestSolarPvRecommendations:
|
|||
|
||||
@pytest.fixture
|
||||
def property_instance_has_solar_pv(self):
|
||||
# Setup the property_instance without existing solar pv
|
||||
# Setup the property_instance with existing solar pv
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {"photo-supply": "40", "county": "Huntingdonshire",
|
||||
"property-type": "House"}
|
||||
epc_record.photo_supply = 40.0 # Use float, not string
|
||||
epc_record.county = "Huntingdonshire"
|
||||
epc_record.property_type = "House"
|
||||
property_instance_has_solar_pv = Property(id=1, address="", postcode="", epc_record=epc_record)
|
||||
property_instance_has_solar_pv.roof = {"is_flat": True, "thermal_transmittance": None}
|
||||
property_instance_has_solar_pv.already_installed = []
|
||||
|
|
@ -49,7 +50,9 @@ class TestSolarPvRecommendations:
|
|||
def property_instance_valid_all(self):
|
||||
# Setup a valid property_instance that passes all conditions
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {"property-type": "House", "photo-supply": None, "county": "Huntingdonshire"}
|
||||
epc_record.property_type = "House"
|
||||
epc_record.photo_supply = None
|
||||
epc_record.county = "Huntingdonshire"
|
||||
property_instance_valid_all = Property(id=1, address="", postcode="", epc_record=epc_record)
|
||||
property_instance_valid_all.roof_area = 40
|
||||
property_instance_valid_all.number_of_floors = 2
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import pytest
|
||||
import numpy as np
|
||||
from unittest.mock import Mock, MagicMock
|
||||
|
||||
from recommendations.WallRecommendations import WallRecommendations
|
||||
from backend.Property import Property
|
||||
from recommendations.recommendation_utils import is_diminishing_returns
|
||||
|
|
@ -15,9 +14,12 @@ class TestWallRecommendations:
|
|||
def mock_wall_rec_instance(self):
|
||||
# Creating a mock instance of WallRecommendations with the necessary attributes
|
||||
property_mock = Mock()
|
||||
property_mock.full_sap_epc = {"lodgement-date": "2000-01-01"} # or any date you want
|
||||
property_mock.data = {"construction-age-band": "1950",
|
||||
"county": "Derbyshire"} # or any other data that fits your tests
|
||||
epc_record = EPCRecord()
|
||||
epc_record.construction_age_band = "1950"
|
||||
epc_record.county = "Derbyshire"
|
||||
epc_record.lodgement_date = "2000-01-01"
|
||||
property_mock.epc_record = epc_record
|
||||
property_mock.full_sap_epc = {"lodgement-date": "2000-01-01"}
|
||||
|
||||
mock_wall_rec_instance = WallRecommendations(
|
||||
property_mock, materials=materials
|
||||
|
|
@ -96,6 +98,11 @@ class TestWallRecommendations:
|
|||
This property is not in a conservation area, however it's a flat so we don't recommend external wall insulation
|
||||
"""
|
||||
|
||||
epc_record = EPCRecord()
|
||||
epc_record.county = "Greater London Authority"
|
||||
epc_record.property_type = "Flat"
|
||||
epc_record.walls_energy_eff = "Very Poor"
|
||||
|
||||
p = Mock(
|
||||
id=2,
|
||||
year_built=1930,
|
||||
|
|
@ -116,7 +123,7 @@ class TestWallRecommendations:
|
|||
'is_sandstone_or_limestone': False, 'insulation_thickness': 'none', 'external_insulation': False,
|
||||
'internal_insulation': False, 'is_park_home': False
|
||||
},
|
||||
data={"county": "Greater London Authority", 'property-type': 'Flat', 'walls-energy-eff': 'Very Poor'}
|
||||
epc_record=epc_record,
|
||||
)
|
||||
|
||||
recommender = WallRecommendations(
|
||||
|
|
@ -150,6 +157,10 @@ class TestWallRecommendations:
|
|||
This property is not in a conservation area, however it's a flat so we don't recommend external wall insulation
|
||||
"""
|
||||
|
||||
epc_record = EPCRecord()
|
||||
epc_record.county = "Greater London Authority"
|
||||
epc_record.property_type = "Flat"
|
||||
|
||||
p = Mock(
|
||||
id=3,
|
||||
year_built=1991,
|
||||
|
|
@ -157,7 +168,6 @@ class TestWallRecommendations:
|
|||
insulation_wall_area=100,
|
||||
already_installed=[],
|
||||
in_conservation_area="not_in_conservation_area",
|
||||
data={'county': 'Greater London Authority', 'property-type': 'Flat'},
|
||||
walls={
|
||||
'original_description': 'Solid brick, as built, insulated (assumed)',
|
||||
'clean_description': 'Solid brick, as built, insulated',
|
||||
|
|
@ -167,8 +177,8 @@ class TestWallRecommendations:
|
|||
'is_granite_or_whinstone': False, 'is_as_built': True, 'is_cob': False, 'is_assumed': True,
|
||||
'is_sandstone_or_limestone': False, 'insulation_thickness': 'average', 'external_insulation': False,
|
||||
'internal_insulation': False
|
||||
}
|
||||
|
||||
},
|
||||
epc_record=epc_record
|
||||
)
|
||||
|
||||
recommender = WallRecommendations(
|
||||
|
|
@ -247,7 +257,8 @@ class TestWallRecommendationsBase:
|
|||
property_mock.in_conservation_area = "not_in_conservation_area"
|
||||
property_mock.restricted_measures = False
|
||||
property_mock.insulation_wall_area = 100
|
||||
property_mock.data = {"county": "Derbyshire"}
|
||||
epc_record = EPCRecord(county="Derbyshire", property_type="House")
|
||||
property_mock.epc_record = epc_record
|
||||
property_mock.walls = {
|
||||
"is_cob": False,
|
||||
"is_sandstone_or_limestone": False,
|
||||
|
|
@ -268,21 +279,21 @@ class TestWallRecommendationsBase:
|
|||
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"}
|
||||
wall_recommendations_instance.property.epc_record.property_type = "Flat"
|
||||
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"}
|
||||
# Set property_type on the EPCRecord directly
|
||||
wall_recommendations_instance.property.epc_record.property_type = "House"
|
||||
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", "walls-energy-eff": "Very Poor"}
|
||||
epc_record = EPCRecord(county="Derbyshire", walls_energy_eff="Very Poor", property_type="House")
|
||||
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)',
|
||||
|
|
@ -315,8 +326,7 @@ class TestCavityWallRecommensations:
|
|||
assert np.isclose(recommender.recommendations[0]["total"], 925)
|
||||
|
||||
def test_fill_partial_filled_cavity(self):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {"county": "County Durham", "walls-energy-eff": "Poor"}
|
||||
epc_record = EPCRecord(county="County Durham", walls_energy_eff="Poor", property_type="House")
|
||||
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)',
|
||||
|
|
@ -349,10 +359,8 @@ class TestCavityWallRecommensations:
|
|||
assert np.isclose(recommender.recommendations[0]["total"], 925.0)
|
||||
|
||||
def test_system_built_wall(self):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"property-type": "House", "county": "Derbyshire", "built-form": "Detached", "walls-energy-eff": "Very Poor"
|
||||
}
|
||||
epc_record = EPCRecord(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)',
|
||||
|
|
@ -387,21 +395,11 @@ class TestCavityWallRecommensations:
|
|||
assert recommender2.estimated_u_value == 1
|
||||
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"] == 150
|
||||
|
||||
assert np.isclose(recommender2.recommendations[1]["new_u_value"], 0.26)
|
||||
assert np.isclose(recommender2.recommendations[1]["total"], 23400)
|
||||
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",
|
||||
"walls-energy-eff": "Very Poor"
|
||||
}
|
||||
input_property3 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
|
||||
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||
walls_energy_eff="Very Poor")
|
||||
input_property3 = Property(id=1, postcode="F4k3 3", address="323 fake street", epc_record=epc_record)
|
||||
input_property3.walls = {
|
||||
'original_description': 'Timber frame, as built, no insulation (assumed)',
|
||||
'clean_description': 'Timber frame, as built, no insulation',
|
||||
|
|
@ -413,14 +411,12 @@ class TestCavityWallRecommensations:
|
|||
'insulation_thickness': 'none', 'external_insulation': False,
|
||||
'internal_insulation': False
|
||||
}
|
||||
input_property3.age_band = "B"
|
||||
input_property3.insulation_wall_area = 99
|
||||
input_property3.age_band = "F"
|
||||
input_property3.insulation_wall_area = 120
|
||||
input_property3.restricted_measures = False
|
||||
input_property3.construction_age_band = "England and Wales: 1950-1966"
|
||||
input_property3.construction_age_band = "England and Wales: 1976-1982"
|
||||
input_property3.already_installed = []
|
||||
|
||||
assert input_property3.walls["is_timber_frame"]
|
||||
|
||||
recommender3 = WallRecommendations(
|
||||
property_instance=input_property3,
|
||||
materials=materials
|
||||
|
|
@ -431,25 +427,14 @@ class TestCavityWallRecommensations:
|
|||
recommender3.recommend()
|
||||
|
||||
assert recommender3.recommendations
|
||||
assert len(recommender3.recommendations) == 2
|
||||
assert recommender3.estimated_u_value == 1.9
|
||||
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"] == 150.0
|
||||
|
||||
assert np.isclose(recommender3.recommendations[1]["new_u_value"], 0.29)
|
||||
assert np.isclose(recommender3.recommendations[1]["total"], 19305.0)
|
||||
assert recommender3.recommendations[1]["parts"][0]["type"] == "internal_wall_insulation"
|
||||
assert recommender3.recommendations[1]["parts"][0]["depth"] == 95.0
|
||||
assert recommender3.estimated_u_value == 0.45
|
||||
assert np.isclose(recommender3.recommendations[0]["new_u_value"], 0.17)
|
||||
assert np.isclose(recommender3.recommendations[0]["total"], 35802.0)
|
||||
|
||||
def test_granite_or_whinstone_wall(self):
|
||||
epc_record = EPCRecord()
|
||||
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)
|
||||
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||
walls_energy_eff="Very Poor")
|
||||
input_property4 = Property(id=1, postcode="F4k3 4", address="423 fake street", epc_record=epc_record)
|
||||
input_property4.walls = {
|
||||
'original_description': 'Granite or whinstone, as built, no insulation (assumed)',
|
||||
'clean_description': 'Granite or whinstone, as built, no insulation',
|
||||
|
|
@ -461,14 +446,12 @@ class TestCavityWallRecommensations:
|
|||
'insulation_thickness': 'none', 'external_insulation': False,
|
||||
'internal_insulation': False
|
||||
}
|
||||
input_property4.age_band = "A"
|
||||
input_property4.insulation_wall_area = 223
|
||||
input_property4.age_band = "F"
|
||||
input_property4.insulation_wall_area = 120
|
||||
input_property4.restricted_measures = False
|
||||
input_property4.construction_age_band = "England and Wales: before 1900"
|
||||
input_property4.construction_age_band = "England and Wales: 1976-1982"
|
||||
input_property4.already_installed = []
|
||||
|
||||
assert input_property4.walls["is_granite_or_whinstone"]
|
||||
|
||||
recommender4 = WallRecommendations(
|
||||
property_instance=input_property4,
|
||||
materials=materials
|
||||
|
|
@ -478,45 +461,29 @@ class TestCavityWallRecommensations:
|
|||
|
||||
recommender4.recommend()
|
||||
|
||||
assert recommender4.recommendations
|
||||
assert len(recommender4.recommendations) == 2
|
||||
assert recommender4.estimated_u_value == 2.3
|
||||
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"] == 150
|
||||
|
||||
assert np.isclose(recommender4.recommendations[1]["new_u_value"], 0.3)
|
||||
assert np.isclose(recommender4.recommendations[1]["total"], 43485.0)
|
||||
assert recommender4.recommendations[1]["parts"][0]["type"] == "internal_wall_insulation"
|
||||
assert recommender4.recommendations[1]["parts"][0]["depth"] == 95
|
||||
assert not recommender4.recommendations
|
||||
|
||||
def test_cob_wall(self):
|
||||
epc_record = EPCRecord()
|
||||
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)
|
||||
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||
walls_energy_eff="Very Poor")
|
||||
input_property5 = Property(id=1, postcode="F4k3 5", address="523 fake street", epc_record=epc_record)
|
||||
input_property5.walls = {
|
||||
'original_description': 'Cob, as built',
|
||||
'clean_description': 'Cob, as built',
|
||||
'original_description': 'Cob, as built, no insulation (assumed)',
|
||||
'clean_description': 'Cob, as built, no insulation',
|
||||
'thermal_transmittance': None, 'thermal_transmittance_unit': None,
|
||||
'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': True, 'is_assumed': False,
|
||||
'is_as_built': True, 'is_cob': True, 'is_assumed': True,
|
||||
'is_sandstone_or_limestone': False, 'is_park_home': False,
|
||||
'insulation_thickness': 'none', 'external_insulation': False,
|
||||
'internal_insulation': False
|
||||
}
|
||||
input_property5.age_band = "E"
|
||||
input_property5.insulation_wall_area = 77
|
||||
input_property5.age_band = "F"
|
||||
input_property5.insulation_wall_area = 120
|
||||
input_property5.restricted_measures = False
|
||||
input_property5.construction_age_band = "England and Wales: 1967-1975"
|
||||
input_property5.construction_age_band = "England and Wales: 1976-1982"
|
||||
input_property5.already_installed = []
|
||||
|
||||
assert input_property5.walls["is_cob"]
|
||||
|
||||
recommender5 = WallRecommendations(
|
||||
property_instance=input_property5,
|
||||
materials=materials
|
||||
|
|
@ -526,15 +493,11 @@ class TestCavityWallRecommensations:
|
|||
|
||||
recommender5.recommend()
|
||||
|
||||
# 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",
|
||||
"walls-energy-eff": "Very Poor"
|
||||
}
|
||||
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||
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)',
|
||||
|
|
@ -542,13 +505,13 @@ class TestCavityWallRecommensations:
|
|||
'thermal_transmittance': None, 'thermal_transmittance_unit': None,
|
||||
'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_as_built': True, 'is_cob': False, 'is_assumed': True,
|
||||
'is_sandstone_or_limestone': True, 'is_park_home': False,
|
||||
'insulation_thickness': 'none', 'external_insulation': False,
|
||||
'internal_insulation': False
|
||||
}
|
||||
input_property6.age_band = "F"
|
||||
input_property6.insulation_wall_area = 350
|
||||
input_property6.insulation_wall_area = 120
|
||||
input_property6.restricted_measures = False
|
||||
input_property6.construction_age_band = "England and Wales: 1976-1982"
|
||||
input_property6.already_installed = []
|
||||
|
|
@ -562,11 +525,4 @@ class TestCavityWallRecommensations:
|
|||
|
||||
recommender6.recommend()
|
||||
|
||||
# For sandstone walls, we only recommend internal wall insulation
|
||||
assert recommender6.recommendations
|
||||
assert len(recommender6.recommendations) == 1
|
||||
assert recommender6.estimated_u_value == 1
|
||||
assert np.isclose(recommender6.recommendations[0]["new_u_value"], 0.26)
|
||||
assert np.isclose(recommender6.recommendations[0]["total"], 68250.0)
|
||||
assert recommender6.recommendations[0]["parts"][0]["type"] == "internal_wall_insulation"
|
||||
assert recommender6.recommendations[0]["parts"][0]["depth"] == 95
|
||||
assert not recommender6.recommendations
|
||||
|
|
|
|||
|
|
@ -29,15 +29,14 @@ class TestWindowRecommendations:
|
|||
:return:
|
||||
"""
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Wychavon",
|
||||
"multi-glaze-proportion": 0,
|
||||
"uprn": 0,
|
||||
"windows-energy-eff": "Very Poor",
|
||||
"floor-area": 2.5,
|
||||
"number-habitable-rooms": 5,
|
||||
"number-heated-rooms": 5,
|
||||
}
|
||||
epc_record.county = "Wychavon"
|
||||
epc_record.multi_glaze_proportion = 0
|
||||
epc_record.uprn = 0
|
||||
epc_record.windows_energy_eff = "Very Poor"
|
||||
epc_record.floor_area = 2.5
|
||||
epc_record.number_habitable_rooms = 5
|
||||
epc_record.number_heated_rooms = 5
|
||||
|
||||
property_1 = Property(
|
||||
id=1,
|
||||
postcode='1',
|
||||
|
|
@ -79,12 +78,11 @@ class TestWindowRecommendations:
|
|||
:return:
|
||||
"""
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Wychavon",
|
||||
"multi-glaze-proportion": 33,
|
||||
"uprn": 0,
|
||||
"windows-energy-eff": "Good" # This has been observed in the EPC data
|
||||
}
|
||||
epc_record.county = "Wychavon"
|
||||
epc_record.multi_glaze_proportion = 33
|
||||
epc_record.uprn = 0
|
||||
epc_record.windows_energy_eff = "Good" # This has been observed in the EPC data
|
||||
|
||||
property_2 = Property(
|
||||
id=1,
|
||||
postcode='1',
|
||||
|
|
@ -124,11 +122,10 @@ class TestWindowRecommendations:
|
|||
:return:
|
||||
"""
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Wychavon",
|
||||
"multi-glaze-proportion": 100,
|
||||
"uprn": 0
|
||||
}
|
||||
epc_record.county = "Wychavon"
|
||||
epc_record.multi_glaze_proportion = 100
|
||||
epc_record.uprn = 0
|
||||
|
||||
property_3 = Property(
|
||||
id=1,
|
||||
postcode='1',
|
||||
|
|
@ -154,11 +151,10 @@ class TestWindowRecommendations:
|
|||
|
||||
def test_fully_secondary_glazed(self):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Wychavon",
|
||||
"multi-glaze-proportion": 100,
|
||||
"uprn": 0
|
||||
}
|
||||
epc_record.county = "Wychavon"
|
||||
epc_record.multi_glaze_proportion = 100
|
||||
epc_record.uprn = 0
|
||||
|
||||
property_4 = Property(
|
||||
id=1,
|
||||
postcode='1',
|
||||
|
|
@ -185,12 +181,11 @@ class TestWindowRecommendations:
|
|||
|
||||
def test_partial_secondary_glazing(self):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Wychavon",
|
||||
"multi-glaze-proportion": 50,
|
||||
"uprn": 0,
|
||||
"windows-energy-eff": "Poor" # This has been observed in the EPC data
|
||||
}
|
||||
epc_record.county = "Wychavon"
|
||||
epc_record.multi_glaze_proportion = 50
|
||||
epc_record.uprn = 0
|
||||
epc_record.windows_energy_eff = "Poor" # This has been observed in the EPC data
|
||||
|
||||
property_5 = Property(
|
||||
id=1,
|
||||
postcode='1',
|
||||
|
|
@ -225,12 +220,10 @@ class TestWindowRecommendations:
|
|||
|
||||
def test_single_glazed_restricted_measures(self):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Wychavon",
|
||||
"multi-glaze-proportion": 0,
|
||||
"uprn": 0,
|
||||
"windows-energy-eff": "Very Poor"
|
||||
}
|
||||
epc_record.county = "Wychavon"
|
||||
epc_record.multi_glaze_proportion = 0
|
||||
epc_record.uprn = 0
|
||||
epc_record.windows_energy_eff = "Very Poor"
|
||||
|
||||
property_6 = Property(
|
||||
id=1,
|
||||
|
|
@ -270,11 +263,10 @@ class TestWindowRecommendations:
|
|||
|
||||
def test_full_triple_glazed(self):
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Wychavon",
|
||||
"multi-glaze-proportion": 100,
|
||||
"uprn": 0
|
||||
}
|
||||
epc_record.county = "Wychavon"
|
||||
epc_record.multi_glaze_proportion = 100
|
||||
epc_record.uprn = 0
|
||||
|
||||
property_7 = Property(
|
||||
id=1,
|
||||
postcode='1',
|
||||
|
|
@ -303,11 +295,10 @@ class TestWindowRecommendations:
|
|||
We don't recommend anything here
|
||||
"""
|
||||
epc_record = EPCRecord()
|
||||
epc_record.prepared_epc = {
|
||||
"county": "Wychavon",
|
||||
"multi-glaze-proportion": 80,
|
||||
"uprn": 1
|
||||
}
|
||||
epc_record.county = "Wychavon"
|
||||
epc_record.multi_glaze_proportion = 80
|
||||
epc_record.uprn = 1
|
||||
|
||||
property_8 = Property(
|
||||
id=1,
|
||||
postcode='1',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue