from dataclasses import dataclass from typing import List, Literal, Optional, Sequence, Tuple PhoneKind = Literal["mobile", "telephone"] @dataclass(frozen=True) class PhoneEntry: """One phone number as Abri lists it for a tenant, in document order.""" kind: PhoneKind number: str priority: Optional[str] = None @dataclass(frozen=True) class SelectedPhoneNumbers: """The numbers a contact carries: best of each kind plus the runner-up.""" mobile: Optional[str] telephone: Optional[str] secondary: Optional[str] # TODO(#1474): confirm the priority ordering with Abri — this assumes the # lowest numeric priority marks the preferred number, and that entries with # a missing or non-numeric priority rank last (ties keep document order). def _priority_sort_key(entry: PhoneEntry) -> Tuple[int, int]: """Sort key: lowest numeric priority first; unusable priorities last.""" try: return (0, int((entry.priority or "").strip())) except ValueError: return (1, 0) def _best(entries: List[PhoneEntry]) -> Optional[PhoneEntry]: ranked = sorted(entries, key=_priority_sort_key) # stable: ties keep document order return ranked[0] if ranked else None def _number(entry: Optional[PhoneEntry]) -> Optional[str]: return entry.number.strip() if entry is not None else None def select_phone_numbers(entries: Sequence[PhoneEntry]) -> SelectedPhoneNumbers: """Best mobile, best landline, then the best of the leftovers of both kinds. All three picks use the same rule: lowest numeric priority wins, entries without a usable priority sort last, ties break by document order, blank numbers are ignored. """ usable = [entry for entry in entries if entry.number.strip()] best_mobile = _best([e for e in usable if e.kind == "mobile"]) best_telephone = _best([e for e in usable if e.kind == "telephone"]) runner_up = _best( [e for e in usable if e is not best_mobile and e is not best_telephone] ) return SelectedPhoneNumbers( mobile=_number(best_mobile), telephone=_number(best_telephone), secondary=_number(runner_up), )