import numpy as np 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 } # 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}, ] 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}, ] 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): value = cls.UPRN_VALUE_LOOKUP.get(property_instance.uprn) if not value: return { "current_value": None, "lower_bound_increased_value": None, "upper_bound_increased_value": None, "average_increased_value": None, "average_increase": None } 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 and nationwide 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] kf_increase = kf_increase[0]["increase_percentage"] if kf_increase else None nw_increase = nw_increase[0]["increase_percentage"] if nw_increase else None all_increases = [x for x in [msm_increase, lloyds_increase, kf_increase, nw_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": value, "lower_bound_increased_value": value * (1 + min_increase), "upper_bound_increased_value": value * (1 + max_increase), "average_increased_value": value * (1 + avg_increase), "average_increase": value * (1 + avg_increase) - value }