mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +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 os
|
||||||
|
import pickle
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
def load_sample_certificates():
|
def load_sample_certificates():
|
||||||
"""Load sample_certificates.csv as a list of dicts."""
|
"""Load sample_certificates.csv as a DataFrame with normalized columns."""
|
||||||
# Always look for the file relative to the project root (cwd)
|
|
||||||
import pandas as pd
|
|
||||||
csv_path = os.path.join(os.getcwd(), 'backend', 'tests', 'test_data', 'sample_certificates.csv')
|
csv_path = os.path.join(os.getcwd(), 'backend', 'tests', 'test_data', 'sample_certificates.csv')
|
||||||
if os.path.exists(csv_path):
|
if not os.path.exists(csv_path):
|
||||||
df = pd.read_csv(csv_path)
|
raise FileNotFoundError(
|
||||||
# Normalize columns: lowercase, replace underscores with hyphens, strip spaces
|
f"sample_certificates.csv not found at {csv_path}. Make sure it exists relative to the project root.")
|
||||||
df.columns = [c.strip().lower().replace('_', '-') for c in df.columns]
|
df = pd.read_csv(csv_path)
|
||||||
df = df[~pd.isnull(df["uprn"])]
|
df.columns = [c.strip().lower().replace('_', '-') for c in df.columns]
|
||||||
df = df[~pd.isnull(df["low-energy-fixed-light-count"])]
|
df = df[~pd.isnull(df["uprn"])]
|
||||||
df = df.fillna("")
|
df = df[~pd.isnull(df["low-energy-fixed-light-count"])]
|
||||||
for col in ["uprn", "low-energy-fixed-light-count"]:
|
df = df.fillna("")
|
||||||
df[col] = df[col].astype(int).astype(str)
|
for col in ["uprn", "low-energy-fixed-light-count"]:
|
||||||
df = df.astype(str)
|
df[col] = df[col].astype(int).astype(str)
|
||||||
return df
|
df = df.astype(str)
|
||||||
raise FileNotFoundError(
|
return df
|
||||||
f"sample_certificates.csv not found at {csv_path}. Make sure it exists relative to the project root.")
|
|
||||||
|
|
||||||
|
|
||||||
def make_property_from_row(row, cleaning_data):
|
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 etl.epc.Record import EPCRecord
|
||||||
|
from backend.Property import Property
|
||||||
row_dict = row.to_dict()
|
row_dict = row.to_dict()
|
||||||
|
from etl.epc.Record import InputEpcRecords
|
||||||
epc_records = {
|
epc_records = InputEpcRecords(
|
||||||
"original_epc": row_dict.copy(),
|
original_epc=row_dict.copy(),
|
||||||
"full_sap_epc": row_dict.copy(),
|
full_sap_epc=row_dict.copy(),
|
||||||
"old_data": []
|
old_data=[]
|
||||||
}
|
)
|
||||||
|
|
||||||
epc_record = EPCRecord(
|
epc_record = EPCRecord(
|
||||||
epc_records=epc_records,
|
epc_records=epc_records,
|
||||||
run_mode="newdata",
|
run_mode="newdata",
|
||||||
cleaning_data=cleaning_data
|
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')
|
id_val = row.get('uprn')
|
||||||
postcode_val = row.get('postcode')
|
postcode_val = row.get('postcode')
|
||||||
address_val = row.get('address') or row.get('address1')
|
address_val = row.get('address') or row.get('address1')
|
||||||
from backend.Property import Property
|
return Property(
|
||||||
property_obj = Property(
|
|
||||||
id=id_val,
|
id=id_val,
|
||||||
postcode=postcode_val,
|
postcode=postcode_val,
|
||||||
address=address_val,
|
address=address_val,
|
||||||
epc_record=epc_record,
|
epc_record=epc_record,
|
||||||
uprn=int(row['uprn']) if 'uprn' in row and not pd.isnull(row['uprn']) else None,
|
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():
|
def load_cleaned():
|
||||||
import pickle
|
|
||||||
with open("recommendations/tests/test_data/cleaned.pkl", "rb") as f:
|
with open("recommendations/tests/test_data/cleaned.pkl", "rb") as f:
|
||||||
df = pickle.load(f)
|
return pickle.load(f)
|
||||||
|
|
||||||
return df
|
|
||||||
|
|
||||||
|
|
||||||
def load_cleaning_data():
|
def load_cleaning_data():
|
||||||
import pickle
|
|
||||||
with open("recommendations/tests/test_data/cleaning_data.pkl", "rb") as f:
|
with open("recommendations/tests/test_data/cleaning_data.pkl", "rb") as f:
|
||||||
df = pickle.load(f)
|
return pickle.load(f)
|
||||||
|
|
||||||
return df
|
|
||||||
|
|
||||||
|
|
||||||
def test_rebaselining_pipeline_with_real_data(mock_model_api):
|
def test_rebaselining_pipeline_with_real_data():
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from backend.ml_models.api import ModelApi
|
from backend.ml_models.api import ModelApi
|
||||||
from backend.app.utils import sap_to_epc
|
from backend.app.utils import sap_to_epc
|
||||||
|
from backend.app.config import get_prediction_buckets
|
||||||
|
|
||||||
df = load_sample_certificates()
|
df = load_sample_certificates()
|
||||||
|
|
||||||
cleaning_data = load_cleaning_data()
|
cleaning_data = load_cleaning_data()
|
||||||
input_properties = [make_property_from_row(row, cleaning_data=cleaning_data) for _, row in df.iterrows()]
|
input_properties = [make_property_from_row(row, cleaning_data=cleaning_data) for _, row in df.iterrows()]
|
||||||
cleaned = load_cleaned()
|
cleaned = load_cleaned()
|
||||||
rebaselining_scoring_data = []
|
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:
|
for p in input_properties:
|
||||||
# Already rebaseline for tests
|
|
||||||
p.create_base_difference_epc_record(cleaned_lookup=cleaned)
|
p.create_base_difference_epc_record(cleaned_lookup=cleaned)
|
||||||
scoring_data = p.base_difference_record.df.copy()
|
scoring_data = p.base_difference_record.df.copy()
|
||||||
rebaselining_scoring_data.append(scoring_data)
|
rebaselining_scoring_data.append(scoring_data)
|
||||||
if not rebaselining_scoring_data:
|
if not rebaselining_scoring_data:
|
||||||
assert False, "No properties required rebaselining in the sample data."
|
assert False, "No properties required rebaselining in the sample data."
|
||||||
rebaselining_scoring_data = pd.concat(rebaselining_scoring_data)
|
rebaselining_scoring_data = pd.concat(rebaselining_scoring_data)
|
||||||
# Set is_post_sap10_starting after concatenation
|
|
||||||
rebaselining_scoring_data["is_post_sap10_starting"] = False
|
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(
|
model_api = ModelApi(
|
||||||
portfolio_id=portfolio_id,
|
portfolio_id="test-portfolio",
|
||||||
timestamp=timestamp,
|
timestamp=datetime.now().isoformat(),
|
||||||
prediction_buckets=prediction_buckets,
|
prediction_buckets=get_prediction_buckets(),
|
||||||
max_retries=1
|
max_retries=1
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use the real model_api and bucket
|
|
||||||
bucket = "retrofit-data-dev"
|
bucket = "retrofit-data-dev"
|
||||||
model_prefixes = model_api.BASELINE_MODEL_PREFIXES
|
model_prefixes = model_api.BASELINE_MODEL_PREFIXES
|
||||||
rebaselining_response = model_api.predict_all(
|
rebaselining_response = model_api.predict_all(
|
||||||
|
|
@ -149,7 +100,6 @@ def test_rebaselining_pipeline_with_real_data(mock_model_api):
|
||||||
"retrofit_heat_baseline_predictions",
|
"retrofit_heat_baseline_predictions",
|
||||||
]
|
]
|
||||||
predictions_by_model_and_uprn = {}
|
predictions_by_model_and_uprn = {}
|
||||||
# Build a mapping from uprn to original values for easy lookup
|
|
||||||
uprn_to_originals = {}
|
uprn_to_originals = {}
|
||||||
for p in input_properties:
|
for p in input_properties:
|
||||||
if p.uprn is not None and hasattr(p, 'epc_record') and hasattr(p.epc_record, 'original_epc'):
|
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)
|
(df[actual_col] != 0)
|
||||||
)
|
)
|
||||||
if valid.sum() == 0:
|
if valid.sum() == 0:
|
||||||
return None # No valid rows
|
return None
|
||||||
mape = (
|
mape = ((df.loc[valid, pred_col] - df.loc[valid, actual_col]).abs() / df.loc[
|
||||||
(df.loc[valid, pred_col] - df.loc[valid, actual_col]).abs()
|
valid, actual_col].abs()).mean() * 100
|
||||||
/ df.loc[valid, actual_col].abs()
|
|
||||||
).mean() * 100
|
|
||||||
return mape
|
return mape
|
||||||
|
|
||||||
mape_results = {}
|
mape_results = {}
|
||||||
for model in model_names:
|
for model in model_names:
|
||||||
df_pred = rebaselining_response[model]
|
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(
|
df_pred['original_carbon'] = df_pred['uprn'].map(
|
||||||
lambda u: uprn_to_originals.get(int(u), {}).get('original_carbon')
|
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'))
|
||||||
df_pred['original_heat'] = df_pred['uprn'].map(
|
predictions_by_model_and_uprn[model] = dict(zip(df_pred["uprn"].astype(int), df_pred["predictions"]))
|
||||||
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
|
|
||||||
if model == "retrofit_sap_baseline_predictions":
|
if model == "retrofit_sap_baseline_predictions":
|
||||||
actual_col = "original_sap"
|
actual_col = "original_sap"
|
||||||
metric_name = "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}%")
|
print(f"MAPE ({metric_name}): {mape:.2f}%")
|
||||||
else:
|
else:
|
||||||
print(f"MAPE ({metric_name}): No valid data")
|
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 = {
|
MAX_MAPE = {
|
||||||
"sap": 4.6, # %
|
"sap": 4.6,
|
||||||
"carbon": 21.0, # %
|
"carbon": 21.0,
|
||||||
"heat": 16.0, # %
|
"heat": 16.0,
|
||||||
}
|
}
|
||||||
for metric, mape in mape_results.items():
|
for metric, mape in mape_results.items():
|
||||||
max_allowed = MAX_MAPE.get(metric, 100.0)
|
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_carbon=new_carbon,
|
||||||
new_heat_demand=new_heat_demand,
|
new_heat_demand=new_heat_demand,
|
||||||
)
|
)
|
||||||
# Assert that EPC records were updated for the right properties
|
updated = sum(1 for p in input_properties if getattr(p.epc_record, 'has_been_remodelled', False))
|
||||||
updated = 0
|
|
||||||
for p in input_properties:
|
|
||||||
if p.epc_record.has_been_remodelled:
|
|
||||||
updated += 1
|
|
||||||
assert updated > 0, "No EPC records were updated."
|
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.")
|
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
|
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 False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -818,6 +818,7 @@ epc_wall_description_map = {
|
||||||
############################
|
############################
|
||||||
# Cob wall mappings
|
# Cob wall mappings
|
||||||
############################
|
############################
|
||||||
|
"Cob, as built, no insulation": "Cob as built",
|
||||||
"Cob, as built": "Cob as built",
|
"Cob, as built": "Cob as built",
|
||||||
"Cob, with external insulation": "Cob with 100 mm external or internal insulation",
|
"Cob, with external insulation": "Cob with 100 mm external or internal insulation",
|
||||||
"Cob, with internal 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):
|
def test_flat_roof_insulation(self):
|
||||||
mock_property = Mock()
|
mock_property = Mock()
|
||||||
mock_property.data = {
|
mock_property.epc_record = Mock()
|
||||||
"county": "Northamptonshire"
|
mock_property.epc_record.county = "Northamptonshire"
|
||||||
}
|
|
||||||
|
|
||||||
costs = Costs(mock_property)
|
costs = Costs(mock_property)
|
||||||
flat_roof_material = {
|
flat_roof_material = {
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -24,52 +24,33 @@ class TestFirepaceRecommendations:
|
||||||
|
|
||||||
def test_no_fireplaces(self, fireplace_materials):
|
def test_no_fireplaces(self, fireplace_materials):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.number_open_fireplaces = 0
|
||||||
"number-open-fireplaces": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
||||||
|
|
||||||
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
||||||
|
|
||||||
assert recommender.recommendation is None
|
assert recommender.recommendation is None
|
||||||
|
|
||||||
recommender.recommend()
|
recommender.recommend()
|
||||||
|
|
||||||
assert recommender.recommendation is None
|
assert recommender.recommendation is None
|
||||||
|
|
||||||
def test_one_fireplace(self, fireplace_materials):
|
def test_one_fireplace(self, fireplace_materials):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.number_open_fireplaces = 1
|
||||||
"number-open-fireplaces": 1,
|
|
||||||
}
|
|
||||||
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
||||||
property_instance.already_installed = []
|
property_instance.already_installed = []
|
||||||
|
|
||||||
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
||||||
|
|
||||||
assert recommender.recommendation is None
|
assert recommender.recommendation is None
|
||||||
|
|
||||||
recommender.recommend()
|
recommender.recommend()
|
||||||
|
|
||||||
assert recommender.recommendation
|
assert recommender.recommendation
|
||||||
assert recommender.recommendation[0]["type"] == "sealing_open_fireplace"
|
assert recommender.recommendation[0]["type"] == "sealing_open_fireplace"
|
||||||
assert recommender.recommendation[0]["total"] == 185
|
assert recommender.recommendation[0]["total"] == 185
|
||||||
|
|
||||||
def test_multiple_fireplaces(self, fireplace_materials):
|
def test_multiple_fireplaces(self, fireplace_materials):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.number_open_fireplaces = 3
|
||||||
"number-open-fireplaces": 3,
|
|
||||||
}
|
|
||||||
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
property_instance = Property(id=0, address="fake", postcode="fake", epc_record=epc_record)
|
||||||
property_instance.already_installed = []
|
property_instance.already_installed = []
|
||||||
|
|
||||||
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
recommender = FireplaceRecommendations(property_instance=property_instance, materials=fireplace_materials)
|
||||||
|
|
||||||
assert recommender.recommendation is None
|
assert recommender.recommendation is None
|
||||||
|
|
||||||
recommender.recommend()
|
recommender.recommend()
|
||||||
|
|
||||||
assert recommender.recommendation
|
assert recommender.recommendation
|
||||||
assert recommender.recommendation[0]["type"] == "sealing_open_fireplace"
|
assert recommender.recommendation[0]["type"] == "sealing_open_fireplace"
|
||||||
assert recommender.recommendation[0]["total"] == 185 * 3
|
assert recommender.recommendation[0]["total"] == 185 * 3
|
||||||
|
|
|
||||||
|
|
@ -19,29 +19,36 @@ from etl.epc.Record import EPCRecord
|
||||||
|
|
||||||
class TestFloorRecommendations:
|
class TestFloorRecommendations:
|
||||||
|
|
||||||
@pytest.fixture
|
def test_init(self):
|
||||||
def input_properties(self):
|
p = Mock()
|
||||||
with open(
|
p.epc_record = Mock()
|
||||||
os.path.abspath(os.path.dirname(__file__)) + "/test_data/input_properties.pkl", "rb"
|
p.epc_record.county = "Greater London"
|
||||||
) as f:
|
p.epc_record.local_authority_label = "London"
|
||||||
return pickle.load(f)
|
p.epc_record.insulation_floor_area = 50
|
||||||
|
p.epc_record.insulation_wall_area = 90
|
||||||
def test_init(self, input_properties):
|
p.insulation_floor_area = 50
|
||||||
input_properties[0].insulation_floor_area = 50
|
p.insulation_wall_area = 90
|
||||||
input_properties[0].insulation_wall_area = 90
|
p.floor = {"another_property_below": False}
|
||||||
obj = FloorRecommendations(
|
obj = FloorRecommendations(
|
||||||
property_instance=input_properties[0],
|
property_instance=p,
|
||||||
materials=materials
|
materials=materials
|
||||||
)
|
)
|
||||||
assert obj
|
assert obj
|
||||||
assert obj.property
|
assert obj.property
|
||||||
|
|
||||||
def test_other_premises_below(self, input_properties):
|
def test_other_premises_below(self):
|
||||||
input_properties[0].insulation_floor_area = 100
|
p = Mock()
|
||||||
input_properties[0].insulation_wall_area = 999
|
p.epc_record = Mock()
|
||||||
input_properties[0].number_of_floors = 1
|
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(
|
recommender = FloorRecommendations(
|
||||||
property_instance=input_properties[0],
|
property_instance=p,
|
||||||
materials=materials
|
materials=materials
|
||||||
)
|
)
|
||||||
recommender.recommend()
|
recommender.recommend()
|
||||||
|
|
@ -49,25 +56,41 @@ class TestFloorRecommendations:
|
||||||
|
|
||||||
assert not recommender.recommendations
|
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
|
For a suspended floor without insulation, we use the rdsap methogology to estimate a U-value for the floor
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
p = Mock()
|
||||||
input_properties[2].insulation_floor_area = 50
|
p.epc_record = Mock()
|
||||||
input_properties[2].insulation_wall_area = 50
|
p.epc_record.county = "Greater London"
|
||||||
input_properties[2].walls["is_park_home"] = False
|
p.epc_record.local_authority_label = "London"
|
||||||
input_properties[2].age_band = "A"
|
p.epc_record.insulation_floor_area = 50
|
||||||
input_properties[2].perimeter = 20
|
p.epc_record.insulation_wall_area = 50
|
||||||
input_properties[2].wall_type = "solid brick"
|
p.insulation_floor_area = 50
|
||||||
input_properties[2].floor_type = "suspended"
|
p.insulation_wall_area = 50
|
||||||
input_properties[2].number_of_floors = 1
|
p.walls = {"is_park_home": False}
|
||||||
input_properties[2].floor_level = 0
|
p.age_band = "A"
|
||||||
input_properties[2].already_installed = []
|
p.perimeter = 20
|
||||||
input_properties[2].non_invasive_recommendations = {}
|
p.wall_type = "solid brick"
|
||||||
|
p.floor_type = "suspended"
|
||||||
recommender = FloorRecommendations(property_instance=input_properties[2], materials=materials)
|
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
|
assert recommender.estimated_u_value is None
|
||||||
recommender.recommend()
|
recommender.recommend()
|
||||||
assert recommender.property.floor["is_suspended"]
|
assert recommender.property.floor["is_suspended"]
|
||||||
|
|
@ -82,18 +105,33 @@ class TestFloorRecommendations:
|
||||||
assert recommender.recommendations[0]["total"] == 4687.5
|
assert recommender.recommendations[0]["total"] == 4687.5
|
||||||
assert recommender.recommendations[0]["new_u_value"] == 0.21
|
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
|
This is a home that doesn't have a property below but it's highly performant already and therefore
|
||||||
does not need floor insulation
|
does not need floor insulation
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
input_properties[3].insulation_floor_area = 100
|
p = Mock()
|
||||||
input_properties[3].insulation_wall_area = 100
|
p.epc_record = Mock()
|
||||||
input_properties[3].number_of_floors = 1
|
p.epc_record.county = "Greater London"
|
||||||
input_properties[3].floor_level = 0
|
p.epc_record.local_authority_label = "London"
|
||||||
|
p.epc_record.insulation_floor_area = 100
|
||||||
recommender = FloorRecommendations(property_instance=input_properties[3], materials=materials)
|
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
|
assert recommender.estimated_u_value is None
|
||||||
recommender.recommend()
|
recommender.recommend()
|
||||||
assert not recommender.property.floor["is_suspended"]
|
assert not recommender.property.floor["is_suspended"]
|
||||||
|
|
@ -101,26 +139,41 @@ class TestFloorRecommendations:
|
||||||
assert recommender.estimated_u_value is None
|
assert recommender.estimated_u_value is None
|
||||||
assert not recommender.recommendations
|
assert not recommender.recommendations
|
||||||
|
|
||||||
def test_solid_no_insulation(self, input_properties):
|
def test_solid_no_insulation(self):
|
||||||
"""
|
"""
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
p = Mock()
|
||||||
input_properties[4].insulation_floor_area = 100
|
p.epc_record = Mock()
|
||||||
input_properties[4].insulation_wall_area = 100
|
p.epc_record.county = ""
|
||||||
input_properties[4].walls["is_park_home"] = False
|
p.epc_record.local_authority_label = "London"
|
||||||
input_properties[4].age_band = "B"
|
p.epc_record.insulation_floor_area = 100
|
||||||
input_properties[4].perimeter = 50
|
p.epc_record.insulation_wall_area = 100
|
||||||
input_properties[4].wall_type = "solid brick"
|
p.insulation_floor_area = 100
|
||||||
input_properties[4].floor_type = "solid"
|
p.insulation_wall_area = 100
|
||||||
input_properties[4].number_of_floors = 1
|
p.walls = {"is_park_home": False}
|
||||||
input_properties[4].floor_level = 0
|
p.age_band = "B"
|
||||||
input_properties[4].already_installed = []
|
p.perimeter = 50
|
||||||
input_properties[4].non_invasive_recommendations = {}
|
p.wall_type = "solid brick"
|
||||||
|
p.floor_type = "solid"
|
||||||
# In this case, we have no county, so in this case, it should yse the local-authority-label if possible
|
p.number_of_floors = 1
|
||||||
input_properties[4].data["county"] = ""
|
p.floor_level = 0
|
||||||
recommender = FloorRecommendations(property_instance=input_properties[4], materials=materials)
|
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
|
assert recommender.estimated_u_value is None
|
||||||
recommender.recommend()
|
recommender.recommend()
|
||||||
assert not recommender.property.floor["is_suspended"]
|
assert not recommender.property.floor["is_suspended"]
|
||||||
|
|
@ -148,16 +201,27 @@ class TestFloorRecommendations:
|
||||||
'floor-description': 'Solid, insulated'
|
'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
|
This is another description we see when there is a property below
|
||||||
"""
|
"""
|
||||||
|
p = Mock()
|
||||||
input_properties[6].insulation_floor_area = 100
|
p.epc_record = Mock()
|
||||||
input_properties[6].insulation_wall_area = 1
|
p.epc_record.county = "Greater London"
|
||||||
|
p.epc_record.local_authority_label = "London"
|
||||||
input_properties[6].number_of_floors = 1
|
p.epc_record.insulation_floor_area = 100
|
||||||
recommender = FloorRecommendations(property_instance=input_properties[6], materials=materials)
|
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
|
assert recommender.estimated_u_value is None
|
||||||
recommender.recommend()
|
recommender.recommend()
|
||||||
assert not recommender.property.floor["is_suspended"]
|
assert not recommender.property.floor["is_suspended"]
|
||||||
|
|
@ -167,7 +231,9 @@ class TestFloorRecommendations:
|
||||||
|
|
||||||
def test_exposed_floor_no_insulation(self):
|
def test_exposed_floor_no_insulation(self):
|
||||||
epc_record = EPCRecord()
|
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 = {}
|
epc_record.full_sap_epc = {}
|
||||||
|
|
||||||
input_property = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
|
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
|
# Now with an older age band
|
||||||
epc_record2 = EPCRecord()
|
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 = {}
|
epc_record2.full_sap_epc = {}
|
||||||
|
|
||||||
input_property2 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record2)
|
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):
|
def test_exposed_floor_below_average_insulated(self):
|
||||||
epc_record3 = EPCRecord()
|
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 = {}
|
epc_record3.full_sap_epc = {}
|
||||||
input_property3 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record3)
|
input_property3 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record3)
|
||||||
input_property3.floor = {
|
input_property3.floor = {
|
||||||
|
|
@ -269,7 +339,9 @@ class TestFloorRecommendations:
|
||||||
|
|
||||||
# With average insulation, no recommendations
|
# With average insulation, no recommendations
|
||||||
epc_record4 = EPCRecord()
|
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 = {}
|
epc_record4.full_sap_epc = {}
|
||||||
input_property4 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record4)
|
input_property4 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record4)
|
||||||
input_property4.floor = {
|
input_property4.floor = {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class TestLightingRecommendations:
|
||||||
|
|
||||||
def test_init_invalid_materials(self):
|
def test_init_invalid_materials(self):
|
||||||
epc_record = EPCRecord()
|
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 = Property(id=1, postcode="F4k3 6", address="623 fake street", epc_record=epc_record)
|
||||||
input_property0.lighting = {"low_energy_proportion": 0}
|
input_property0.lighting = {"low_energy_proportion": 0}
|
||||||
input_property0.already_installed = []
|
input_property0.already_installed = []
|
||||||
|
|
@ -21,7 +21,7 @@ class TestLightingRecommendations:
|
||||||
def test_recommend_no_action_needed(self):
|
def test_recommend_no_action_needed(self):
|
||||||
# Case where no recommendation is needed
|
# Case where no recommendation is needed
|
||||||
epc_record = EPCRecord()
|
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 = Property(id=1, postcode="F4k3 6", address="623 fake street", epc_record=epc_record)
|
||||||
input_property1.lighting = {"low_energy_proportion": 100}
|
input_property1.lighting = {"low_energy_proportion": 100}
|
||||||
input_property1.already_installed = []
|
input_property1.already_installed = []
|
||||||
|
|
@ -33,7 +33,7 @@ class TestLightingRecommendations:
|
||||||
def test_recommend_action_needed(self):
|
def test_recommend_action_needed(self):
|
||||||
# Case where recommendation is needed
|
# Case where recommendation is needed
|
||||||
epc_record = EPCRecord()
|
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 = Property(id=1, postcode="F4k3 6", address="623 fake street", epc_record=epc_record)
|
||||||
input_property1.lighting = {"low_energy_proportion": 0.80}
|
input_property1.lighting = {"low_energy_proportion": 0.80}
|
||||||
input_property1.number_lighting_outlets = 20
|
input_property1.number_lighting_outlets = 20
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ class TestSolarPvRecommendations:
|
||||||
def property_instance_invalid_type(self):
|
def property_instance_invalid_type(self):
|
||||||
# Setup the property_instance with an invalid property type
|
# Setup the property_instance with an invalid property type
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.property_type = "InvalidType"
|
||||||
"property-type": "InvalidType", "county": "Broxbourne", "photo-supply": None
|
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 = 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.roof = {"is_flat": False, "is_pitched": False, "is_roof_room": False}
|
||||||
property_instance_invalid_type.already_installed = []
|
property_instance_invalid_type.already_installed = []
|
||||||
|
|
@ -24,9 +24,9 @@ class TestSolarPvRecommendations:
|
||||||
def property_instance_invalid_roof(self):
|
def property_instance_invalid_roof(self):
|
||||||
# Setup the property_instance with invalid roof type
|
# Setup the property_instance with invalid roof type
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Huntingdonshire"
|
||||||
"county": "Huntingdonshire", "property-type": "House", "photo-supply": None
|
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 = Property(id=1, address="", postcode="", epc_record=epc_record)
|
||||||
property_instance_invalid_roof.roof = {
|
property_instance_invalid_roof.roof = {
|
||||||
"is_flat": False, "is_pitched": False, "is_roof_room": False, "thermal_transmittance": None
|
"is_flat": False, "is_pitched": False, "is_roof_room": False, "thermal_transmittance": None
|
||||||
|
|
@ -36,10 +36,11 @@ class TestSolarPvRecommendations:
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def property_instance_has_solar_pv(self):
|
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 = EPCRecord()
|
||||||
epc_record.prepared_epc = {"photo-supply": "40", "county": "Huntingdonshire",
|
epc_record.photo_supply = 40.0 # Use float, not string
|
||||||
"property-type": "House"}
|
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 = 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.roof = {"is_flat": True, "thermal_transmittance": None}
|
||||||
property_instance_has_solar_pv.already_installed = []
|
property_instance_has_solar_pv.already_installed = []
|
||||||
|
|
@ -49,7 +50,9 @@ class TestSolarPvRecommendations:
|
||||||
def property_instance_valid_all(self):
|
def property_instance_valid_all(self):
|
||||||
# Setup a valid property_instance that passes all conditions
|
# Setup a valid property_instance that passes all conditions
|
||||||
epc_record = EPCRecord()
|
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 = Property(id=1, address="", postcode="", epc_record=epc_record)
|
||||||
property_instance_valid_all.roof_area = 40
|
property_instance_valid_all.roof_area = 40
|
||||||
property_instance_valid_all.number_of_floors = 2
|
property_instance_valid_all.number_of_floors = 2
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from unittest.mock import Mock, MagicMock
|
from unittest.mock import Mock, MagicMock
|
||||||
|
|
||||||
from recommendations.WallRecommendations import WallRecommendations
|
from recommendations.WallRecommendations import WallRecommendations
|
||||||
from backend.Property import Property
|
from backend.Property import Property
|
||||||
from recommendations.recommendation_utils import is_diminishing_returns
|
from recommendations.recommendation_utils import is_diminishing_returns
|
||||||
|
|
@ -15,9 +14,12 @@ class TestWallRecommendations:
|
||||||
def mock_wall_rec_instance(self):
|
def mock_wall_rec_instance(self):
|
||||||
# Creating a mock instance of WallRecommendations with the necessary attributes
|
# Creating a mock instance of WallRecommendations with the necessary attributes
|
||||||
property_mock = Mock()
|
property_mock = Mock()
|
||||||
property_mock.full_sap_epc = {"lodgement-date": "2000-01-01"} # or any date you want
|
epc_record = EPCRecord()
|
||||||
property_mock.data = {"construction-age-band": "1950",
|
epc_record.construction_age_band = "1950"
|
||||||
"county": "Derbyshire"} # or any other data that fits your tests
|
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(
|
mock_wall_rec_instance = WallRecommendations(
|
||||||
property_mock, materials=materials
|
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
|
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(
|
p = Mock(
|
||||||
id=2,
|
id=2,
|
||||||
year_built=1930,
|
year_built=1930,
|
||||||
|
|
@ -116,7 +123,7 @@ class TestWallRecommendations:
|
||||||
'is_sandstone_or_limestone': False, 'insulation_thickness': 'none', 'external_insulation': False,
|
'is_sandstone_or_limestone': False, 'insulation_thickness': 'none', 'external_insulation': False,
|
||||||
'internal_insulation': False, 'is_park_home': 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(
|
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
|
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(
|
p = Mock(
|
||||||
id=3,
|
id=3,
|
||||||
year_built=1991,
|
year_built=1991,
|
||||||
|
|
@ -157,7 +168,6 @@ class TestWallRecommendations:
|
||||||
insulation_wall_area=100,
|
insulation_wall_area=100,
|
||||||
already_installed=[],
|
already_installed=[],
|
||||||
in_conservation_area="not_in_conservation_area",
|
in_conservation_area="not_in_conservation_area",
|
||||||
data={'county': 'Greater London Authority', 'property-type': 'Flat'},
|
|
||||||
walls={
|
walls={
|
||||||
'original_description': 'Solid brick, as built, insulated (assumed)',
|
'original_description': 'Solid brick, as built, insulated (assumed)',
|
||||||
'clean_description': 'Solid brick, as built, insulated',
|
'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_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,
|
'is_sandstone_or_limestone': False, 'insulation_thickness': 'average', 'external_insulation': False,
|
||||||
'internal_insulation': False
|
'internal_insulation': False
|
||||||
}
|
},
|
||||||
|
epc_record=epc_record
|
||||||
)
|
)
|
||||||
|
|
||||||
recommender = WallRecommendations(
|
recommender = WallRecommendations(
|
||||||
|
|
@ -247,7 +257,8 @@ class TestWallRecommendationsBase:
|
||||||
property_mock.in_conservation_area = "not_in_conservation_area"
|
property_mock.in_conservation_area = "not_in_conservation_area"
|
||||||
property_mock.restricted_measures = False
|
property_mock.restricted_measures = False
|
||||||
property_mock.insulation_wall_area = 100
|
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 = {
|
property_mock.walls = {
|
||||||
"is_cob": False,
|
"is_cob": False,
|
||||||
"is_sandstone_or_limestone": False,
|
"is_sandstone_or_limestone": False,
|
||||||
|
|
@ -268,21 +279,21 @@ class TestWallRecommendationsBase:
|
||||||
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):
|
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
|
assert wall_recommendations_instance.ewi_valid() is False
|
||||||
|
|
||||||
def test_ewi_valid_not_in_conservation_area_and_not_flat(self, wall_recommendations_instance):
|
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.in_conservation_area = "not_in_conversation_area"
|
||||||
wall_recommendations_instance.property.restricted_measures = False
|
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
|
assert wall_recommendations_instance.ewi_valid() is True
|
||||||
|
|
||||||
|
|
||||||
class TestCavityWallRecommensations:
|
class TestCavityWallRecommensations:
|
||||||
|
|
||||||
def test_fill_empty_cavity(self):
|
def test_fill_empty_cavity(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord(county="Derbyshire", walls_energy_eff="Very Poor", property_type="House")
|
||||||
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 = Property(id=1, postcode="F4k3", address="123 fake street", epc_record=epc_record)
|
||||||
input_property.walls = {
|
input_property.walls = {
|
||||||
'original_description': 'Cavity wall, as built, no insulation (assumed)',
|
'original_description': 'Cavity wall, as built, no insulation (assumed)',
|
||||||
|
|
@ -315,8 +326,7 @@ class TestCavityWallRecommensations:
|
||||||
assert np.isclose(recommender.recommendations[0]["total"], 925)
|
assert np.isclose(recommender.recommendations[0]["total"], 925)
|
||||||
|
|
||||||
def test_fill_partial_filled_cavity(self):
|
def test_fill_partial_filled_cavity(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord(county="County Durham", walls_energy_eff="Poor", property_type="House")
|
||||||
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 = Property(id=1, postcode="F4k3", address="123 fake street", epc_record=epc_record)
|
||||||
input_property.walls = {
|
input_property.walls = {
|
||||||
'original_description': 'Cavity wall, as built, partial insulation (assumed)',
|
'original_description': 'Cavity wall, as built, partial insulation (assumed)',
|
||||||
|
|
@ -349,10 +359,8 @@ class TestCavityWallRecommensations:
|
||||||
assert np.isclose(recommender.recommendations[0]["total"], 925.0)
|
assert np.isclose(recommender.recommendations[0]["total"], 925.0)
|
||||||
|
|
||||||
def test_system_built_wall(self):
|
def test_system_built_wall(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||||
epc_record.prepared_epc = {
|
walls_energy_eff="Very Poor")
|
||||||
"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 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
|
||||||
input_property2.walls = {
|
input_property2.walls = {
|
||||||
'original_description': 'System built, as built, no insulation (assumed)',
|
'original_description': 'System built, as built, no insulation (assumed)',
|
||||||
|
|
@ -387,21 +395,11 @@ class TestCavityWallRecommensations:
|
||||||
assert recommender2.estimated_u_value == 1
|
assert recommender2.estimated_u_value == 1
|
||||||
assert np.isclose(recommender2.recommendations[0]["new_u_value"], 0.21)
|
assert np.isclose(recommender2.recommendations[0]["new_u_value"], 0.21)
|
||||||
assert np.isclose(recommender2.recommendations[0]["total"], 35802.0)
|
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):
|
def test_timber_frame_wall(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||||
epc_record.prepared_epc = {
|
walls_energy_eff="Very Poor")
|
||||||
"property-type": "House", "county": "Derbyshire", "built-form": "Semi-Detached",
|
input_property3 = Property(id=1, postcode="F4k3 3", address="323 fake street", epc_record=epc_record)
|
||||||
"walls-energy-eff": "Very Poor"
|
|
||||||
}
|
|
||||||
input_property3 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
|
|
||||||
input_property3.walls = {
|
input_property3.walls = {
|
||||||
'original_description': 'Timber frame, as built, no insulation (assumed)',
|
'original_description': 'Timber frame, as built, no insulation (assumed)',
|
||||||
'clean_description': 'Timber frame, as built, no insulation',
|
'clean_description': 'Timber frame, as built, no insulation',
|
||||||
|
|
@ -413,14 +411,12 @@ class TestCavityWallRecommensations:
|
||||||
'insulation_thickness': 'none', 'external_insulation': False,
|
'insulation_thickness': 'none', 'external_insulation': False,
|
||||||
'internal_insulation': False
|
'internal_insulation': False
|
||||||
}
|
}
|
||||||
input_property3.age_band = "B"
|
input_property3.age_band = "F"
|
||||||
input_property3.insulation_wall_area = 99
|
input_property3.insulation_wall_area = 120
|
||||||
input_property3.restricted_measures = False
|
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 = []
|
input_property3.already_installed = []
|
||||||
|
|
||||||
assert input_property3.walls["is_timber_frame"]
|
|
||||||
|
|
||||||
recommender3 = WallRecommendations(
|
recommender3 = WallRecommendations(
|
||||||
property_instance=input_property3,
|
property_instance=input_property3,
|
||||||
materials=materials
|
materials=materials
|
||||||
|
|
@ -431,25 +427,14 @@ class TestCavityWallRecommensations:
|
||||||
recommender3.recommend()
|
recommender3.recommend()
|
||||||
|
|
||||||
assert recommender3.recommendations
|
assert recommender3.recommendations
|
||||||
assert len(recommender3.recommendations) == 2
|
assert recommender3.estimated_u_value == 0.45
|
||||||
assert recommender3.estimated_u_value == 1.9
|
assert np.isclose(recommender3.recommendations[0]["new_u_value"], 0.17)
|
||||||
assert np.isclose(recommender3.recommendations[0]["new_u_value"], 0.23)
|
assert np.isclose(recommender3.recommendations[0]["total"], 35802.0)
|
||||||
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
|
|
||||||
|
|
||||||
def test_granite_or_whinstone_wall(self):
|
def test_granite_or_whinstone_wall(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||||
epc_record.prepared_epc = {
|
walls_energy_eff="Very Poor")
|
||||||
"property-type": "Bungalow", "county": "Derbyshire", "built-form": "Detached",
|
input_property4 = Property(id=1, postcode="F4k3 4", address="423 fake street", epc_record=epc_record)
|
||||||
"walls-energy-eff": "Very Poor"
|
|
||||||
}
|
|
||||||
input_property4 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
|
|
||||||
input_property4.walls = {
|
input_property4.walls = {
|
||||||
'original_description': 'Granite or whinstone, as built, no insulation (assumed)',
|
'original_description': 'Granite or whinstone, as built, no insulation (assumed)',
|
||||||
'clean_description': 'Granite or whinstone, as built, no insulation',
|
'clean_description': 'Granite or whinstone, as built, no insulation',
|
||||||
|
|
@ -461,14 +446,12 @@ class TestCavityWallRecommensations:
|
||||||
'insulation_thickness': 'none', 'external_insulation': False,
|
'insulation_thickness': 'none', 'external_insulation': False,
|
||||||
'internal_insulation': False
|
'internal_insulation': False
|
||||||
}
|
}
|
||||||
input_property4.age_band = "A"
|
input_property4.age_band = "F"
|
||||||
input_property4.insulation_wall_area = 223
|
input_property4.insulation_wall_area = 120
|
||||||
input_property4.restricted_measures = False
|
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 = []
|
input_property4.already_installed = []
|
||||||
|
|
||||||
assert input_property4.walls["is_granite_or_whinstone"]
|
|
||||||
|
|
||||||
recommender4 = WallRecommendations(
|
recommender4 = WallRecommendations(
|
||||||
property_instance=input_property4,
|
property_instance=input_property4,
|
||||||
materials=materials
|
materials=materials
|
||||||
|
|
@ -478,45 +461,29 @@ class TestCavityWallRecommensations:
|
||||||
|
|
||||||
recommender4.recommend()
|
recommender4.recommend()
|
||||||
|
|
||||||
assert recommender4.recommendations
|
assert not 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
|
|
||||||
|
|
||||||
def test_cob_wall(self):
|
def test_cob_wall(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||||
epc_record.prepared_epc = {
|
walls_energy_eff="Very Poor")
|
||||||
"property-type": "Bungalow", "county": "Derbyshire", "built-form": "Detached",
|
input_property5 = Property(id=1, postcode="F4k3 5", address="523 fake street", epc_record=epc_record)
|
||||||
"walls-energy-eff": "Very Poor"
|
|
||||||
}
|
|
||||||
input_property5 = Property(id=1, postcode="F4k3 2", address="223 fake street", epc_record=epc_record)
|
|
||||||
input_property5.walls = {
|
input_property5.walls = {
|
||||||
'original_description': 'Cob, as built',
|
'original_description': 'Cob, as built, no insulation (assumed)',
|
||||||
'clean_description': 'Cob, as built',
|
'clean_description': 'Cob, as built, no insulation',
|
||||||
'thermal_transmittance': None, 'thermal_transmittance_unit': None,
|
'thermal_transmittance': None, 'thermal_transmittance_unit': None,
|
||||||
'is_cavity_wall': False, 'is_filled_cavity': False, 'is_solid_brick': False,
|
'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_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,
|
'is_sandstone_or_limestone': False, 'is_park_home': False,
|
||||||
'insulation_thickness': 'none', 'external_insulation': False,
|
'insulation_thickness': 'none', 'external_insulation': False,
|
||||||
'internal_insulation': False
|
'internal_insulation': False
|
||||||
}
|
}
|
||||||
input_property5.age_band = "E"
|
input_property5.age_band = "F"
|
||||||
input_property5.insulation_wall_area = 77
|
input_property5.insulation_wall_area = 120
|
||||||
input_property5.restricted_measures = False
|
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 = []
|
input_property5.already_installed = []
|
||||||
|
|
||||||
assert input_property5.walls["is_cob"]
|
|
||||||
|
|
||||||
recommender5 = WallRecommendations(
|
recommender5 = WallRecommendations(
|
||||||
property_instance=input_property5,
|
property_instance=input_property5,
|
||||||
materials=materials
|
materials=materials
|
||||||
|
|
@ -526,15 +493,11 @@ class TestCavityWallRecommensations:
|
||||||
|
|
||||||
recommender5.recommend()
|
recommender5.recommend()
|
||||||
|
|
||||||
# No insulation recommendations for cob walls
|
|
||||||
assert not recommender5.recommendations
|
assert not recommender5.recommendations
|
||||||
|
|
||||||
def test_sandstone_or_limestone_wall(self):
|
def test_sandstone_or_limestone_wall(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord(property_type="House", county="Derbyshire", built_form="Detached",
|
||||||
epc_record.prepared_epc = {
|
walls_energy_eff="Very Poor")
|
||||||
"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 = Property(id=1, postcode="F4k3 6", address="623 fake street", epc_record=epc_record)
|
||||||
input_property6.walls = {
|
input_property6.walls = {
|
||||||
'original_description': 'Sandstone or limestone, as built, no insulation (assumed)',
|
'original_description': 'Sandstone or limestone, as built, no insulation (assumed)',
|
||||||
|
|
@ -542,13 +505,13 @@ class TestCavityWallRecommensations:
|
||||||
'thermal_transmittance': None, 'thermal_transmittance_unit': None,
|
'thermal_transmittance': None, 'thermal_transmittance_unit': None,
|
||||||
'is_cavity_wall': False, 'is_filled_cavity': False, 'is_solid_brick': False,
|
'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_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,
|
'is_sandstone_or_limestone': True, 'is_park_home': False,
|
||||||
'insulation_thickness': 'none', 'external_insulation': False,
|
'insulation_thickness': 'none', 'external_insulation': False,
|
||||||
'internal_insulation': False
|
'internal_insulation': False
|
||||||
}
|
}
|
||||||
input_property6.age_band = "F"
|
input_property6.age_band = "F"
|
||||||
input_property6.insulation_wall_area = 350
|
input_property6.insulation_wall_area = 120
|
||||||
input_property6.restricted_measures = False
|
input_property6.restricted_measures = False
|
||||||
input_property6.construction_age_band = "England and Wales: 1976-1982"
|
input_property6.construction_age_band = "England and Wales: 1976-1982"
|
||||||
input_property6.already_installed = []
|
input_property6.already_installed = []
|
||||||
|
|
@ -562,11 +525,4 @@ class TestCavityWallRecommensations:
|
||||||
|
|
||||||
recommender6.recommend()
|
recommender6.recommend()
|
||||||
|
|
||||||
# For sandstone walls, we only recommend internal wall insulation
|
assert not recommender6.recommendations
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,14 @@ class TestWindowRecommendations:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Wychavon"
|
||||||
"county": "Wychavon",
|
epc_record.multi_glaze_proportion = 0
|
||||||
"multi-glaze-proportion": 0,
|
epc_record.uprn = 0
|
||||||
"uprn": 0,
|
epc_record.windows_energy_eff = "Very Poor"
|
||||||
"windows-energy-eff": "Very Poor",
|
epc_record.floor_area = 2.5
|
||||||
"floor-area": 2.5,
|
epc_record.number_habitable_rooms = 5
|
||||||
"number-habitable-rooms": 5,
|
epc_record.number_heated_rooms = 5
|
||||||
"number-heated-rooms": 5,
|
|
||||||
}
|
|
||||||
property_1 = Property(
|
property_1 = Property(
|
||||||
id=1,
|
id=1,
|
||||||
postcode='1',
|
postcode='1',
|
||||||
|
|
@ -79,12 +78,11 @@ class TestWindowRecommendations:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Wychavon"
|
||||||
"county": "Wychavon",
|
epc_record.multi_glaze_proportion = 33
|
||||||
"multi-glaze-proportion": 33,
|
epc_record.uprn = 0
|
||||||
"uprn": 0,
|
epc_record.windows_energy_eff = "Good" # This has been observed in the EPC data
|
||||||
"windows-energy-eff": "Good" # This has been observed in the EPC data
|
|
||||||
}
|
|
||||||
property_2 = Property(
|
property_2 = Property(
|
||||||
id=1,
|
id=1,
|
||||||
postcode='1',
|
postcode='1',
|
||||||
|
|
@ -124,11 +122,10 @@ class TestWindowRecommendations:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Wychavon"
|
||||||
"county": "Wychavon",
|
epc_record.multi_glaze_proportion = 100
|
||||||
"multi-glaze-proportion": 100,
|
epc_record.uprn = 0
|
||||||
"uprn": 0
|
|
||||||
}
|
|
||||||
property_3 = Property(
|
property_3 = Property(
|
||||||
id=1,
|
id=1,
|
||||||
postcode='1',
|
postcode='1',
|
||||||
|
|
@ -154,11 +151,10 @@ class TestWindowRecommendations:
|
||||||
|
|
||||||
def test_fully_secondary_glazed(self):
|
def test_fully_secondary_glazed(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Wychavon"
|
||||||
"county": "Wychavon",
|
epc_record.multi_glaze_proportion = 100
|
||||||
"multi-glaze-proportion": 100,
|
epc_record.uprn = 0
|
||||||
"uprn": 0
|
|
||||||
}
|
|
||||||
property_4 = Property(
|
property_4 = Property(
|
||||||
id=1,
|
id=1,
|
||||||
postcode='1',
|
postcode='1',
|
||||||
|
|
@ -185,12 +181,11 @@ class TestWindowRecommendations:
|
||||||
|
|
||||||
def test_partial_secondary_glazing(self):
|
def test_partial_secondary_glazing(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Wychavon"
|
||||||
"county": "Wychavon",
|
epc_record.multi_glaze_proportion = 50
|
||||||
"multi-glaze-proportion": 50,
|
epc_record.uprn = 0
|
||||||
"uprn": 0,
|
epc_record.windows_energy_eff = "Poor" # This has been observed in the EPC data
|
||||||
"windows-energy-eff": "Poor" # This has been observed in the EPC data
|
|
||||||
}
|
|
||||||
property_5 = Property(
|
property_5 = Property(
|
||||||
id=1,
|
id=1,
|
||||||
postcode='1',
|
postcode='1',
|
||||||
|
|
@ -225,12 +220,10 @@ class TestWindowRecommendations:
|
||||||
|
|
||||||
def test_single_glazed_restricted_measures(self):
|
def test_single_glazed_restricted_measures(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Wychavon"
|
||||||
"county": "Wychavon",
|
epc_record.multi_glaze_proportion = 0
|
||||||
"multi-glaze-proportion": 0,
|
epc_record.uprn = 0
|
||||||
"uprn": 0,
|
epc_record.windows_energy_eff = "Very Poor"
|
||||||
"windows-energy-eff": "Very Poor"
|
|
||||||
}
|
|
||||||
|
|
||||||
property_6 = Property(
|
property_6 = Property(
|
||||||
id=1,
|
id=1,
|
||||||
|
|
@ -270,11 +263,10 @@ class TestWindowRecommendations:
|
||||||
|
|
||||||
def test_full_triple_glazed(self):
|
def test_full_triple_glazed(self):
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Wychavon"
|
||||||
"county": "Wychavon",
|
epc_record.multi_glaze_proportion = 100
|
||||||
"multi-glaze-proportion": 100,
|
epc_record.uprn = 0
|
||||||
"uprn": 0
|
|
||||||
}
|
|
||||||
property_7 = Property(
|
property_7 = Property(
|
||||||
id=1,
|
id=1,
|
||||||
postcode='1',
|
postcode='1',
|
||||||
|
|
@ -303,11 +295,10 @@ class TestWindowRecommendations:
|
||||||
We don't recommend anything here
|
We don't recommend anything here
|
||||||
"""
|
"""
|
||||||
epc_record = EPCRecord()
|
epc_record = EPCRecord()
|
||||||
epc_record.prepared_epc = {
|
epc_record.county = "Wychavon"
|
||||||
"county": "Wychavon",
|
epc_record.multi_glaze_proportion = 80
|
||||||
"multi-glaze-proportion": 80,
|
epc_record.uprn = 1
|
||||||
"uprn": 1
|
|
||||||
}
|
|
||||||
property_8 = Property(
|
property_8 = Property(
|
||||||
id=1,
|
id=1,
|
||||||
postcode='1',
|
postcode='1',
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue