Merge pull request #628 from Hestia-Homes/eco-eligiblity-bug

Fixing WallAttributes bugs for Peabody run
This commit is contained in:
KhalimCK 2026-01-02 20:46:56 +08:00 committed by GitHub
commit eb6c7191ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 112 additions and 59 deletions

View file

@ -614,6 +614,9 @@ class Property:
# Handling edge case for walls # Handling edge case for walls
fill_with = False if description == "walls-description" else None fill_with = False if description == "walls-description" else None
fill_dict = dict(zip(template.keys(), [fill_with] * len(template))) fill_dict = dict(zip(template.keys(), [fill_with] * len(template)))
if description == "walls-description":
fill_dict["thermal_transmittance_unit"] = None
fill_dict["insulation_thickness"] = "none"
fill_dict.update( fill_dict.update(
{ {

View file

@ -1,3 +1,4 @@
from sqlalchemy import text
from sqlalchemy import insert, delete, select from sqlalchemy import insert, delete, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
@ -365,84 +366,122 @@ def delete_property_batch(session: Session, property_ids: list[int]):
if not property_ids: if not property_ids:
return return
# -------------------------------------------------- params = {"property_ids": property_ids}
# Shared subqueries (computed once)
# --------------------------------------------------
plan_ids = (
select(Plan.id)
.where(Plan.property_id.in_(property_ids))
)
recommendation_ids = ( # --------------------------------------------------
select(Recommendation.id) # recommendation_materials (via recommendation)
.where(Recommendation.property_id.in_(property_ids)) # --------------------------------------------------
) session.execute(
text("""
funding_package_ids = ( DELETE FROM recommendation_materials rm
select(FundingPackage.id) USING recommendation r
.where(FundingPackage.plan_id.in_(plan_ids)) WHERE rm.recommendation_id = r.id
AND r.property_id = ANY(:property_ids)
"""),
params,
) )
# -------------------------------------------------- # --------------------------------------------------
# Leaf tables FIRST # plan_recommendations (via plan)
# -------------------------------------------------- # --------------------------------------------------
session.execute( session.execute(
delete(RecommendationMaterials) text("""
.where(RecommendationMaterials.recommendation_id.in_(recommendation_ids)) DELETE FROM plan_recommendations pr
) USING plan p
WHERE pr.plan_id = p.id
session.execute( AND p.property_id = ANY(:property_ids)
delete(PlanRecommendations) """),
.where(PlanRecommendations.plan_id.in_(plan_ids)) params,
)
session.execute(
delete(FundingPackageMeasures)
.where(FundingPackageMeasures.funding_package_id.in_(funding_package_ids))
)
session.execute(
delete(InspectionModel)
.where(InspectionModel.property_id.in_(property_ids))
) )
# -------------------------------------------------- # --------------------------------------------------
# Mid-level tables # funding_package_measures
# -------------------------------------------------- # --------------------------------------------------
session.execute( session.execute(
delete(FundingPackage) text("""
.where(FundingPackage.id.in_(funding_package_ids)) DELETE FROM funding_package_measures fpm
) USING funding_package fp, plan p
WHERE fpm.funding_package_id = fp.id
session.execute( AND fp.plan_id = p.id
delete(Recommendation) AND p.property_id = ANY(:property_ids)
.where(Recommendation.id.in_(recommendation_ids)) """),
) params,
session.execute(
delete(Plan)
.where(Plan.id.in_(plan_ids))
) )
# -------------------------------------------------- # --------------------------------------------------
# Property-scoped tables # inspections (direct)
# -------------------------------------------------- # --------------------------------------------------
session.execute( session.execute(
delete(PropertyDetailsEpcModel) text("""
.where(PropertyDetailsEpcModel.property_id.in_(property_ids)) DELETE FROM inspections
) WHERE property_id = ANY(:property_ids)
"""),
session.execute( params,
delete(PropertyTargetsModel)
.where(PropertyTargetsModel.property_id.in_(property_ids))
) )
# -------------------------------------------------- # --------------------------------------------------
# Properties LAST # funding_package
# -------------------------------------------------- # --------------------------------------------------
session.execute( session.execute(
delete(PropertyModel) text("""
.where(PropertyModel.id.in_(property_ids)) DELETE FROM funding_package fp
USING plan p
WHERE fp.plan_id = p.id
AND p.property_id = ANY(:property_ids)
"""),
params,
)
# --------------------------------------------------
# recommendation (direct — CRITICAL FIX)
# --------------------------------------------------
session.execute(
text("""
DELETE FROM recommendation
WHERE property_id = ANY(:property_ids)
"""),
params,
)
# --------------------------------------------------
# plan (direct)
# --------------------------------------------------
session.execute(
text("""
DELETE FROM plan
WHERE property_id = ANY(:property_ids)
"""),
params,
)
# --------------------------------------------------
# property-scoped tables
# --------------------------------------------------
session.execute(
text("""
DELETE FROM property_details_epc
WHERE property_id = ANY(:property_ids)
"""),
params,
)
session.execute(
text("""
DELETE FROM property_targets
WHERE property_id = ANY(:property_ids)
"""),
params,
)
# --------------------------------------------------
# properties LAST
# --------------------------------------------------
session.execute(
text("""
DELETE FROM property
WHERE id = ANY(:property_ids)
"""),
params,
) )
@ -481,11 +520,14 @@ def clear_portfolio_in_batches(
return return
total = (len(property_ids) + property_batch_size - 1) // property_batch_size total = (len(property_ids) + property_batch_size - 1) // property_batch_size
import time
for i, batch in enumerate(chunked(property_ids, property_batch_size), start=1): for i, batch in enumerate(chunked(property_ids, property_batch_size), start=1):
print(f"Deleting batch {i}/{total} ({len(batch)} properties)") print(f"Deleting batch {i}/{total} ({len(batch)} properties)")
start_time = time.time()
with db_session() as session: with db_session() as session:
delete_property_batch(session, batch) delete_property_batch(session, batch)
finish_time = time.time()
print(f"Batch {i} deleted in {finish_time - start_time:.2f} seconds")
# scenario deletion happens AFTER all properties are gone # scenario deletion happens AFTER all properties are gone
delete_portfolio_scenarios_if_empty(portfolio_id) delete_portfolio_scenarios_if_empty(portfolio_id)

View file

@ -730,7 +730,7 @@ async def model_engine(body: PlanTriggerRequest):
epc_searcher.ordnance_survey_client.property_type = addr.property_type epc_searcher.ordnance_survey_client.property_type = addr.property_type
# For the moment, our OS API access is unavailable, so we skip and interpolate # For the moment, our OS API access is unavailable, so we skip and interpolate
epc_searcher.find_property(skip_os=True, api_data=epc_api_data, overwrite_sap05=True) epc_searcher.find_property(skip_os=True, api_data=None, overwrite_sap05=True)
epc_searcher.set_uprn_source(file_format=body.file_format) epc_searcher.set_uprn_source(file_format=body.file_format)
lookup_key = ( lookup_key = (

View file

@ -100,6 +100,7 @@ class HotWaterAttributes(Definitions):
WELSH_TEXT = { WELSH_TEXT = {
"ogçör brif system": "from main system", "ogçör brif system": "from main system",
"o r brif system": "from main system", "o r brif system": "from main system",
"or brif system": "from main system",
"ogçör brif system, adfer gwres nwyon ffliw": "from main system, flue gas heat recovery", "ogçör brif system, adfer gwres nwyon ffliw": "from main system, flue gas heat recovery",
"bwyler/cylchredydd nwy": "gas boiler/circulator", "bwyler/cylchredydd nwy": "gas boiler/circulator",
"ogçör brif system, dim thermostat ar y silindr": "from main system, no cylinder thermostat", "ogçör brif system, dim thermostat ar y silindr": "from main system, no cylinder thermostat",

View file

@ -39,6 +39,8 @@ class WallAttributes(Definitions):
"Waliau ceudod, fel yGÇÖu hadeiladwyd, wediGÇÖu hinswleiddio (rhagdybiaeth)": "Cavity wall, as built, " "Waliau ceudod, fel yGÇÖu hadeiladwyd, wediGÇÖu hinswleiddio (rhagdybiaeth)": "Cavity wall, as built, "
"insulated (assumed)", "insulated (assumed)",
"Waliau ceudod, fel yGÇÖu hadeiladwyd, wediGÇÖu hinswleiddio": "Cavity wall, as built, insulated", "Waliau ceudod, fel yGÇÖu hadeiladwyd, wediGÇÖu hinswleiddio": "Cavity wall, as built, insulated",
"Waliau ceudod, fel yu hadeiladwyd, wediu hinswleiddio (rhagdybiaeth)": "Cavity wall, as built, insulated ("
"assumed)",
"Gwenithfaen neu risgraig, fel yGÇÖu hadeiladwyd, dim inswleiddio (rhagdybiaeth)": "Granite or whinstone, " "Gwenithfaen neu risgraig, fel yGÇÖu hadeiladwyd, dim inswleiddio (rhagdybiaeth)": "Granite or whinstone, "
"as built, no insulation (" "as built, no insulation ("
"assumed)", "assumed)",
@ -146,6 +148,9 @@ class WallAttributes(Definitions):
for key in self.DEFAULT_KEYS: for key in self.DEFAULT_KEYS:
result[key] = False result[key] = False
result["thermal_transmittance_unit"] = None
result["insulation_thickness"] = "none"
return result return result
description = self.description.lower() description = self.description.lower()

View file

@ -72,6 +72,7 @@ class WallRecommendations(Definitions):
'Timber frame, as built, partial insulation': 'Timber frame, with external insulation', 'Timber frame, as built, partial insulation': 'Timber frame, with external insulation',
"Sandstone or limestone, as built, no insulation": "Sandstone or limestone, with external insulation", "Sandstone or limestone, as built, no insulation": "Sandstone or limestone, with external insulation",
"Sandstone, as built, no insulation": "Sandstone, with external insulation", "Sandstone, as built, no insulation": "Sandstone, with external insulation",
"Sandstone, as built, partial insulation": "Sandstone, with external insulation",
} }
# These are the ending descriptions we consider for walls with internal insulation # These are the ending descriptions we consider for walls with internal insulation
@ -88,6 +89,7 @@ class WallRecommendations(Definitions):
'Timber frame, as built, partial insulation': 'Timber frame, with internal insulation', 'Timber frame, as built, partial insulation': 'Timber frame, with internal insulation',
"Sandstone or limestone, as built, no insulation": "Sandstone or limestone, with internal insulation", "Sandstone or limestone, as built, no insulation": "Sandstone or limestone, with internal insulation",
"Sandstone, as built, no insulation": "Sandstone, with internal insulation", "Sandstone, as built, no insulation": "Sandstone, with internal insulation",
"Sandstone, as built, partial insulation": "Sandstone, with internal insulation",
} }
def __init__( def __init__(