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}]