From 84bcb99ad994d8e3ac5c9e7b034af8f0a3b9b70a Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 5 Dec 2023 11:02:12 +0000 Subject: [PATCH] Added further placeholder valuation figures and added mvp valuation increase methodology --- .idea/Model.iml | 2 +- .idea/misc.xml | 2 +- backend/app/db/models/materials.py | 2 +- backend/ml_models/Valuation.py | 109 +++++++++++++++++++++++++++-- 4 files changed, 105 insertions(+), 10 deletions(-) diff --git a/.idea/Model.iml b/.idea/Model.iml index b0f9c00d..4413bb06 100644 --- a/.idea/Model.iml +++ b/.idea/Model.iml @@ -7,7 +7,7 @@ - + diff --git a/.idea/misc.xml b/.idea/misc.xml index 1122b380..6f308057 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,7 +3,7 @@ - + diff --git a/backend/app/db/models/materials.py b/backend/app/db/models/materials.py index 1a41f14f..f887fc25 100644 --- a/backend/app/db/models/materials.py +++ b/backend/app/db/models/materials.py @@ -35,7 +35,7 @@ class MaterialType(enum.Enum): low_energy_lighting_installation = "low_energy_lighting_installation" flat_roof_preparation = "flat_roof_preparation" flat_roof_vapour_barrier = "flat_roof_vapour_barrier" - flat_roof_waterpoofing = "flat_roof_waterpoofing" + flat_roof_waterproofing = "flat_roof_waterproofing" class DepthUnit(enum.Enum): diff --git a/backend/ml_models/Valuation.py b/backend/ml_models/Valuation.py index ad296409..6888e45e 100644 --- a/backend/ml_models/Valuation.py +++ b/backend/ml_models/Valuation.py @@ -1,22 +1,117 @@ +import numpy as np + + class PropertyValuation: """ This is a placeholder class for the property valuation model """ UPRN_VALUE_LOOKUP = { - 15038202: {"current_value": 202000, "increase_percentage": 0.05725}, - 37024763: {"current_value": 213000, "increase_percentage": 0.025}, + 15038202: 202000, + 37024763: 213000, + 100070478545: 212000, + 100070297696: 235000, + 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 } + # 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): - data = cls.UPRN_VALUE_LOOKUP.get(property_instance.uprn) + value = cls.UPRN_VALUE_LOOKUP.get(property_instance.uprn) - if not data: + if not value: raise ValueError("Have not implemented valuation for this property") - new_valuation = (1 + data["increase_percentage"]) * data["current_value"] + 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] - increase = round(new_valuation - data["current_value"], 2) + msm_increase, lloyds_increase = cls.get_increase(epc_band_range) - return increase + # 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), + }