Compose repository and matcher into scored historic EPC matches 🟥

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jun-te Kim 2026-06-29 14:51:33 +00:00
parent d084a0a785
commit d7fd093c4f
2 changed files with 86 additions and 0 deletions

View file

@ -0,0 +1,33 @@
from __future__ import annotations
from typing import Optional
from datatypes.epc.domain.historic_epc_matching import (
HistoricEpcMatches,
rank_historic_epc,
)
from repositories.historic_epc.historic_epc_repository import (
HistoricEpcRepository,
PostcodeNotFound,
)
class HistoricEpcResolver:
"""Resolves an address to a historic-EPC match by composing the repository
(fetch a postcode's records) with the matcher (score them against the
address). This is where ``address2uprn`` plugs onto the old-EPC backup.
"""
def __init__(self, repo: HistoricEpcRepository) -> None:
self._repo = repo
def match(self, user_address: str, postcode: str) -> HistoricEpcMatches:
"""All of the postcode's historic EPCs scored against ``user_address``."""
raise NotImplementedError
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)."""
raise NotImplementedError

View file

@ -0,0 +1,53 @@
"""HistoricEpcResolver composes the repository with the address matcher.
Exercised against a fake in-memory repository (a dict of postcode -> records),
so the resolver's composition is tested with no S3 and no network — the matcher
and the repo each have their own tests.
"""
from __future__ import annotations
import dataclasses
from datatypes.epc.domain.historic_epc import HistoricEpc
from datatypes.epc.domain.historic_epc_matching import HistoricEpcMatches
from repositories.historic_epc.historic_epc_repository import HistoricEpcRepository
from repositories.historic_epc.historic_epc_resolver import HistoricEpcResolver
def _hist(address: str, uprn: str) -> HistoricEpc:
fields = {f.name: "" for f in dataclasses.fields(HistoricEpc)}
fields["address"] = address
fields["uprn"] = uprn
return HistoricEpc(**fields)
class _FakeRepo(HistoricEpcRepository):
def __init__(self, by_postcode: dict[str, list[HistoricEpc]]) -> None:
self._by_postcode = by_postcode
def get_for_postcode(self, postcode: str) -> list[HistoricEpc]:
key = postcode.upper().replace(" ", "")
return self._by_postcode.get(key, [])
def test_match_composes_repo_and_matcher_into_scored_matches():
# Arrange
repo = _FakeRepo(
{
"AB338AL": [
_hist("48 GORDON ROAD", "200"),
_hist("47 GORDON ROAD", "100"),
]
}
)
resolver = HistoricEpcResolver(repo)
# Act
result = resolver.match("47 Gordon Road", "AB33 8AL")
# Assert
assert isinstance(result, HistoricEpcMatches)
assert result.postcode == "AB338AL"
assert len(result.matches) == 2
assert result.top().record.address == "47 GORDON ROAD"