From 6f579b6939711de60d3e6928aa93589775020df6 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 1 Aug 2023 11:38:35 +0100 Subject: [PATCH] adding additional attributes to property for posting to the database --- backend/Property.py | 72 +++++++++++++++++++++++++++++++ backend/app/plan/router.py | 87 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) diff --git a/backend/Property.py b/backend/Property.py index 25865a4f..eb16189e 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -38,6 +38,10 @@ class Property(BaseUtility): self.in_conservation_area = None self.year_built = None + self.energy = None + self.ventilation = None + self.solar_pv = None + if epc_client: self.epc_client = epc_client else: @@ -76,6 +80,70 @@ class Property(BaseUtility): """ self.coordinates = {key.lower(): value for key, value in coordinates.items()} + def set_energy(self): + """ + Extracts and formats data about the home's energy and co2 consumption + To being with, this is just formatting epc data + + Data: + - primary_energy_consumption + This is based on the "energy-consumption-current" field in the EPC data. + Current estimated total energy consumption for the property in a 12 month period (kWh/m2). Displayed on EPC + as the current primary energy use per square metre of floor area. + + - co2_emissions + This is based on the "co2-emissions-current" field in the EPC data. + CO₂ emissions per year in tonnes/year. + """ + + self.energy = { + "primary_energy_consumption": float(self.data["energy-consumption-current"]), + "co2_emissions": float(self.data["co2-emissions-current"]), + } + + def set_ventilation(self): + """ + Extracts and formats data about the home's ventilation + To being with, this is just formatting epc data + + Data: + - ventilation + This is based on the "ventilation-type" field in the EPC data. + Ventilation type of the property. + """ + + ventilation = self.data["mechanical-ventilation"] + # perform some simple cleaning - when checking 300k properties, the only unique values were + # {'', 'mechanical, supply and extract', 'NO DATA!', 'natural', 'mechanical, extract only'} + if ventilation in self.DATA_ANOMALY_MATCHES or ventilation in [""]: + ventilation = None + + self.ventilation = { + "ventilation": ventilation, + } + + def set_solar_pv(self): + """ + Extracts and formats data about the home's solar pv + To being with, this is just formatting epc data + + Data: + - solar_pv + This is based on the "photo-supply" field in the EPC data. + + When checking 100k properties, either the value was "" or a stringified number + """ + + solar_pv = self.data["photo-supply"] + if solar_pv == "": + solar_pv = None + else: + solar_pv = float(solar_pv) + + self.solar_pv = { + "solar_pv": solar_pv, + } + def get_components(self, cleaned): """ Given the cleaning that has been performed, we'll use this to identify the property @@ -90,6 +158,10 @@ class Property(BaseUtility): if not self.data: raise ValueError("Property does not contain data") + self.set_energy() + self.set_ventilation() + self.set_solar_pv() + for description, attribute in cleaned.items(): if self.data[description] in self.DATA_ANOMALY_MATCHES: diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index a8dd36e6..2a12abd1 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -1,3 +1,4 @@ +import datetime from fastapi import APIRouter, Depends from backend.app.dependencies import validate_token from backend.app.plan.schemas import PlanTriggerRequest @@ -93,6 +94,15 @@ async def trigger_plan(body: PlanTriggerRequest): if not is_new: continue + # TODO: push property targets + # TODO: Need to add heat demand target + property_targets = { + "property_id": property_id, + "portfolio_id": body.portfolio_id, + "created_at": datetime.datetime.now(), + "epc": body.goal_value, + } + input_properties.append( Property( postcode=config['postcode'], @@ -187,4 +197,81 @@ async def trigger_plan(body: PlanTriggerRequest): recommendations.extend(wall_recomendations.recommendations) + # Once we're done, we'll store: + # 1) the property data + # 2) the property details (epc) + # 3) the recommendations + + # Upload property data + for p in input_properties: + property_data = { + "creation_status": "COMPLETE", + "uprn": int(p.data["uprn"]), + "has_pre_condition_report": True, + "has_recommendations": True, + "property_type": p.data["property-type"], + "built_form": p.data["built-form"], + "local_authority": p.data["local-authority-label"], + "constituency": p.data["constituency-label"], + "number_of_rooms": p.data["number-habitable-rooms"], + "year_built": p.year_built, + "tenure": p.data["tenure"], + "current_epc_rating": p.data["current-energy-rating"], + "current_sap_points": p.data["current-energy-efficiency"] + } + + def clean_upload_data(to_update, to_clean_values): + for k, v in to_update.items(): + if v in to_clean_values: + to_update[k] = None + return to_update + + property_data = clean_upload_data(property_data, to_clean_values=p.DATA_ANOMALY_MATCHES) + + rating_lookup = { + "Very Good": 5, + "Good": 4, + "Average": 3, + "Poor": 2, + "Very Poor": 1, + "N/A": None + } + + property_details_epc = { + "property_id": p.id, + "portfolio_id": body.portfolio_id, + "full_address": p.data["address"], + "total_floor_area": float(p.data["total-floor-area"]), + "walls": p.walls["cleaned_description"], + "walls_rating": rating_lookup[p.data["walls-energy-eff"]], + "roof": p.roof["cleaned_description"], + "roof_rating": rating_lookup[p.data["roof-energy-eff"]], + "floor": p.floor["cleaned_description"], + "floor_rating": rating_lookup[p.data["floor-energy-eff"]], + "windows": p.windows["cleaned_description"], + "windows_rating": rating_lookup[p.data["windows-energy-eff"]], + "heating": p.main_heating["cleaned_description"], + "heating_rating": rating_lookup[p.data["mainheat-energy-eff"]], + "heating_controls": p.main_heating_controls["cleaned_description"], + "heating_controls_rating": rating_lookup[p.data["mainheatc-energy-eff"]], + "hot_water": p.hotwater["cleaned_description"], + "hot_water_rating": rating_lookup[p.data["hot-water-energy-eff"]], + "lighting": p.lighting["cleaned_description"], + "lighting_rating": rating_lookup[p.data["lighting-energy-eff"]], + "ventilation": p.ventilation["ventilation"], + "solar_pv": p.solar_pv["cleaned_description"], + "solar_hot_water": p.solar_hot_water["cleaned_description"], + "wind_turbine": p.wind_turbine["cleaned_description"], + "floor_height": p.data["floor-height"], + "heat_loss_corridor": p.data["heat-loss-corridor"], + "unheated_corridor_length": p.data["unheated-corridor-length"], + "number_of_open_fireplaces": int(p.data["number-open-fireplaces"]), + "number_of_extensions": int(p.data["extension-count"]), + "number_of_storeys": int(p.data["flat-storey-count"]), + "mains_gas": p.data["mains-gas-flag"], + "energy_tarrif": p.data["energy-tariff"], + "primary_energy_consumption": p.energy["primary-energy-consumption"], + "co2_emissions": p.energy["co2-emissions"], + } + return {"recommendations": recommendations}