diff --git a/.gitignore b/.gitignore index 68bc17f9..947e0397 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,82 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +.idea/artifacts +.idea/compiler.xml +.idea/jarRepositories.xml +.idea/modules.xml +.idea/*.iml +.idea/modules +*.iml +*.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/Model.iml b/.idea/Model.iml new file mode 100644 index 00000000..a7ea3cf1 --- /dev/null +++ b/.idea/Model.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..a55e7a17 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..9d66ac63 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,77 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..242c02bb --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..62dd107c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/toolchains.xml b/.idea/toolchains.xml new file mode 100644 index 00000000..45814209 --- /dev/null +++ b/.idea/toolchains.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..629b9b28 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Model Repository + +This repository contains the code pertaining to the development +of the data science and machine learning products being utilised by +Hestia. + +The different folders in this repository relate to services +that can be used independently, or can be imported and used as +part of a larger application + +# Folders + +### epc_data/ +This folder contains related to the reading and preparation of +epc data \ No newline at end of file diff --git a/epc_data/EpcClean.py b/epc_data/EpcClean.py new file mode 100644 index 00000000..08f024f2 --- /dev/null +++ b/epc_data/EpcClean.py @@ -0,0 +1,256 @@ +import re +from typing import List, Dict, Any, Union, Tuple, Optional +from collections import Counter + + +class EpcClean: + """ + Container for methods which we utilise for cleaning EPC data + """ + + U_VALUE_REGEX = re.compile(r"(\d+\.\d+)") + UNIT_REGEX = re.compile(r"(w/m-¦k)") + + CLEANING_FIELDS: List[str] = [ + "roof-description", + "floor-description", + "walls-description", + "mainheat-description" + ] + + def __init__(self, data: List[Dict[str, Any]]) -> None: + """ + EpcClean constructor. + + :param data: List of dictionaries containing EPC data. + """ + self.data: List[Dict[str, Any]] = data + self.unique_vals: Dict[str, Any] = {} + self.cleaned: Dict[str, List[Any]] = {} + + def clean(self) -> None: + """ + Cleans the EPC data, mapping text fields to property attributes. + """ + self._init_empty_cleaned_obj() + + for field in self.CLEANING_FIELDS: + self.unique_vals[field] = Counter([v[field] for v in self.data]) + + for description in self.unique_vals["roof-description"].keys(): + self.cleaned["roof-description"].append( + { + "original_description": description, + **self.clean_roof(description) + } + ) + + def _init_empty_cleaned_obj(self) -> None: + """ + Initializes an empty object for cleaned data. + """ + self.cleaned = {field: [] for field in self.CLEANING_FIELDS} + + @staticmethod + def _search_split_roof_description(desc: str) -> str: + """ + Searches roof descriptions and looks for key words, determining a description about the roof's insulation. + + :param desc: Description to be searched. + :return: Result of the search. + """ + if desc == "insulated": + return "average" + if desc == "limited": + return "below average" + raise NotImplementedError("Handle me") + + def _find_insulation_thickness( + self, description_lower: str, is_pitched: bool, is_roof_room: bool, is_flat: bool + ) -> Union[int, str, None]: + """ + Finds insulation thickness in the description. + + :param description_lower: Lowercase description. + :param is_pitched: Whether the roof is pitched. + :param is_roof_room: Whether there is a room in the roof. + :param is_flat: Whether the roof is flat. + :return: Insulation thickness if found, else None. + """ + if "no insulation" in description_lower: + return 0 + + if is_pitched: + try: + thickness = description_lower.split("pitched,")[-1].split("mm")[0].strip() + if "+" in thickness: + return thickness + try: + return int(thickness) + except ValueError as int_error: + raise ValueError(int_error) + except ValueError as _: + if "invalid input" in description_lower: + return None + desc = description_lower.split("pitched,")[-1].strip().split(" ")[0] + return self._search_split_roof_description(desc) + + if is_roof_room: + desc_split_lookup = { + "ceiling insulated": "average", + "thatched": "average", + } + # Just search for specific phrases + desc_split = description_lower.split("roof room(s),")[-1].strip() + res = desc_split_lookup.get(desc_split) + if res: + return res + + desc = desc_split.split(" ")[0] + return self._search_split_roof_description(desc) + + if is_flat: + # Just search for specific phrases + desc = description_lower.split("flat,")[-1].lstrip().split(" ")[0] + return self._search_split_roof_description(desc) + + return None + + def _extract_thermal_transmittence(self, description_lower: str) -> Tuple[Union[float, None], Union[str, None]]: + """ + Extracts thermal transmittance from the description. + + :param description_lower: Lowercase description. + :return: Tuple containing U-value and unit. + """ + # Find U-value + u_value = re.search(self.U_VALUE_REGEX, description_lower) + if u_value is not None: + u_value = float(u_value.group(1)) + else: + u_value = None + + # Find unit + unit = re.search(self.UNIT_REGEX, description_lower) + if unit is not None: + unit = unit.group(1) + else: + unit = None + + return u_value, unit + + @staticmethod + def _make_clean_roof_output( + is_valid: bool, + at_rafters: bool, + is_pitched: bool, + is_roof_room: bool, + has_loft: bool, + insulation_thickness: str | int | None, + has_dwelling_above: bool, + assumed: bool, + is_flat: bool, + is_thatched: bool, + thermal_transmittence: Optional[float], + thermal_transmittence_unit: Optional[str] + ) -> Dict[str, Union[bool, str, None]]: + """ + Utility function to ensure all the keys are present in the output. + + :param is_valid: True if the roof descrption is valid, False otherwise + :param at_rafters: True if the insulation is at the rafters, False otherwise + :param is_pitched: True if the roof is pitched, False otherwise + :param is_roof_room: True if there is a room in the roof, False otherwise + :param has_loft: True if there is a loft, False otherwise + :param insulation_thickness: The thickness of the insulation + :param has_dwelling_above: True if there is a dwelling above, False otherwise + :param assumed: True if the roof type was assumed based on property age, False otherwise + :param is_flat: True if the roof is flat, False otherwise + :param is_thatched: True if the roof is thatched, False otherwise + :param thermal_transmittence: The thermal transmittence value of the roof, if known + :param thermal_transmittence_unit: The unit of thermal transmittence, if known + :return: A dictionary containing all the information about the roof. + """ + + return { + "is_valid": is_valid, + "at_rafters": at_rafters, + "is_pitched": is_pitched, + "is_roof_room": is_roof_room, + "has_loft": has_loft, + "insulation_thickness": insulation_thickness, + "has_dwelling_above": has_dwelling_above, + "assumed": assumed, + "is_flat": is_flat, + "is_thatched": is_thatched, + "thermal_transmittence": thermal_transmittence, + "thermal_transmittence_unit": thermal_transmittence_unit + } + + def clean_roof(self, description: str) -> Dict[str, Union[str, bool, int, None]]: + """ + We aim to extract features about the roof, so we can characterise it. We will check: + - If the roof is pitched + - If there is a room roof + - if there is a loft + - If it has insulation + - if so, what degree of insulation + + :param description: Description of the roof. + :return: Dictionary of attributes of the roof. + """ + description_lower = description.lower().strip() + + if "another dwelling above" in description_lower or "other premises above" in description_lower: + return self._make_clean_roof_output( + is_valid="invalid" not in description_lower, + at_rafters="at rafters" in description_lower, + is_pitched=False, + is_roof_room=False, + has_loft=False, + insulation_thickness=0, + has_dwelling_above=True, + assumed="assumed" in description_lower, + is_flat="flat" in description_lower, + is_thatched=False, + thermal_transmittence=None, + thermal_transmittence_unit=None + ) + + is_pitched = "pitched" in description_lower + is_roof_room = "roof room" in description_lower + has_loft = "loft" in description_lower + is_flat = "flat" in description_lower + is_thatched = "thatched" in description_lower + at_rafters = "at rafters" in description_lower + + thermal_transmittence, thermal_transmittence_unit, insulation_thickness = None, None, None + if "insulation" in description_lower or "insulated" in description_lower: + insulation_thickness = self._find_insulation_thickness(description_lower, is_pitched, is_roof_room, is_flat) + elif "thermal transmittance" in description_lower: + thermal_transmittence, thermal_transmittence_unit = self._extract_thermal_transmittence(description_lower) + elif is_thatched: + # Search for these features: + thermal_transmittence, thermal_transmittence_unit = self._extract_thermal_transmittence(description_lower) + insulation_thickness = self._find_insulation_thickness( + description_lower, is_pitched, is_roof_room, is_flat + ) + elif description_lower == "pitched": + thermal_transmittence, thermal_transmittence_unit, insulation_thickness = None, None, None + else: + raise NotImplementedError("Not handled this") + + return self._make_clean_roof_output( + is_valid="invalid" not in description_lower, + at_rafters=at_rafters, + is_pitched=is_pitched, + is_roof_room=is_roof_room, + has_loft=has_loft, + insulation_thickness=insulation_thickness, + has_dwelling_above=False, + assumed="assumed" in description_lower, + is_flat=is_flat, + is_thatched=is_thatched, + thermal_transmittence=thermal_transmittence, + thermal_transmittence_unit=thermal_transmittence_unit + ) diff --git a/epc_data/Property.py b/epc_data/Property.py new file mode 100644 index 00000000..da963ef1 --- /dev/null +++ b/epc_data/Property.py @@ -0,0 +1,31 @@ +from epc_api.client import EpcClient +from epc_data.config import EPC_AUTH_TOKEN + + +class Property: + + def __init__(self, postcode, address1, epc_client=None, data=None): + self.postcode = postcode + self.address1 = address1 + self.data = data + + if epc_client: + self.epc_client = epc_client + else: + self.epc_client = EpcClient(auth_token=EPC_AUTH_TOKEN) + + def search_address_epc(self): + """ + This method searches for an address in the EPC database and returns the first result + :return: property data + """ + if self.data: + return + + # This will fail if a property does not have an EPC - this has been documented as a case to handle + response = self.epc_client.domestic.search(params={"address": self.address1, "postcode": self.postcode}) + + if len(response["rows"]) > 1: + raise Exception("More than one result found for this address - investigate me") + + self.data = response["rows"][0] diff --git a/epc_data/README.md b/epc_data/README.md new file mode 100644 index 00000000..17f1227f --- /dev/null +++ b/epc_data/README.md @@ -0,0 +1,49 @@ +# Environment setup + +We're using conda to manage environments to circumvent the +issues with Mac M1. This documentation will also cover Pycharm setup. + +We're working in python 3.10 so + +```commandline +conda create -n hestia-data python=3.10 +``` + +Then activate the environment + +```commandline +conda activate hestia-data +``` + +To set up with Pycharm, run + +```commandline +which python +``` + +and grab the path to the python executable. Then in Pycharm, go to +Settings > Project > Python Interpreter and click the gear icon +to add a new interpreter. Select Conda and either paste the path to the python executable +and click OK, or select the conda environment from the dropdown. + +You may need to restart Pycharm for the new interpreter to be recognised. + +To install project dependencies navigate to /epc_data and run + +```commandline +pip install -r requirements.txt +``` + +### Running Tests + +If you are not in a virtual environment, activate it with + +```commandline +conda activate envName +``` + +Then run + +```commandline +python -m pytest +``` diff --git a/epc_data/__init__.py b/epc_data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/epc_data/app.py b/epc_data/app.py new file mode 100644 index 00000000..7788b7d2 --- /dev/null +++ b/epc_data/app.py @@ -0,0 +1,42 @@ +from tqdm import tqdm + +from epc_data.temp_inputs import input_data +from epc_data.Property import Property +from epc_data.config import EPC_AUTH_TOKEN +from epc_api.client import EpcClient +from epc_data.downloader import pagenated_epc_download +from epc_data.EpcClean import EpcClean + + +def handler(): + # To begin with, the input data is a list of dictionaries, however we would read this file in + + epc_client = EpcClient(auth_token=EPC_AUTH_TOKEN) + + input_properties = [ + Property(postcode=config['postcode'], address1=config['address1'], epc_client=epc_client) + for config in input_data + ] + + for p in input_properties: + p.search_address_epc() + + local_authorities = {p.data['local-authority'] for p in input_properties} + + data = [] + for la in tqdm(local_authorities): + data.extend( + pagenated_epc_download( + client=epc_client, + params={"local-authority": la}, + page_size=5000, + n_pages=10, + ) + ) + + cleaner = EpcClean(data) + + cleaner.clean() + + import pandas as pd + df = pd.DataFrame(cleaner.cleaned["roof-description"]) diff --git a/epc_data/config.py b/epc_data/config.py new file mode 100644 index 00000000..b4c8db13 --- /dev/null +++ b/epc_data/config.py @@ -0,0 +1,6 @@ +import os +from dotenv import load_dotenv + +load_dotenv(dotenv_path='epc_data/.env') + +EPC_AUTH_TOKEN = os.environ.get('EPC_AUTH_TOKEN') diff --git a/epc_data/downloader.py b/epc_data/downloader.py new file mode 100644 index 00000000..b1df1f73 --- /dev/null +++ b/epc_data/downloader.py @@ -0,0 +1,26 @@ +import time + + +def pagenated_epc_download(client, params, page_size, n_pages, verbose=0, slowdown=0.1): + offset_from = 0 + n_completed = 0 + results = [] + complete = False + while not complete: + if verbose: + print("Pulling for page %s" % str(int(offset_from / page_size) + 1)) + time.sleep(slowdown) + search_resp = client.domestic.search(params=params, offset_from=offset_from, size=page_size) + + # Note: We can only make 10k queries for a single set of search queries. + # It might make sense to download data via zip for machine learning since we don't need this + # data to be perfectly up to date + if search_resp is None: + break + results.extend(search_resp["rows"]) + if n_completed == n_pages: + complete = True + else: + offset_from += page_size + + return results diff --git a/epc_data/requirements.txt b/epc_data/requirements.txt new file mode 100644 index 00000000..908ce76c --- /dev/null +++ b/epc_data/requirements.txt @@ -0,0 +1,6 @@ +epc-api-python +python-dotenv +tqdm +pandas +mypy +pytest \ No newline at end of file diff --git a/epc_data/temp_inputs.py b/epc_data/temp_inputs.py new file mode 100644 index 00000000..264a7cb2 --- /dev/null +++ b/epc_data/temp_inputs.py @@ -0,0 +1,11 @@ +# Temporary input data +input_data = [ + { + "address1": "28 Distillery Wharf", + "postcode": "w6 9bf" + }, + { + "address1": "Flat 14 Godley V C House", + "postcode": "E2 0LP" + } +] diff --git a/epc_data/tests/__init__.py b/epc_data/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/epc_data/tests/test_EpcClean.py b/epc_data/tests/test_EpcClean.py new file mode 100644 index 00000000..901d250e --- /dev/null +++ b/epc_data/tests/test_EpcClean.py @@ -0,0 +1,94 @@ +import pytest +import pickle +from epc_data.EpcClean import EpcClean +from pathlib import Path +from epc_data.tests.test_data.EpcClean_test_roof_cases import clean_roof_test_cases + +# For local testing +if __file__ == "": + input_data_path = Path("./epc_data/tests/test_data/EpcClean_inputs.obj") +else: + current_file_path = Path(__file__) + input_data_path = current_file_path.parent / 'test_data' / 'EpcClean_inputs.obj' + + +class TestEpcClean: + + @staticmethod + def load_data(path): + with open(path, "rb") as file: + return pickle.load(file) + + @pytest.fixture(autouse=True) + def setup_class(self): + self.cleaner = EpcClean(self.load_data(input_data_path)) + + def test_clean(self): + self.cleaner.clean() + assert len(self.cleaner.cleaned["roof-description"]) == len(self.cleaner.unique_vals["roof-description"]) + + def test__init_empty_cleaned_obj(self): + self.cleaner._init_empty_cleaned_obj() + assert all([len(values) == 0 for values in self.cleaner.cleaned.values()]) + + def test__search_split_roof_description(self): + assert self.cleaner._search_split_roof_description("insulated") == "average" + assert self.cleaner._search_split_roof_description("limited") == "below average" + with pytest.raises(NotImplementedError): + self.cleaner._search_split_roof_description("unknown") + + def test__find_insulation_thickness(self): + assert self.cleaner._find_insulation_thickness("no insulation", False, False, False) == 0 + + def test__extract_thermal_transmittence(self): + description = "U-value of 2.3 w/m-¦k" + assert self.cleaner._extract_thermal_transmittence(description) == (2.3, "w/m-¦k") + + def test_clean_roof(self): + result = self.cleaner.clean_roof('Pitched, 270 mm loft insulation') + + # change the expected output based on your requirement + expected_output = { + "is_valid": True, + "at_rafters": False, + "is_pitched": True, + "is_roof_room": False, + "has_loft": True, + "insulation_thickness": 270, + "has_dwelling_above": False, + "assumed": False, + "is_flat": False, + "is_thatched": False, + "thermal_transmittence": None, + "thermal_transmittence_unit": None + } + + assert result == expected_output + + for test_case in clean_roof_test_cases: + result = self.cleaner.clean_roof(test_case['original_description']) + # Ensure the output ordering is correct + expected_result = {key: test_case[key] for key in result.keys()} + expected_result["desc"] = test_case["original_description"] + result["desc"] = test_case["original_description"] + assert result == expected_result + + def test_clean_roof_with_dwelling_above(self): + result = self.cleaner.clean_roof('(another dwelling above)') + + expected_output = { + "is_valid": True, + "at_rafters": False, + "is_pitched": False, + "is_roof_room": False, + "has_loft": False, + "insulation_thickness": 0, + "has_dwelling_above": True, + "assumed": False, + "is_flat": False, + "is_thatched": False, + "thermal_transmittence": None, + "thermal_transmittence_unit": None, + } + + assert result == expected_output diff --git a/epc_data/tests/test_data/EpcClean_inputs.obj b/epc_data/tests/test_data/EpcClean_inputs.obj new file mode 100644 index 00000000..817cb6f4 Binary files /dev/null and b/epc_data/tests/test_data/EpcClean_inputs.obj differ diff --git a/epc_data/tests/test_data/EpcClean_test_roof_cases.py b/epc_data/tests/test_data/EpcClean_test_roof_cases.py new file mode 100644 index 00000000..344d4b55 --- /dev/null +++ b/epc_data/tests/test_data/EpcClean_test_roof_cases.py @@ -0,0 +1,261 @@ +clean_roof_test_cases = [ + {'original_description': 'Flat, limited insulation (assumed)', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': 'below average', 'has_dwelling_above': False, 'assumed': True, + 'is_flat': True, 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, + 'is_valid': True, 'at_rafters': False}, + {'original_description': '(another dwelling above)', 'is_pitched': False, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': 0, 'has_dwelling_above': True, 'assumed': False, 'is_flat': False, 'is_thatched': False, + 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Pitched, insulated (assumed)', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': 'average', 'has_dwelling_above': False, 'assumed': True, + 'is_flat': False, 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, + 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Flat, insulated (assumed)', 'is_pitched': False, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': 'average', 'has_dwelling_above': False, 'assumed': True, 'is_flat': True, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 200 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 200, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 50 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 50, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, no insulation (assumed)', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': 0, 'has_dwelling_above': False, 'assumed': True, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 150 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 150, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Flat, no insulation (assumed)', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': 0, 'has_dwelling_above': False, 'assumed': True, 'is_flat': True, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Roof room(s), insulated (assumed)', 'is_pitched': False, 'is_roof_room': True, + 'has_loft': False, 'insulation_thickness': 'average', 'has_dwelling_above': False, 'assumed': True, + 'is_flat': False, 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, + 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Pitched, 350 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 350, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 300 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 300, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.14 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.14, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, no insulation', 'is_pitched': True, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': 0, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, 'is_thatched': False, + 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Pitched, 100 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 100, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': '(other premises above)', 'is_pitched': False, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': 0, 'has_dwelling_above': True, 'assumed': False, 'is_flat': False, 'is_thatched': False, + 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Pitched, 270 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 270, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, limited insulation (assumed)', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': 'below average', 'has_dwelling_above': False, 'assumed': True, + 'is_flat': False, 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, + 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Pitched, 250 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 250, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, insulated at rafters', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': 'average', 'has_dwelling_above': False, 'assumed': False, + 'is_flat': False, 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, + 'is_valid': True, 'at_rafters': True}, + {'original_description': 'Pitched', 'is_pitched': True, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Roof room(s), insulated', 'is_pitched': False, 'is_roof_room': True, 'has_loft': False, + 'insulation_thickness': 'average', 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 75 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 75, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Roof room(s), no insulation (assumed)', 'is_pitched': False, 'is_roof_room': True, + 'has_loft': False, 'insulation_thickness': 0, 'has_dwelling_above': False, 'assumed': True, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Flat, insulated', 'is_pitched': False, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': 'average', 'has_dwelling_above': False, 'assumed': False, 'is_flat': True, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.1 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.1, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, *** INVALID INPUT Code : 57 *** loft insulation', 'is_pitched': True, + 'is_roof_room': False, 'has_loft': True, 'insulation_thickness': None, 'has_dwelling_above': False, + 'assumed': False, 'is_flat': False, 'is_thatched': False, 'thermal_transmittence': None, + 'thermal_transmittence_unit': None, 'is_valid': False, 'at_rafters': False}, + {'original_description': 'Pitched, 12 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 12, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.10 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.1, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.13 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.13, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.11 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.11, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Roof room(s), ceiling insulated', 'is_pitched': False, 'is_roof_room': True, + 'has_loft': False, 'insulation_thickness': 'average', 'has_dwelling_above': False, 'assumed': False, + 'is_flat': False, 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, + 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.12 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.12, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.20 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.2, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.16 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.16, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.15 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.15, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 0 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 0, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.08 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.08, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Flat, limited insulation', 'is_pitched': False, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': 'below average', 'has_dwelling_above': False, 'assumed': False, 'is_flat': True, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Roof room(s), limited insulation (assumed)', 'is_pitched': False, 'is_roof_room': True, + 'has_loft': False, 'insulation_thickness': 'below average', 'has_dwelling_above': False, 'assumed': True, + 'is_flat': False, 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, + 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Pitched, insulated', 'is_pitched': True, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': 'average', 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.18 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.18, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, limited insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': 'below average', 'has_dwelling_above': False, 'assumed': False, + 'is_flat': False, 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, + 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.19 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.19, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.17 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.17, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.48 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.48, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.42 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.42, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.58 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.58, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.45 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.45, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 1.00 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 1.0, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.2 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.2, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 400+ mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': '400+', 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 25 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 25, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.49 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.49, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.37 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.37, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Thatched', 'is_pitched': False, 'is_roof_room': False, 'has_loft': False, + 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, 'is_thatched': True, + 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.09 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.09, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.21 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.21, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.24 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.24, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Pitched, 400 mm loft insulation', 'is_pitched': True, 'is_roof_room': False, + 'has_loft': True, 'insulation_thickness': 400, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.23 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.23, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.28 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.28, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.4 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.4, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Thatched, with additional insulation', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': True, 'thermal_transmittence': None, 'thermal_transmittence_unit': None, 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.26 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.26, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}, + {'original_description': 'Average thermal transmittance 0.22 W/m-¦K', 'is_pitched': False, 'is_roof_room': False, + 'has_loft': False, 'insulation_thickness': None, 'has_dwelling_above': False, 'assumed': False, 'is_flat': False, + 'is_thatched': False, 'thermal_transmittence': 0.22, 'thermal_transmittence_unit': 'w/m-¦k', 'is_valid': True, + 'at_rafters': False}]