Annotate locals assigned from cross-module calls in the historic-EPC stack 🟪

Review ask (dancafc): resolver/repository locals now carry explicit types
(matches: list[ScoredHistoricEpc], records: list[HistoricEpc], df:
pd.DataFrame, ...) so the flow reads without chasing callee signatures.
CLAUDE.md's Type Safety section gains the rule so future sessions enforce it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-04 14:59:13 +00:00
parent 246834fac0
commit cbffae07b8
3 changed files with 12 additions and 6 deletions

View file

@ -34,4 +34,7 @@ All new code must pass `pyright` with zero errors under `typeCheckingMode = stri
Use Optional over | None
Annotate all function return types. Use `dict[str, Any]` for untyped external API
payloads — never bare `dict`. Add `pandas-stubs` when introducing pandas to a module.
Annotate locals assigned from cross-module calls (e.g. `matches: list[ScoredHistoricEpc]
= rank_historic_epc(...)`) — the reader shouldn't need the callee's signature to follow
the flow; inference-only locals are fine within a module's own helpers.

View file

@ -2,8 +2,10 @@ from __future__ import annotations
from typing import Optional
from datatypes.epc.domain.historic_epc import HistoricEpc
from datatypes.epc.domain.historic_epc_matching import (
HistoricEpcMatches,
ScoredHistoricEpc,
rank_historic_epc,
)
from domain.postcode import Postcode
@ -24,8 +26,8 @@ class HistoricEpcResolver:
if not user_address:
raise ValueError("user_address must be non-empty")
pc = Postcode(postcode)
records = self._repo.get_for_postcode(pc)
matches = rank_historic_epc(records, user_address)
records: list[HistoricEpc] = self._repo.get_for_postcode(pc)
matches: list[ScoredHistoricEpc] = rank_historic_epc(records, user_address)
return HistoricEpcMatches(
user_address=user_address,
postcode=str(pc),
@ -37,11 +39,11 @@ class HistoricEpcResolver:
) -> Optional[tuple[str, str, float]]:
"""``(uprn, matched_address, lexiscore)`` for an unambiguous rank-1
match, else None (no data / ambiguous tie / zero score)."""
matches = self.match(user_address, postcode)
uprn = matches.unambiguous_uprn()
matches: HistoricEpcMatches = self.match(user_address, postcode)
uprn: Optional[str] = matches.unambiguous_uprn()
if not uprn or uprn == "nan":
return None
top = matches.top()
top: Optional[ScoredHistoricEpc] = matches.top()
if top is None:
return None
return uprn, top.record.address, top.lexiscore

View file

@ -3,6 +3,7 @@ from __future__ import annotations
from collections.abc import Hashable
from typing import Any
import pandas as pd
from botocore.exceptions import ClientError
from datatypes.epc.domain.historic_epc import HistoricEpc
@ -59,7 +60,7 @@ class HistoricEpcS3Repository(HistoricEpcRepository):
raise PostcodeNotFound(f"{postcode.value!r} is not a valid UK postcode")
key = f"{self._root_prefix.rstrip('/')}/{postcode}/data.csv.gz"
try:
df = self._client.read_csv_gz(key)
df: pd.DataFrame = self._client.read_csv_gz(key)
except ClientError as e:
if e.response.get("Error", {}).get("Code") in ("NoSuchKey", "404"):
return []