Model/epc_data/attributes/RoofAttributes.py
2023-06-09 09:40:53 +01:00

178 lines
7.3 KiB
Python

from typing import Dict, Union, Optional
from epc_data.attributes.attribute_utils import extract_thermal_transmittence, search_split_description
class RoofAttributes:
def __init__(self, description):
"""
:param description: Description of the roof.
"""
self.description: str = description
def clean(self) -> 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
:return: Dictionary of attributes of the roof.
"""
description_lower = self.description.lower().strip()
if "another dwelling above" in description_lower or "other premises above" in description_lower:
return self._make_clean_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 = extract_thermal_transmittence(description_lower)
elif is_thatched:
# Search for these features:
thermal_transmittence, thermal_transmittence_unit = 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_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
)
@staticmethod
def _make_clean_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
}
@staticmethod
def _find_insulation_thickness(
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 search_split_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 search_split_description(desc)
if is_flat:
# Just search for specific phrases
desc = description_lower.split("flat,")[-1].lstrip().split(" ")[0]
return search_split_description(desc)
return None