mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Added U-value and perimeter estimation functions
This commit is contained in:
parent
d40d20497f
commit
a565a35e9a
4 changed files with 280 additions and 6 deletions
|
|
@ -15,6 +15,7 @@ class UvalueEstimations:
|
||||||
self.walls_decile_data = {}
|
self.walls_decile_data = {}
|
||||||
self.roofs = None
|
self.roofs = None
|
||||||
self.floors = None
|
self.floors = None
|
||||||
|
self.floors_decile_data = {}
|
||||||
|
|
||||||
def get_estimates(self, cleaner: EpcClean):
|
def get_estimates(self, cleaner: EpcClean):
|
||||||
"""
|
"""
|
||||||
|
|
@ -150,6 +151,10 @@ class UvalueEstimations:
|
||||||
]
|
]
|
||||||
|
|
||||||
self.floors = u_value_summary
|
self.floors = u_value_summary
|
||||||
|
self.floors_decile_data = {
|
||||||
|
"decile_labels": decile_labels,
|
||||||
|
"decile_boundaries": decile_boundaries
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def classify_into_deciles(df: pd.DataFrame, column: str) -> (pd.DataFrame, list, list):
|
def classify_into_deciles(df: pd.DataFrame, column: str) -> (pd.DataFrame, list, list):
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,13 @@ def handler():
|
||||||
floors_df["address1"].values[2]
|
floors_df["address1"].values[2]
|
||||||
floors_df["original_description"].values[2]
|
floors_df["original_description"].values[2]
|
||||||
|
|
||||||
|
df = pd.DataFrame(
|
||||||
|
[
|
||||||
|
x.data for x in input_properties
|
||||||
|
]
|
||||||
|
)
|
||||||
|
df["property-type"].unique()
|
||||||
|
|
||||||
from model_data.recommendations.FloorRecommendations import FloorRecommendations
|
from model_data.recommendations.FloorRecommendations import FloorRecommendations
|
||||||
self = FloorRecommendations(property_instance=input_properties[2], uvalue_estimates=uvalue_estimates)
|
self = FloorRecommendations(property_instance=input_properties[2], uvalue_estimates=uvalue_estimates)
|
||||||
|
|
||||||
|
|
|
||||||
123
model_data/rdsap_tables.py
Normal file
123
model_data/rdsap_tables.py
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
"""
|
||||||
|
This script contains standard tables which are defined in rdsap. The most recent version of sap/rdsap is
|
||||||
|
based on the 2012 version, however the government is currently working on releasing a new version, and there
|
||||||
|
we will need to re-visit this
|
||||||
|
"""
|
||||||
|
|
||||||
|
age_band_data = [
|
||||||
|
{
|
||||||
|
"age_band": "A",
|
||||||
|
"England_Wales": "before 1900",
|
||||||
|
"Scotland": "before 1919",
|
||||||
|
"Northern_Ireland": "before 1919",
|
||||||
|
"Park_home_UK": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "B",
|
||||||
|
"England_Wales": "1900-1929",
|
||||||
|
"Scotland": "1919-1929",
|
||||||
|
"Northern_Ireland": "1919-1929",
|
||||||
|
"Park_home_UK": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "C",
|
||||||
|
"England_Wales": "1930-1949",
|
||||||
|
"Scotland": "1930-1949",
|
||||||
|
"Northern_Ireland": "1930-1949",
|
||||||
|
"Park_home_UK": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "D",
|
||||||
|
"England_Wales": "1950-1966",
|
||||||
|
"Scotland": "1950-1964",
|
||||||
|
"Northern_Ireland": "1950-1973",
|
||||||
|
"Park_home_UK": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "E",
|
||||||
|
"England_Wales": "1967-1975",
|
||||||
|
"Scotland": "1965-1975",
|
||||||
|
"Northern_Ireland": "1974-1977",
|
||||||
|
"Park_home_UK": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "F",
|
||||||
|
"England_Wales": "1976-1982",
|
||||||
|
"Scotland": "1976-1983",
|
||||||
|
"Northern_Ireland": "1978-1985",
|
||||||
|
"Park_home_UK": "before 1983"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "G",
|
||||||
|
"England_Wales": "1983-1990",
|
||||||
|
"Scotland": "1984-1991",
|
||||||
|
"Northern_Ireland": "1986-1991",
|
||||||
|
"Park_home_UK": "1983-1995"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "H",
|
||||||
|
"England_Wales": "1991-1995",
|
||||||
|
"Scotland": "1992-1998",
|
||||||
|
"Northern_Ireland": "1992-1999",
|
||||||
|
"Park_home_UK": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "I",
|
||||||
|
"England_Wales": "1996-2002",
|
||||||
|
"Scotland": "1999-2002",
|
||||||
|
"Northern_Ireland": "2000-2006",
|
||||||
|
"Park_home_UK": "1996-2005"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "J",
|
||||||
|
"England_Wales": "2003-2006",
|
||||||
|
"Scotland": "2003-2007",
|
||||||
|
"Northern_Ireland": None,
|
||||||
|
"Park_home_UK": None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "K",
|
||||||
|
"England_Wales": "2007-2011",
|
||||||
|
"Scotland": "2008-2011",
|
||||||
|
"Northern_Ireland": "2007-2013",
|
||||||
|
"Park_home_UK": "2006 onwards"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age_band": "L",
|
||||||
|
"England_Wales": "2012 onwards",
|
||||||
|
"Scotland": "2012 onwards",
|
||||||
|
"Northern_Ireland": "2014 onwards",
|
||||||
|
"Park_home_UK": None
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
default_wall_thickness = [
|
||||||
|
{
|
||||||
|
"type": "stone", "A": 500, "B": 500, "C": 500, "D": 500, "E": 450, "F": 420, "G": 420, "H": 420,
|
||||||
|
"I": 450, "J_K_L": 450
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "solid brick", "A": 220, "B": 220, "C": 220, "D": 220, "E": 240, "F": 250, "G": 270, "H": 270,
|
||||||
|
"I": 300, "J_K_L": 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cavity", "A": 250, "B": 250, "C": 250, "D": 250, "E": 250, "F": 260, "G": 270, "H": 270,
|
||||||
|
"I": 300, "J_K_L": 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "timber frame", "A": 150, "B": 150, "C": 150, "D": 250, "E": 270, "F": 270, "G": 270, "H": 270,
|
||||||
|
"I": 300, "J_K_L": 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cob", "A": 540, "B": 540, "C": 540, "D": 540, "E": 540, "F": 540, "G": 560, "H": 560, "I": 590,
|
||||||
|
"J_K_L": 590
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "system build", "A": 250, "B": 250, "C": 250, "D": 250, "E": 250, "F": 300, "G": 300, "H": 300,
|
||||||
|
"I": 300, "J_K_L": 300
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "park home", "A": None, "B": None, "C": None, "D": None, "E": None, "F": 50, "G": None,
|
||||||
|
"H": None, "I": 50, "J_K_L": 100
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
import math
|
||||||
from model_data.BaseUtility import BaseUtility
|
from model_data.BaseUtility import BaseUtility
|
||||||
from model_data.Property import Property
|
from model_data.Property import Property
|
||||||
from model_data.analysis.UvalueEstimations import UvalueEstimations
|
from model_data.analysis.UvalueEstimations import UvalueEstimations
|
||||||
|
from model_data.rdsap_tables import default_wall_thickness, age_band_data
|
||||||
|
|
||||||
|
|
||||||
class FloorRecommendations(BaseUtility):
|
class FloorRecommendations(BaseUtility):
|
||||||
|
|
@ -11,6 +13,10 @@ class FloorRecommendations(BaseUtility):
|
||||||
# diminishing returns. This value should be verified with Osmosis (TODO)
|
# diminishing returns. This value should be verified with Osmosis (TODO)
|
||||||
DIMINISHING_RETURNS_U_VALUE = 0.2
|
DIMINISHING_RETURNS_U_VALUE = 0.2
|
||||||
|
|
||||||
|
REGION_LOOKUP = {
|
||||||
|
"England and Wales": "England_Wales",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, property_instance: Property, uvalue_estimates: UvalueEstimations):
|
def __init__(self, property_instance: Property, uvalue_estimates: UvalueEstimations):
|
||||||
self.property = property_instance
|
self.property = property_instance
|
||||||
self.uvalue_estimates = uvalue_estimates
|
self.uvalue_estimates = uvalue_estimates
|
||||||
|
|
@ -20,9 +26,85 @@ class FloorRecommendations(BaseUtility):
|
||||||
# Will contains a list of recommended measures
|
# Will contains a list of recommended measures
|
||||||
self.recommendations = []
|
self.recommendations = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _estimate_perimeter(floor_area, num_rooms):
|
||||||
|
# Compute average room size based on total floor area and number of rooms
|
||||||
|
avg_room_size = floor_area / num_rooms
|
||||||
|
|
||||||
|
# Estimate total side length for square layout
|
||||||
|
total_side_length = math.sqrt(avg_room_size * num_rooms)
|
||||||
|
|
||||||
|
# Compute the perimeter
|
||||||
|
perimeter = total_side_length * 4
|
||||||
|
|
||||||
|
return perimeter
|
||||||
|
|
||||||
|
def _estimate_suspended_floor_u_value(
|
||||||
|
self, floor_area, number_of_rooms, insulation_thickness, wall_type, region, age_band
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Estimate the u-value of a suspended floor, based on RdSap methodology
|
||||||
|
Default U-value for UNINSULATED suspended floor, based on RdSAP methodology
|
||||||
|
https://files.bregroup.com/bre-co-uk-file-library-copy/filelibrary/SAP/2012/RdSAP-9.93/RdSAP_2012_9.93.pdf
|
||||||
|
|
||||||
|
w = wall thickness, where these estimates are based on the RD SAP methodology, as in table S3
|
||||||
|
A = floor area
|
||||||
|
Exposed perimeter = P
|
||||||
|
soil type clas thermal conductivity lambda_g = 1.5 W/mK
|
||||||
|
Rsi = 0.17m^2K/W
|
||||||
|
Rse = 0.04m^2K/W
|
||||||
|
Rf = 0.001 * d_ins / 0.035 where d_ins is the insulation thickness in mm
|
||||||
|
height above external ground h = 0.3m
|
||||||
|
average wind speed at 10m height v=5m/s
|
||||||
|
wind sheilding factor fw = 0.05
|
||||||
|
vantilation factor E = 0.003 m^2/m
|
||||||
|
U-value of walls to underfloor space Uw = 1.5 W/m^2K
|
||||||
|
|
||||||
|
# Calulations for suspended ground floors, example for 5 bedroom house with permiter estimated at
|
||||||
|
44.36214602563767
|
||||||
|
1) dg = w + lambda_g x (Rsi + Rse) = 0.5 + 1.5 * (0.17 + 0.04) = 0.615
|
||||||
|
2) B = 2 * A/P = 2 * 123.0 / 44.36214602563767 = 5.545268253204708
|
||||||
|
3) Ug = 2 * lambda_g * log(pi * B/dg + 1)/(pi * B + dg) =
|
||||||
|
2 * 1.5 * log(3.141592653589793 * 5.545268253204708/0.615 + 1) / (3.141592653589793 * 5.545268253204708
|
||||||
|
+ 0.615) = 0.5619604457160708
|
||||||
|
4) Ux = (2 * h * Uw /B) + (1450 * E * v * fw/B) = (2 * 0.3 * 1.5 / 5.545268253204708) + (1450 * 0.003 * 5 *
|
||||||
|
0.05/5.545268253204708) = 0.35841367978030436
|
||||||
|
5) U = 1/ (2 * Rsi + Rf + 1/(Ug + Ux)) = 1 / (2 * 0.17 + 0 + 1/(0.5619604457160708 + 0.35841367978030436)) =
|
||||||
|
0.701
|
||||||
|
"""
|
||||||
|
age_band_letter = [x for x in age_band_data if x[region] == age_band][0]["age_band"]
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
# We need width in meters
|
||||||
|
"w": [x[age_band_letter] for x in default_wall_thickness if x["type"] == wall_type][0] / 1000,
|
||||||
|
"lambda_g": 1.5,
|
||||||
|
"Rsi": 0.17,
|
||||||
|
"Rse": 0.04,
|
||||||
|
"Rf": 0.001 * insulation_thickness / 0.035,
|
||||||
|
"h": 0.3,
|
||||||
|
"v": 5,
|
||||||
|
"fw": 0.05,
|
||||||
|
"E": 0.003,
|
||||||
|
"Uw": 1.5,
|
||||||
|
}
|
||||||
|
|
||||||
|
dg = defaults["w"] + defaults["lambda_g"] * (defaults["Rsi"] + defaults["Rse"])
|
||||||
|
|
||||||
|
# P is the exposed perimeter, which we estimate as we not have this data
|
||||||
|
p = self._estimate_perimeter(floor_area=floor_area, num_rooms=number_of_rooms)
|
||||||
|
b = 2 * floor_area / p
|
||||||
|
u_g = 2 * defaults["lambda_g"] * math.log(math.pi * b / dg + 1) / (math.pi * b + dg)
|
||||||
|
u_x = (2 * defaults["h"] * defaults["Uw"] / b) + (1450 * defaults["E"] * defaults["v"] * defaults["fw"] / b)
|
||||||
|
# This is the final estimated U-value
|
||||||
|
u = 1 / (2 * defaults["Rsi"] + defaults["Rf"] + 1 / (u_g + u_x))
|
||||||
|
|
||||||
|
return u
|
||||||
|
|
||||||
def recommend(self):
|
def recommend(self):
|
||||||
is_suspended = self.property.floor["is_suspended"]
|
is_suspended = self.property.floor["is_suspended"]
|
||||||
insulation_thickness = self.property.floor["insulation_thickness"]
|
insulation_thickness = self.property.floor["insulation_thickness"]
|
||||||
|
# Check which floor the property is on
|
||||||
|
|
||||||
self.property.year_built
|
self.property.year_built
|
||||||
self.property.data["floor-energy-eff"]
|
self.property.data["floor-energy-eff"]
|
||||||
self.property.data["floor-env-eff"]
|
self.property.data["floor-env-eff"]
|
||||||
|
|
@ -35,10 +117,67 @@ class FloorRecommendations(BaseUtility):
|
||||||
|
|
||||||
if is_suspended:
|
if is_suspended:
|
||||||
if insulation_thickness == "none":
|
if insulation_thickness == "none":
|
||||||
uvalue = None
|
|
||||||
else:
|
region_str, age_band = self.property.data["construction-age-band"].split(":")
|
||||||
uvalue = self.uvalue_estimates.get_estimate(
|
region_str = region_str.strip()
|
||||||
component="floor",
|
age_band = age_band.strip()
|
||||||
description="",
|
region = self.REGION_LOOKUP[region_str]
|
||||||
thickness=insulation_thickness
|
|
||||||
|
uvalue = self._estimate_suspended_floor_u_value(
|
||||||
|
floor_area=float(self.property.data["total-floor-area"]),
|
||||||
|
number_of_rooms=float(self.property.data["number-habitable-rooms"]),
|
||||||
|
insulation_thickness=0,
|
||||||
|
wall_type='solid brick',
|
||||||
|
region=region,
|
||||||
|
age_band=age_band,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
uvalue = self._get_floors_uvalue_estimate()
|
||||||
|
|
||||||
|
def _get_floors_uvalue_estimate(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Wrapper function which contains the methodology to extract a property's walls u-value estimate
|
||||||
|
when we don't have a true value and if we can't base our assumption off of the material
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
total_floor_area_group_decile = self.uvalue_estimates.classify_decile_newvalues(
|
||||||
|
decile_boundaries=self.uvalue_estimates.floors_decile_data["decile_boundaries"],
|
||||||
|
decile_labels=self.uvalue_estimates.floors_decile_data["decile_labels"],
|
||||||
|
new_values=[float(self.property.data["total-floor-area"])],
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
u_value_estimate = self.uvalue_estimates.floors[
|
||||||
|
(self.uvalue_estimates.floors["local-authority"] == self.property.data["local-authority"]) &
|
||||||
|
(self.uvalue_estimates.floors["property-type"] == self.property.data["property-type"]) &
|
||||||
|
(self.uvalue_estimates.floors["built-form"] == self.property.data["built-form"]) &
|
||||||
|
(self.uvalue_estimates.floors["floor-energy-eff"] == self.property.data["floor-energy-eff"]) &
|
||||||
|
(self.uvalue_estimates.floors["floor-env-eff"] == self.property.data["floor-env-eff"]) &
|
||||||
|
(self.uvalue_estimates.floors["total-floor-area_group"] == total_floor_area_group_decile)
|
||||||
|
]
|
||||||
|
|
||||||
|
if u_value_estimate.empty:
|
||||||
|
raise ValueError("No U-value estimate found for the given property")
|
||||||
|
|
||||||
|
# Because of how spuriously populated the data is for number-habitable-rooms and number-heated-rooms,
|
||||||
|
# we will try and filter on these to see if we get a result
|
||||||
|
|
||||||
|
habitable_rooms_filter = (
|
||||||
|
self.uvalue_estimates.walls["number-habitable-rooms"] == self.property.data["number-habitable-rooms"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if any(habitable_rooms_filter):
|
||||||
|
u_value_estimate = u_value_estimate[habitable_rooms_filter]
|
||||||
|
|
||||||
|
heated_rooms_filter = (
|
||||||
|
self.uvalue_estimates.walls["number-heated-rooms"] == self.property.data["number-heated-rooms"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if any(heated_rooms_filter):
|
||||||
|
u_value_estimate = u_value_estimate[heated_rooms_filter]
|
||||||
|
|
||||||
|
# It's possible for us to have multiple rows if we didn't do a habitable/heated rooms filter so we
|
||||||
|
# average
|
||||||
|
|
||||||
|
return u_value_estimate["median_thermal_transmittance"].mean()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue