mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
251 lines
11 KiB
Python
251 lines
11 KiB
Python
import numpy as np
|
|
from scipy.constants import value
|
|
|
|
|
|
class PropertyValuation:
|
|
"""
|
|
This is a placeholder class for the property valuation model
|
|
"""
|
|
|
|
UPRN_VALUE_LOOKUP = {
|
|
15038202: 202000,
|
|
37024763: 213000,
|
|
100070478545: 212000,
|
|
100070297696: 662000, # Based on Zoopla's estimation of nearby house, 8 bloomfield road
|
|
100070476394: 222000, # Based on Zoopla's estimation of next door, 20 Parkside
|
|
100071264896: 128000,
|
|
# Based on next door neighbour: https://themovemarket.com/tools/propertyprices/flat-2-queens-wood-house-219
|
|
# -brandwood-road-birmingham-b14-6pu
|
|
100070533688: 218000, # Based on Zoopla's estimation of 95 Tenby Road, which is also mid terrace
|
|
100070505235: 344000, # Based on Zoopla's estimation of 131 School road, which is also semi-detached
|
|
100070513306: 182000, # Based on Zoopla's estimation of 61 Simmons Drive
|
|
100071306896: 77000, # Based on Flat 2 of 44 Wedgewood Road on Zoopla
|
|
100021192109: 650000, # Based on Zoopla
|
|
766249482: 358000, # Based on Zoopla estimate for 19 Spring Lane, 3 bedroom semi-detached
|
|
100120703802: 277000, # Based on Zoopla
|
|
10014469685: 286000, # Based on Zoopla
|
|
10001328782: 196000, # Based on Zoopla
|
|
# Urban Splash - valuations from The Move Market
|
|
10023345430: 74_000,
|
|
10023345435: 99_000,
|
|
10023345436: 62_000,
|
|
10023345441: 62_000,
|
|
10094183503: 2_988_000,
|
|
10094183499: 123_000,
|
|
10070056824: 70_000,
|
|
110070056242: 100_000,
|
|
10070056243: 130_000,
|
|
10070056817: 130_000,
|
|
10094183501: 185_000,
|
|
10070056250: 71_000,
|
|
10094183500: 185_000,
|
|
10070056843: 67_000,
|
|
10070056844: 67_000,
|
|
10070056241: 76_000,
|
|
10070056834: 63_000,
|
|
10023345439: 62_000,
|
|
10070056815: 101_000,
|
|
10070056816: 101_000,
|
|
10094183498: 101_000,
|
|
10070056840: 673_000,
|
|
10070056848: 76_000,
|
|
10070056849: 76_000,
|
|
10070056829: 76_000,
|
|
10070056920: 76_000,
|
|
10023345463: 76_000,
|
|
# IMMO Dudley Pilot - search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
|
|
90070461: 172_000, # Based on Zoopla
|
|
90022227: 181_000, # Based on Zoopla
|
|
90106884: 180_000, # Based on Zoopla
|
|
90051858: 201_000, # Based on Zoopla
|
|
90060989: 172_000, # Based on Zoopla
|
|
90048026: 196_000, # Based on Zoopla
|
|
90077535: 192_000, # Based on Zoopla
|
|
90093693: 279_000, # Based on Zoopla
|
|
90055152: 149_000, # Based on Zoopla
|
|
90028499: 238_000, # Based on Zoopla
|
|
# IMMO Dudley Pilot 2- search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
|
|
90039318: 177_000, # Based on Zoopla
|
|
90038384: 170_000, # Based on Zoopla
|
|
90105380: 185_000, # Based on Zoopla
|
|
90124001: 165_000, # Based on Zoopla
|
|
90013980: 148_000, # Based on Zoopla
|
|
90087154: 184_000, # Based on Zoopla
|
|
90046817: 167_000, # Based on Zoopla
|
|
# Goldman Sachs Pilot for inrto - search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
|
|
100070358888: 153_000, # Based on Zoopla
|
|
10090436544: 282_000, # Based on Zoopla
|
|
100070365751: 177_000, # Based on Zoopla
|
|
10095952767: 168_000, # Based on Zoopla
|
|
100070520130: 177_000, # Based on Zoopla
|
|
100070333957: 185_000, # Based on Zoopla
|
|
100070543258: 211_000, # Based on Zoopla
|
|
# Vander Elliot Pilot - search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
|
|
41018850: 104_000, # Based on Zoopla
|
|
38237316: 74_000, # Based on Zoopla
|
|
38237317: 74_000, # Based on Zoopla
|
|
41052320: 70_000, # Based on Zoopla
|
|
41052321: 70_000, # Based on Zoopla
|
|
41052322: 38_000, # Based on Zoopla
|
|
41222759: 38_000, # Based on Zoopla
|
|
41222760: 46_000, # Based on Zoopla
|
|
41222761: 270_000, # Based on Zoopla
|
|
41212534: 38_000, # Based on Zoopla
|
|
# Northern Group Pilot - search by going to https://www.zoopla.co.uk/property/uprn/{uprn}/
|
|
10070868263: 194_000, # Based on Zoopla
|
|
10070868244: 195_000, # Based on Zoopla
|
|
# Places For People Pilot
|
|
200140644: 385_000,
|
|
200140645: 481_000,
|
|
200140646: 372_000,
|
|
200140647: 481_000,
|
|
200140648: 373_000,
|
|
200140649: 373_000,
|
|
# Vander Elliot Intrusive surveys
|
|
12103116: 1_537_000,
|
|
12103117: 1_404_000,
|
|
# GLA Proposal
|
|
100020606627: 409_000
|
|
}
|
|
|
|
# We base our valuation uplifts on a number of sources
|
|
# https://www.moneysupermarket.com/gas-and-electricity/value-of-efficiency/
|
|
MSM_MAPPING = [
|
|
{"start": "G", "end": "F", "increase_percentage": 0.06},
|
|
{"start": "F", "end": "E", "increase_percentage": 0.01},
|
|
{"start": "E", "end": "D", "increase_percentage": 0.01},
|
|
{"start": "D", "end": "C", "increase_percentage": 0.02},
|
|
{"start": "C", "end": "B", "increase_percentage": 0.04},
|
|
{"start": "B", "end": "A", "increase_percentage": 0.0},
|
|
]
|
|
|
|
# https://www.lloydsbankinggroup.com/media/press-releases/2021/halifax/homebuyers-pay-a-green-premium-of-40000
|
|
# -for-the-most-energy-efficient-properties.html
|
|
LLOYDS_MAPPING = [
|
|
{"start": "G", "end": "F", "increase_percentage": 0.038},
|
|
{"start": "F", "end": "E", "increase_percentage": 0.029},
|
|
{"start": "E", "end": "D", "increase_percentage": 0.024},
|
|
{"start": "D", "end": "C", "increase_percentage": 0.02},
|
|
{"start": "C", "end": "B", "increase_percentage": 0.02},
|
|
{"start": "B", "end": "A", "increase_percentage": 0.018},
|
|
]
|
|
|
|
KNIGHT_FRANK_MAPPING = [
|
|
{"start": "D", "end": "C", "increase_percentage": 0.03},
|
|
{"start": "D", "end": "B", "increase_percentage": 0.088},
|
|
{"start": "D", "end": "A", "increase_percentage": 0.088},
|
|
]
|
|
|
|
NATIONWIDE_MAPPING = [
|
|
# {"start": "G", "end": "D", "increase_percentage": 0.035},
|
|
# {"start": "F", "end": "D", "increase_percentage": 0.035},
|
|
# {"start": "D", "end": "B", "increase_percentage": 0.017},
|
|
# {"start": "D", "end": "A", "increase_percentage": 0.017},
|
|
]
|
|
|
|
# Found here: https://www.rightmove.co.uk/news/articles/property-news/green-premium-epc-ratings/
|
|
# F -> C is + 15%
|
|
# E -> C is +7%
|
|
# D -> C is +3%
|
|
RIGHTMOVE_MAPPING = [
|
|
{"start": "G", "end": "C", "increase_percentage": 0.15},
|
|
{"start": "G", "end": "B", "increase_percentage": 0.15},
|
|
{"start": "G", "end": "A", "increase_percentage": 0.15},
|
|
|
|
{"start": "F", "end": "C", "increase_percentage": 0.15},
|
|
{"start": "F", "end": "B", "increase_percentage": 0.15},
|
|
{"start": "F", "end": "A", "increase_percentage": 0.15},
|
|
|
|
{"start": "E", "end": "C", "increase_percentage": 0.07},
|
|
{"start": "E", "end": "B", "increase_percentage": 0.07},
|
|
{"start": "E", "end": "A", "increase_percentage": 0.07},
|
|
|
|
{"start": "D", "end": "C", "increase_percentage": 0.03},
|
|
{"start": "D", "end": "B", "increase_percentage": 0.03},
|
|
{"start": "D", "end": "A", "increase_percentage": 0.03},
|
|
|
|
]
|
|
|
|
# Additional sources:
|
|
# https://superhomes.org.uk/wp-content/uploads/2024/05/The-Impact-of-Retrofit-on-Residential-Property-Market
|
|
# -Values-7-rotated-1.pdf
|
|
|
|
EPC_BANDS = ["G", "F", "E", "D", "C", "B", "A"]
|
|
|
|
@classmethod
|
|
def get_increase(cls, epc_band_range):
|
|
|
|
increases = []
|
|
for i in range(len(epc_band_range)):
|
|
|
|
if i == len(epc_band_range) - 1:
|
|
break
|
|
|
|
current = epc_band_range[i]
|
|
next = epc_band_range[i + 1]
|
|
|
|
msm_increase = [x for x in cls.MSM_MAPPING if x["start"] == current and x["end"] == next][0]
|
|
lloyds_increase = [x for x in cls.LLOYDS_MAPPING if x["start"] == current and x["end"] == next][0]
|
|
|
|
increases.append(
|
|
{
|
|
"start": current,
|
|
"end": next,
|
|
"msm_increase": msm_increase["increase_percentage"],
|
|
"lloyds_increase": lloyds_increase["increase_percentage"],
|
|
}
|
|
)
|
|
|
|
# We now aggregate the increases. The should be compound increases so we multiply them together
|
|
msm_increase = np.prod([1 + x["msm_increase"] for x in increases]) - 1
|
|
lloyds_increase = np.prod([1 + x["lloyds_increase"] for x in increases]) - 1
|
|
|
|
return msm_increase, lloyds_increase
|
|
|
|
@classmethod
|
|
def estimate(cls, property_instance, target_epc):
|
|
current_value = (
|
|
property_instance.valuation if property_instance.valuation else
|
|
cls.UPRN_VALUE_LOOKUP.get(property_instance.uprn)
|
|
)
|
|
|
|
if not current_value:
|
|
return {
|
|
"current_value": 0,
|
|
"lower_bound_increased_value": 0,
|
|
"upper_bound_increased_value": 0,
|
|
"average_increased_value": 0,
|
|
"average_increase": 0
|
|
}
|
|
|
|
current_epc = property_instance.data["current-energy-rating"]
|
|
# We get the spectrum of ratings between the current and target EPC
|
|
epc_band_range = cls.EPC_BANDS[cls.EPC_BANDS.index(current_epc): cls.EPC_BANDS.index(target_epc) + 1]
|
|
|
|
msm_increase, lloyds_increase = cls.get_increase(epc_band_range)
|
|
|
|
# We now use the knight frank, nationwide and Rightmove data to get further valuation evidence, if we have it
|
|
kf_increase = [x for x in cls.KNIGHT_FRANK_MAPPING if x["start"] == current_epc and x["end"] == target_epc]
|
|
nw_increase = [x for x in cls.NATIONWIDE_MAPPING if x["start"] == current_epc and x["end"] == target_epc]
|
|
rm_increase = [x for x in cls.RIGHTMOVE_MAPPING if x["start"] == current_epc and x["end"] == target_epc]
|
|
|
|
kf_increase = kf_increase[0]["increase_percentage"] if kf_increase else None
|
|
nw_increase = nw_increase[0]["increase_percentage"] if nw_increase else None
|
|
rm_increase = rm_increase[0]["increase_percentage"] if rm_increase else None
|
|
|
|
all_increases = [
|
|
x for x in [msm_increase, lloyds_increase, kf_increase, nw_increase, rm_increase] if x is not None
|
|
]
|
|
|
|
max_increase = max(all_increases)
|
|
min_increase = min(all_increases)
|
|
|
|
avg_increase = np.mean(all_increases)
|
|
|
|
return {
|
|
"current_value": current_value,
|
|
"lower_bound_increased_value": float(current_value * (1 + min_increase)),
|
|
"upper_bound_increased_value": float(current_value * (1 + max_increase)),
|
|
"average_increased_value": float(current_value * (1 + avg_increase)),
|
|
"average_increase": float(current_value * (1 + avg_increase) - current_value)
|
|
}
|