from __future__ import annotations from collections.abc import Iterable, Iterator from domain.addresses.user_address import UserAddress from domain.postcode import Postcode def iter_postcode_grouped_batches( addresses: Iterable[UserAddress], *, max_batch_size: int = 500, ) -> Iterator[list[UserAddress]]: if max_batch_size < 1: raise ValueError("max_batch_size must be >= 1") groups = _group_by_postcode_in_order(addresses) buffer: list[UserAddress] = [] for group in groups.values(): group_len = len(group) # Oversize single-Postcode group: flush buffer first, then dispatch # the group as its own batch. Mirrors the legacy # ``if group_len >= batch_size`` branch. if group_len >= max_batch_size: if buffer: yield buffer buffer = [] yield group continue # Adding this group would overflow: flush buffer before appending. if len(buffer) + group_len > max_batch_size: yield buffer buffer = [] buffer.extend(group) # Final flush. if buffer: yield buffer def _group_by_postcode_in_order( addresses: Iterable[UserAddress], ) -> dict[Postcode, list[UserAddress]]: groups: dict[Postcode, list[UserAddress]] = {} for address in addresses: groups.setdefault(address.postcode, []).append(address) return groups