"""Map a Landlord-Override enum *value* to the gov-EPC API *code* space. The `property_overrides` fact layer stores resolved overrides as enum-value strings ("House", "Detached"); the EPC-API cohort certs carry numeric codes ("0", "2") — `EpcPropertyData.property_type = str(schema.property_type)`. EPC Prediction filters `comparable.epc.property_type == target.property_type`, so a target attribute sourced from overrides must be translated into the code space or no comparable ever matches (ADR-0031, the wiring-handover "every cohort comes back empty" gotcha). Codes are the gov RdSAP/SAP table values in `datatypes/epc/domain/epc_codes.csv`. This module owns "unresolvable": a value that maps to no code returns None. """ from __future__ import annotations from typing import Optional # property_type codes (epc_codes.csv, `property_type` rows — stable across the # RdSAP/SAP schemas that carry each member). "Park home" exists only from # SAP-17.0 / RdSAP-17.0 onward; the code itself is stable where present. _PROPERTY_TYPE_CODES: dict[str, str] = { "House": "0", "Bungalow": "1", "Flat": "2", "Maisonette": "3", "Park home": "4", } # built_form codes (epc_codes.csv, `built_form` rows). "Not Recorded" lodges as # the non-numeric "NR", but cohort comparables carry `str(int)` for built_form, # so an "NR" target could never match — and built_form is the SOFT filter, so a # non-match only widens the cohort. We therefore treat "Not Recorded" (and the # classifier "Unknown") as "no usable built-form signal" → None. _BUILT_FORM_CODES: dict[str, str] = { "Detached": "1", "Semi-Detached": "2", "End-Terrace": "3", "Mid-Terrace": "4", "Enclosed End-Terrace": "5", "Enclosed Mid-Terrace": "6", } def property_type_to_code(override_value: str) -> Optional[str]: """The gov-EPC `property_type` code for a Landlord-Override value, or None when it has no code ("Unknown", or any unmapped value) — which gates the Property out of prediction, as `property_type` is the hard cohort filter.""" return _PROPERTY_TYPE_CODES.get(override_value) def built_form_to_code(override_value: str) -> Optional[str]: """The gov-EPC `built_form` code for a Landlord-Override value, or None when it has no usable code ("Unknown", "Not Recorded", or any unmapped value). built_form is the soft filter, so None simply leaves it unconditioned.""" return _BUILT_FORM_CODES.get(override_value)