From fc3f6a6f9472366a6a78fffbf3e0985f063b7d83 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 7 Jul 2026 11:43:03 +0000 Subject: [PATCH] =?UTF-8?q?Built-form=20filter=20resolves=20with=20the=20s?= =?UTF-8?q?ame=20precedence=20as=20property=20type=20=F0=9F=9F=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- backend/app/modelling/property_filters.py | 62 +++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/backend/app/modelling/property_filters.py b/backend/app/modelling/property_filters.py index 5f29e33ac..a09aedb94 100644 --- a/backend/app/modelling/property_filters.py +++ b/backend/app/modelling/property_filters.py @@ -67,6 +67,15 @@ def resolve_filtered_property_ids( if row.id is not None and types.get(row.id) in wanted ] + if filters.built_forms is not None: + wanted_forms = set(filters.built_forms) + forms = _resolved_built_forms(session, candidates) + candidates = [ + row + for row in candidates + if row.id is not None and forms.get(row.id) in wanted_forms + ] + return [ FilteredProperty(property_id=row.id, postcode=row.postcode) for row in candidates @@ -88,6 +97,59 @@ _PROPERTY_TYPE_CODES: dict[str, str] = { "4": "Park home", } +_BUILT_FORM_CODES: dict[str, str] = { + "1": "Detached", + "2": "Semi-Detached", + "3": "End-Terrace", + "4": "Mid-Terrace", + "5": "Enclosed End-Terrace", + "6": "Enclosed Mid-Terrace", +} + + +def _resolved_built_forms( + session: Session, candidates: list[PropertyRow] +) -> dict[int, str]: + """Each property's effective built_form per the ADR-0056 precedence: + override (building_part 0) → lodged EPC → predicted EPC → legacy column.""" + property_ids = [row.id for row in candidates if row.id is not None] + by_legacy_column = { + row.id: row.built_form + for row in candidates + if row.id is not None and row.built_form is not None + } + overrides = session.exec( + select(PropertyOverrideRow) + .where(col(PropertyOverrideRow.property_id).in_(property_ids)) + .where(PropertyOverrideRow.building_part == 0) + .where(PropertyOverrideRow.override_component == "built_form_type") + ).all() + by_override = {o.property_id: o.override_value for o in overrides} + + epcs = session.exec( + select(EpcPropertyModel) + .where(col(EpcPropertyModel.property_id).in_(property_ids)) + .where(col(EpcPropertyModel.source).in_(("lodged", "predicted"))) + ).all() + by_epc_source: dict[str, dict[int, str]] = {"lodged": {}, "predicted": {}} + for epc in epcs: + if epc.property_id is not None and epc.built_form: + value = epc.built_form + by_epc_source[epc.source][epc.property_id] = _BUILT_FORM_CODES.get( + value, value + ) + + resolved: dict[int, str] = {} + for pid in property_ids: + resolved[pid] = ( + by_override.get(pid) + or by_epc_source["lodged"].get(pid) + or by_epc_source["predicted"].get(pid) + or by_legacy_column.get(pid) + or _UNKNOWN + ) + return resolved + def _resolved_property_types( session: Session, candidates: list[PropertyRow]