From cbffae07b8c381f6c6c39809c35b4620c486d30b Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sat, 4 Jul 2026 14:59:13 +0000 Subject: [PATCH] =?UTF-8?q?Annotate=20locals=20assigned=20from=20cross-mod?= =?UTF-8?q?ule=20calls=20in=20the=20historic-EPC=20stack=20=F0=9F=9F=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CLAUDE.md | 3 +++ repositories/historic_epc/historic_epc_resolver.py | 12 +++++++----- .../historic_epc/historic_epc_s3_repository.py | 3 ++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 857c7083c..e498eeea5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. diff --git a/repositories/historic_epc/historic_epc_resolver.py b/repositories/historic_epc/historic_epc_resolver.py index 48dda7f6a..1c73efa4b 100644 --- a/repositories/historic_epc/historic_epc_resolver.py +++ b/repositories/historic_epc/historic_epc_resolver.py @@ -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 diff --git a/repositories/historic_epc/historic_epc_s3_repository.py b/repositories/historic_epc/historic_epc_s3_repository.py index c8409ff80..735afa842 100644 --- a/repositories/historic_epc/historic_epc_s3_repository.py +++ b/repositories/historic_epc/historic_epc_s3_repository.py @@ -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 []