mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
787 lines
30 KiB
Python
787 lines
30 KiB
Python
from recommendations.recommendation_utils import convert_thickness_to_numeric
|
|
from etl.epc_clean.epc_attributes.RoofAttributes import RoofAttributes
|
|
from etl.epc_clean.epc_attributes.WallAttributes import WallAttributes
|
|
from etl.epc_clean.epc_attributes.FloorAttributes import FloorAttributes
|
|
|
|
|
|
class Eligibility:
|
|
"""
|
|
Given the epc data about a property, this class holds the logic for determining if the home
|
|
is eligible for a specific retrofit measure.
|
|
|
|
For example, this could be whether the loft has insulation below a standardised threshold, or
|
|
if it has an empty cavity
|
|
|
|
Further to this, this class is responsible for determining if the property is suitable for specific funding
|
|
schemes
|
|
"""
|
|
|
|
loft = None
|
|
cavity = None
|
|
solid_wall = None
|
|
room_roof = None
|
|
flat_roof = None
|
|
suspended_floor = None
|
|
solid_floor = None
|
|
|
|
# schemes based on Warmfront now
|
|
gbis_warmfront = None
|
|
eco4_warmfront = None
|
|
# Schemes based on full eligibility
|
|
gbis = None
|
|
eco4 = None
|
|
|
|
# If the loft has less than 100mm of insulation, we classify the home has needing loft insulation
|
|
LOFT_INSULATION_THRESHOLD = 100
|
|
HIGH_LOFT_INSULATION_THRESHOLD = 269
|
|
|
|
# Because EPCS have different values for tenure, we need to remap them to a common set of values
|
|
tenure_remap = {
|
|
'NO DATA!': "unknown",
|
|
'Not defined - use in the case of a new dwelling for which the intended tenure in not known. It is no':
|
|
"unknown",
|
|
'Owner-occupied': 'Owner-occupied',
|
|
'Rented (private)': 'Rented (private)',
|
|
'Rented (social)': 'Rented (social)',
|
|
'owner-occupied': 'Owner-occupied',
|
|
'rental (private)': 'Rented (private)',
|
|
'rental (social)': 'Rented (social)',
|
|
'unknown': "unknown",
|
|
}
|
|
|
|
def __init__(self, epc, cleaned):
|
|
self.epc = epc
|
|
self.cleaned = cleaned
|
|
|
|
self.walls = self.parse_fabric("walls-description")
|
|
self.roof = self.parse_fabric("roof-description")
|
|
self.floor = self.parse_fabric("floor-description")
|
|
|
|
self.tenure = self.tenure_remap.get(self.epc["tenure"], None)
|
|
|
|
def parse_fabric(self, key):
|
|
|
|
# Get the cleaned version of the description
|
|
remapped = [
|
|
data for data in self.cleaned[key] if
|
|
data["original_description"] == self.epc[key]
|
|
]
|
|
if remapped:
|
|
return remapped[0]
|
|
|
|
if "SAP05:" in self.epc[key]:
|
|
# This is a placeholder method for handling this but this will occur in the case of a very old
|
|
# EPC and therefore we just skip
|
|
self.epc[key] = "(assumed)"
|
|
|
|
if key == "walls-description":
|
|
cleaner_cls = WallAttributes(self.epc[key])
|
|
|
|
elif key == "roof-description":
|
|
cleaner_cls = RoofAttributes(self.epc[key])
|
|
|
|
elif key == "floor-description":
|
|
cleaner_cls = FloorAttributes(self.epc[key])
|
|
|
|
else:
|
|
raise ValueError("Invalid key")
|
|
output = cleaner_cls.process()
|
|
output["clean_description"] = cleaner_cls.description.replace("(assumed)", "").rstrip().capitalize()
|
|
|
|
return output
|
|
|
|
def loft_insulation(self, loft_thickness_threshold: int = None):
|
|
"""
|
|
Given the description of roof, this function determines whether or not the property is suitable for loft
|
|
insulation. A loft existing insulation with a thickness below loft_thickness_threshold, is deemed to
|
|
be suitable for loft insulation
|
|
:param loft_thickness_threshold: Integer, Optional. If provided, any loft found with insulation lower than
|
|
this thickness is deemed to be suitable for loft insulation. If this
|
|
parameter is not provided, this method will default to the variable specified
|
|
in LOFT_INSULATION_THRESHOLD
|
|
"""
|
|
|
|
loft_thickness_threshold = (
|
|
self.LOFT_INSULATION_THRESHOLD if loft_thickness_threshold is None else loft_thickness_threshold
|
|
)
|
|
|
|
high_loft_thickness_threshold = self.HIGH_LOFT_INSULATION_THRESHOLD
|
|
|
|
# We firstly check if the roof is a loft
|
|
is_loft = self.roof["is_pitched"] and (not self.roof["is_roof_room"])
|
|
|
|
if not is_loft:
|
|
self.loft = {
|
|
"suitability": False,
|
|
"thickness": None,
|
|
"reason": "roof not loft",
|
|
"thickness_classification": None
|
|
}
|
|
return
|
|
|
|
# If it is a loft, we'll convert the textual thickenss to a numerical value we can easily use
|
|
insulation_thickness = convert_thickness_to_numeric(
|
|
string_thickness=self.roof["insulation_thickness"],
|
|
is_pitched=self.roof["is_pitched"],
|
|
is_flat=self.roof["is_flat"]
|
|
)
|
|
|
|
if insulation_thickness <= 100:
|
|
thickness_classification = "0-100mm"
|
|
elif insulation_thickness <= high_loft_thickness_threshold:
|
|
thickness_classification = "100-270mm"
|
|
else:
|
|
thickness_classification = "270mm+"
|
|
|
|
if insulation_thickness <= loft_thickness_threshold:
|
|
# We produce a thiclkness classification for the loft
|
|
# 0 - 100mm insulation
|
|
# 100 - 270mm insulation
|
|
# 270mm+ insulation
|
|
|
|
self.loft = {
|
|
"suitability": True,
|
|
"thickness": insulation_thickness,
|
|
"reason": None,
|
|
"thickness_classification": thickness_classification
|
|
}
|
|
return
|
|
|
|
# Insulation is already thick enough
|
|
self.loft = {
|
|
"suitability": False,
|
|
"thickness": insulation_thickness,
|
|
"reason": "existing insulation",
|
|
"thickness_classification": thickness_classification
|
|
}
|
|
return
|
|
|
|
def cavity_insulation(self):
|
|
|
|
"""
|
|
Given the description of the walls, this function determines if the property is suitable for cavity wall
|
|
insulation
|
|
:return:
|
|
"""
|
|
|
|
is_cavity = self.walls["is_cavity_wall"]
|
|
is_empty = (not self.walls["is_filled_cavity"])
|
|
is_as_built = (
|
|
self.walls["is_as_built"] and self.walls["insulation_thickness"] not in ["average", "above average"]
|
|
and self.walls["is_assumed"]
|
|
)
|
|
is_partial_filled = "partial" in self.walls["clean_description"].lower()
|
|
# We look for potentially under performing cavities - anything that is assumed, as built and insulated
|
|
is_underperforming = (
|
|
self.walls["is_as_built"] and self.walls["insulation_thickness"] in ["average"] and self.walls["is_assumed"]
|
|
)
|
|
|
|
is_unfilled_cavity = is_cavity and (is_empty and not is_partial_filled)
|
|
is_partial_filled_cavity = is_cavity and is_partial_filled
|
|
is_assumed_filled_cavity = is_cavity and is_as_built
|
|
is_underperforming_cavity = is_cavity and is_underperforming
|
|
|
|
# Check if it has internal or external wall insulation
|
|
has_internal_wall_insulation = self.walls["internal_insulation"]
|
|
has_external_wall_insulation = self.walls["external_insulation"]
|
|
|
|
if has_internal_wall_insulation or has_external_wall_insulation:
|
|
self.cavity = {
|
|
"suitability": False,
|
|
"type": "internal or external wall insulation"
|
|
}
|
|
return
|
|
|
|
if is_unfilled_cavity:
|
|
self.cavity = {
|
|
"suitability": True,
|
|
"type": "empty",
|
|
}
|
|
return
|
|
|
|
if is_assumed_filled_cavity:
|
|
self.cavity = {
|
|
"suitability": True,
|
|
"type": "as built assumed",
|
|
}
|
|
return
|
|
|
|
if is_partial_filled_cavity:
|
|
self.cavity = {
|
|
"suitability": True,
|
|
"type": "partial"
|
|
}
|
|
return
|
|
|
|
if is_underperforming_cavity:
|
|
self.cavity = {
|
|
"suitability": True,
|
|
"type": "underperforming"
|
|
}
|
|
return
|
|
|
|
self.cavity = {
|
|
"suitability": False,
|
|
"type": "full"
|
|
}
|
|
|
|
def solid_wall_insulation(self):
|
|
"""
|
|
Given the description of the walls, this function determines if the property is suitable for solid wall
|
|
insulation
|
|
:return:
|
|
"""
|
|
|
|
is_solid = self.walls["is_solid_brick"]
|
|
is_insulated = self.walls["insulation_thickness"] in ["average", "above average"]
|
|
|
|
if is_solid and is_insulated:
|
|
self.solid_wall = {
|
|
"suitability": True,
|
|
}
|
|
return
|
|
|
|
self.solid_wall = {
|
|
"suitability": False,
|
|
}
|
|
|
|
def room_roof_insulation(self):
|
|
is_room_roof = self.roof["is_roof_room"]
|
|
|
|
if not is_room_roof:
|
|
self.room_roof = {
|
|
"suitability": False,
|
|
"thickness": None
|
|
}
|
|
return
|
|
|
|
insulation_thickness = convert_thickness_to_numeric(
|
|
self.roof["insulation_thickness"],
|
|
self.roof["is_pitched"],
|
|
self.roof["is_flat"]
|
|
)
|
|
|
|
self.room_roof = {
|
|
"suitability": is_room_roof and insulation_thickness == 0,
|
|
"thickness": insulation_thickness
|
|
}
|
|
|
|
def flat_roof_insulation(self):
|
|
is_flat = self.roof["is_flat"]
|
|
|
|
if not is_flat:
|
|
self.flat_roof = {
|
|
"suitability": False,
|
|
"thickness": None
|
|
}
|
|
return
|
|
|
|
insulation_thickness = convert_thickness_to_numeric(
|
|
self.roof["insulation_thickness"],
|
|
self.roof["is_pitched"],
|
|
self.roof["is_flat"]
|
|
)
|
|
|
|
self.flat_roof = {
|
|
"suitability": is_flat and insulation_thickness <= 100,
|
|
"thickness": insulation_thickness
|
|
}
|
|
|
|
def suspended_floor_insulation(self):
|
|
|
|
if "no_data" in self.floor.keys():
|
|
if self.floor["no_data"]:
|
|
self.suspended_floor = {
|
|
"suitability": False,
|
|
}
|
|
return
|
|
|
|
is_suspended = self.floor["is_suspended"]
|
|
is_insulated = self.floor["insulation_thickness"] in ["average", "above average"]
|
|
|
|
self.suspended_floor = {
|
|
"suitability": is_suspended and (not is_insulated),
|
|
}
|
|
return
|
|
|
|
def solid_floor_insulation(self):
|
|
|
|
if "no_data" in self.floor.keys():
|
|
if self.floor["no_data"]:
|
|
self.solid_floor = {
|
|
"suitability": False,
|
|
}
|
|
return
|
|
|
|
is_solid = self.floor["is_solid"]
|
|
is_insulated = self.floor["insulation_thickness"] in ["average", "above average"]
|
|
|
|
self.solid_floor = {
|
|
"suitability": is_solid and (not is_insulated),
|
|
}
|
|
return
|
|
|
|
def check_gbis_warmfront(self):
|
|
"""
|
|
The Eligibility criteria for the Great British Insulation Scheme (GBIS) can be found here:
|
|
https://www.ofgem.gov.uk/environmental-and-social-schemes/great-british-insulation-scheme/homeowners-and-tenants
|
|
|
|
At a high level, the criteria is the following:
|
|
- The home must be within council tax bands A-D in England, A-E in Scotland, A-E in Wales
|
|
- It must have an EPC rating of D or below
|
|
|
|
For the moment, we won't check whether a property is in the correct council tax band. There is likely
|
|
to be public data for this since there is a govenment website which allows you to search for properties:
|
|
https://www.gov.uk/council-tax-bands
|
|
This data is possibly contained on the council tax valuation list but it remains to be see (seems unlikely)
|
|
whether or not the data is openly accessible
|
|
https://www.gov.uk/government/statistics/quality-assurance-of-administrative-data-in-the-uk-house-price-index
|
|
/valuation-office-agency-council-tax-valuation-lists
|
|
|
|
Currently, we tailor this module to the Warmfront Team and their delivery capabilities (both practically and
|
|
commercially). Therefore, we will check:
|
|
1) Whether the property is an EPC D or below
|
|
2) Whether the property is suitible for cavity wall insulation
|
|
|
|
However, GBIS applies to many insulation measures, which can be seen in the ofgem document
|
|
|
|
GBIS does not have any minimum upgrade requirement so we don't need to simulate the post retrofit sap score
|
|
using the machine learning model
|
|
"""
|
|
|
|
# Check if the property is suitable for cavity wall
|
|
self.cavity_insulation()
|
|
|
|
current_sap = int(self.epc["current-energy-efficiency"])
|
|
# We have a strict suitability check and a non-strict check
|
|
|
|
# Perfect strictness
|
|
if (self.cavity["type"] == "empty") and (current_sap < 69):
|
|
self.gbis_warmfront = {
|
|
"eligible": True,
|
|
"strict": True,
|
|
"message": "Perfect suitability",
|
|
}
|
|
return
|
|
|
|
# Near perfect
|
|
if self.cavity["suitability"] and (current_sap < 69):
|
|
self.gbis_warmfront = {
|
|
"eligible": True,
|
|
"strict": True,
|
|
"message": "Near perfect suitability",
|
|
}
|
|
return
|
|
|
|
self.gbis_warmfront = {
|
|
"eligible": False,
|
|
"strict": False,
|
|
"message": "All conditions fail",
|
|
}
|
|
|
|
def check_eco4_warmfront(self):
|
|
"""
|
|
This funciton will check if the property is eligible for funding under the ECO4 scheme
|
|
|
|
For the moment, this function will consider just measures that can be implemented by the
|
|
Warmfront team, therefore we will only check if a property has an uninsulated loft AND uninsulated
|
|
cavity
|
|
|
|
We use Ofgem's V1.1 ECO 4 guidance document for the conditions under which a property is elligible
|
|
This document can be found here:
|
|
https://www.ofgem.gov.uk/sites/default/files/2023-02/ECO4%20Delivery%20Guidance%20v1.1%20%281%29.pdf
|
|
|
|
The conditions (to be reviewed) to be eligible for retrofit, under ECO4, are the following:
|
|
1) The property is a social home (This is assumed prior to this function as this code will often
|
|
be run on property lists provided by a HA
|
|
2) The property is an EPC E or below
|
|
3) The property has an unfilled cavity and uninsulated loft
|
|
4) After retrofit, the property will hit an EPC C
|
|
|
|
Note: This criteria will likely be adjusted depending on the properties that can be served right now
|
|
|
|
If the post_retrofit_sap is provided, then is this value is 69 or higher, the property will be deemed
|
|
to be eligible for ECO4 funding. If the post_retrofit_sap is not provided, the property will be
|
|
deemed to be eligible, conditional to the post_retrofit_sap score check
|
|
:param post_retrofit_sap:
|
|
:return:
|
|
"""
|
|
|
|
current_sap = int(self.epc["current-energy-efficiency"])
|
|
self.cavity_insulation()
|
|
self.loft_insulation()
|
|
|
|
# We put in a placeholder when the roof is not a loft
|
|
if self.loft["reason"] == "roof not loft":
|
|
self.loft["thickness"] = 999
|
|
|
|
# Case 1: No conditions meet
|
|
if not self.cavity["suitability"] and (self.loft["thickness"] > 100) and current_sap >= 55:
|
|
self.eco4_warmfront = {
|
|
"eligible": False,
|
|
"strict": False,
|
|
"message": "All conditions fail",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 2 - perfect match
|
|
if (self.cavity["type"] == "empty") and (self.loft["thickness"] <= 100) and (current_sap < 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": True,
|
|
"strict": True,
|
|
"message": "Perfect suitability",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 2.5 - near perfect match - but we would not recommend this using the model
|
|
if self.cavity["suitability"] and (self.loft["thickness"] <= 100) and (current_sap < 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": True,
|
|
"strict": True,
|
|
"message": "Near perfect suitability",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 3 - cavity is suitable, loft is within 150mm, sap is good
|
|
if self.cavity["suitability"] and (self.loft["thickness"] <= 150) and (current_sap < 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": True,
|
|
"strict": False,
|
|
"message": "Meets cavity, loft borderline, meets sap",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 3 - cavity is suitable, loft is not, sap is good
|
|
if self.cavity["suitability"] and (self.loft["thickness"] > 150) and (current_sap < 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": True,
|
|
"strict": False,
|
|
"message": "Meets cavity and sap",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 4 - cavity is not suitable, loft is, sap is not - we say this is not elifible
|
|
if not self.cavity["suitability"] and (self.loft["thickness"] <= 100) and (current_sap < 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": False,
|
|
"strict": False,
|
|
"message": "failed fabric check",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 5 - cavity and loft suitable, sap too high
|
|
if self.cavity["suitability"] and (self.loft["thickness"] <= 150) and (current_sap >= 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": True,
|
|
"strict": False,
|
|
"message": "Meets fabric, fails SAP check",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 6 - meets just cavity
|
|
if self.cavity["suitability"] and (self.loft["thickness"] > 100) and (current_sap >= 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": True,
|
|
"strict": False,
|
|
"message": "Meets just cavity",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 7 - fails cavity, loft but meets sap
|
|
if not self.cavity["suitability"] and (self.loft["thickness"] > 100) and (current_sap < 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": False,
|
|
"strict": False,
|
|
"message": "Fails cavity and loft, meets SAP",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
# Case 8 - fails cavity, meets loft, fails sap
|
|
if not self.cavity["suitability"] and (self.loft["thickness"] <= 100) and (current_sap >= 55):
|
|
self.eco4_warmfront = {
|
|
"eligible": False,
|
|
"strict": False,
|
|
"message": "Fails cavity, meets loft, fails SAP",
|
|
"cavity_type": self.cavity["type"],
|
|
"loft_type": self.loft["thickness_classification"]
|
|
}
|
|
return
|
|
|
|
raise ValueError("Implement me")
|
|
|
|
def check_gbis(self):
|
|
|
|
"""
|
|
The Eligibility criteria for the Great British Insulation Scheme (GBIS) can be found here:
|
|
https://www.ofgem.gov.uk/environmental-and-social-schemes/great-british-insulation-scheme/homeowners-and-tenants
|
|
|
|
Full delivery guidance and be downloaded here:
|
|
https://www.ofgem.gov.uk/sites/default/files/2023-08/Great%20British%20Insulation%20Scheme%20Delivery
|
|
%20Guidance%20V101693416860968.pdf
|
|
|
|
For social housing, the criteria is the following:
|
|
|
|
If the property is currently an EPC D:
|
|
- It's valid for innovation measures only but not a heating control measure
|
|
- The property must be rented at below the market rate. All eligible social housing is treated based on the
|
|
low income group, therefore the tennant must be in receipt of one the eligible benefits
|
|
|
|
If the property is currently an EPC E or below:
|
|
- It's valid for all eligible insulation measures
|
|
- The property must be rented at below the market rate. All eligible social housing is treated based on the
|
|
low income group, therefore the tennant must be in receipt of one the eligible benefits
|
|
|
|
From GBIS guidance document:
|
|
Determining whether the premises are let below market rate
|
|
|
|
3.101 Social housing under this provision will only be eligible where the housing is let below
|
|
the market rate. The supplier must produce a declaration signed by a social landlord
|
|
providing confirmation that the social housing premises are let below the market rate,
|
|
or where the premises are currently void, have previously and will be let below the
|
|
market rate. The declaration to be signed by a social landlord is included within the
|
|
Eligibility and Pre-Retrofit Declaration form. This declaration form must be retained by
|
|
suppliers and be available on request for audit purposes.
|
|
|
|
3.102 Where social housing is let at or above the market rate, the property can be treated as
|
|
a private domestic premises, where the occupant meets the eligibility requirements.
|
|
See section on PRS from paragraph 1.13 for more information.
|
|
|
|
This method searches ALL of the possible measures that can be implemented under GBIS. This includes:
|
|
- cavity wall (including party wall)
|
|
- loft
|
|
- solid wall
|
|
- pitched roof
|
|
- flat roof
|
|
- under-floor
|
|
- solid floor
|
|
- park home
|
|
- room-in-roof
|
|
|
|
:return:
|
|
"""
|
|
|
|
self.cavity_insulation()
|
|
self.loft_insulation()
|
|
self.solid_wall_insulation()
|
|
self.room_roof_insulation()
|
|
self.flat_roof_insulation()
|
|
self.suspended_floor_insulation()
|
|
self.solid_floor_insulation()
|
|
|
|
current_sap = int(self.epc["current-energy-efficiency"])
|
|
is_below_e = current_sap <= 54
|
|
is_below_c = current_sap <= 68
|
|
|
|
needs_measure = (
|
|
self.cavity["suitability"] or
|
|
self.loft["suitability"] or
|
|
self.solid_wall["suitability"] or
|
|
self.room_roof["suitability"] or
|
|
self.flat_roof["suitability"] or
|
|
self.suspended_floor["suitability"] or
|
|
self.solid_floor["suitability"]
|
|
)
|
|
|
|
if self.tenure == "Rented (social)":
|
|
|
|
if is_below_c and (not is_below_e):
|
|
# this is a placeholder methodology
|
|
self.gbis = {
|
|
"eligible": int(self.epc["potential-energy-efficiency"]) > 68,
|
|
"message": "contingent on innovation measure delivery"
|
|
}
|
|
return
|
|
elif is_below_e:
|
|
self.gbis = {
|
|
"eligible": needs_measure,
|
|
"message": "eligible under fabric measure"
|
|
}
|
|
return
|
|
else:
|
|
self.gbis = {
|
|
"eligible": False,
|
|
"message": "not eligible"
|
|
}
|
|
return
|
|
|
|
elif self.tenure == "Rented (private)":
|
|
self.gbis = {
|
|
"eligible": is_below_c and needs_measure,
|
|
"message": "eligible under fabric measure"
|
|
}
|
|
return
|
|
elif self.tenure == "Owner-occupied":
|
|
self.gbis = {
|
|
"eligible": False,
|
|
"message": "Out-of-scope"
|
|
}
|
|
return
|
|
|
|
elif (self.tenure is None) or self.tenure == "unknown":
|
|
self.gbis = {
|
|
"eligible": needs_measure,
|
|
"message": "unknown tenure"
|
|
}
|
|
return
|
|
else:
|
|
raise ValueError("Implement me other tenure types")
|
|
|
|
def check_eco4(self):
|
|
"""
|
|
Because ECO4 supports nearly all measures. If we have commercial agreements in place then a large number
|
|
of homes would be eligible for eco funding, if identified.
|
|
|
|
These are the eligibility criteria we consider for this process:
|
|
Privately rented, Help to heat group
|
|
- Sap E-G
|
|
- Must receive one of solid wall insulation, first time central heating or district heating control
|
|
- The property must already have cavity walls and roof insulated
|
|
|
|
Social Housing, SAP D
|
|
- Innovation measures and insulation measures to meet the minimum insulation requirement
|
|
- Improvement to at least band C
|
|
- Fabric measures
|
|
- If receiving any heating measures, must have at least one insulation measure first
|
|
|
|
Social Housing, SAP E-G
|
|
- Insulation measures, first time central heating, renewable heating, district heating connection,
|
|
innovation measures
|
|
- Improvement to D (F & G properties) or C (E properties)
|
|
- If receiving any heating measure, must already have cavity and roof insulation
|
|
|
|
Privately rented, ECO4 Flex route 1, 2, 3, 4
|
|
- Must have SAP E-G
|
|
- Most measures eligible, but must receive one of solid wall insulation, first time central heating,
|
|
renewable heating and district heating control
|
|
- Improvement to D (F & G properties) or C (E properties)
|
|
- All homes receiving heating measures must first have insulated cavity/roof
|
|
|
|
|
|
The flex routes are given here:
|
|
https://so-eco.co.uk/what-is-eco4-flex/#:~:text=One%20way%20to%20gain%20ECO4,
|
|
including%20elderly%20residents%20and%20lodgers.
|
|
|
|
:return:
|
|
"""
|
|
|
|
self.cavity_insulation()
|
|
self.loft_insulation()
|
|
self.solid_wall_insulation()
|
|
self.room_roof_insulation()
|
|
self.flat_roof_insulation()
|
|
self.suspended_floor_insulation()
|
|
self.solid_floor_insulation()
|
|
|
|
current_sap = int(self.epc["current-energy-efficiency"])
|
|
is_below_e = current_sap <= 54
|
|
is_below_c = current_sap <= 68
|
|
sap_potential = int(self.epc["potential-energy-efficiency"])
|
|
|
|
first_time_central_heating = "boiler" not in self.epc["mainheat-description"].lower()
|
|
|
|
needs_fabric_measure = (
|
|
self.cavity["suitability"] or
|
|
self.loft["suitability"] or
|
|
self.solid_wall["suitability"] or
|
|
self.room_roof["suitability"] or
|
|
self.flat_roof["suitability"] or
|
|
self.suspended_floor["suitability"] or
|
|
self.solid_floor["suitability"]
|
|
)
|
|
|
|
if current_sap <= 38 and sap_potential >= 55:
|
|
# sap needs to get to at least a D
|
|
expected_to_meet_upgrades = True
|
|
elif current_sap <= 68 and sap_potential >= 69:
|
|
# sap needs to get to at least a C
|
|
expected_to_meet_upgrades = True
|
|
else:
|
|
expected_to_meet_upgrades = False
|
|
|
|
if self.tenure == "Rented (social)":
|
|
if is_below_c and (not is_below_e) and expected_to_meet_upgrades:
|
|
# If the property is a D, then it's eligible under innovation measures but requires improvement to a
|
|
# band C
|
|
self.eco4 = {
|
|
"eligible": True,
|
|
"message": "eligible under innovation measure and improvement to band C"
|
|
}
|
|
elif is_below_e and expected_to_meet_upgrades:
|
|
# If the property is an E or below, then it's eligible under fabric measures or heating/innovation
|
|
# measures
|
|
|
|
message = "eligible under fabric measures, with sufficient post retrofit sap improvement" if (
|
|
needs_fabric_measure) else (
|
|
"eligible under heating and innovation measures, with sufficient post retrofit sap improvement"
|
|
)
|
|
|
|
self.eco4 = {"eligible": True, "message": message}
|
|
else:
|
|
if (current_sap <= 68) and expected_to_meet_upgrades:
|
|
raise ValueError("something is wrong")
|
|
self.eco4 = {
|
|
"eligible": False,
|
|
"message": "not eligible, above EPC C"
|
|
}
|
|
|
|
return
|
|
|
|
if self.tenure == 'Rented (private)':
|
|
# For private homes, the property needs to be an E or below
|
|
|
|
# For private homes, the cavity must be filled and the roof insulated
|
|
cavity_filled = not self.cavity["suitability"]
|
|
roof_insulated = (not self.loft["suitability"]) and (not self.room_roof["suitability"]) and (
|
|
not self.flat_roof["suitability"])
|
|
|
|
if is_below_e and cavity_filled and roof_insulated and expected_to_meet_upgrades:
|
|
|
|
if self.solid_wall["suitability"]:
|
|
self.eco4 = {
|
|
"eligible": True,
|
|
"message": "eligible under solid wall insulation, conditional on post retrofit sap and help "
|
|
"to heat/ECO flex route"
|
|
}
|
|
elif first_time_central_heating:
|
|
|
|
self.eco4 = {
|
|
"eligible": True,
|
|
"message": "eligible under first time central heating, conditional on post retrofit sap and "
|
|
"help to heat/ECO flex route"
|
|
}
|
|
else:
|
|
self.eco4 = {
|
|
"eligible": False,
|
|
"message": "not eligible at this time"
|
|
}
|
|
|
|
return
|
|
|
|
else:
|
|
self.eco4 = {
|
|
"eligible": False,
|
|
"message": "not eligible at this time, EPC too high"
|
|
}
|
|
|
|
self.eco4 = {
|
|
"eligible": False,
|
|
"message": "Out of scope"
|
|
}
|