mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
added in basic process for sloping ceiling
This commit is contained in:
parent
e8b7a569ff
commit
64eb2e2f20
3 changed files with 143 additions and 11 deletions
|
|
@ -9,7 +9,9 @@ TYPICAL_MEASURE_TYPES = [
|
||||||
]
|
]
|
||||||
|
|
||||||
WALL_INSULATION_MEASURES = ["internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation"]
|
WALL_INSULATION_MEASURES = ["internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation"]
|
||||||
ROOF_INSULATION_MEASURES = ["loft_insulation", "flat_roof_insulation", "room_roof_insulation"]
|
ROOF_INSULATION_MEASURES = [
|
||||||
|
"loft_insulation", "flat_roof_insulation", "room_roof_insulation", "sloping_ceiling_insulation"
|
||||||
|
]
|
||||||
|
|
||||||
# Both all and roof insulaiton measures are eligible for ECO4. These are the remaining fabric and heating measures
|
# Both all and roof insulaiton measures are eligible for ECO4. These are the remaining fabric and heating measures
|
||||||
# This is based on th measures we have recommendations for
|
# This is based on th measures we have recommendations for
|
||||||
|
|
@ -31,7 +33,7 @@ SPECIFIC_MEASURES = (
|
||||||
|
|
||||||
INSULATION_MEASURES = [
|
INSULATION_MEASURES = [
|
||||||
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation",
|
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation",
|
||||||
"loft_insulation", "flat_roof_insulation", "room_roof_insulation",
|
"loft_insulation", "flat_roof_insulation", "room_roof_insulation", "sloping_ceiling_insulation",
|
||||||
"suspended_floor_insulation", "solid_floor_insulation",
|
"suspended_floor_insulation", "solid_floor_insulation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -46,7 +48,9 @@ MEASURE_MAP = {
|
||||||
"wall_insulation": [
|
"wall_insulation": [
|
||||||
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation",
|
"internal_wall_insulation", "external_wall_insulation", "cavity_wall_insulation",
|
||||||
],
|
],
|
||||||
"roof_insulation": ["loft_insulation", "flat_roof_insulation", "room_roof_insulation"],
|
"roof_insulation": [
|
||||||
|
"loft_insulation", "flat_roof_insulation", "room_roof_insulation", "sloping_ceiling_insulation"
|
||||||
|
],
|
||||||
"floor_insulation": ["suspended_floor_insulation", "solid_floor_insulation"],
|
"floor_insulation": ["suspended_floor_insulation", "solid_floor_insulation"],
|
||||||
"heating": ["boiler_upgrade", "high_heat_retention_storage_heaters", "air_source_heat_pump"],
|
"heating": ["boiler_upgrade", "high_heat_retention_storage_heaters", "air_source_heat_pump"],
|
||||||
"windows": ["double_glazing", "secondary_glazing"],
|
"windows": ["double_glazing", "secondary_glazing"],
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
from typing import Mapping, Any
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from recommendations.county_to_region import county_to_region_map
|
from recommendations.county_to_region import county_to_region_map
|
||||||
from utils.logger import setup_logger
|
from utils.logger import setup_logger
|
||||||
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
from backend.ml_models.AnnualBillSavings import AnnualBillSavings
|
||||||
|
|
@ -166,7 +168,8 @@ class Costs:
|
||||||
"room_roof_insulation": 0.26,
|
"room_roof_insulation": 0.26,
|
||||||
"heater_removal": 0.1,
|
"heater_removal": 0.1,
|
||||||
"sealing_open_fireplace": 0.1,
|
"sealing_open_fireplace": 0.1,
|
||||||
"mechanical_ventilation": 0.26
|
"mechanical_ventilation": 0.26,
|
||||||
|
"sloping_ceiling_insulation": 0.26 # Similar to IWI so using the same contingency
|
||||||
}
|
}
|
||||||
|
|
||||||
# Preliminaries are a percentage of the total cost of the work and covers the cost of site-specific costs
|
# Preliminaries are a percentage of the total cost of the work and covers the cost of site-specific costs
|
||||||
|
|
@ -935,3 +938,66 @@ class Costs:
|
||||||
"labour_hours": 80,
|
"labour_hours": 80,
|
||||||
"labour_days": 10,
|
"labour_days": 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _estimate_number_of_days_for_sloping_ceiling(insulation_roof_area: float) -> float:
|
||||||
|
"""
|
||||||
|
Estimate labour days required to insulate an existing sloping ceiling.
|
||||||
|
|
||||||
|
Heuristic model based on retrofit guidance (Checkatrade, The Green Age)
|
||||||
|
and analogy with internal wall insulation.
|
||||||
|
|
||||||
|
Assumptions:
|
||||||
|
- ~30 m² of sloping ceiling takes ~4 working days
|
||||||
|
- Small jobs still require multiple days (setup, stripping, reboarding)
|
||||||
|
- Larger areas benefit from economies of scale, but not linearly
|
||||||
|
|
||||||
|
:param insulation_roof_area: m² of sloping ceiling to be insulated
|
||||||
|
"""
|
||||||
|
|
||||||
|
base_days = 4
|
||||||
|
base_area = 30 # m2 reference case
|
||||||
|
labour_exponent = 0.85
|
||||||
|
min_days = 2
|
||||||
|
|
||||||
|
labour_days = max(
|
||||||
|
min_days,
|
||||||
|
base_days * (insulation_roof_area / base_area) ** labour_exponent
|
||||||
|
)
|
||||||
|
|
||||||
|
return labour_days
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sloping_ceiling_insulation(cls, insulation_roof_area: float) -> Mapping[str, Any]:
|
||||||
|
"""
|
||||||
|
This costing for this is based on Checkatrade desktop research, since we are yet to receive installer quotes.
|
||||||
|
:param insulation_roof_area: Area of the sloping ceiling to be insulated
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
################
|
||||||
|
# Assumptions
|
||||||
|
################
|
||||||
|
# Sources:
|
||||||
|
# https://www.checkatrade.com/blog/cost-guides/vaulted-ceiling-cost/
|
||||||
|
# https://www.thegreenage.co.uk/can-i-insulate-my-sloping-ceiling/
|
||||||
|
# These assumptions last updated 21/02/2026
|
||||||
|
insulation_cost_per_m2 = 52 # The actual install process is quite similar to IWI
|
||||||
|
labour_rate = 250 # per day
|
||||||
|
contingency_rate = cls.CONTINGENCIES["sloping_ceiling_insulation"]
|
||||||
|
|
||||||
|
labour_days = cls._estimate_number_of_days_for_sloping_ceiling(insulation_roof_area)
|
||||||
|
labour_hours = labour_days * 8
|
||||||
|
|
||||||
|
total = (insulation_cost_per_m2 * insulation_roof_area) + (labour_rate * labour_days)
|
||||||
|
|
||||||
|
# Assume VAT included in the total => total is 120% of subtotal
|
||||||
|
vat = total - (total / 1.2)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total": total,
|
||||||
|
"contingency": total * contingency_rate,
|
||||||
|
"contingency_rate": contingency_rate,
|
||||||
|
"vat": vat,
|
||||||
|
"labour_hours": labour_hours,
|
||||||
|
"labour_days": labour_days,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -324,10 +324,11 @@ class RoofRecommendations:
|
||||||
)
|
)
|
||||||
|
|
||||||
self.estimated_u_value = u_value
|
self.estimated_u_value = u_value
|
||||||
|
# The Roof is already compliant - in this case, the u-value is beyond the requirements for
|
||||||
|
# Building Regs Part L and so we don't recommend anything
|
||||||
if (u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE) or all(
|
if (u_value <= self.BUILDING_REGULATIONS_PART_L_MAX_U_VALUE) or all(
|
||||||
m not in measures for m in MEASURE_MAP["roof_insulation"]
|
m not in measures for m in MEASURE_MAP["roof_insulation"]
|
||||||
):
|
):
|
||||||
# The Roof is already compliant
|
|
||||||
return
|
return
|
||||||
|
|
||||||
non_invasive_recommendations = self.property.non_invasive_recommendations
|
non_invasive_recommendations = self.property.non_invasive_recommendations
|
||||||
|
|
@ -381,14 +382,12 @@ class RoofRecommendations:
|
||||||
has_room_roof_recommendation=has_room_roof_recommendation
|
has_room_roof_recommendation=has_room_roof_recommendation
|
||||||
)
|
)
|
||||||
|
|
||||||
##################################################
|
################################################################
|
||||||
# ~~~~~ Loft Insulation Recommendation Logic ~~~~~
|
# ~~~~~ Loft Insulation Recommendation Logic ~~~~~
|
||||||
##################################################
|
################################################################
|
||||||
# We firstly handle non-intrusive recommendations, which may override the normal roof insulation recommendations
|
|
||||||
if needs_loft_insulation:
|
if needs_loft_insulation:
|
||||||
self.recommend_roof_insulation(
|
self.recommend_roof_insulation(
|
||||||
u_value=u_value,
|
u_value=u_value,
|
||||||
insulation_thickness=self.insulation_thickness,
|
|
||||||
phase=phase,
|
phase=phase,
|
||||||
is_flat=False,
|
is_flat=False,
|
||||||
is_pitched=True,
|
is_pitched=True,
|
||||||
|
|
@ -396,10 +395,12 @@ class RoofRecommendations:
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
# ~~~~~ Flat Roof Insulation Recommendation Logic ~~~~~
|
||||||
|
################################################################
|
||||||
if needs_flat_roof_insulation:
|
if needs_flat_roof_insulation:
|
||||||
self.recommend_roof_insulation(
|
self.recommend_roof_insulation(
|
||||||
u_value=u_value,
|
u_value=u_value,
|
||||||
insulation_thickness=0,
|
|
||||||
phase=phase,
|
phase=phase,
|
||||||
is_flat=True,
|
is_flat=True,
|
||||||
is_pitched=False,
|
is_pitched=False,
|
||||||
|
|
@ -407,12 +408,21 @@ class RoofRecommendations:
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
# ~~~~~ Room Roof Insulation Recommendation Logic ~~~~~
|
||||||
|
################################################################
|
||||||
# There are cases where the property might have a room roof as the second roof, but we have a recommendation for
|
# There are cases where the property might have a room roof as the second roof, but we have a recommendation for
|
||||||
# it, so we allow this override
|
# it, so we allow this override
|
||||||
if needs_rir_insulation:
|
if needs_rir_insulation:
|
||||||
self.recommend_room_roof_insulation(u_value, phase, default_u_values)
|
self.recommend_room_roof_insulation(u_value, phase, default_u_values)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
####################################################################################################
|
||||||
|
# ~~~~~ Sloping Ceiling Insulation Recommendation Logic ~~~~~
|
||||||
|
####################################################################################################
|
||||||
|
if needs_sloping_ceiling:
|
||||||
|
self.recommend_sloping_ceiling()
|
||||||
|
|
||||||
raise NotImplementedError("Implement me")
|
raise NotImplementedError("Implement me")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -432,7 +442,7 @@ class RoofRecommendations:
|
||||||
raise ValueError("Invalid material type")
|
raise ValueError("Invalid material type")
|
||||||
|
|
||||||
def recommend_roof_insulation(
|
def recommend_roof_insulation(
|
||||||
self, u_value, insulation_thickness, phase, is_pitched, is_flat, default_u_values
|
self, u_value, phase, is_pitched, is_flat, default_u_values
|
||||||
):
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -773,3 +783,55 @@ class RoofRecommendations:
|
||||||
)
|
)
|
||||||
|
|
||||||
self.recommendations = recommendations
|
self.recommendations = recommendations
|
||||||
|
|
||||||
|
def recommend_sloping_ceiling(self, phase: int, u_value, sloping_ceiling_recommendation: dict = None):
|
||||||
|
"""
|
||||||
|
Recommend insulation for a sloping ceiling
|
||||||
|
Since we don't have any materials from installers for this specific recommendation, we
|
||||||
|
do not iterate through any materials. Instead, we provide a single recommendation, we estimated
|
||||||
|
prices based on desk research.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_description = "Pitched, insulated"
|
||||||
|
new_efficiency = "Good"
|
||||||
|
|
||||||
|
roof_ending_config = RoofAttributes(new_description).process()
|
||||||
|
roof_simulation_config = check_simulation_difference(
|
||||||
|
new_config=roof_ending_config, old_config=self.property.roof, prefix="roof_"
|
||||||
|
)
|
||||||
|
|
||||||
|
# We pull out new u-values, based on 75mm of insulation, with u-values defined from Elmhurst
|
||||||
|
new_u_value = 0.5 # This doesn't change, regardless of starting u-value
|
||||||
|
|
||||||
|
simulation_config = {
|
||||||
|
**roof_simulation_config,
|
||||||
|
"roof_thermal_transmittance_ending": new_u_value,
|
||||||
|
"roof_energy_eff_ending": new_efficiency
|
||||||
|
}
|
||||||
|
|
||||||
|
cost_result = self.costs.sloping_ceiling_insulation(
|
||||||
|
roof_area=self.property.roof_area # For a pitched roof, this is the pitched roof area
|
||||||
|
)
|
||||||
|
|
||||||
|
self.recommendations = [
|
||||||
|
{
|
||||||
|
"phase": phase,
|
||||||
|
"parts": [],
|
||||||
|
"type": "sloping_ceiling_insulation",
|
||||||
|
"measure_type": "sloping_ceiling_insulation",
|
||||||
|
"description": "Insulate sloping ceilings at the rafters and re-decorate",
|
||||||
|
"starting_u_value": u_value,
|
||||||
|
"new_u_value": None,
|
||||||
|
"sap_points": sloping_ceiling_recommendation.get("sap_points", None),
|
||||||
|
"simulation_config": simulation_config,
|
||||||
|
"description_simulation": {
|
||||||
|
"roof-description": new_description,
|
||||||
|
"roof-energy-eff": new_efficiency
|
||||||
|
},
|
||||||
|
**cost_result,
|
||||||
|
"already_installed": "sloping_ceiling_insulation" in self.property.already_installed,
|
||||||
|
"survey": sloping_ceiling_recommendation.get("survey", None),
|
||||||
|
"innovation_rate": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue