started work on peabody

This commit is contained in:
Khalim Conn-Kowlessar 2025-11-25 12:15:06 +00:00
parent fd2d893499
commit caeb776428
7 changed files with 37 additions and 17 deletions

View file

@ -2,7 +2,6 @@ import time
import random import random
import pandas as pd import pandas as pd
from adhoc.investigation import newest_epc
from backend.SearchEpc import SearchEpc from backend.SearchEpc import SearchEpc
from etl.find_my_epc.RetrieveFindMyEpc import RetrieveFindMyEpc from etl.find_my_epc.RetrieveFindMyEpc import RetrieveFindMyEpc
from tqdm import tqdm from tqdm import tqdm

View file

@ -3,7 +3,6 @@ from pydantic_settings import BaseSettings
from typing import Optional from typing import Optional
class Settings(BaseSettings): class Settings(BaseSettings):
API_KEY: str API_KEY: str
API_KEY_NAME: str = "X-API-KEY" API_KEY_NAME: str = "X-API-KEY"
@ -43,7 +42,8 @@ class Settings(BaseSettings):
AWS_DEFAULT_REGION: Optional[str] = None AWS_DEFAULT_REGION: Optional[str] = None
class Config: class Config:
env_file = "backend.env" env_file = "backend/.env"
@lru_cache() @lru_cache()
def get_settings(): def get_settings():

View file

@ -14,6 +14,7 @@ db_string = connection_string.format(
db_engine = create_engine(db_string, pool_size=5, max_overflow=5) db_engine = create_engine(db_string, pool_size=5, max_overflow=5)
def get_db_session(): def get_db_session():
if db_engine is None: if db_engine is None:
raise RuntimeError("Database is not configured. Set DATABASE_URL in environment variables.") raise RuntimeError("Database is not configured. Set DATABASE_URL in environment variables.")

View file

@ -64,7 +64,7 @@ def extract_property_request_data(
x for x in already_installed if x for x in already_installed if
(x["address"] == config["address"]) and (x["postcode"] == config["postcode"]) (x["address"] == config["address"]) and (x["postcode"] == config["postcode"])
), []) ), [])
# Because we have some non-invasive recommendations that match on address and postcode, but not UPRN # Because we have some non-invasive recommendations that match on address and postcode, but not UPRN
# we need to check existence of uprn # we need to check existence of uprn
has_uprn = "uprn" in non_invasive_recommendations[0] if non_invasive_recommendations else False has_uprn = "uprn" in non_invasive_recommendations[0] if non_invasive_recommendations else False

View file

@ -392,6 +392,26 @@ def parse_heating_system(config):
return None return None
def check_duplicate_uprns(plan_input):
"""
Simple function to check if the input data contains duplicated UPRNS.
If there are duplicates, an exception will be rasied
:return:
"""
# Check for duplicate UPRNS
input_uprns = [x.get("uprn") for x in plan_input if "uprn" in x and x.get("uprn")]
if input_uprns:
# Check for dupes
if len(input_uprns) != len(set(input_uprns)):
# Find the duplicate UPRNs
duplicates = set([x for x in input_uprns if input_uprns.count(x) > 1])
# de-dupe input_uprns
raise ValueError(f"Duplicate UPRNs in the input data: {duplicates}")
return True
async def model_engine(body: PlanTriggerRequest): async def model_engine(body: PlanTriggerRequest):
logger.info("Model Engine triggered with body: %s", json.loads(body.model_dump_json())) logger.info("Model Engine triggered with body: %s", json.loads(body.model_dump_json()))
@ -480,16 +500,8 @@ async def model_engine(body: PlanTriggerRequest):
if body.index_start is not None and body.index_end is not None: if body.index_start is not None and body.index_end is not None:
plan_input = plan_input[body.index_start:body.index_end] plan_input = plan_input[body.index_start:body.index_end]
# Check for duplicate UPRNS # Confirm no duplicate UPRNS
input_uprns = [x.get("uprn") for x in plan_input if "uprn" in x and x.get("uprn")] check_duplicate_uprns(plan_input)
if input_uprns:
# Check for dupes
if len(input_uprns) != len(set(input_uprns)):
# Find the duplicate UPRNs
duplicates = set([x for x in input_uprns if input_uprns.count(x) > 1])
# de-dupe input_uprns
raise ValueError(f"Duplicate UPRNs in the input data: {duplicates}")
# If we have patches or overrides, we should read them in here # If we have patches or overrides, we should read them in here
patches, already_installed, non_invasive_recommendations, valuation_data = get_request_property_data(body) patches, already_installed, non_invasive_recommendations, valuation_data = get_request_property_data(body)
@ -528,9 +540,7 @@ async def model_engine(body: PlanTriggerRequest):
if (body.event_type == "remote_assessment") and config.get("property_type") == "Flat": if (body.event_type == "remote_assessment") and config.get("property_type") == "Flat":
# We're running a remote assessment for a flat - we go and grab the associated # We're running a remote assessment for a flat - we go and grab the associated
# UPRNS for other units in the same building # UPRNS for other units in the same building
associated_uprns = get_associated_uprns( associated_uprns = get_associated_uprns(session, postcode=config["postcode"], uprn=uprn)
session, postcode=config["postcode"], uprn=uprn
)
epc_searcher = SearchEpc( epc_searcher = SearchEpc(
address1=address1, address1=address1,
@ -1140,6 +1150,7 @@ async def model_engine(body: PlanTriggerRequest):
) )
property_value_increase_ranges[p.id] = valuations property_value_increase_ranges[p.id] = valuations
# TODO - this is not right, especially if the existing run failed
if p.is_new: if p.is_new:
property_details_epc = p.get_property_details_epc( property_details_epc = p.get_property_details_epc(
portfolio_id=body.portfolio_id, rating_lookup=rating_lookup, portfolio_id=body.portfolio_id, rating_lookup=rating_lookup,

View file

@ -77,6 +77,14 @@ archetypes = sustainability_data[
"Floor Area Band"] "Floor Area Band"]
].drop_duplicates() ].drop_duplicates()
# Potential reductions:
# 1) Split roof insulation into > 100mm loft and <= 100mm loft
# 2) Group all of the glazed together (e.g. double glazed, secondary glazed, triple glazed)
# 3) Group up boiler efficiency A-C, D - F, G? or someting like this
# 4) Group up main fuel into gas, electric, oil, other?
# 5) Wall Construction - group up Sandstone and Granite into one category
# 6) Reduce or remove floor construction
# Maps the property types to the format recognised by the EPC api # Maps the property types to the format recognised by the EPC api
property_type_map = {} property_type_map = {}
# Maps the build form to the format recognised by the OS api # Maps the build form to the format recognised by the OS api

View file

@ -398,6 +398,7 @@ class RetrieveFindMyEpc:
extracted_address_cleaned = ( extracted_address_cleaned = (
extracted_address.replace(",", "").replace(" ", "").lower() extracted_address.replace(",", "").replace(" ", "").lower()
) )
if not extracted_address_cleaned.startswith(self.address_cleaned): if not extracted_address_cleaned.startswith(self.address_cleaned):
continue continue