diff --git a/backend/address2UPRN/main.py b/backend/address2UPRN/main.py index fcc6a11c3..ccab46b1e 100644 --- a/backend/address2UPRN/main.py +++ b/backend/address2UPRN/main.py @@ -38,22 +38,29 @@ def get_epc_data_with_postcode(postcode: str) -> pd.DataFrame: service = EpcClientService(auth_token=token) results = service.search_by_postcode(postcode) return pd.DataFrame( - [{"address": r.address_line_1, "uprn": r.uprn} for r in results] + [ + { + "address": r.address_line_1, + "uprn": r.uprn, + "certificate_number": r.certificate_number, + } + for r in results + ] ) def get_uprn_from_historic_epc( user_inputed_address: str, postcode: str, -) -> Optional[tuple[str, str, float]]: +) -> Optional[tuple[str, str, float, Optional[str]]]: """Resolve a UPRN via historic EPC S3 data. - Returns (uprn, address, lexiscore) when the historic dataset agrees on a - single rank-1 UPRN, None otherwise (no stored data, zero score, or - ambiguous top rank). The score gate is `unambiguous_uprn`'s own (score > 0); - the 0.7 heuristic used for the new-EPC source isn't applied here because - historic addresses use a more verbose format that systematically depresses - lexiscores. + Returns (uprn, address, lexiscore, certificate_number) when the historic + dataset agrees on a single rank-1 UPRN, None otherwise (no stored data, + zero score, or ambiguous top rank). The score gate is `unambiguous_uprn`'s + own (score > 0); the 0.7 heuristic used for the new-EPC source isn't + applied here because historic addresses use a more verbose format that + systematically depresses lexiscores. """ repo = HistoricEpcS3Repository.with_default_s3_client() return HistoricEpcResolver(repo).resolve_uprn(user_inputed_address, postcode) @@ -63,7 +70,7 @@ def get_uprn_with_epc_df( user_inputed_address: str, epc_df: pd.DataFrame, verbose: bool = False, -) -> Optional[str | tuple[str, str, float]]: +) -> Optional[str | tuple[str, str, float, Optional[str]]]: """ Return uprn (str) using a pre-fetched EPC dataframe. This avoids calling the API multiple times for the same postcode. @@ -92,6 +99,7 @@ def get_uprn_with_epc_df( address = top_rank_df["address"].values[0] score = float(top_rank_df["lexiscore"].values[0]) + certificate_number = top_rank_df["certificate_number"].values[0] logger.info(f"Address found to be: {address}, with lexiscore {score}") # Safe to return the agreed UPRN @@ -102,7 +110,7 @@ def get_uprn_with_epc_df( return None if verbose: - return (found_uprn, address, score) + return (found_uprn, address, score, certificate_number) else: return found_uprn @@ -124,7 +132,7 @@ def get_uprn( """ df = get_epc_data_with_postcode(postcode=postcode) - result: Optional[tuple[str, str, float]] = get_uprn_with_epc_df( + result: Optional[tuple[str, str, float, Optional[str]]] = get_uprn_with_epc_df( user_inputed_address=user_inputed_address, epc_df=df, verbose=True, @@ -397,6 +405,7 @@ def handler(event, context, local=False): "address2uprn_uprn": "invalid postcode", "address2uprn_address": "invalid postcode", "address2uprn_lexiscore": "invalid postcode", + "address2uprn_certificate_number": "invalid postcode", "address2uprn_status": "invalid_postcode", } ) @@ -439,7 +448,9 @@ def handler(event, context, local=False): continue # Get UPRN using the pre-fetched EPC data with all return options - result: Optional[tuple[str, str, float]] = get_uprn_with_epc_df( + result: Optional[ + tuple[str, str, float, Optional[str]] + ] = get_uprn_with_epc_df( user_inputed_address=address2uprn_user_input, epc_df=epc_df, verbose=True, @@ -468,7 +479,7 @@ def handler(event, context, local=False): # Parse result tuple if successful if result: - uprn, found_address, score = result + uprn, found_address, score, certificate_number = result logger.info( f"Found UPRN for {address2uprn_user_input} in {postcode}: {uprn} (score: {score})" ) @@ -478,6 +489,7 @@ def handler(event, context, local=False): "uprn": uprn, "address": found_address, "lexiscore": score, + "certificate_number": certificate_number, "norm_address": norm_address, "error": None, } @@ -492,6 +504,7 @@ def handler(event, context, local=False): "uprn": None, "address": None, "lexiscore": None, + "certificate_number": None, "norm_address": norm_address, "error": None, } @@ -508,6 +521,7 @@ def handler(event, context, local=False): "uprn": None, "address": None, "lexiscore": None, + "certificate_number": None, "norm_address": "", "error": str(e), } @@ -526,6 +540,9 @@ def handler(event, context, local=False): "address2uprn_uprn": final_uprn, "address2uprn_address": rec["address"] if final_uprn else None, "address2uprn_lexiscore": rec["lexiscore"], + "address2uprn_certificate_number": ( + rec["certificate_number"] if final_uprn else None + ), "address2uprn_status": ( "error" if rec["error"] is not None else status ), diff --git a/repositories/historic_epc/historic_epc_resolver.py b/repositories/historic_epc/historic_epc_resolver.py index 21a7457be..37413f6c7 100644 --- a/repositories/historic_epc/historic_epc_resolver.py +++ b/repositories/historic_epc/historic_epc_resolver.py @@ -49,9 +49,10 @@ class HistoricEpcResolver: def resolve_uprn( self, user_address: str, postcode: str - ) -> Optional[tuple[str, str, float]]: - """``(uprn, matched_address, lexiscore)`` for an unambiguous rank-1 - match, else None (no data / ambiguous tie / zero score).""" + ) -> Optional[tuple[str, str, float, Optional[str]]]: + """``(uprn, matched_address, lexiscore, certificate_number)`` for an + unambiguous rank-1 match, else None (no data / ambiguous tie / zero + score). ``certificate_number`` is the historic dataset's ``lmk_key``.""" matches: HistoricEpcMatches = self.match(user_address, postcode) uprn: Optional[str] = matches.unambiguous_uprn() if not uprn or uprn == "nan": @@ -59,4 +60,4 @@ class HistoricEpcResolver: top: Optional[ScoredHistoricEpc] = matches.top() if top is None: return None - return uprn, top.record.address, top.lexiscore + return uprn, top.record.address, top.lexiscore, top.record.lmk_key diff --git a/tests/repositories/historic_epc/test_historic_epc_resolver.py b/tests/repositories/historic_epc/test_historic_epc_resolver.py index c4b05f8b7..f28787c1b 100644 --- a/tests/repositories/historic_epc/test_historic_epc_resolver.py +++ b/tests/repositories/historic_epc/test_historic_epc_resolver.py @@ -73,10 +73,11 @@ def test_resolve_uprn_returns_unambiguous_match(): # Assert assert result is not None - uprn, address, score = result + uprn, address, score, certificate_number = result assert uprn == "100" assert address == "47 GORDON ROAD" assert score > 0 + assert certificate_number == "" def test_resolve_uprn_is_none_when_postcode_has_no_data():